147 lines
4.9 KiB
TypeScript
147 lines
4.9 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
|
|
import { toast } from 'sonner';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger
|
|
} from '@/components/ui/dialog';
|
|
import { Input } from '@/components/ui/input';
|
|
import { approvalsQueryOptions, crmReferenceQueryOptions } from '../api/queries';
|
|
import { approveQuotationMutation, rejectQuotationMutation } from '../api/mutations';
|
|
import { BranchBadge, SectionCard, TimelineList } from './shared';
|
|
import { formatCurrency } from '../utils/format';
|
|
|
|
function DecisionDialog({
|
|
label,
|
|
description,
|
|
isPending,
|
|
onConfirm
|
|
}: {
|
|
label: string;
|
|
description: string;
|
|
isPending: boolean;
|
|
onConfirm: (comment: string) => void;
|
|
}) {
|
|
const [comment, setComment] = useState('');
|
|
|
|
return (
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<Button variant='outline' size='sm'>
|
|
{label}
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>{label}</DialogTitle>
|
|
<DialogDescription>{description}</DialogDescription>
|
|
</DialogHeader>
|
|
<Input value={comment} onChange={(event) => setComment(event.target.value)} placeholder='Comment' />
|
|
<DialogFooter>
|
|
<Button isLoading={isPending} onClick={() => onConfirm(comment)}>
|
|
Confirm
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
export function ApprovalsPage() {
|
|
const { data } = useSuspenseQuery(approvalsQueryOptions({ page: 1, limit: 10 }));
|
|
const { data: reference } = useSuspenseQuery(crmReferenceQueryOptions());
|
|
const baseApproveMutation = approveQuotationMutation;
|
|
const baseRejectMutation = rejectQuotationMutation;
|
|
|
|
const approveMutation = useMutation({
|
|
...baseApproveMutation,
|
|
onSuccess: async (...args) => {
|
|
await baseApproveMutation.onSuccess?.(...args);
|
|
toast.success('Approve mock สำเร็จ');
|
|
}
|
|
});
|
|
|
|
const rejectMutation = useMutation({
|
|
...baseRejectMutation,
|
|
onSuccess: async (...args) => {
|
|
await baseRejectMutation.onSuccess?.(...args);
|
|
toast.success('Reject mock สำเร็จ');
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div className='grid gap-4 xl:grid-cols-12'>
|
|
<SectionCard
|
|
title='Pending Approval List'
|
|
description='Sequential approval MVP'
|
|
className='xl:col-span-7'
|
|
>
|
|
<div className='space-y-3'>
|
|
{data.items.map((item) => {
|
|
const branch = reference.branches.find((value) => value.id === item.branchId);
|
|
return (
|
|
<div key={item.id} className='rounded-xl border bg-muted/10 p-4'>
|
|
<div className='flex flex-wrap items-start justify-between gap-3'>
|
|
<div>
|
|
<div className='flex items-center gap-2'>
|
|
<p className='font-semibold'>{item.quotationCode}</p>
|
|
{branch ? <BranchBadge branch={branch} /> : null}
|
|
</div>
|
|
<p className='text-muted-foreground mt-1 text-sm'>
|
|
{item.productType} / Submitter: {item.submitterName}
|
|
</p>
|
|
<p className='mt-2 text-sm font-medium'>
|
|
Level {item.approverLevel} / {formatCurrency(item.amount)}
|
|
</p>
|
|
</div>
|
|
<div className='flex gap-2'>
|
|
<DecisionDialog
|
|
label='Approve'
|
|
description='อนุมัติ quotation mock นี้'
|
|
isPending={approveMutation.isPending}
|
|
onConfirm={(comment) =>
|
|
approveMutation.mutate({ quotationId: item.quotationId, comment })
|
|
}
|
|
/>
|
|
<DecisionDialog
|
|
label='Reject'
|
|
description='Reject quotation mock นี้'
|
|
isPending={rejectMutation.isPending}
|
|
onConfirm={(comment) =>
|
|
rejectMutation.mutate({ quotationId: item.quotationId, comment })
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</SectionCard>
|
|
|
|
<SectionCard
|
|
title='Approval Timeline'
|
|
description='ภาพรวมเส้นทางการอนุมัติ'
|
|
className='xl:col-span-5'
|
|
>
|
|
<TimelineList
|
|
items={data.items.map((item) => ({
|
|
id: item.id,
|
|
title: `${item.quotationCode} / Level ${item.approverLevel}`,
|
|
detail: `${item.submitterName} ส่งอนุมัติสำหรับ ${item.productType}`,
|
|
timestamp: '2026-06-11'
|
|
}))}
|
|
/>
|
|
</SectionCard>
|
|
</div>
|
|
);
|
|
}
|