118 lines
4.3 KiB
TypeScript
118 lines
4.3 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { toast } from "sonner"
|
|
import { InvoiceStatus } from "@garage/api"
|
|
import { confirm } from "@/shared/components/confirm-dialog"
|
|
import { badgeVariants } from "@/shared/components/ui/badge"
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/shared/components/ui/select"
|
|
import { cn } from "@/shared/lib/utils"
|
|
import { formatEnum } from "@/shared/utils/formatters"
|
|
import { useAuthApi } from "@/shared/useApi"
|
|
|
|
type InvoiceStatusValue = (typeof InvoiceStatus)[number]
|
|
|
|
type InvoiceStatusBadgeProps = {
|
|
invoice: {
|
|
status?: string | null
|
|
id: number | string
|
|
}
|
|
}
|
|
|
|
const STATUS_TRIGGER_CLASS_NAMES: Record<InvoiceStatusValue, string> = {
|
|
draft: "border-slate-200 bg-slate-100 text-slate-700 dark:border-slate-800 dark:bg-slate-900/70 dark:text-slate-200",
|
|
open: "border-blue-200 bg-blue-100 text-blue-700 dark:border-blue-900 dark:bg-blue-950/70 dark:text-blue-200",
|
|
over_due: "border-red-200 bg-red-100 text-red-700 dark:border-red-900 dark:bg-red-950/70 dark:text-red-200",
|
|
paid: "border-emerald-200 bg-emerald-100 text-emerald-700 dark:border-emerald-900 dark:bg-emerald-950/70 dark:text-emerald-200",
|
|
partially_paid: "border-amber-200 bg-amber-100 text-amber-700 dark:border-amber-900 dark:bg-amber-950/70 dark:text-amber-200",
|
|
un_paid: "border-orange-200 bg-orange-100 text-orange-700 dark:border-orange-900 dark:bg-orange-950/70 dark:text-orange-200",
|
|
}
|
|
|
|
function isInvoiceStatus(value?: string | null): value is InvoiceStatusValue {
|
|
return !!value && InvoiceStatus.includes(value as InvoiceStatusValue)
|
|
}
|
|
|
|
export default function InvoiceStatusBadge({ invoice }: InvoiceStatusBadgeProps) {
|
|
const api = useAuthApi()
|
|
const router = useRouter()
|
|
const [isUpdating, setIsUpdating] = useState(false)
|
|
const [status, setStatus] = useState<InvoiceStatusValue | undefined>(
|
|
isInvoiceStatus(invoice.status) ? invoice.status : undefined,
|
|
)
|
|
|
|
useEffect(() => {
|
|
setStatus(isInvoiceStatus(invoice.status) ? invoice.status : undefined)
|
|
}, [invoice.status])
|
|
|
|
const handleStatusChange = async (nextStatus: string) => {
|
|
if (!isInvoiceStatus(nextStatus) || nextStatus === status || isUpdating) {
|
|
return
|
|
}
|
|
|
|
const currentStatus = status
|
|
const confirmed = await confirm({
|
|
title: "Change Invoice Status",
|
|
description: currentStatus
|
|
? `Change invoice status from ${formatEnum(currentStatus)} to ${formatEnum(nextStatus)}?`
|
|
: `Change invoice status to ${formatEnum(nextStatus)}?`,
|
|
confirmLabel: "Update",
|
|
})
|
|
|
|
if (!confirmed) {
|
|
return
|
|
}
|
|
|
|
setIsUpdating(true)
|
|
|
|
try {
|
|
const promise = api.invoices.update(String(invoice.id), { status: nextStatus })
|
|
|
|
toast.promise(promise, {
|
|
loading: `Updating invoice status to ${formatEnum(nextStatus)}...`,
|
|
success: "Invoice status updated successfully.",
|
|
error: "Failed to update invoice status.",
|
|
})
|
|
|
|
await promise
|
|
setStatus(nextStatus)
|
|
router.refresh()
|
|
} finally {
|
|
setIsUpdating(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Select value={status} onValueChange={handleStatusChange} disabled={isUpdating}>
|
|
<SelectTrigger
|
|
size="sm"
|
|
aria-label="Invoice status"
|
|
className={cn(
|
|
badgeVariants({ variant: "outline" }),
|
|
"h-8 gap-1.5 rounded-full px-3 py-1 text-xs font-medium shadow-none focus-visible:ring-2 [&_svg]:size-3.5",
|
|
status
|
|
? STATUS_TRIGGER_CLASS_NAMES[status]
|
|
: "border-border bg-muted/50 text-muted-foreground",
|
|
)}
|
|
>
|
|
<SelectValue placeholder="Set status">
|
|
{formatEnum(status ?? null)}
|
|
</SelectValue>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{InvoiceStatus.map((value) => (
|
|
<SelectItem key={value} value={value}>
|
|
{formatEnum(value)}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
)
|
|
}
|