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

93 lines
3.2 KiB
TypeScript

import { z } from "zod"
import { EmployeeType, WageType } from "@garage/api"
const relationFieldSchema = z
.object({ value: z.string(), label: z.string() })
.nullable()
const optionalNumericField = z.preprocess(
(value) => {
if (value === "" || value === null || value === undefined) {
return null
}
return Number(value)
},
z.number().finite().nullable(),
)
const optionalIntegerIdField = z.preprocess(
(value) => {
if (value === "" || value === null || value === undefined) {
return null
}
return Number(value)
},
z.number().int().positive().nullable(),
)
const optionalTrimmedString = (max: number, msg?: string) =>
z.preprocess(
(value) => (typeof value !== "string" ? value : value.trim() === "" ? undefined : value.trim()),
z.string().max(max, msg ?? `Must be at most ${max} characters`).optional(),
)
const STATUS_OPTIONS = ["active", "inactive"] as const
const TYPE_OPTIONS = EmployeeType
const WAGE_TYPE_OPTIONS = WageType
const GENDER_OPTIONS = ["male", "female"] as const
const MARITAL_OPTIONS = ["single", "married"] as const
const optionalDate = z.preprocess(
(value) => (typeof value !== "string" || value.trim() === "" ? undefined : value),
z.string().optional(),
)
const employeeFormSchema = z.object({
department: relationFieldSchema,
country: relationFieldSchema,
shop_calender: relationFieldSchema,
shop_timing: relationFieldSchema,
role_id: optionalIntegerIdField,
first_name: z.string().trim().min(1, "First name is required").max(50, "First name must be at most 50 characters"),
last_name: z.string().trim().min(1, "Last name is required").max(50, "Last name must be at most 50 characters"),
email: z.string().trim().min(1, "Email is required").email("Enter a valid email address").max(100, "Email must be at most 100 characters"),
password: z.string().min(6, "Password must be at least 6 characters"),
phone: optionalTrimmedString(30),
designation: optionalTrimmedString(50),
salary: optionalNumericField,
wage_type: z.enum(WAGE_TYPE_OPTIONS).nullable(),
status: z.enum(STATUS_OPTIONS),
type: z.enum(TYPE_OPTIONS),
track_attendance: z.boolean(),
notify_owner_when_punch_in_out: z.boolean(),
geo_fence_radius: optionalNumericField,
// Extended profile fields
address: optionalTrimmedString(500),
date_of_birth: optionalDate,
gender: z.enum(GENDER_OPTIONS).nullable().optional(),
marital_status: z.enum(MARITAL_OPTIONS).nullable().optional(),
national_id: optionalTrimmedString(50),
hire_date: optionalDate,
emergency_contact_name: optionalTrimmedString(100),
emergency_contact_phone: optionalTrimmedString(30),
bank_name: optionalTrimmedString(100),
bank_account_number: optionalTrimmedString(50),
bank_iban: optionalTrimmedString(50),
hourly_rate: optionalNumericField,
commission_rate: optionalNumericField,
})
type EmployeeFormValues = z.infer<typeof employeeFormSchema>
export {
employeeFormSchema,
relationFieldSchema,
STATUS_OPTIONS,
TYPE_OPTIONS,
WAGE_TYPE_OPTIONS,
GENDER_OPTIONS,
MARITAL_OPTIONS,
}
export type { EmployeeFormValues }