init
This commit is contained in:
146
src/features/crm/components/approvals-page.tsx
Normal file
146
src/features/crm/components/approvals-page.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user