humam kerdiah 3c55be5052 feat: add payroll, time clocks, and leave request modules
- Implemented PayrollPage for managing payroll runs with CRUD operations.
- Created TimeClocksPage for tracking employee clock-ins and clock-outs.
- Developed TimeSheetsPage for displaying employee timesheets.
- Added EmployeeCertificationForm and EmployeeDocumentForm for managing employee certifications and documents.
- Introduced LeaveRequestForm for submitting and managing leave requests.
- Added usePermissions hook for UI-side permission checks.
- Created LeaveRequestsClient and PayrollClient for API interactions.
2026-05-21 13:30:06 +04:00

103 lines
4.0 KiB
TypeScript

"use client"
import { ResourcePage } from "@/shared/data-view/resource-page"
import { ColumnHeader } from "@/shared/data-view/table-view"
import FormDialog from "@/shared/components/form-dialog"
import { PayrollRunForm } from "@/modules/payroll/payroll-run-form"
import { PAYROLL_ROUTES } from "@garage/api"
import type { PayrollClient } from "@garage/api"
import { Badge } from "@/shared/components/ui/badge"
import { useRouter } from "next/navigation"
import { formatEnum } from "@/shared/utils/formatters"
type RunRow = {
id: number
reference?: string | null
period_start?: string
period_end?: string
status: string
entries_count?: number
generated_at?: string | null
finalized_at?: string | null
paid_at?: string | null
}
function formatDate(value: unknown) {
if (!value || typeof value !== "string") return "—"
const d = new Date(value)
return Number.isNaN(d.getTime()) ? value : d.toLocaleDateString()
}
function statusVariant(status: string): "default" | "secondary" | "outline" {
if (status === "paid") return "default"
if (status === "finalized") return "outline"
return "secondary"
}
export default function PayrollPage() {
const router = useRouter()
return (
<ResourcePage<PayrollClient>
pageTitle="Payroll"
routeKey={PAYROLL_ROUTES.RUNS_INDEX}
getClient={(api) => api.payroll}
statusFilter={{
statuses: ["draft", "finalized", "paid"],
paramKey: "status",
}}
onRowClick={(row) => router.push(`/productivity/payroll/${(row as any).id}`)}
headerProps={({ invalidateQuery }) => ({
actions: (
<FormDialog title="Payroll Run">
{() => (
<PayrollRunForm
onSuccess={(runId) => {
invalidateQuery()
if (runId) router.push(`/productivity/payroll/${runId}`)
}}
/>
)}
</FormDialog>
),
})}
columns={({ actionsColumn }) => [
{
accessorKey: "reference",
header: ({ column }) => <ColumnHeader column={column} title="Reference" />,
cell: ({ row }) => <span className="font-mono text-xs">{(row.original as any).reference ?? `#${(row.original as any).id}`}</span>,
},
{
accessorKey: "period_start",
header: ({ column }) => <ColumnHeader column={column} title="Period Start" />,
cell: ({ row }) => formatDate((row.original as any).period_start),
},
{
accessorKey: "period_end",
header: ({ column }) => <ColumnHeader column={column} title="Period End" />,
cell: ({ row }) => formatDate((row.original as any).period_end),
},
{
accessorKey: "entries_count",
header: ({ column }) => <ColumnHeader column={column} title="Entries" />,
cell: ({ row }) => (row.original as any).entries_count ?? "—",
},
{
accessorKey: "status",
header: ({ column }) => <ColumnHeader column={column} title="Status" />,
cell: ({ row }) => {
const s = (row.original as any).status as string
return <Badge variant={statusVariant(s)}>{formatEnum(s)}</Badge>
},
},
{
accessorKey: "finalized_at",
header: ({ column }) => <ColumnHeader column={column} title="Finalized" />,
cell: ({ row }) => formatDate((row.original as any).finalized_at),
},
actionsColumn({ onEdit: undefined }),
]}
/>
)
}