update timekeeping
This commit is contained in:
parent
8cc13276f0
commit
54e5d8e780
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Admin\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Traits\HasFilterRequest;
|
||||
use App\Traits\HasOrderByRequest;
|
||||
use App\Traits\HasSearchRequest;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Modules\Admin\app\Models\Admin;
|
||||
use Modules\Admin\app\Models\Tracking;
|
||||
|
||||
use function PHPSTORM_META\type;
|
||||
|
||||
class TimekeepingController extends Controller
|
||||
{
|
||||
use HasOrderByRequest;
|
||||
use HasFilterRequest;
|
||||
use HasSearchRequest;
|
||||
|
||||
public function get(Request $request)
|
||||
{
|
||||
// dd($request->date);
|
||||
$now = Carbon::create($request->year, $request->month);
|
||||
// $now = Carbon::create(2024, 5, 30); // Nếu muốn khởi tạo với một ngày cụ thể
|
||||
$daysInMonth = $now->daysInMonth;
|
||||
// return response()->json(['status'=> true, 'data'=>$request->all()]);
|
||||
// Lấy ngày đầu tháng
|
||||
$startOfMonth = $now->startOfMonth()->toDateString();
|
||||
// Lấy ngày cuối tháng
|
||||
$endOfMonth = $now->endOfMonth()->toDateString();
|
||||
$admins = Admin::all();
|
||||
$history = DB::table('tracking')->select('*')
|
||||
->whereBetween('tracking.created_at', [$startOfMonth, $endOfMonth])->orderBy('tracking.created_at', 'asc')->get();
|
||||
$history = collect($history);
|
||||
$result = [];
|
||||
foreach ($admins as $admin) {
|
||||
$user_data = [];
|
||||
for ($i = 1; $i <= $daysInMonth; $i++) {
|
||||
// Tạo ngày cụ thể trong tháng
|
||||
$date = Carbon::create($now->year, $now->month, $i)->format('Y-m-d');
|
||||
// Kiểm tra xem có mục nào trong $history có created_at trùng với $date
|
||||
$hasEntry = $history->filter(function ($entry) use ($date, $admin) {
|
||||
return Carbon::parse($entry->created_at)->format('Y-m-d') === $date && $entry->user_id == $admin->id;
|
||||
});
|
||||
|
||||
if (count($hasEntry) > 0) {
|
||||
$values = array_values($hasEntry->toArray());
|
||||
$last_checkin = null;
|
||||
$total = 0;
|
||||
foreach ($values as $value) {
|
||||
$createdAt = Carbon::parse($value->created_at);
|
||||
if ($value->status == 'check out' && $last_checkin != null) {
|
||||
$lastCheckInTime = Carbon::parse($last_checkin);
|
||||
// Tính thời gian làm việc bằng hiệu của thời gian check out và check in
|
||||
$workingTime = $createdAt->diffInSeconds($lastCheckInTime);
|
||||
$total += $workingTime;
|
||||
}
|
||||
|
||||
if ($value->status == 'check in') {
|
||||
$last_checkin = $createdAt;
|
||||
}
|
||||
}
|
||||
$user_data[] = ['values'=>array_values($hasEntry->toArray()), 'total' => $total, 'day' => $i];
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = ['user' => $admin, 'history' => $user_data];
|
||||
}
|
||||
return response()->json(['status'=> true, 'data'=>$result]);
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ class TrackingController extends Controller
|
|||
// Order by
|
||||
$this->orderByRequest($tracking, $request);
|
||||
|
||||
$tracking->orderBy('created_at', 'desc');
|
||||
// Filter
|
||||
$this->filterRequest(
|
||||
builder: $tracking,
|
||||
|
|
@ -59,6 +60,8 @@ class TrackingController extends Controller
|
|||
['status' => true]
|
||||
);
|
||||
|
||||
$users = Admin::where('permission', '!=', 'admin')->get();
|
||||
$responseData['users'] = $users;
|
||||
return response()->json($responseData);
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +75,9 @@ class TrackingController extends Controller
|
|||
]);
|
||||
|
||||
$payload = $request->only(['name', 'time_string', 'status']);
|
||||
if($request->has('created_at')){
|
||||
$payload['created_at'] = $request->created_at;
|
||||
}
|
||||
$user = Admin::where('name', $payload['name'])->first();
|
||||
if ($user) {
|
||||
$payload['user_id'] = $user->id;
|
||||
|
|
|
|||
|
|
@ -4,17 +4,12 @@ use Illuminate\Support\Facades\Route;
|
|||
use Modules\Admin\app\Http\Controllers\AdminController;
|
||||
use Modules\Admin\app\Http\Controllers\BannerController;
|
||||
use Modules\Admin\app\Http\Controllers\ClientController;
|
||||
use Modules\Admin\app\Http\Controllers\ContactController;
|
||||
use Modules\Admin\app\Http\Controllers\CountryController;
|
||||
use Modules\Admin\app\Http\Controllers\CustomThemeController;
|
||||
use Modules\Admin\app\Http\Controllers\DashboardController;
|
||||
use Modules\Admin\app\Http\Controllers\DiscountController;
|
||||
use Modules\Admin\app\Http\Controllers\DiscountTypeController;
|
||||
use Modules\Admin\app\Http\Controllers\JiraController;
|
||||
use Modules\Admin\app\Http\Controllers\OrderController;
|
||||
use Modules\Admin\app\Http\Controllers\PackageController;
|
||||
use Modules\Admin\app\Http\Controllers\SNCheckController;
|
||||
use Modules\Admin\app\Http\Controllers\SettingController;
|
||||
use Modules\Admin\app\Http\Controllers\TimekeepingController;
|
||||
use Modules\Admin\app\Http\Controllers\TrackingController;
|
||||
use Modules\Admin\app\Http\Middleware\AdminMiddleware;
|
||||
|
||||
|
|
@ -55,28 +50,6 @@ Route::middleware('api')
|
|||
Route::get('/clear-cache', [SettingController::class, 'clearCache']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'package',
|
||||
], function () {
|
||||
Route::get('/all', [PackageController::class, 'all']);
|
||||
Route::post('/create', [PackageController::class, 'create']);
|
||||
Route::post('/update', [PackageController::class, 'update']);
|
||||
Route::get('/delete', [PackageController::class, 'delete']);
|
||||
Route::post('/updates', [PackageController::class, 'updates']);
|
||||
Route::post('/deletes', [PackageController::class, 'deletes']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'discount',
|
||||
], function () {
|
||||
Route::get('/get', [DiscountController::class, 'get']);
|
||||
Route::post('/create', [DiscountController::class, 'create']);
|
||||
Route::post('/update', [DiscountController::class, 'update']);
|
||||
Route::get('/delete', [DiscountController::class, 'delete']);
|
||||
Route::post('/updates', [DiscountController::class, 'updates']);
|
||||
Route::post('/deletes', [DiscountController::class, 'deletes']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'client',
|
||||
], function () {
|
||||
|
|
@ -99,25 +72,6 @@ Route::middleware('api')
|
|||
Route::post('/deletes', [BannerController::class, 'deletes']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'order',
|
||||
], function () {
|
||||
Route::get('/get', [OrderController::class, 'get']);
|
||||
Route::post('/update', [OrderController::class, 'update']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'sn-check-history',
|
||||
], function () {
|
||||
Route::get('/get', [SNCheckController::class, 'get']);
|
||||
Route::get('/show-detail', [SNCheckController::class, 'showDetail']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'discount-type',
|
||||
], function () {
|
||||
Route::get('/all', [DiscountTypeController::class, 'all']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'custom-theme',
|
||||
|
|
@ -130,11 +84,6 @@ Route::middleware('api')
|
|||
], function () {
|
||||
Route::get('/', [CountryController::class, 'all']);
|
||||
});
|
||||
Route::group([
|
||||
'prefix' => 'contact',
|
||||
], function () {
|
||||
Route::get('/get', [ContactController::class, 'get']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'dashboard',
|
||||
|
|
@ -146,13 +95,26 @@ Route::middleware('api')
|
|||
Route::group([
|
||||
'prefix' => 'jira',
|
||||
], function () {
|
||||
|
||||
Route::get('/fetch-issues', [JiraController::class, 'fetchAllIssues']);
|
||||
Route::get('/export-issues', [JiraController::class, 'exportToExcel']);
|
||||
Route::get('/all-project', [JiraController::class, 'getAllProject']);
|
||||
Route::get('/all-issue-by-project', [JiraController::class, 'fetchIssuesByProject']);
|
||||
Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'timekeeping',
|
||||
], function () {
|
||||
Route::get('/', [TimekeepingController::class, 'get']);
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'tracking',
|
||||
], function () {
|
||||
Route::post('/create', [TrackingController::class, 'create']);
|
||||
Route::post('/update', [TrackingController::class, 'update']);
|
||||
Route::get('/delete', [TrackingController::class, 'delete']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -161,7 +123,6 @@ Route::middleware('api')
|
|||
], function () {
|
||||
Route::get('/', [TrackingController::class, 'get']);
|
||||
Route::post('/scan-create', [TrackingController::class, 'create']);
|
||||
Route::get('/delete', [TrackingController::class, 'delete']);
|
||||
// Route::get('/clear-cache', [SettingController::class, 'clearCache']);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -53,11 +53,16 @@ export const statisticRevenuesByMonth = API_URL + 'v1/admin/dashboard/statistics
|
|||
|
||||
// Tracking
|
||||
export const getListTracking = API_URL + 'v1/admin/tracking'
|
||||
export const addTracking = API_URL + 'v1/admin/tracking/create'
|
||||
export const updateTracking = API_URL + 'v1/admin/tracking/update'
|
||||
export const deleteTracking = API_URL + 'v1/admin/tracking/delete'
|
||||
|
||||
// // Tracking
|
||||
// Worklogs
|
||||
export const fetchAllIssues = API_URL + 'v1/admin/jira/fetch-issues'
|
||||
export const exportIssues = API_URL + 'v1/admin/jira/export-issues'
|
||||
export const getAllProjects = API_URL + 'v1/admin/jira/all-project'
|
||||
export const getAllIssuesByProject = API_URL + 'v1/admin/jira/all-issue-by-project'
|
||||
export const getAllUserWorklogs = API_URL + 'v1/admin/jira/worklogs'
|
||||
|
||||
//Timekeeping
|
||||
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
|
||||
|
|
@ -19,6 +19,7 @@ import {
|
|||
import { notifications } from '@mantine/notifications'
|
||||
import {
|
||||
IconBrush,
|
||||
IconCalendar,
|
||||
IconLayoutSidebarLeftExpand,
|
||||
IconLayoutSidebarRightExpand,
|
||||
IconLogout,
|
||||
|
|
@ -38,6 +39,7 @@ import classes from './NavbarSimpleColored.module.css'
|
|||
|
||||
const data = [
|
||||
// { link: '/dashboard', label: 'Dashboard', icon: IconHome },
|
||||
{ link: '/timekeeping', label: 'Timekeeping', icon: IconCalendar },
|
||||
{ link: '/tracking', label: 'Check in/out', icon: IconScan },
|
||||
{ link: '/worklogs', label: 'Worklogs', icon: IconReport },
|
||||
{ link: '/jira', label: 'Jira', icon: IconSubtask },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
.title {
|
||||
background-color: light-dark(var(white), var(--mantine-color-dark-7));
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--mantine-spacing-sm) var(--mantine-spacing-lg)
|
||||
var(--mantine-spacing-sm);
|
||||
border-bottom: solid rgba(201, 201, 201, 0.377) 1px;
|
||||
}
|
||||
|
||||
.optionIcon {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.deleteIcon {
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.deleteIcon:hover {
|
||||
background-color: rgba(203, 203, 203, 0.809);
|
||||
}
|
||||
|
||||
.editIcon {
|
||||
color: rgb(9, 132, 132);
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.editIcon:hover {
|
||||
background-color: rgba(203, 203, 203, 0.809);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: light-dark(white, #2d353c);
|
||||
text-align: center;
|
||||
border: solid 1px rgb(255, 145, 0);
|
||||
}
|
||||
|
||||
.dialogText {
|
||||
color: light-dark(#2d353c, white);
|
||||
}
|
||||
|
||||
.tableTr:hover {
|
||||
background-color: rgb(205, 255, 255);
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
import { getTheTimesheet } from '@/api/Admin'
|
||||
import { get } from '@/rtk/helpers/apiService'
|
||||
import { Box, Select, Table, Text, Tooltip } from '@mantine/core'
|
||||
import { IconCheck, IconX } from '@tabler/icons-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import classes from './Timekeeping.module.css'
|
||||
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
email_verified_at: string | null
|
||||
permission: string
|
||||
remember_token: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
interface HistoryValue {
|
||||
id: number
|
||||
name: string
|
||||
user_id: number
|
||||
status: string
|
||||
time_string: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
interface History {
|
||||
values: HistoryValue[]
|
||||
total: number
|
||||
day: number
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
user: User
|
||||
history: History[]
|
||||
}
|
||||
|
||||
const Timekeeping = () => {
|
||||
let permission = ['staff']
|
||||
const [daysInMonth, setDaysInMonth] = useState(
|
||||
Array.from({ length: 31 }, (_, index) => index + 1),
|
||||
)
|
||||
const [data, setData] = useState<UserData[]>([])
|
||||
const [date, setDate] = useState({
|
||||
month: (new Date().getMonth() + 1).toString(),
|
||||
year: new Date().getFullYear().toString(),
|
||||
})
|
||||
|
||||
const getTimeSheet = async () => {
|
||||
try {
|
||||
const res = await get(getTheTimesheet, {
|
||||
month: date.month,
|
||||
year: date.year,
|
||||
})
|
||||
if (res.status) {
|
||||
setData(res.data.filter((u:UserData)=>permission.includes(u.user.permission)))
|
||||
setDaysInMonth(
|
||||
Array.from({ length: getDaysInMonth() }, (_, index) => index + 1),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
function getDayName(dateString: string) {
|
||||
// Tạo đối tượng Date từ chuỗi ngày tháng năm
|
||||
const date = new Date(dateString)
|
||||
|
||||
// Lấy ngày trong tuần (0-6)
|
||||
const dayNumber = date.getDay()
|
||||
|
||||
// Mảng chứa các tên ngày bằng tiếng Việt
|
||||
const daysInVietnamese = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
||||
|
||||
// Trả về tên ngày bằng tiếng Việt
|
||||
return daysInVietnamese[dayNumber]
|
||||
}
|
||||
|
||||
function getDaysInMonth() {
|
||||
// Tạo một đối tượng Date cho ngày đầu tiên của tháng tiếp theo
|
||||
// Chú ý: tháng trong đối tượng Date là từ 0 (tháng 1) đến 11 (tháng 12)
|
||||
const days = new Date(parseInt(date.year), parseInt(date.month), 0)
|
||||
// Trả về ngày, tức là số ngày trong tháng
|
||||
return days.getDate()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getTimeSheet()
|
||||
}, [date])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes.title}>
|
||||
<h3>
|
||||
<Text>Admin/</Text>
|
||||
Timekeeping
|
||||
</h3>
|
||||
</div>
|
||||
<Box w="50%" display={'flex'}>
|
||||
<Select
|
||||
w="10%"
|
||||
value={date.month}
|
||||
size='xs'
|
||||
label="Month"
|
||||
data={Array.from({ length: 12 }, (_, index) =>
|
||||
(1 + index).toString(),
|
||||
)}
|
||||
onChange={(e) => {
|
||||
setDate({ ...date, month: e! })
|
||||
}}
|
||||
></Select>
|
||||
<Select
|
||||
w="10%"
|
||||
value={date.year}
|
||||
size='xs'
|
||||
ml={'sm'}
|
||||
label="Year"
|
||||
data={Array.from({ length: 20 }, (_, index) =>
|
||||
(2023 + 1 + index).toString(),
|
||||
)}
|
||||
onChange={(e) => {
|
||||
setDate({ ...date, year: e! })
|
||||
}}
|
||||
></Select>
|
||||
</Box>
|
||||
<Box>
|
||||
<Table striped highlightOnHover withTableBorder withColumnBorders mt={'md'}>
|
||||
<Table.Thead>
|
||||
<Table.Tr bg={'#228be66b'}>
|
||||
<Table.Th>Day</Table.Th>
|
||||
{daysInMonth.map((d) => {
|
||||
return <Table.Th key={d} ta={'center'}>{d}</Table.Th>
|
||||
})}
|
||||
</Table.Tr>
|
||||
<Table.Tr bg={'#228be66b'}>
|
||||
<Table.Th></Table.Th>
|
||||
{daysInMonth.map((d) => {
|
||||
return (
|
||||
<Table.Th key={d} ta={'center'}>
|
||||
{getDayName(`${date.year}-${date.month}-${d}`)}
|
||||
</Table.Th>
|
||||
)
|
||||
})}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{data.map((user) => {
|
||||
return (
|
||||
<Table.Tr
|
||||
key={user.user.id}
|
||||
className={classes.tableTr}
|
||||
>
|
||||
<Table.Td >{user.user.name}</Table.Td>
|
||||
{daysInMonth.map((d) => {
|
||||
var total =
|
||||
user.history.find((h) => h.day === d)?.total ?? 0
|
||||
return (
|
||||
<Table.Td key={d} ta={'center'}>
|
||||
{total / 60 / 60 < 8 && total !== 0 ? (
|
||||
<Tooltip
|
||||
multiline
|
||||
label={
|
||||
<div>
|
||||
{`Total: ${(total / 60 / 60).toFixed(1)}h`}
|
||||
{user.history
|
||||
.find((h) => h.day === d)
|
||||
?.values.map((v) => {
|
||||
return (
|
||||
<p>{v.status + ': ' + v.time_string}</p>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<IconCheck
|
||||
size={20}
|
||||
style={{
|
||||
backgroundColor: 'orange',
|
||||
color: 'white',
|
||||
borderRadius:"5px",
|
||||
padding: "2px",
|
||||
fontWeight: 700
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : total >= 8 ? (
|
||||
<Tooltip
|
||||
multiline
|
||||
label={
|
||||
<div>
|
||||
{`Total: ${(total / 60 / 60).toFixed(1)}h`}
|
||||
{user.history
|
||||
.find((h) => h.day === d)
|
||||
?.values.map((v) => {
|
||||
return (
|
||||
<p>{v.status + ': ' + v.time_string}</p>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<IconCheck
|
||||
size={20}
|
||||
style={{
|
||||
backgroundColor: 'green',
|
||||
color: 'white',
|
||||
borderRadius:"5px",
|
||||
padding: "2px"
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<IconX
|
||||
size={20}
|
||||
style={{
|
||||
backgroundColor: '#ff4646',
|
||||
color: 'white',
|
||||
borderRadius:"5px",
|
||||
padding: "2px"
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Table.Td>
|
||||
)
|
||||
})}
|
||||
</Table.Tr>
|
||||
)
|
||||
})}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Timekeeping
|
||||
|
|
@ -21,6 +21,18 @@
|
|||
border-radius: 25%;
|
||||
}
|
||||
|
||||
|
||||
.editIcon {
|
||||
color: rgb(9, 132, 132);
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.editIcon:hover {
|
||||
background-color: rgba(203, 203, 203, 0.809);
|
||||
}
|
||||
|
||||
.deleteIcon:hover {
|
||||
background-color: rgba(203, 203, 203, 0.809);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,55 @@
|
|||
import { deleteTracking, getListTracking } from "@/api/Admin"
|
||||
import { DataTablePagination } from "@/components/DataTable/DataTable"
|
||||
import { Xdelete } from "@/rtk/helpers/CRUD"
|
||||
import { get } from "@/rtk/helpers/apiService"
|
||||
import { Box, Button, Dialog, Group, Text } from "@mantine/core"
|
||||
import { IconTrash } from "@tabler/icons-react"
|
||||
import moment from "moment"
|
||||
import { useEffect, useState } from "react"
|
||||
import { addTracking, deleteTracking, getListTracking, updateTracking } from '@/api/Admin'
|
||||
import { DataTablePagination } from '@/components/DataTable/DataTable'
|
||||
import { Xdelete, create, update } from '@/rtk/helpers/CRUD'
|
||||
import { get } from '@/rtk/helpers/apiService'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
Group,
|
||||
Modal,
|
||||
Select,
|
||||
Text
|
||||
} from '@mantine/core'
|
||||
import { DateTimePicker } from '@mantine/dates'
|
||||
import { useForm } from '@mantine/form'
|
||||
import { IconEdit, IconTrash } from '@tabler/icons-react'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import classes from './Tracking.module.css'
|
||||
|
||||
type TLog = {
|
||||
id: number
|
||||
name: string
|
||||
status: string
|
||||
time_string: Date
|
||||
}
|
||||
|
||||
type TListTracking = {
|
||||
data: TLog[]
|
||||
users: User[]
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
email_verified_at: string | null
|
||||
permission: string
|
||||
remember_token: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
const Tracking = () => {
|
||||
const [listTracking, setListTracking] = useState({
|
||||
data: []
|
||||
const [listTracking, setListTracking] = useState<TListTracking>({
|
||||
data: [],
|
||||
users: [],
|
||||
})
|
||||
const [action, setAction] = useState('')
|
||||
const [item, setItem] = useState({id: 0})
|
||||
const [item, setItem] = useState({ id: 0 })
|
||||
const [activeBtn, setActiveBtn] = useState(false)
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
const columns = [
|
||||
{
|
||||
name: 'id',
|
||||
|
|
@ -40,7 +76,7 @@ const Tracking = () => {
|
|||
size: '15%',
|
||||
header: 'Created At',
|
||||
render: (row: any) => {
|
||||
return moment(row.updated_at).format('YYYY/MM/DD - HH:mm:ss')
|
||||
return moment(row.created_at).format('YYYY/MM/DD - HH:mm:ss')
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -49,9 +85,7 @@ const Tracking = () => {
|
|||
header: 'Action',
|
||||
render: (row: any) => {
|
||||
return (
|
||||
<Box
|
||||
className={classes.optionIcon}
|
||||
>
|
||||
<Box className={classes.optionIcon}>
|
||||
<IconTrash
|
||||
className={classes.deleteIcon}
|
||||
onClick={() => {
|
||||
|
|
@ -61,6 +95,16 @@ const Tracking = () => {
|
|||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<IconEdit
|
||||
className={classes.editIcon}
|
||||
onClick={() => {
|
||||
setAction('edit')
|
||||
console.log(row)
|
||||
form.setValues(row)
|
||||
}}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
|
|
@ -85,7 +129,17 @@ const Tracking = () => {
|
|||
},
|
||||
]
|
||||
|
||||
const getAllTracking = async () =>{
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
id: 0,
|
||||
name: '',
|
||||
time_string: new Date(),
|
||||
status: '',
|
||||
created_at: '',
|
||||
},
|
||||
})
|
||||
|
||||
const getAllTracking = async () => {
|
||||
try {
|
||||
const searchParams = new URLSearchParams(window.location.search)
|
||||
const params = {}
|
||||
|
|
@ -99,7 +153,7 @@ const Tracking = () => {
|
|||
}
|
||||
|
||||
const res = await get(getListTracking, params)
|
||||
if(res.status){
|
||||
if (res.status) {
|
||||
setListTracking(res)
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -107,6 +161,41 @@ const Tracking = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleCreate = async (values: TLog) => {
|
||||
try {
|
||||
const res = await create(addTracking, {
|
||||
status: values.status,
|
||||
name: values.name,
|
||||
time_string: moment(values.time_string).format('YYYY-MM-DD HH:mm:ss'),
|
||||
created_at: values.time_string
|
||||
}, getAllTracking)
|
||||
if (res === true) {
|
||||
setAction('')
|
||||
form.reset()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdate = async (values: TLog) => {
|
||||
try {
|
||||
const res = await update(updateTracking, {
|
||||
status: values.status,
|
||||
name: values.name,
|
||||
time_string: moment(values.time_string).format('YYYY-MM-DD HH:mm:ss'),
|
||||
created_at: new Date(values.time_string),
|
||||
id: values.id
|
||||
}, getAllTracking)
|
||||
if (res === true) {
|
||||
setAction('')
|
||||
form.reset()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await Xdelete(deleteTracking, { id: id }, getAllTracking)
|
||||
|
|
@ -115,15 +204,15 @@ const Tracking = () => {
|
|||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(listTracking.data.length === 0){
|
||||
useEffect(() => {
|
||||
if (listTracking.data.length === 0) {
|
||||
getAllTracking()
|
||||
}
|
||||
setInterval(()=>{
|
||||
if(window.location.pathname.includes('tracking')){
|
||||
getAllTracking()
|
||||
}
|
||||
}, 7000)
|
||||
// setInterval(() => {
|
||||
// if (window.location.pathname.includes('tracking')) {
|
||||
// getAllTracking()
|
||||
// }
|
||||
// }, 7000)
|
||||
}, [])
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -132,28 +221,101 @@ const Tracking = () => {
|
|||
<Text>Admin/</Text>
|
||||
Tracking
|
||||
</h3>
|
||||
{/* <Button
|
||||
<Button
|
||||
m={5}
|
||||
onClick={() => {
|
||||
setAction('add')
|
||||
form.reset()
|
||||
}}
|
||||
> */}
|
||||
{/* Add discount */}
|
||||
{/* </Button> */}
|
||||
>
|
||||
+ Add
|
||||
</Button>
|
||||
</div>
|
||||
<Box p={20}>
|
||||
{
|
||||
listTracking.data.length>0 && <DataTablePagination
|
||||
filterInfo={filterInfo}
|
||||
data={listTracking}
|
||||
columns={columns}
|
||||
searchInput
|
||||
size=""
|
||||
/>
|
||||
}
|
||||
{listTracking.data.length > 0 && (
|
||||
<DataTablePagination
|
||||
filterInfo={filterInfo}
|
||||
data={listTracking}
|
||||
columns={columns}
|
||||
searchInput
|
||||
size=""
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Add/Edit User modal */}
|
||||
<Modal
|
||||
opened={action === 'add' || action === 'edit'}
|
||||
onClose={() => {
|
||||
setAction('')
|
||||
form.reset()
|
||||
}}
|
||||
title={
|
||||
<Text pl={'sm'} fw={700} fz={'lg'}>
|
||||
{action === 'add' ? 'Add log' : 'Update log'}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
setDisableBtn(true)
|
||||
action === 'edit'
|
||||
? await handleUpdate(values)
|
||||
: await handleCreate(values)
|
||||
setDisableBtn(false)
|
||||
})}
|
||||
>
|
||||
<Box pl={'md'} pr={'md'}>
|
||||
<Select
|
||||
label={'Name'}
|
||||
placeholder="Banner x"
|
||||
maxLength={255}
|
||||
required
|
||||
data={listTracking.users.map((i: User) => i.name)}
|
||||
value={form.values.name}
|
||||
error={form.errors.name}
|
||||
onChange={(e) => form.setFieldValue('name', e!)}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label={'Status'}
|
||||
required
|
||||
data={['check in', 'check out']}
|
||||
value={form.values.status}
|
||||
error={form.errors.status}
|
||||
onChange={(e) => form.setFieldValue('status', e!)}
|
||||
/>
|
||||
<DateTimePicker
|
||||
label={'Time'}
|
||||
value={new Date(form.values.time_string)}
|
||||
onChange={(e) => form.setFieldValue('time_string', e!)}
|
||||
></DateTimePicker>
|
||||
|
||||
<Box ta={'center'}>
|
||||
{action === 'add' ? (
|
||||
<Button
|
||||
mt={'lg'}
|
||||
bg={'green'}
|
||||
type="submit"
|
||||
disabled={disableBtn}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
mt={'lg'}
|
||||
bg={'green'}
|
||||
type="submit"
|
||||
disabled={disableBtn}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<Dialog
|
||||
className={classes.dialog}
|
||||
opened={action === 'delete'}
|
||||
|
|
@ -195,4 +357,4 @@ const Tracking = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export default Tracking
|
||||
export default Tracking
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ const Worklogs = () => {
|
|||
label="From date:"
|
||||
value={new Date(date.startDate)}
|
||||
w={'20%'}
|
||||
clearable
|
||||
// clearable
|
||||
onChange={(e) => {
|
||||
setDate({ ...date, startDate: moment(e).format('YYYY-MM-DD') })
|
||||
}}
|
||||
|
|
@ -230,7 +230,8 @@ const Worklogs = () => {
|
|||
size="xs"
|
||||
label="To date:"
|
||||
value={new Date(date.endDate)}
|
||||
clearable
|
||||
|
||||
// clearable
|
||||
m={'0 10px'}
|
||||
w={'20%'}
|
||||
onChange={(e) => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import Dashboard from '@/pages/Dashboard/Dashboard'
|
|||
import GeneralSetting from '@/pages/GeneralSetting/GeneralSetting'
|
||||
import Jira from '@/pages/Jira/Jira'
|
||||
import PageNotFound from '@/pages/NotFound/NotFound'
|
||||
import Timekeeping from '@/pages/Timekeeping/Timekeeping'
|
||||
import Tracking from '@/pages/Tracking/Tracking'
|
||||
import PageWelcome from '@/pages/Welcome/Welcome'
|
||||
import Worklogs from '@/pages/Worklogs/Worklogs'
|
||||
|
|
@ -19,7 +20,7 @@ const mainRoutes = [
|
|||
// element: <ProtectedRoute mode="home"><PageHome /></ProtectedRoute>,
|
||||
element: (
|
||||
<ProtectedRoute mode="home">
|
||||
<Navigate to="/tracking"></Navigate>
|
||||
<Navigate to="/timekeeping"></Navigate>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
|
|
@ -115,6 +116,20 @@ const mainRoutes = [
|
|||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/timekeeping',
|
||||
element: (
|
||||
<ProtectedRoute mode="route">
|
||||
<BasePage
|
||||
main={
|
||||
<>
|
||||
<Timekeeping />
|
||||
</>
|
||||
}
|
||||
></BasePage>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// path: '/packages',
|
||||
// element: (
|
||||
|
|
|
|||
Loading…
Reference in New Issue