Refactor/main flow #41

Merged
DeepnaPooja merged 2 commits from refactor/main_flow into dev 2026-09-23 14:06:57 +05:30
4 changed files with 159 additions and 38 deletions
Showing only changes of commit 80db899091 - Show all commits
@@ -12,6 +12,7 @@ import type { InvoiceResponse, InvoiceStatus, PaymentResponse, PaymentMethod, Pa
import { PaymentSheet } from '../../payments/_components/payment-sheet'
import { PaymentViewSheet } from '../../payments/_components/payment-view-sheet'
import { InvoiceActionDialog } from '../../orders/_components/invoice-action-dialog'
const INVOICE_STATUS_VARIANT: Record<InvoiceStatus, 'default' | 'secondary' | 'outline'> = {
Draft: 'secondary',
@@ -58,6 +59,8 @@ export default function InvoiceViewPage() {
const [loading, setLoading] = useState(true)
const [paymentSheetOpen, setPaymentSheetOpen] = useState(false)
const [viewPayment, setViewPayment] = useState<PaymentResponse | null>(null)
const [actionOpen, setActionOpen] = useState(false)
const [actionMode, setActionMode] = useState<'print' | 'finalize'>('print')
const fetchInvoice = useCallback(async () => {
const inv = await invoicesApi.get(params.id)
@@ -86,26 +89,14 @@ export default function InvoiceViewPage() {
}
}, [fetchInvoice, fetchPayments, router])
async function handlePrint() {
if (!invoice) return
try {
const updated = await invoicesApi.print(invoice.id)
setInvoice(updated)
toast.add({ title: 'Invoice printed', type: 'success' })
} catch (err) {
toast.add({ title: 'Failed to print invoice', description: getApiError(err), type: 'error' })
}
function handlePrint() {
setActionMode('print')
setActionOpen(true)
}
async function handleFinalize() {
if (!invoice) return
try {
const updated = await invoicesApi.finalize(invoice.id)
setInvoice(updated)
toast.add({ title: 'Invoice finalized', type: 'success' })
} catch (err) {
toast.add({ title: 'Failed to finalize invoice', description: getApiError(err), type: 'error' })
}
function handleFinalize() {
setActionMode('finalize')
setActionOpen(true)
}
if (loading) {
@@ -240,6 +231,14 @@ export default function InvoiceViewPage() {
payment={viewPayment}
invoiceName={`${invoice.storeName} — ${truncId(invoice.id)}`}
/>
<InvoiceActionDialog
open={actionOpen}
onClose={() => setActionOpen(false)}
onSuccess={fetchInvoice}
invoice={invoice}
mode={actionMode}
/>
</div>
)
}
@@ -0,0 +1,119 @@
'use client'
import { useEffect, useState } from 'react'
import { useForm, zodResolver, z } from '@/lib/form'
import { FormField } from '@/components/shared/form-field'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { FieldGroup } from '@/components/ui/field'
import { toast } from '@/components/ui/toast'
import { invoices as invoicesApi, users as usersApi, getApiError, getPaginatedItems } from '@/lib/api/index'
import type { InvoiceResponse, UserResponse } from '@/types/api'
const schema = z.object({
userId: z.string().min(1, 'This is required'),
})
type FormValues = z.infer<typeof schema>
interface InvoiceActionDialogProps {
open: boolean
onClose: () => void
onSuccess: () => void
invoice: InvoiceResponse | null
mode: 'print' | 'finalize'
}
const COPY = {
print: { title: 'Mark Invoice as Printed', label: 'Printed By', action: 'Mark as Printed', success: 'Invoice marked as printed' },
finalize: { title: 'Finalize Invoice', label: 'Finalized By', action: 'Finalize Invoice', success: 'Invoice finalized' },
}
export function InvoiceActionDialog({ open, onClose, onSuccess, invoice, mode }: InvoiceActionDialogProps) {
const [usersList, setUsersList] = useState<UserResponse[]>([])
const copy = COPY[mode]
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { userId: '' },
})
useEffect(() => {
if (!open) return
form.reset({ userId: '' })
usersApi.list({ pageSize: 200 })
.then((res) => setUsersList(getPaginatedItems(res)))
.catch((err) => toast.add({ title: 'Failed to load users', description: getApiError(err), type: 'error' }))
}, [open])
if (!invoice) return null
async function onSubmit(values: FormValues) {
try {
if (mode === 'print') await invoicesApi.print(invoice!.id, values.userId)
else await invoicesApi.finalize(invoice!.id, values.userId)
toast.add({ title: copy.success, type: 'success' })
onSuccess()
onClose()
} catch (err) {
toast.add({ title: `Failed to ${mode} invoice`, description: getApiError(err), type: 'error' })
}
}
return (
<Dialog open={open} onOpenChange={(v) => { if (!v) onClose() }}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{copy.title}</DialogTitle>
<DialogDescription>{invoice.storeName} — Rs. {invoice.totalAmount.toLocaleString()}</DialogDescription>
</DialogHeader>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6">
<FieldGroup>
<FormField
control={form.control}
name="userId"
label={copy.label}
description="Temporary — required until auth resolves the logged-in user automatically."
required
render={({ field }) => (
<Select value={field.value as string} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="Select user..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="" disabled>Select user...</SelectItem>
{usersList.map((user) => (
<SelectItem key={user.id} value={user.id}>{user.fullName}</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</FieldGroup>
<DialogFooter>
<Button type="button" variant="outline" onClick={onClose}>Cancel</Button>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? 'Saving…' : copy.action}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}
@@ -10,6 +10,7 @@ import { invoices as invoicesApi, getApiError, getPaginatedItems } from '@/lib/a
import type { InvoiceResponse, InvoiceStatus, PaymentStatus } from '@/types/api'
import { OrderViewSheet } from './order-view-sheet'
import { InvoiceActionDialog } from './invoice-action-dialog'
const fmtDate = (v: string) =>
new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
@@ -38,6 +39,8 @@ export function OrdersClient() {
const [viewOpen, setViewOpen] = useState(false)
const [selectedInvoice, setSelectedInvoice] = useState<InvoiceResponse | null>(null)
const [actionOpen, setActionOpen] = useState(false)
const [actionMode, setActionMode] = useState<'print' | 'finalize'>('print')
const fetchData = useCallback(async (p: number, ps: number) => {
setLoading(true)
@@ -61,24 +64,16 @@ export function OrdersClient() {
setViewOpen(true)
}
async function handlePrint(row: InvoiceResponse) {
try {
await invoicesApi.print(row.id)
toast.add({ title: 'Invoice marked as printed', type: 'success' })
fetchData(page, pageSize)
} catch (err) {
toast.add({ title: 'Failed to print invoice', description: getApiError(err), type: 'error' })
}
function handlePrint(row: InvoiceResponse) {
setSelectedInvoice(row)
setActionMode('print')
setActionOpen(true)
}
async function handleFinalize(row: InvoiceResponse) {
try {
await invoicesApi.finalize(row.id)
toast.add({ title: 'Invoice finalized', type: 'success' })
fetchData(page, pageSize)
} catch (err) {
toast.add({ title: 'Failed to finalize invoice', description: getApiError(err), type: 'error' })
}
function handleFinalize(row: InvoiceResponse) {
setSelectedInvoice(row)
setActionMode('finalize')
setActionOpen(true)
}
const tableConfig = useMemo<TableConfig<InvoiceResponse>>(() => ({
@@ -187,6 +182,14 @@ export function OrdersClient() {
onClose={() => setViewOpen(false)}
invoice={selectedInvoice}
/>
<InvoiceActionDialog
open={actionOpen}
onClose={() => setActionOpen(false)}
onSuccess={() => fetchData(page, pageSize)}
invoice={selectedInvoice}
mode={actionMode}
/>
</>
)
}
+4 -4
View File
@@ -78,11 +78,11 @@ export const invoices = {
get: (id: string) =>
api.get<InvoiceResponse>(`/invoices/${id}`).then(r => r.data),
print: (id: string) =>
api.post<InvoiceResponse>(`/invoices/${id}/print`).then(r => r.data),
print: (id: string, printedByUserId?: string | null) =>
api.post<InvoiceResponse>(`/invoices/${id}/print`, { printedByUserId }).then(r => r.data),
finalize: (id: string) =>
api.post<InvoiceResponse>(`/invoices/${id}/finalize`).then(r => r.data),
finalize: (id: string, finalizedByUserId?: string | null) =>
api.post<InvoiceResponse>(`/invoices/${id}/finalize`, { finalizedByUserId }).then(r => r.data),
}
// ─── Payments ─────────────────────────────────────────────────────────────────