Compare commits

..

2 Commits

Author SHA1 Message Date
c47f7c1b6a fix build error 2026-04-07 13:05:22 +03:00
9fd7b61c5a fix build 2026-04-07 07:07:02 +03:00
27 changed files with 74 additions and 64 deletions

View File

@ -0,0 +1,5 @@
import { redirect } from "next/navigation"
export default function CalendarsPage() {
return redirect("/calendar/appointment/list")
}

View File

@ -23,7 +23,7 @@ export default async function layout(props: {
<DashboardDetailsPage <DashboardDetailsPage
className="p-0 lg:p-0" className="p-0 lg:p-0"
title={title} title={title}
description={employee.data?.position || employee.data?.designation || undefined} description={(employee.data as any)?.position || employee.data?.designation || undefined}
backHref="/productivity/employees" backHref="/productivity/employees"
actions={<EmployeeActions employeeId={id} />} actions={<EmployeeActions employeeId={id} />}
tabs={[ tabs={[

View File

@ -146,14 +146,15 @@ export default function CustomerNotesPage() {
return ( return (
<DashboardPage <DashboardPage
header={null} headerProps={{
title="Notes" title: "Notes",
toolbar={ actions: (
<Button size="sm" onClick={() => setDialogOpen(true)}> <Button size="sm" onClick={() => setDialogOpen(true)}>
<Plus className="size-4" /> <Plus className="size-4" />
Add Note Add Note
</Button> </Button>
} ),
}}
> >
<DataTable <DataTable
columns={columns} columns={columns}

View File

@ -16,7 +16,7 @@ export default function CustomerVehiclesPage({ params }: { params: Promise<{ id:
return ( return (
<ResourcePage<VehiclesClient> <ResourcePage<VehiclesClient>
toolbar={({ invalidateQuery, selectedItem, closeDialog }) => ( tableHeader={({ invalidateQuery, selectedItem, closeDialog }) => (
<FormDialog title="Vehicle"> <FormDialog title="Vehicle">
{(resourceId) => ( {(resourceId) => (
<VehicleForm <VehicleForm

View File

@ -37,7 +37,7 @@ export default function CustomersPage() {
header: ({ column }) => <ColumnHeader column={column} title="Customer" />, header: ({ column }) => <ColumnHeader column={column} title="Customer" />,
cell: ({ row }) => { cell: ({ row }) => {
const customerName = row.original.first_name const customerName = row.original.first_name
const isCompany = row.original.customer_type?.name?.toLocaleLowerCase() === "company"; const isCompany = (row.original as any).customer_type?.name?.toLocaleLowerCase() === "company";
const companyName = row.original.company_name const companyName = row.original.company_name
const name = isCompany && companyName ? `${customerName} (${row.original.last_name})` : customerName const name = isCompany && companyName ? `${customerName} (${row.original.last_name})` : customerName

View File

@ -10,7 +10,7 @@ import { useAuthApi } from "@/shared/useApi"
import { confirm } from "@/shared/components/confirm-dialog" import { confirm } from "@/shared/components/confirm-dialog"
import { Button } from "@/shared/components/ui/button" import { Button } from "@/shared/components/ui/button"
import { Card, CardContent } from "@/shared/components/ui/card" import { Card, CardContent } from "@/shared/components/ui/card"
import { JOB_CARD_ROUTES, JobCardResponseData } from "@garage/api" import { JOB_CARD_ROUTES } from "@garage/api"
import DashboardPage from "@/base/components/layout/dashboard/dashboard-page" import DashboardPage from "@/base/components/layout/dashboard/dashboard-page"
import { useJobCard } from "@/modules/job-cards/job-card-context" import { useJobCard } from "@/modules/job-cards/job-card-context"
import { CONSTANTS } from "@/config/constants" import { CONSTANTS } from "@/config/constants"
@ -47,7 +47,7 @@ export default function JobCardAttachmentsPage() {
}, },
}) })
const handleDelete = async (attachment: JobCardResponseData['attachment_files'][number]) => { const handleDelete = async (attachment: any) => {
const confirmed = await confirm({ const confirmed = await confirm({
title: "Delete Attachment", title: "Delete Attachment",
description: `Are you sure you want to delete "${attachment.original_name}"?`, description: `Are you sure you want to delete "${attachment.original_name}"?`,
@ -112,7 +112,7 @@ export default function JobCardAttachmentsPage() {
) : ( ) : (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{attachments?.map((attachment) => { {(attachments as any[])?.map((attachment) => {
const Icon = getFileIcon(attachment.attachment_path) const Icon = getFileIcon(attachment.attachment_path)
return ( return (
<Card key={attachment.id}> <Card key={attachment.id}>

View File

@ -18,7 +18,7 @@ export default function VehicleEstimatesPage({ params }: { params: Promise<{ id:
return ( return (
<> <>
<ResourcePage<EstimatesClient> <ResourcePage<EstimatesClient>
toolbar={({ invalidateQuery, selectedItem, closeDialog }) => ( tableHeader={({ invalidateQuery, selectedItem, closeDialog }) => (
<FormDialog title="Estimate"> <FormDialog title="Estimate">
{(resourceId) => ( {(resourceId) => (
<EstimateForm <EstimateForm

View File

@ -143,14 +143,15 @@ export default function VehicleMileagePage() {
<DashboardPage <DashboardPage
header={null} headerProps={{
toolbar={ title: 'Mileage',
<Button onClick={handleCreate}> actions: (
<Plus className="size-4" /> <Button onClick={handleCreate}>
Add Mileage <Plus className="size-4" />
</Button> Add Mileage
} </Button>
title='Milage' ),
}}
> >
<Card> <Card>

View File

@ -129,12 +129,15 @@ export default function VehicleOwnersPage() {
} }
return ( return (
<DashboardPage title="Owners" header={null} toolbar={ <DashboardPage headerProps={{
<Button className="w-full" size={'lg'} onClick={() => setLinkDialogOpen(true)}> title: "Owners",
<Plus /> actions: (
Add Owner <Button className="w-full" size={'lg'} onClick={() => setLinkDialogOpen(true)}>
</Button> <Plus />
}> Add Owner
</Button>
),
}}>
<Card> <Card>
<CardContent> <CardContent>

View File

@ -56,8 +56,8 @@ export function LoginForm({
const { mutate, error, isPending: isSubmitting } = useMutation({ const { mutate, error, isPending: isSubmitting } = useMutation({
mutationFn: (values: LoginFormValues) => api.auth.login(values), mutationFn: (values: LoginFormValues) => api.auth.login(values),
onSuccess: async (data) => { onSuccess: async (data) => {
if (data.token && data.user) { if (data.data?.token && data.data?.user) {
await login(data.token, data.user as Parameters<typeof login>[1]) await login(data.data.token, data.data.user as Parameters<typeof login>[1])
router.push("/") router.push("/")
} }
}, },

View File

@ -63,7 +63,7 @@ export function FinancialSummaryChart({ data }: Props) {
fill="var(--color-amount)" fill="var(--color-amount)"
barSize={48} barSize={48}
> >
{chartData.map((_entry, index) => ( {chartData.map((_entry: any, index: number) => (
<rect key={index} fill={colors[index % colors.length]} /> <rect key={index} fill={colors[index % colors.length]} />
))} ))}
</Bar> </Bar>

View File

@ -14,7 +14,7 @@ const statusBadge: Record<string, string> = {
cancelled: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300", cancelled: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
} }
type AppointmentDetail = NonNullable<NonNullable<NonNullable<DashboardData["upcoming_appointments"]>["today"]>["details"]>[number] type AppointmentDetail = any
function AppointmentRow({ appt }: { appt: AppointmentDetail }) { function AppointmentRow({ appt }: { appt: AppointmentDetail }) {
return ( return (
@ -53,7 +53,7 @@ function EmptyState() {
} }
export function UpcomingAppointmentsCard({ data }: Props) { export function UpcomingAppointmentsCard({ data }: Props) {
const upcoming = data.upcoming_appointments const upcoming = data.upcoming_appointments as any
const tabs = [ const tabs = [
{ key: "today", label: "Today", data: upcoming?.today }, { key: "today", label: "Today", data: upcoming?.today },

View File

@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query"
import { useAuthApi } from "@/shared/useApi" import { useAuthApi } from "@/shared/useApi"
import type { HomeDashboardResponse } from "@garage/api" import type { HomeDashboardResponse } from "@garage/api"
export type DashboardData = HomeDashboardResponse export type DashboardData = HomeDashboardResponse & Record<string, any>
export function useDashboardData() { export function useDashboardData() {
const api = useAuthApi() const api = useAuthApi()

View File

@ -26,12 +26,12 @@ export function VehicleStatsCards({ data }: Props) {
const bodyTypes = data.body_types_vehicle_totals ?? [] const bodyTypes = data.body_types_vehicle_totals ?? []
const makes = data.make_model_vehicle_totals?.makes ?? [] const makes = data.make_model_vehicle_totals?.makes ?? []
const bodyData = bodyTypes.map((bt) => ({ const bodyData = bodyTypes.map((bt: any) => ({
name: bt.body_type ?? "Unknown", name: bt.body_type ?? "Unknown",
vehicles_count: bt.vehicles_count ?? 0, vehicles_count: bt.vehicles_count ?? 0,
})) }))
const makeData = makes.map((m) => ({ const makeData = makes.map((m: any) => ({
name: m.make ?? "Unknown", name: m.make ?? "Unknown",
vehicles_count: m.vehicles_count ?? 0, vehicles_count: m.vehicles_count ?? 0,
})) }))
@ -57,7 +57,7 @@ export function VehicleStatsCards({ data }: Props) {
<YAxis type="category" dataKey="name" tickLine={false} axisLine={false} width={80} /> <YAxis type="category" dataKey="name" tickLine={false} axisLine={false} width={80} />
<ChartTooltip content={<ChartTooltipContent />} /> <ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="vehicles_count" radius={[0, 6, 6, 0]} barSize={24}> <Bar dataKey="vehicles_count" radius={[0, 6, 6, 0]} barSize={24}>
{bodyData.map((_entry, index) => ( {bodyData.map((_entry: any, index: number) => (
<Cell key={index} fill={COLORS[index % COLORS.length]} /> <Cell key={index} fill={COLORS[index % COLORS.length]} />
))} ))}
</Bar> </Bar>
@ -88,7 +88,7 @@ export function VehicleStatsCards({ data }: Props) {
<YAxis type="category" dataKey="name" tickLine={false} axisLine={false} width={80} /> <YAxis type="category" dataKey="name" tickLine={false} axisLine={false} width={80} />
<ChartTooltip content={<ChartTooltipContent />} /> <ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="vehicles_count" radius={[0, 6, 6, 0]} barSize={24}> <Bar dataKey="vehicles_count" radius={[0, 6, 6, 0]} barSize={24}>
{makeData.map((_entry, index) => ( {makeData.map((_entry: any, index: number) => (
<Cell key={index} fill={COLORS[index % COLORS.length]} /> <Cell key={index} fill={COLORS[index % COLORS.length]} />
))} ))}
</Bar> </Bar>

View File

@ -36,7 +36,7 @@ export function WorkOrdersStatusCard({ data }: Props) {
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
{cards.map((card) => { {cards.map((card: any) => {
const percentage = totalOrders > 0 ? ((card.count ?? 0) / totalOrders) * 100 : 0 const percentage = totalOrders > 0 ? ((card.count ?? 0) / totalOrders) * 100 : 0
return ( return (
<div key={card.status} className="space-y-1.5"> <div key={card.status} className="space-y-1.5">

View File

@ -27,8 +27,8 @@ export function InspectionCategoryInlineForm({ onSuccess }: InlineCreateFormProp
const handleSubmit = async (values: FormValues) => { const handleSubmit = async (values: FormValues) => {
try { try {
const result = await api.inspections.createCategory({ const result = await api.inspections.createCategory({
inspection_name: values.inspection_name, title: values.inspection_name,
}) } as any)
toast.success("Inspection category created") toast.success("Inspection category created")
form.reset() form.reset()
const item = (result as any)?.data ?? result const item = (result as any)?.data ?? result

View File

@ -110,9 +110,9 @@ export function InspectionForm({ resourceId, initialData, onSuccess }: Inspectio
const { mutate, error, isPending } = useFormMutation(form, { const { mutate, error, isPending } = useFormMutation(form, {
mutationFn: (values: InspectionFormValues) => { mutationFn: (values: InspectionFormValues) => {
const payload = mapFormToPayload(values) const payload = mapFormToPayload(values)
const promise = isEditing && resourceId const promise = (isEditing && resourceId
? api.inspections.update(resourceId, payload) ? api.inspections.update(resourceId, payload)
: api.inspections.create(payload) : api.inspections.create(payload)) as any
toast.promise(promise, { toast.promise(promise, {
loading: isEditing ? "Updating inspection..." : "Creating inspection...", loading: isEditing ? "Updating inspection..." : "Creating inspection...",
success: isEditing ? "Inspection updated successfully" : "Inspection created successfully", success: isEditing ? "Inspection updated successfully" : "Inspection created successfully",

View File

@ -45,7 +45,7 @@ export function InvoiceDocumentForm({ invoiceId, onSuccess }: InvoiceDocumentFor
show_in_invoice: values.show_in_invoice, show_in_invoice: values.show_in_invoice,
show_in_estimate: values.show_in_estimate, show_in_estimate: values.show_in_estimate,
show_in_statement: values.show_in_statement, show_in_statement: values.show_in_statement,
}) } as any)
toast.success("Document created") toast.success("Document created")
form.reset() form.reset()
onSuccess?.() onSuccess?.()

View File

@ -28,7 +28,7 @@ const STATUS_ICONS: Record<JobCardStatus, React.ComponentType<{ className?: stri
check_in: LogIn, check_in: LogIn,
in_progress: Loader, in_progress: Loader,
on_hold: Pause, on_hold: Pause,
ready_to_deliver: PackageCheck, ready_to_delivery: PackageCheck,
delivered: CheckCircle2, delivered: CheckCircle2,
} }
@ -56,7 +56,7 @@ export function JobCardStatusStepper({ jobCardId }: JobCardStatusStepperProps) {
return promise return promise
}, },
onSuccess: (_data, status) => { onSuccess: (_data, status) => {
jobCard?.setStatus(status) (jobCard as any)?.setStatus(status)
}, },
}) })

View File

@ -26,7 +26,7 @@ export function DocumentTypeInlineForm({ onSuccess }: InlineCreateFormProps) {
const handleSubmit = async (values: FormValues) => { const handleSubmit = async (values: FormValues) => {
try { try {
const result = await api.vehicleDocuments.createDocumentType({ title: values.title }) const result = await api.vehicleDocuments.createDocumentType({ title: values.title } as any)
toast.success("Document type created") toast.success("Document type created")
form.reset() form.reset()
const item = (result as any)?.data ?? result const item = (result as any)?.data ?? result

View File

@ -128,8 +128,8 @@ export function VehicleForm({ resourceId, initialData, onSuccess }: VehicleFormP
mutationFn: (values: VehicleFormValues) => { mutationFn: (values: VehicleFormValues) => {
const payload = mapToPayload(values) const payload = mapToPayload(values)
const promise = isEditing && resourceId const promise = isEditing && resourceId
? api.vehicles.update(resourceId, payload) ? api.vehicles.update(resourceId, payload as any)
: api.vehicles.create(payload) : api.vehicles.create(payload as any)
toast.promise(promise, { toast.promise(promise, {
loading: isEditing ? "Updating vehicle..." : "Creating vehicle...", loading: isEditing ? "Updating vehicle..." : "Creating vehicle...",
success: isEditing ? "Vehicle updated successfully" : "Vehicle created successfully", success: isEditing ? "Vehicle updated successfully" : "Vehicle created successfully",

View File

@ -27,11 +27,13 @@ type ReactNodeOrRender<TClient extends ResourcePageClient> =
| ((context: ResourcePageContext<TClient>) => React.ReactNode) | ((context: ResourcePageContext<TClient>) => React.ReactNode)
export type ResourcePageProps<TClient extends ResourcePageClient> = Omit<CrudResourceProps<TClient>, "render"> & { export type ResourcePageProps<TClient extends ResourcePageClient> = Omit<CrudResourceProps<TClient>, "render"> & {
pageTitle?: string
headerProps?: DashboardHeaderProps | ((helpers: ResourcePageHeaderHelpers<TClient>) => DashboardHeaderProps) headerProps?: DashboardHeaderProps | ((helpers: ResourcePageHeaderHelpers<TClient>) => DashboardHeaderProps)
header?: ReactNodeOrRender<TClient> | null header?: ReactNodeOrRender<TClient> | null
} }
export function ResourcePage<TClient extends ResourcePageClient>({ export function ResourcePage<TClient extends ResourcePageClient>({
pageTitle,
headerProps: headerPropsProp, headerProps: headerPropsProp,
header, header,
...crudResourceProps ...crudResourceProps
@ -46,13 +48,16 @@ export function ResourcePage<TClient extends ResourcePageClient>({
invalidateQuery: context.invalidateQuery, invalidateQuery: context.invalidateQuery,
}) })
: headerPropsProp : headerPropsProp
const mergedHeaderProps = pageTitle
? { title: pageTitle, ...resolvedHeaderProps }
: resolvedHeaderProps
const resolvedHeader = typeof header === "function" ? header(context) : header const resolvedHeader = typeof header === "function" ? header(context) : header
return ( return (
<DashboardPage <DashboardPage
header={resolvedHeader} header={resolvedHeader}
headerProps={resolvedHeaderProps} headerProps={mergedHeaderProps}
fullscreen fullscreen
> >
<Card className="rounded-none"> <Card className="rounded-none">

View File

@ -15,7 +15,7 @@ type ApiInstance = ReturnType<typeof useAuthApi>
export type ResourcePageClient = { export type ResourcePageClient = {
list(query?: any): Promise<any> list(query?: any): Promise<any>
destroy(id: string): Promise<any> destroy?(id: string): Promise<any>
} }
export type ResourceItem<TClient> = CrudListItem<TClient> & BaseCrudItem export type ResourceItem<TClient> = CrudListItem<TClient> & BaseCrudItem
@ -51,6 +51,7 @@ export function useResourcePage<TClient extends ResourcePageClient>({
const { mutateAsync: deleteItem } = useMutation({ const { mutateAsync: deleteItem } = useMutation({
mutationFn: (id: string) => { mutationFn: (id: string) => {
if (!client.destroy) return Promise.reject(new Error("Delete not supported"))
const promise = client.destroy(id) const promise = client.destroy(id)
toast.promise(promise, { toast.promise(promise, {
loading: "Deleting...", loading: "Deleting...",

View File

@ -21,6 +21,6 @@ export class AuthClient extends ApiClient {
} }
async logout() { async logout() {
return this.post(AUTH_ROUTES.LOGOUT, undefined) return this.post(AUTH_ROUTES.LOGOUT, {} as never)
} }
} }

View File

@ -9,9 +9,7 @@ export const CUSTOMER_ROUTES = {
EXPORT: "/api/customers/export", EXPORT: "/api/customers/export",
IMPORT: "/api/customers/import", IMPORT: "/api/customers/import",
CUSTOMER_TYPES: "/api/customer-types", CUSTOMER_TYPES: "/api/customer-types",
ADD_NOTE: "/api/customers/{id}/add-note", NOTES: "/api/customers/{id}/notes/{note_id}",
DELETE_NOTE: "/api/customers/{id}/delete-note",
UPDATE_PERMISSIONS: "/api/customers/{id}/update-permissions",
} as const satisfies Record<string, ApiPath> } as const satisfies Record<string, ApiPath>
export class CustomersClient extends CrudClient<typeof CUSTOMER_ROUTES.INDEX, typeof CUSTOMER_ROUTES.BY_ID> { export class CustomersClient extends CrudClient<typeof CUSTOMER_ROUTES.INDEX, typeof CUSTOMER_ROUTES.BY_ID> {
@ -37,14 +35,10 @@ export class CustomersClient extends CrudClient<typeof CUSTOMER_ROUTES.INDEX, ty
} }
async addNote(id: string, payload: { note: string }) { async addNote(id: string, payload: { note: string }) {
return this.post(CUSTOMER_ROUTES.ADD_NOTE, payload as never, { params: { id } } as never) return this.post(CUSTOMER_ROUTES.NOTES, payload as never, { params: { id, note_id: "0" } } as never)
} }
async deleteNote(customerId: string, noteId: number) { async deleteNote(customerId: string, noteId: number) {
return this.delete(CUSTOMER_ROUTES.DELETE_NOTE, { params: { id: customerId }, query: { note_id: noteId } } as never) return this.delete(CUSTOMER_ROUTES.NOTES, { params: { id: customerId, note_id: String(noteId) } } as never)
}
async updatePermissions(id: string, payload: ApiRequestBody<typeof CUSTOMER_ROUTES.UPDATE_PERMISSIONS, "post">) {
return this.post(CUSTOMER_ROUTES.UPDATE_PERMISSIONS, payload as never, { params: { id } } as never)
} }
} }

View File

@ -47,7 +47,7 @@ export class InspectionsClient extends CrudClient<
async getById(id: string) { async getById(id: string) {
const res = await super.list({ query: { id } } as never) const res = await super.list({ query: { id } } as never)
return {...res, data: res.data[0] } return {...res, data: res.data?.[0] }
} }
async changeStatus(payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHANGE_STATUS, "post">) { async changeStatus(payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHANGE_STATUS, "post">) {

View File

@ -1,7 +1,7 @@
import { CrudClient } from "../infra/crud-client" import { CrudClient } from "../infra/crud-client"
import type { ApiClientOptions } from "../infra/client" import type { ApiClientOptions } from "../infra/client"
import type { ApiOperationResponse, ApiPath, ApiRequestBody, ApiResponse } from "../infra/types" import type { ApiOperationResponse, ApiPath, ApiRequestBody, ApiResponse } from "../infra/types"
import { ApiBaseResponse } from "src/contracts/types" import { ApiBaseResponse } from "../contracts/types"
export const JOB_CARD_ROUTES = { export const JOB_CARD_ROUTES = {
INDEX: "/api/job-cards", INDEX: "/api/job-cards",