|
|
@@ -111,6 +111,7 @@ export function ServicePageClient({
|
|
|
const [pendingInvoiceDate, setPendingInvoiceDate] = useState<Date>(today)
|
|
|
const [pendingDueDate, setPendingDueDate] = useState<Date>(suggestedDueDate)
|
|
|
const [updatingDates, setUpdatingDates] = useState(false)
|
|
|
+ const [customizingDates, setCustomizingDates] = useState(false)
|
|
|
|
|
|
const formatDate = (date: Date) =>
|
|
|
date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
|
|
|
@@ -515,6 +516,7 @@ export function ServicePageClient({
|
|
|
if (!open) {
|
|
|
dateCheckResolveRef.current?.(false)
|
|
|
dateCheckResolveRef.current = null
|
|
|
+ setCustomizingDates(false)
|
|
|
}
|
|
|
setShowDateCheck(open)
|
|
|
}}
|
|
|
@@ -528,105 +530,141 @@ export function ServicePageClient({
|
|
|
<DialogDescription>{t('page.datesExpiredDescription')}</DialogDescription>
|
|
|
</DialogHeader>
|
|
|
|
|
|
- <div className="space-y-3">
|
|
|
- <Button
|
|
|
- variant="secondary"
|
|
|
- size="sm"
|
|
|
- className="w-full"
|
|
|
- onClick={() => {
|
|
|
- const now = new Date(new Date().toISOString().split('T')[0])
|
|
|
- setPendingInvoiceDate(now)
|
|
|
- setPendingDueDate(
|
|
|
- defaultDueDays > 0
|
|
|
- ? new Date(now.getTime() + defaultDueDays * 86400000)
|
|
|
- : new Date(now.getTime() + 14 * 86400000)
|
|
|
- )
|
|
|
- }}
|
|
|
- >
|
|
|
- {t('page.datesExpiredSetToday')}
|
|
|
- </Button>
|
|
|
-
|
|
|
- <div className="space-y-1">
|
|
|
- <Label className="text-xs">{t('basicInfo.invoiceDate')}</Label>
|
|
|
- <Popover>
|
|
|
- <PopoverTrigger asChild>
|
|
|
- <Button
|
|
|
- variant="outline"
|
|
|
- className="w-full justify-start text-left font-normal h-9 text-sm"
|
|
|
- >
|
|
|
- <CalendarIcon className="mr-2 h-3.5 w-3.5" />
|
|
|
- <span suppressHydrationWarning>{formatDate(pendingInvoiceDate)}</span>
|
|
|
- </Button>
|
|
|
- </PopoverTrigger>
|
|
|
- <PopoverContent className="w-auto p-0" align="start">
|
|
|
- <Calendar
|
|
|
- mode="single"
|
|
|
- selected={pendingInvoiceDate}
|
|
|
- onSelect={(d) => d && setPendingInvoiceDate(d)}
|
|
|
- />
|
|
|
- </PopoverContent>
|
|
|
- </Popover>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="space-y-1">
|
|
|
- <Label className="text-xs">{t('basicInfo.invoiceDueDate')}</Label>
|
|
|
- <Popover>
|
|
|
- <PopoverTrigger asChild>
|
|
|
- <Button
|
|
|
- variant="outline"
|
|
|
- className="w-full justify-start text-left font-normal h-9 text-sm"
|
|
|
- >
|
|
|
- <CalendarIcon className="mr-2 h-3.5 w-3.5" />
|
|
|
- <span suppressHydrationWarning>{formatDate(pendingDueDate)}</span>
|
|
|
- </Button>
|
|
|
- </PopoverTrigger>
|
|
|
- <PopoverContent className="w-auto p-0" align="start">
|
|
|
- <Calendar
|
|
|
- mode="single"
|
|
|
- selected={pendingDueDate}
|
|
|
- onSelect={(d) => d && setPendingDueDate(d)}
|
|
|
- />
|
|
|
- </PopoverContent>
|
|
|
- </Popover>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="flex flex-col gap-2 pt-2">
|
|
|
- <Button
|
|
|
- disabled={updatingDates}
|
|
|
- onClick={async () => {
|
|
|
- if (updatingDates) return
|
|
|
- setUpdatingDates(true)
|
|
|
- try {
|
|
|
- await updateServiceRecord({
|
|
|
- id: record.id,
|
|
|
- invoiceDate: toISODate(pendingInvoiceDate),
|
|
|
- invoiceDueDate: toISODate(pendingDueDate),
|
|
|
- })
|
|
|
+ {!customizingDates ? (
|
|
|
+ <div className="flex flex-col gap-2 pt-2">
|
|
|
+ <Button
|
|
|
+ disabled={updatingDates}
|
|
|
+ onClick={async () => {
|
|
|
+ if (updatingDates) return
|
|
|
+ const now = new Date(new Date().toISOString().split('T')[0])
|
|
|
+ const due =
|
|
|
+ defaultDueDays > 0
|
|
|
+ ? new Date(now.getTime() + defaultDueDays * 86400000)
|
|
|
+ : new Date(now.getTime() + 14 * 86400000)
|
|
|
+ setPendingInvoiceDate(now)
|
|
|
+ setPendingDueDate(due)
|
|
|
+ setUpdatingDates(true)
|
|
|
+ try {
|
|
|
+ await updateServiceRecord({
|
|
|
+ id: record.id,
|
|
|
+ invoiceDate: toISODate(now),
|
|
|
+ invoiceDueDate: toISODate(due),
|
|
|
+ })
|
|
|
+ setShowDateCheck(false)
|
|
|
+ dateCheckResolveRef.current?.(true)
|
|
|
+ dateCheckResolveRef.current = null
|
|
|
+ router.refresh()
|
|
|
+ } finally {
|
|
|
+ setUpdatingDates(false)
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {updatingDates && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
|
+ {t('page.datesExpiredUseToday')}
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ disabled={updatingDates}
|
|
|
+ onClick={() => {
|
|
|
setShowDateCheck(false)
|
|
|
dateCheckResolveRef.current?.(true)
|
|
|
dateCheckResolveRef.current = null
|
|
|
- router.refresh()
|
|
|
- } finally {
|
|
|
- setUpdatingDates(false)
|
|
|
- }
|
|
|
- }}
|
|
|
- >
|
|
|
- {updatingDates && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
|
- {t('page.datesExpiredUpdate')}
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- variant="outline"
|
|
|
- disabled={updatingDates}
|
|
|
- onClick={() => {
|
|
|
- setShowDateCheck(false)
|
|
|
- dateCheckResolveRef.current?.(true)
|
|
|
- dateCheckResolveRef.current = null
|
|
|
- }}
|
|
|
- >
|
|
|
- {t('page.datesExpiredProceed')}
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {t('page.datesExpiredProceed')}
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ disabled={updatingDates}
|
|
|
+ onClick={() => setCustomizingDates(true)}
|
|
|
+ >
|
|
|
+ {t('page.datesExpiredCustomize')}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <div className="space-y-3">
|
|
|
+ <div className="space-y-1">
|
|
|
+ <Label className="text-xs">{t('basicInfo.invoiceDate')}</Label>
|
|
|
+ <Popover>
|
|
|
+ <PopoverTrigger asChild>
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ className="w-full justify-start text-left font-normal h-9 text-sm"
|
|
|
+ >
|
|
|
+ <CalendarIcon className="mr-2 h-3.5 w-3.5" />
|
|
|
+ <span suppressHydrationWarning>{formatDate(pendingInvoiceDate)}</span>
|
|
|
+ </Button>
|
|
|
+ </PopoverTrigger>
|
|
|
+ <PopoverContent className="w-auto p-0" align="start">
|
|
|
+ <Calendar
|
|
|
+ mode="single"
|
|
|
+ selected={pendingInvoiceDate}
|
|
|
+ onSelect={(d) => d && setPendingInvoiceDate(d)}
|
|
|
+ />
|
|
|
+ </PopoverContent>
|
|
|
+ </Popover>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-1">
|
|
|
+ <Label className="text-xs">{t('basicInfo.invoiceDueDate')}</Label>
|
|
|
+ <Popover>
|
|
|
+ <PopoverTrigger asChild>
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ className="w-full justify-start text-left font-normal h-9 text-sm"
|
|
|
+ >
|
|
|
+ <CalendarIcon className="mr-2 h-3.5 w-3.5" />
|
|
|
+ <span suppressHydrationWarning>{formatDate(pendingDueDate)}</span>
|
|
|
+ </Button>
|
|
|
+ </PopoverTrigger>
|
|
|
+ <PopoverContent className="w-auto p-0" align="start">
|
|
|
+ <Calendar
|
|
|
+ mode="single"
|
|
|
+ selected={pendingDueDate}
|
|
|
+ onSelect={(d) => d && setPendingDueDate(d)}
|
|
|
+ />
|
|
|
+ </PopoverContent>
|
|
|
+ </Popover>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="flex flex-col gap-2 pt-2">
|
|
|
+ <Button
|
|
|
+ disabled={updatingDates}
|
|
|
+ onClick={async () => {
|
|
|
+ if (updatingDates) return
|
|
|
+ setUpdatingDates(true)
|
|
|
+ try {
|
|
|
+ await updateServiceRecord({
|
|
|
+ id: record.id,
|
|
|
+ invoiceDate: toISODate(pendingInvoiceDate),
|
|
|
+ invoiceDueDate: toISODate(pendingDueDate),
|
|
|
+ })
|
|
|
+ setShowDateCheck(false)
|
|
|
+ setCustomizingDates(false)
|
|
|
+ dateCheckResolveRef.current?.(true)
|
|
|
+ dateCheckResolveRef.current = null
|
|
|
+ router.refresh()
|
|
|
+ } finally {
|
|
|
+ setUpdatingDates(false)
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {updatingDates && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
|
+ {t('page.datesExpiredUpdate')}
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ disabled={updatingDates}
|
|
|
+ onClick={() => setCustomizingDates(false)}
|
|
|
+ >
|
|
|
+ {t('page.datesExpiredBack')}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
</DialogContent>
|
|
|
</Dialog>
|
|
|
</div>
|