updates
This commit is contained in:
parent
c47f7c1b6a
commit
38ef10da4d
@ -58,11 +58,11 @@ export default function JobCardsPage() {
|
||||
title: "Job Cards",
|
||||
actions: (
|
||||
<FormDialog classNames={{ dialogContent: 'min-w-6xl' }} title="Job Card" >
|
||||
{(resourceId) => (
|
||||
{(resourceId, {close}) => (
|
||||
<JobCardForm
|
||||
resourceId={resourceId}
|
||||
initialData={selectedItem}
|
||||
onSuccess={invalidateQuery}
|
||||
onSuccess={()=>{ invalidateQuery(); close();}}
|
||||
/>
|
||||
)}
|
||||
</FormDialog>
|
||||
|
||||
@ -55,9 +55,9 @@ export function LoginForm({
|
||||
|
||||
const { mutate, error, isPending: isSubmitting } = useMutation({
|
||||
mutationFn: (values: LoginFormValues) => api.auth.login(values),
|
||||
onSuccess: async (data) => {
|
||||
if (data.data?.token && data.data?.user) {
|
||||
await login(data.data.token, data.data.user as Parameters<typeof login>[1])
|
||||
onSuccess: async (data:any) => {
|
||||
if (data.token && data.user) {
|
||||
await login(data.token, data.user as Parameters<typeof login>[1])
|
||||
router.push("/")
|
||||
}
|
||||
},
|
||||
|
||||
159
apps/dashboard/modules/job-cards/job-card-check-in-dialog.tsx
Normal file
159
apps/dashboard/modules/job-cards/job-card-check-in-dialog.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
"use client"
|
||||
|
||||
import { z } from "zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { toast } from "sonner"
|
||||
import { useAuthApi } from "@/shared/useApi"
|
||||
import {
|
||||
Rhform,
|
||||
RhfTextField,
|
||||
RhfSelectField,
|
||||
RhfDateField,
|
||||
RhfTimeField,
|
||||
RhfAsyncSelectField,
|
||||
} from "@/shared/components/form"
|
||||
import { FieldGroup } from "@/shared/components/ui/field"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/shared/components/ui/dialog"
|
||||
import { DEPARTMENT_ROUTES } from "@garage/api"
|
||||
import { FUEL_LEVEL_OPTIONS } from "./job-card.schema"
|
||||
import { toId } from "@/shared/lib/utils"
|
||||
|
||||
// ── Schema ──
|
||||
|
||||
const checkInSchema = z.object({
|
||||
check_in_date: z.string().optional(),
|
||||
check_in_time: z.string().optional(),
|
||||
km_in: z.string().optional(),
|
||||
fuel_level: z.string().optional(),
|
||||
start_date: z.string().optional(),
|
||||
start_time: z.string().optional(),
|
||||
department: z.object({ value: z.any(), label: z.string() }).nullable().optional(),
|
||||
delivery_date: z.string().optional(),
|
||||
delivery_time: z.string().optional(),
|
||||
})
|
||||
|
||||
type CheckInFormValues = z.infer<typeof checkInSchema>
|
||||
|
||||
const mapLookupOption = (item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.name,
|
||||
})
|
||||
|
||||
const STORE_OBJECT = { storeObject: true } as const
|
||||
|
||||
// ── Props ──
|
||||
|
||||
type JobCardCheckInDialogProps = {
|
||||
jobCardId: string
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
// ── Component ──
|
||||
|
||||
export function JobCardCheckInDialog({
|
||||
jobCardId,
|
||||
open,
|
||||
onOpenChange,
|
||||
onSuccess,
|
||||
}: JobCardCheckInDialogProps) {
|
||||
const api = useAuthApi()
|
||||
|
||||
const now = new Date()
|
||||
const todayStr = now.toISOString().split("T")[0]
|
||||
const currentTime = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`
|
||||
|
||||
const form = useForm<CheckInFormValues>({
|
||||
resolver: zodResolver(checkInSchema),
|
||||
defaultValues: {
|
||||
check_in_date: todayStr,
|
||||
check_in_time: currentTime,
|
||||
km_in: "",
|
||||
fuel_level: "",
|
||||
start_date: "",
|
||||
start_time: "",
|
||||
department: null,
|
||||
delivery_date: "",
|
||||
delivery_time: "",
|
||||
},
|
||||
})
|
||||
|
||||
const handleSubmit = async (values: CheckInFormValues) => {
|
||||
try {
|
||||
await api.jobCards.checkIn(jobCardId, {
|
||||
check_in_date: values.check_in_date || undefined,
|
||||
check_in_time: values.check_in_time || undefined,
|
||||
km_in: values.km_in ? Number(values.km_in) : undefined,
|
||||
fuel_level: values.fuel_level || undefined,
|
||||
start_date: values.start_date || undefined,
|
||||
start_time: values.start_time || undefined,
|
||||
department_id: values.department ? Number(toId(values.department)) : undefined,
|
||||
delivery_date: values.delivery_date || undefined,
|
||||
delivery_time: values.delivery_time || undefined,
|
||||
})
|
||||
toast.success("Job card checked in successfully")
|
||||
form.reset()
|
||||
onSuccess?.()
|
||||
} catch {
|
||||
toast.error("Failed to check in job card")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="min-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-bold">Check In</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Rhform form={form} onSubmit={handleSubmit}>
|
||||
<FieldGroup>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfDateField name="check_in_date" label="Check-in Date" />
|
||||
<RhfTimeField name="check_in_time" label="Check-in Time" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfTextField name="km_in" label="KM In" type="number" placeholder="e.g. 50321" />
|
||||
<RhfSelectField name="fuel_level" label="Fuel Level" options={FUEL_LEVEL_OPTIONS} />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfDateField name="start_date" label="Start Date" />
|
||||
<RhfTimeField name="start_time" label="Start Time" />
|
||||
</div>
|
||||
<RhfAsyncSelectField
|
||||
name="department"
|
||||
label="Department"
|
||||
placeholder="Select department"
|
||||
queryKey={[DEPARTMENT_ROUTES.INDEX]}
|
||||
listFn={() => api.departments.list()}
|
||||
mapOption={(op:any)=> ({value: op.id, label: op.name})}
|
||||
{...STORE_OBJECT}
|
||||
getOptionLabel={op=>op.label}
|
||||
getOptionValue={op=>op}
|
||||
|
||||
/>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfDateField name="delivery_date" label="Delivery Date" />
|
||||
<RhfTimeField name="delivery_time" label="Delivery Time" />
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
{form.formState.isSubmitting ? "Checking in..." : "Check In"}
|
||||
</Button>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
</Rhform>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
139
apps/dashboard/modules/job-cards/job-card-delivery-dialog.tsx
Normal file
139
apps/dashboard/modules/job-cards/job-card-delivery-dialog.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
"use client"
|
||||
|
||||
import { z } from "zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { toast } from "sonner"
|
||||
import { useAuthApi } from "@/shared/useApi"
|
||||
import {
|
||||
Rhform,
|
||||
RhfTextField,
|
||||
RhfSelectField,
|
||||
RhfDateField,
|
||||
RhfTimeField,
|
||||
RhfAsyncSelectField,
|
||||
} from "@/shared/components/form"
|
||||
import { FieldGroup } from "@/shared/components/ui/field"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/shared/components/ui/dialog"
|
||||
import { DEPARTMENT_ROUTES } from "@garage/api"
|
||||
import { FUEL_LEVEL_OPTIONS } from "./job-card.schema"
|
||||
import { toId } from "@/shared/lib/utils"
|
||||
|
||||
// ── Schema ──
|
||||
|
||||
const deliverySchema = z.object({
|
||||
check_out_date: z.string().optional(),
|
||||
check_out_time: z.string().optional(),
|
||||
km_out: z.string().optional(),
|
||||
fuel_level: z.string().optional(),
|
||||
department: z.object({ value: z.number(), label: z.string() }).nullable().optional(),
|
||||
})
|
||||
|
||||
type DeliveryFormValues = z.infer<typeof deliverySchema>
|
||||
|
||||
const mapLookupOption = (item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.name,
|
||||
})
|
||||
|
||||
const STORE_OBJECT = { storeObject: true } as const
|
||||
|
||||
// ── Props ──
|
||||
|
||||
type JobCardDeliveryDialogProps = {
|
||||
jobCardId: string
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
// ── Component ──
|
||||
|
||||
export function JobCardDeliveryDialog({
|
||||
jobCardId,
|
||||
open,
|
||||
onOpenChange,
|
||||
onSuccess,
|
||||
}: JobCardDeliveryDialogProps) {
|
||||
const api = useAuthApi()
|
||||
|
||||
const now = new Date()
|
||||
const todayStr = now.toISOString().split("T")[0]
|
||||
const currentTime = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`
|
||||
|
||||
const form = useForm<DeliveryFormValues>({
|
||||
resolver: zodResolver(deliverySchema),
|
||||
defaultValues: {
|
||||
check_out_date: todayStr,
|
||||
check_out_time: currentTime,
|
||||
km_out: "",
|
||||
fuel_level: "",
|
||||
department: null,
|
||||
},
|
||||
})
|
||||
|
||||
const handleSubmit = async (values: DeliveryFormValues) => {
|
||||
try {
|
||||
await api.jobCards.delivery(jobCardId, {
|
||||
check_out_date: values.check_out_date || undefined,
|
||||
check_out_time: values.check_out_time || undefined,
|
||||
km_out: values.km_out ? Number(values.km_out) : undefined,
|
||||
fuel_level: values.fuel_level || undefined,
|
||||
department_id: values.department ? Number(toId(values.department as any)) : undefined,
|
||||
})
|
||||
toast.success("Job card marked as delivered successfully")
|
||||
form.reset()
|
||||
onSuccess?.()
|
||||
} catch {
|
||||
toast.error("Failed to mark job card as delivered")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="min-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-bold">Delivery</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Rhform form={form} onSubmit={handleSubmit}>
|
||||
<FieldGroup>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfDateField name="check_out_date" label="Check-out Date" />
|
||||
<RhfTimeField name="check_out_time" label="Check-out Time" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfTextField name="km_out" label="KM Out" type="number" placeholder="e.g. 50480" />
|
||||
<RhfSelectField name="fuel_level" label="Fuel Level" options={FUEL_LEVEL_OPTIONS} />
|
||||
</div>
|
||||
<RhfAsyncSelectField
|
||||
name="department"
|
||||
label="Department"
|
||||
placeholder="Select department"
|
||||
queryKey={[DEPARTMENT_ROUTES.INDEX]}
|
||||
listFn={() => api.departments.list()}
|
||||
mapOption={(op: any) => ({ value: op.id, label: op.name })}
|
||||
{...STORE_OBJECT}
|
||||
getOptionLabel={op => op.label}
|
||||
getOptionValue={op => op}
|
||||
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
{form.formState.isSubmitting ? "Delivering..." : "Mark as Delivered"}
|
||||
</Button>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
</Rhform>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@ -28,6 +28,7 @@ import { CrudShowResponse, JobCardsClient, PAYMENT_RECEIVED_ROUTES, PaymentRecei
|
||||
import { ResourcePage } from "@/shared/data-view/resource-page"
|
||||
import PaymentReceivedPage from "@/app/(authenticated)/sales/payment-received/page"
|
||||
import JobCardPaymentsReceived from "./job-card-payments-received"
|
||||
import { formatDate } from "@/shared/utils/formatters"
|
||||
|
||||
|
||||
|
||||
@ -116,7 +117,7 @@ export function JobCardGeneralInfo({ jobCard }: { jobCard: JobCard }) {
|
||||
<InfoItem
|
||||
icon={Calendar}
|
||||
label="Check-in Date"
|
||||
value={jobCard.check_in_date}
|
||||
value={formatDate(jobCard.check_in_date)}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={Gauge}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { toast } from "sonner"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
@ -20,6 +21,8 @@ import {
|
||||
PackageCheck,
|
||||
CheckCircle2,
|
||||
} from "lucide-react"
|
||||
import { JobCardCheckInDialog } from "./job-card-check-in-dialog"
|
||||
import { JobCardDeliveryDialog } from "./job-card-delivery-dialog"
|
||||
|
||||
// ── Status icon mapping ──
|
||||
|
||||
@ -45,6 +48,9 @@ export function JobCardStatusStepper({ jobCardId }: JobCardStatusStepperProps) {
|
||||
|
||||
const currentIndex = JOB_CARD_STATUSES.findIndex((s) => s.value === currentStatus)
|
||||
|
||||
const [checkInOpen, setCheckInOpen] = useState(false)
|
||||
const [deliveryOpen, setDeliveryOpen] = useState(false)
|
||||
|
||||
const { mutate, isPending, variables } = useMutation({
|
||||
mutationFn: async (status: JobCardStatus) => {
|
||||
const promise = api.jobCards.changeStatus(jobCardId, { status })
|
||||
@ -60,9 +66,17 @@ export function JobCardStatusStepper({ jobCardId }: JobCardStatusStepperProps) {
|
||||
},
|
||||
})
|
||||
|
||||
const handleClick = (status: JobCardStatus, index: number) => {
|
||||
const handleClick = (status: JobCardStatus) => {
|
||||
if (isPending) return
|
||||
if (status === currentStatus) return
|
||||
if (status === "check_in") {
|
||||
setCheckInOpen(true)
|
||||
return
|
||||
}
|
||||
if (status === "delivered") {
|
||||
setDeliveryOpen(true)
|
||||
return
|
||||
}
|
||||
mutate(status)
|
||||
}
|
||||
|
||||
@ -82,7 +96,7 @@ export function JobCardStatusStepper({ jobCardId }: JobCardStatusStepperProps) {
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleClick(step.value, index)}
|
||||
onClick={() => handleClick(step.value)}
|
||||
disabled={!isClickable}
|
||||
className={cn(
|
||||
"relative flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium transition-all",
|
||||
@ -120,6 +134,26 @@ export function JobCardStatusStepper({ jobCardId }: JobCardStatusStepperProps) {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<JobCardCheckInDialog
|
||||
jobCardId={jobCardId}
|
||||
open={checkInOpen}
|
||||
onOpenChange={setCheckInOpen}
|
||||
onSuccess={() => {
|
||||
setCheckInOpen(false)
|
||||
;(jobCard as any)?.setStatus("check_in")
|
||||
}}
|
||||
/>
|
||||
|
||||
<JobCardDeliveryDialog
|
||||
jobCardId={jobCardId}
|
||||
open={deliveryOpen}
|
||||
onOpenChange={setDeliveryOpen}
|
||||
onSuccess={() => {
|
||||
setDeliveryOpen(false)
|
||||
;(jobCard as any)?.setStatus("delivered")
|
||||
}}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@ -59,7 +59,9 @@ export function useFormDialog(paramKey?: string) {
|
||||
}
|
||||
|
||||
export default function FormDialog(props: {
|
||||
children: (resourceId: string | null) => React.ReactNode
|
||||
children: (resourceId: string | null,
|
||||
ctx: { open: (resourceId?: string) => void, close: () => void,isOpen:boolean }
|
||||
) => React.ReactNode
|
||||
title: string
|
||||
paramKey?: string
|
||||
classNames?: {
|
||||
@ -85,7 +87,7 @@ export default function FormDialog(props: {
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ScrollArea className={`max-h-[80vh] px-4 ${cn(props.classNames?.scrollArea)}`}>
|
||||
{props.children(resourceId)}
|
||||
{props.children(resourceId, { open, close , isOpen})}
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@ -24549,6 +24549,255 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/job-cards/{id}/check-in": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Job Cards"
|
||||
],
|
||||
"summary": "POST /api/job-cards/{id}/check-in",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"check_in_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"check_in_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"km_in": {
|
||||
"type": "integer"
|
||||
},
|
||||
"fuel_level": {
|
||||
"type": "string"
|
||||
},
|
||||
"start_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"department_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"delivery_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"delivery_time": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"check_in_date": "2026-04-07",
|
||||
"check_in_time": "09:30",
|
||||
"km_in": 50321,
|
||||
"fuel_level": "half",
|
||||
"start_date": "2026-04-07",
|
||||
"start_time": "10:00",
|
||||
"department_id": 1,
|
||||
"delivery_date": "2026-04-08",
|
||||
"delivery_time": "17:15"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"check_in_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"check_in_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"km_in": {
|
||||
"type": "integer"
|
||||
},
|
||||
"fuel_level": {
|
||||
"type": "string"
|
||||
},
|
||||
"start_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"department_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"delivery_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"delivery_time": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"message": "Job card checked in successfully.",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"status": "check_in",
|
||||
"check_in_date": "2026-04-07T00:00:00.000000Z",
|
||||
"check_in_time": "09:30:00",
|
||||
"km_in": 50321,
|
||||
"fuel_level": "half",
|
||||
"start_date": "2026-04-07T00:00:00.000000Z",
|
||||
"start_time": "10:00:00",
|
||||
"department_id": 1,
|
||||
"delivery_date": "2026-04-08T00:00:00.000000Z",
|
||||
"delivery_time": "17:15:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/job-cards/{id}/delivery": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Job Cards"
|
||||
],
|
||||
"summary": "POST /api/job-cards/{id}/delivery",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"check_out_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"check_out_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"km_out": {
|
||||
"type": "integer"
|
||||
},
|
||||
"fuel_level": {
|
||||
"type": "string"
|
||||
},
|
||||
"department_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"check_out_date": "2026-04-08",
|
||||
"check_out_time": "17:15",
|
||||
"km_out": 50480,
|
||||
"fuel_level": "quarter",
|
||||
"department_id": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"check_out_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"check_out_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"km_out": {
|
||||
"type": "integer"
|
||||
},
|
||||
"fuel_level": {
|
||||
"type": "string"
|
||||
},
|
||||
"department_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"message": "Job card marked as delivered successfully.",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"status": "delivered",
|
||||
"check_out_date": "2026-04-08",
|
||||
"check_out_time": "17:15:00",
|
||||
"km_out": 50480,
|
||||
"fuel_level": "quarter",
|
||||
"department_id": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/job-cards/{id}/add-customer-remark": {
|
||||
"post": {
|
||||
"tags": [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "4fac95bb-3550-48ab-9943-212b6e3a11d8",
|
||||
"_postman_id": "bf46649e-48e0-49d5-aa87-1790dcbc3c24",
|
||||
"name": "Reparee Collection",
|
||||
"description": "Auto-generated from OpenAPI spec. Import storage/app/openapi-default.json for the full schema.",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
@ -8025,7 +8025,7 @@
|
||||
"{{table}}"
|
||||
]
|
||||
},
|
||||
"description": "Set {{table}} to job_cards or estimates. Returns next sequence like ORD-0001 or EST-0001."
|
||||
"description": "Set {{table}} to any supported table with a number column. Explicit prefixes: job_cards=ORD-, estimates=EST-, invoices=INV-, purchase_orders=PO-, bills=BILL-, expenses=EXP-, tasks=TASK-, appointments=APPT-, services=SRV-, parts=PART-, expense_items=EXP-ITEM-, inspections=INS-. Returns next sequence like ORD-0001."
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
@ -16771,6 +16771,222 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "POST /api/job-cards/{id}/check-in",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{auth_token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"check_in_date\": \"2026-04-07\",\n \"check_in_time\": \"09:30\",\n \"km_in\": 50321,\n \"fuel_level\": \"half\",\n \"start_date\": \"2026-04-07\",\n \"start_time\": \"10:00\",\n \"department_id\": 1,\n \"delivery_date\": \"2026-04-08\",\n \"delivery_time\": \"17:15\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/job-cards/{{id}}/check-in",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"job-cards",
|
||||
"{{id}}",
|
||||
"check-in"
|
||||
]
|
||||
},
|
||||
"description": "Marks job card as `check_in` and updates check-in context. Optional fields: `check_in_date`, `check_in_time` (HH:MM or HH:MM:SS), `km_in`, `fuel_level`, `start_date`, `start_time`, `department_id`, `delivery_date`, `delivery_time` (HH:MM or HH:MM:SS). If check-in date/time is omitted, server current date/time is used."
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "200 OK",
|
||||
"originalRequest": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{auth_token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"check_in_date\": \"2026-04-07\",\n \"check_in_time\": \"09:30\",\n \"km_in\": 50321,\n \"fuel_level\": \"half\",\n \"start_date\": \"2026-04-07\",\n \"start_time\": \"10:00\",\n \"department_id\": 1,\n \"delivery_date\": \"2026-04-08\",\n \"delivery_time\": \"17:15\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/job-cards/{{id}}/check-in",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"job-cards",
|
||||
"{{id}}",
|
||||
"check-in"
|
||||
]
|
||||
}
|
||||
},
|
||||
"status": "OK",
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": "json",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"cookie": [],
|
||||
"body": "{\n \"message\": \"Job card checked in successfully.\",\n \"data\": {\n \"id\": 1,\n \"status\": \"check_in\",\n \"check_in_date\": \"2026-04-07T00:00:00.000000Z\",\n \"check_in_time\": \"09:30:00\",\n \"km_in\": 50321,\n \"fuel_level\": \"half\",\n \"start_date\": \"2026-04-07T00:00:00.000000Z\",\n \"start_time\": \"10:00:00\",\n \"department_id\": 1,\n \"delivery_date\": \"2026-04-08T00:00:00.000000Z\",\n \"delivery_time\": \"17:15:00\"\n }\n}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "POST /api/job-cards/{id}/delivery",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{auth_token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"check_out_date\": \"2026-04-08\",\n \"check_out_time\": \"17:15\",\n \"km_out\": 50480,\n \"fuel_level\": \"quarter\",\n \"department_id\": 1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/job-cards/{{id}}/delivery",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"job-cards",
|
||||
"{{id}}",
|
||||
"delivery"
|
||||
]
|
||||
},
|
||||
"description": "Marks job card as `delivered` and updates delivery context. Optional fields: `check_out_date`, `check_out_time` (HH:MM or HH:MM:SS), `km_out`, `fuel_level`, `department_id`. If check-out date/time is omitted, server current date/time is used."
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "200 OK",
|
||||
"originalRequest": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{auth_token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"check_out_date\": \"2026-04-08\",\n \"check_out_time\": \"17:15\",\n \"km_out\": 50480,\n \"fuel_level\": \"quarter\",\n \"department_id\": 1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/job-cards/{{id}}/delivery",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"job-cards",
|
||||
"{{id}}",
|
||||
"delivery"
|
||||
]
|
||||
}
|
||||
},
|
||||
"status": "OK",
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": "json",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"cookie": [],
|
||||
"body": "{\n \"message\": \"Job card marked as delivered successfully.\",\n \"data\": {\n \"id\": 1,\n \"status\": \"delivered\",\n \"check_out_date\": \"2026-04-08\",\n \"check_out_time\": \"17:15:00\",\n \"km_out\": 50480,\n \"fuel_level\": \"quarter\",\n \"department_id\": 1\n }\n}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "POST /api/job-cards/{id}/add-customer-remark",
|
||||
"request": {
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { CrudClient } from "../infra/crud-client"
|
||||
import type { ApiClientOptions } from "../infra/client"
|
||||
import type { ApiOperationResponse, ApiPath, ApiRequestBody, ApiResponse } from "../infra/types"
|
||||
import { ApiBaseResponse } from "../contracts/types"
|
||||
import { ApiBaseResponse } from "src/contracts/types"
|
||||
|
||||
export const JOB_CARD_ROUTES = {
|
||||
INDEX: "/api/job-cards",
|
||||
BY_ID: "/api/job-cards/{id}",
|
||||
CHANGE_DATE: "/api/job-cards/{id}/change-date",
|
||||
CHANGE_STATUS: "/api/job-cards/{id}/change-status",
|
||||
CHECK_IN: "/api/job-cards/{id}/check-in",
|
||||
DELIVERY: "/api/job-cards/{id}/delivery",
|
||||
ADD_CUSTOMER_REMARK: "/api/job-cards/{id}/add-customer-remark",
|
||||
EDIT_CUSTOMER_REMARK: "/api/job-cards/{id}/edit-customer-remark",
|
||||
DELETE_CUSTOMER_REMARK: "/api/job-cards/{id}/delete-customer-remark",
|
||||
@ -53,6 +55,14 @@ export class JobCardsClient extends CrudClient<
|
||||
return this.post(JOB_CARD_ROUTES.CHANGE_STATUS, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async checkIn(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.CHECK_IN, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.CHECK_IN, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async delivery(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.DELIVERY, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.DELIVERY, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async addCustomerRemark(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.ADD_CUSTOMER_REMARK, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.ADD_CUSTOMER_REMARK, payload, { params: { id } })
|
||||
}
|
||||
|
||||
@ -16105,6 +16105,189 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/job-cards/{id}/check-in": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** POST /api/job-cards/{id}/check-in */
|
||||
post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
/**
|
||||
* @example {
|
||||
* "check_in_date": "2026-04-07",
|
||||
* "check_in_time": "09:30",
|
||||
* "km_in": 50321,
|
||||
* "fuel_level": "half",
|
||||
* "start_date": "2026-04-07",
|
||||
* "start_time": "10:00",
|
||||
* "department_id": 1,
|
||||
* "delivery_date": "2026-04-08",
|
||||
* "delivery_time": "17:15"
|
||||
* }
|
||||
*/
|
||||
"application/json": {
|
||||
check_in_date?: string;
|
||||
check_in_time?: string;
|
||||
km_in?: number;
|
||||
fuel_level?: string;
|
||||
start_date?: string;
|
||||
start_time?: string;
|
||||
department_id?: number;
|
||||
delivery_date?: string;
|
||||
delivery_time?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
/**
|
||||
* @example {
|
||||
* "message": "Job card checked in successfully.",
|
||||
* "data": {
|
||||
* "id": 1,
|
||||
* "status": "check_in",
|
||||
* "check_in_date": "2026-04-07T00:00:00.000000Z",
|
||||
* "check_in_time": "09:30:00",
|
||||
* "km_in": 50321,
|
||||
* "fuel_level": "half",
|
||||
* "start_date": "2026-04-07T00:00:00.000000Z",
|
||||
* "start_time": "10:00:00",
|
||||
* "department_id": 1,
|
||||
* "delivery_date": "2026-04-08T00:00:00.000000Z",
|
||||
* "delivery_time": "17:15:00"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
"application/json": {
|
||||
message?: string;
|
||||
data?: {
|
||||
id?: number;
|
||||
status?: string;
|
||||
/** Format: date-time */
|
||||
check_in_date?: string;
|
||||
check_in_time?: string;
|
||||
km_in?: number;
|
||||
fuel_level?: string;
|
||||
/** Format: date-time */
|
||||
start_date?: string;
|
||||
start_time?: string;
|
||||
department_id?: number;
|
||||
/** Format: date-time */
|
||||
delivery_date?: string;
|
||||
delivery_time?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/job-cards/{id}/delivery": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** POST /api/job-cards/{id}/delivery */
|
||||
post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
/**
|
||||
* @example {
|
||||
* "check_out_date": "2026-04-08",
|
||||
* "check_out_time": "17:15",
|
||||
* "km_out": 50480,
|
||||
* "fuel_level": "quarter",
|
||||
* "department_id": 1
|
||||
* }
|
||||
*/
|
||||
"application/json": {
|
||||
check_out_date?: string;
|
||||
check_out_time?: string;
|
||||
km_out?: number;
|
||||
fuel_level?: string;
|
||||
department_id?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
/**
|
||||
* @example {
|
||||
* "message": "Job card marked as delivered successfully.",
|
||||
* "data": {
|
||||
* "id": 1,
|
||||
* "status": "delivered",
|
||||
* "check_out_date": "2026-04-08",
|
||||
* "check_out_time": "17:15:00",
|
||||
* "km_out": 50480,
|
||||
* "fuel_level": "quarter",
|
||||
* "department_id": 1
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
"application/json": {
|
||||
message?: string;
|
||||
data?: {
|
||||
id?: number;
|
||||
status?: string;
|
||||
check_out_date?: string;
|
||||
check_out_time?: string;
|
||||
km_out?: number;
|
||||
fuel_level?: string;
|
||||
department_id?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/job-cards/{id}/add-customer-remark": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user