215 lines
7.5 KiB
TypeScript
215 lines
7.5 KiB
TypeScript
import { ScrollArea } from '@mantine/core';
|
|
import { FC, useEffect, useState } from 'react';
|
|
import { Button, Stack } from '../../components';
|
|
import { Dialog } from '../../components/Dialog';
|
|
import { Pagination } from '../../components/Pagination';
|
|
import { LeaveApplication, soriAPIClient } from '../../lib/sori';
|
|
import { ApproveLeaveForm } from './ApproveLeaveForm';
|
|
import { CreateLeaveApplicationForm } from './CreateLeaveApplicationForm';
|
|
import { RejectLeaveForm } from './RejectLeaveForm';
|
|
export const AdminLeaveApplicationScene: FC = () => {
|
|
const [createOpen, setCreateOpen] = useState(false);
|
|
const [approveOpen, setApproveOpen] = useState(false);
|
|
const [rejectOpen, setRejectOpen] = useState(false);
|
|
|
|
const [leaveApplications, setLeaveApplications] = useState<
|
|
LeaveApplication[]
|
|
>([]);
|
|
const [selectedLeaveApplication, setSelectedLeaveApplication] =
|
|
useState<LeaveApplication | null>(null);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const pageSize = 20;
|
|
|
|
const paginatedstaffs = leaveApplications.slice(
|
|
(currentPage - 1) * pageSize,
|
|
currentPage * pageSize,
|
|
);
|
|
const loadLeaveApplications = async () => {
|
|
const leaveApplicationsResult =
|
|
await soriAPIClient.leaveApplications.actions.getLeaveApplications();
|
|
|
|
const leaveApplications = leaveApplicationsResult.unwrap();
|
|
|
|
setLeaveApplications(leaveApplications);
|
|
};
|
|
useEffect(() => {
|
|
loadLeaveApplications();
|
|
}, []);
|
|
|
|
// const onSubmit = (project: Partial<Project>) => {
|
|
// setProjects([...projects, project as Project]);
|
|
// setOpen(false);
|
|
// };
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={createOpen} onRequestClose={() => setCreateOpen(false)}>
|
|
<CreateLeaveApplicationForm
|
|
onClose={() => {
|
|
setCreateOpen(false);
|
|
loadLeaveApplications();
|
|
}}
|
|
open={createOpen}
|
|
/>
|
|
</Dialog>
|
|
<Dialog open={approveOpen} onRequestClose={() => setApproveOpen(false)}>
|
|
{selectedLeaveApplication && (
|
|
<ApproveLeaveForm
|
|
leaveApplication={selectedLeaveApplication}
|
|
onClose={() => {
|
|
setApproveOpen(false);
|
|
setSelectedLeaveApplication(null);
|
|
loadLeaveApplications(); // refresh list after delete
|
|
}}
|
|
/>
|
|
)}
|
|
</Dialog>
|
|
<Dialog open={rejectOpen} onRequestClose={() => setRejectOpen(false)}>
|
|
{selectedLeaveApplication && (
|
|
<RejectLeaveForm
|
|
leaveApplication={selectedLeaveApplication}
|
|
onClose={() => {
|
|
setRejectOpen(false);
|
|
setSelectedLeaveApplication(null);
|
|
loadLeaveApplications(); // refresh list after delete
|
|
}}
|
|
/>
|
|
)}
|
|
</Dialog>
|
|
<Stack className="border-black-2 relative mx-auto mt-20px h-[90%] w-[90%] overflow-hidden border rounded-lg shadow-[0_2px_5px_2px_rgba(0,0,0,0.1)] xl:px-20">
|
|
<div className="h-20 min-h-12 flex flex-col justify-center gap-1 px-8 text-lg text-xl text-black/90 font-bold font-medium dark:text-white/90">
|
|
<div className="flex items-center justify-between">
|
|
<span>Leave Application Management (Admin)</span>
|
|
</div>
|
|
</div>
|
|
<Stack className="relative h-full w-full overflow-hidden">
|
|
<LeaveAppTable
|
|
leaveApplications={paginatedstaffs}
|
|
onApproveClick={(leaveApplication) => {
|
|
setSelectedLeaveApplication(leaveApplication);
|
|
setApproveOpen(true);
|
|
}}
|
|
onRejectClick={(leaveApplication) => {
|
|
setSelectedLeaveApplication(leaveApplication);
|
|
setRejectOpen(true);
|
|
}}
|
|
/>
|
|
|
|
<Pagination
|
|
count={leaveApplications.length}
|
|
pageSize={pageSize}
|
|
siblingCount={1}
|
|
currentPage={currentPage}
|
|
onPageChange={setCurrentPage}
|
|
/>
|
|
</Stack>
|
|
</Stack>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const LeaveAppTable: FC<{
|
|
leaveApplications: LeaveApplication[];
|
|
onApproveClick: (leaveApplication: LeaveApplication) => void;
|
|
onRejectClick: (leaveApplication: LeaveApplication) => void;
|
|
}> = ({ leaveApplications, onApproveClick, onRejectClick }) => {
|
|
return (
|
|
<ScrollArea className="mb-1 h-full w-full overflow-hidden border border-gray-300 rounded-lg shadow-[2px_2px_5px_2px_rgba(0,0,0,0.1)]">
|
|
<table className="min-w-full">
|
|
<thead>
|
|
<tr className="text-gray-700">
|
|
<th className="border-b px-4 py-2">Leave Type</th>
|
|
<th className="border-b px-4 py-2">From Date</th>
|
|
<th className="border-b px-4 py-2">To Date</th>
|
|
<th className="border-b px-4 py-2">Duration (Days)</th>
|
|
<th className="border-b px-4 py-2">Full/Half</th>
|
|
<th className="border-b px-4 py-2">Last Updated</th>
|
|
<th className="border-b px-4 py-2">Status</th>
|
|
<th className="border-b px-4 py-2"></th>
|
|
<th className="border-b px-4 py-2"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{leaveApplications.map((leaveApplication) => (
|
|
<LeaveAppTr
|
|
key={leaveApplication._id.toString()}
|
|
LeaveApplication={leaveApplication}
|
|
onApproveClick={onApproveClick}
|
|
onRejectClick={onRejectClick}
|
|
/>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</ScrollArea>
|
|
);
|
|
};
|
|
|
|
const LeaveAppTr: FC<{
|
|
LeaveApplication: LeaveApplication;
|
|
onApproveClick: (leaveApplication: LeaveApplication) => void;
|
|
onRejectClick: (leaveApplication: LeaveApplication) => void;
|
|
}> = ({ LeaveApplication, onApproveClick, onRejectClick }) => {
|
|
const getLastUpdatedTime = (updatedAt: Date) => {
|
|
const lastUpdated = new Date(updatedAt);
|
|
const today = new Date();
|
|
const diffMs = lastUpdated.getTime() - today.getTime();
|
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
if (-diffDays >= 30) {
|
|
return `> ${Math.trunc(-diffDays / 30)} 個月`;
|
|
}
|
|
return `${-diffDays} day(s)`;
|
|
};
|
|
|
|
const workDayType = LeaveApplication.leaves[0].workDay.type;
|
|
let leaveDuration = 0;
|
|
|
|
if (workDayType === 'full') {
|
|
leaveDuration = LeaveApplication.leaves.length;
|
|
} else {
|
|
leaveDuration = 0.5;
|
|
}
|
|
|
|
return (
|
|
<tr
|
|
key={LeaveApplication._id.toString()}
|
|
className="text-center text-gray-600"
|
|
>
|
|
<td className="border-b px-4 py-2">
|
|
{LeaveApplication.leaves[0].type[0].toUpperCase() +
|
|
LeaveApplication.leaves[0].type.slice(1)}
|
|
</td>
|
|
<td className="border-b px-4 py-2">
|
|
{LeaveApplication.leaves[0].workDay.date}
|
|
</td>
|
|
<td className="border-b px-4 py-2">
|
|
{
|
|
LeaveApplication.leaves[LeaveApplication.leaves.length - 1].workDay
|
|
.date
|
|
}
|
|
</td>
|
|
<td className="border-b px-4 py-2">{leaveDuration}</td>
|
|
<td className="border-b px-4 py-2">
|
|
{LeaveApplication.leaves[0].workDay.type}
|
|
</td>
|
|
<td className="border-b px-4 py-2">{`${getLastUpdatedTime(LeaveApplication.updatedAt)}`}</td>
|
|
<td className="border-b px-4 py-2">{LeaveApplication.status}</td>
|
|
<td className="border-b px-4 py-2">
|
|
<Button
|
|
className="mx-auto border-gray-300 rounded-lg bg-transparent"
|
|
onClick={() => onApproveClick(LeaveApplication)}
|
|
>
|
|
Approve
|
|
</Button>
|
|
</td>
|
|
<td className="border-b px-4 py-2">
|
|
<Button
|
|
className="mx-auto border-gray-300 rounded-lg bg-transparent"
|
|
onClick={() => onRejectClick(LeaveApplication)}
|
|
>
|
|
Reject
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
);
|
|
};
|