thai-uxui

This commit is contained in:
phaichayon
2026-06-19 11:11:17 +07:00
parent c0cc880be2
commit 721653f692
47 changed files with 1135 additions and 538 deletions

View File

@@ -13,7 +13,7 @@ export function getApprovalColumns(): ColumnDef<ApprovalListItem>[] {
id: 'entityCode',
accessorFn: (row) => row.entityCode ?? row.entityId,
header: ({ column }: { column: Column<ApprovalListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Request' />
<DataTableColumnHeader column={column} title='คำขอ' />
),
cell: ({ row }) => (
<div className='flex flex-col'>
@@ -27,8 +27,8 @@ export function getApprovalColumns(): ColumnDef<ApprovalListItem>[] {
</div>
),
meta: {
label: 'Request',
placeholder: 'Search approvals...',
label: 'คำขอ',
placeholder: 'ค้นหางานอนุมัติ...',
variant: 'text' as const,
icon: Icons.search
},
@@ -45,11 +45,11 @@ export function getApprovalColumns(): ColumnDef<ApprovalListItem>[] {
label: 'Status',
variant: 'multiSelect' as const,
options: [
{ value: 'pending', label: 'Pending' },
{ value: 'approved', label: 'Approved' },
{ value: 'rejected', label: 'Rejected' },
{ value: 'returned', label: 'Returned' },
{ value: 'cancelled', label: 'Cancelled' }
{ value: 'pending', label: 'รออนุมัติ' },
{ value: 'approved', label: 'อนุมัติแล้ว' },
{ value: 'rejected', label: 'ไม่อนุมัติ' },
{ value: 'returned', label: 'ตีกลับ' },
{ value: 'cancelled', label: 'ยกเลิก' }
]
},
enableColumnFilter: true
@@ -58,13 +58,13 @@ export function getApprovalColumns(): ColumnDef<ApprovalListItem>[] {
id: 'entityType',
accessorKey: 'entityType',
header: ({ column }: { column: Column<ApprovalListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Entity' />
<DataTableColumnHeader column={column} title='เอกสาร' />
),
cell: ({ row }) => row.original.entityType.replaceAll('_', ' '),
meta: {
label: 'Entity',
label: 'เอกสาร',
variant: 'multiSelect' as const,
options: [{ value: 'quotation', label: 'Quotation' }]
options: [{ value: 'quotation', label: 'ใบเสนอราคา' }]
},
enableColumnFilter: true
},
@@ -72,11 +72,11 @@ export function getApprovalColumns(): ColumnDef<ApprovalListItem>[] {
id: 'currentStep',
accessorKey: 'currentStep',
header: ({ column }: { column: Column<ApprovalListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Current Step' />
<DataTableColumnHeader column={column} title='ขั้นตอนปัจจุบัน' />
),
cell: ({ row }) => (
<div className='flex flex-col'>
<span>Step {row.original.currentStep}</span>
<span> {row.original.currentStep}</span>
<span className='text-muted-foreground text-xs'>
{row.original.currentStepRoleName ?? '-'}
</span>
@@ -87,7 +87,7 @@ export function getApprovalColumns(): ColumnDef<ApprovalListItem>[] {
id: 'requestedBy',
accessorFn: (row) => row.requestedByName ?? row.requestedBy,
header: ({ column }: { column: Column<ApprovalListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Requested By' />
<DataTableColumnHeader column={column} title='ผู้ส่งอนุมัติ' />
),
cell: ({ row }) => row.original.requestedByName ?? row.original.requestedBy
},
@@ -95,7 +95,7 @@ export function getApprovalColumns(): ColumnDef<ApprovalListItem>[] {
id: 'requestedAt',
accessorKey: 'requestedAt',
header: ({ column }: { column: Column<ApprovalListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Requested At' />
<DataTableColumnHeader column={column} title='วันที่ส่งอนุมัติ' />
),
cell: ({ row }) => new Date(row.original.requestedAt).toLocaleString()
}

View File

@@ -60,35 +60,35 @@ export function ApprovalRequestPanel({
const approveAction = useMutation({
...approveApprovalMutation,
onSuccess: () => {
toast.success('Approval step completed');
toast.success('อนุมัติขั้นตอนนี้สำเร็จ');
setRemark('');
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Unable to approve request')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถอนุมัติคำขอได้')
});
const rejectAction = useMutation({
...rejectApprovalMutation,
onSuccess: () => {
toast.success('Approval request rejected');
toast.success('ไม่อนุมัติคำขอสำเร็จ');
setRemark('');
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Unable to reject request')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถไม่อนุมัติคำขอได้')
});
const returnAction = useMutation({
...returnApprovalMutation,
onSuccess: () => {
toast.success('Approval request returned to draft');
toast.success('ตีกลับคำขอเพื่อแก้ไขสำเร็จ');
setRemark('');
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Unable to return request')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถตีกลับคำขอได้')
});
const cancelAction = useMutation({
...cancelApprovalMutation,
onSuccess: () => toast.success('Approval request cancelled'),
onSuccess: () => toast.success('ยกเลิกคำขออนุมัติสำเร็จ'),
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Unable to cancel request')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถยกเลิกคำขอได้')
});
const isActing =
approveAction.isPending ||
@@ -100,30 +100,30 @@ export function ApprovalRequestPanel({
<div className='space-y-6'>
<Card>
<CardHeader>
<CardTitle>Request Information</CardTitle>
<CardDescription>Current workflow state for this approval request.</CardDescription>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className='grid gap-4 md:grid-cols-2 xl:grid-cols-4'>
<FieldItem label='Workflow' value={approval.workflow.name} />
<div className='space-y-1'>
<div className='text-muted-foreground text-xs'>Status</div>
<div className='text-muted-foreground text-xs'></div>
<ApprovalStatusBadge status={approval.request.status} />
</div>
<FieldItem label='Entity Type' value={approval.request.entityType.replaceAll('_', ' ')} />
<FieldItem label='Entity Code' value={approval.entityCode ?? approval.request.entityId} />
<FieldItem label='Entity Title' value={approval.entityTitle} />
<FieldItem label='ประเภทเอกสาร' value={approval.request.entityType.replaceAll('_', ' ')} />
<FieldItem label='รหัสเอกสาร' value={approval.entityCode ?? approval.request.entityId} />
<FieldItem label='ชื่อเอกสาร' value={approval.entityTitle} />
<FieldItem
label='Requested At'
label='วันที่ส่งอนุมัติ'
value={new Date(approval.request.requestedAt).toLocaleString()}
/>
<FieldItem label='Requested By' value={approval.request.requestedBy} />
<FieldItem label='ผู้ส่งอนุมัติ' value={approval.request.requestedBy} />
<FieldItem
label='Current Step'
label='ขั้นตอนปัจจุบัน'
value={
approval.currentStep
? `Step ${approval.currentStep.stepNumber} - ${approval.currentStep.roleName}`
: approval.request.status === 'approved'
? 'Completed'
? 'เสร็จสิ้น'
: '-'
}
/>
@@ -132,7 +132,7 @@ export function ApprovalRequestPanel({
<Button asChild variant='outline'>
<Link href={entityHref}>
<Icons.arrowRight className='mr-2 h-4 w-4' />
Open Related Document
</Link>
</Button>
</div>
@@ -143,7 +143,7 @@ export function ApprovalRequestPanel({
<Card>
<CardHeader>
<CardTitle>Workflow</CardTitle>
<CardDescription>Sequential approver chain configured for this document.</CardDescription>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className='space-y-3'>
{approval.steps.map((step) => {
@@ -157,12 +157,12 @@ export function ApprovalRequestPanel({
>
<div>
<div className='font-medium'>
Step {step.stepNumber} - {step.roleName}
{step.stepNumber} - {step.roleName}
</div>
<div className='text-muted-foreground text-sm'>{step.roleCode}</div>
</div>
<Badge variant={isCurrent ? 'secondary' : isCompleted ? 'default' : 'outline'}>
{isCurrent ? 'Current' : isCompleted ? 'Completed' : 'Pending'}
{isCurrent ? 'ปัจจุบัน' : isCompleted ? 'เสร็จสิ้น' : 'รอดำเนินการ'}
</Badge>
</div>
);
@@ -172,16 +172,16 @@ export function ApprovalRequestPanel({
<Card>
<CardHeader>
<CardTitle>Actions</CardTitle>
<CardTitle></CardTitle>
<CardDescription>
Approvers can approve, reject, or return on their assigned step.
</CardDescription>
</CardHeader>
<CardContent className='space-y-4'>
<Textarea
value={remark}
onChange={(event) => setRemark(event.target.value)}
placeholder='Optional remark for approval history'
placeholder='หมายเหตุเพิ่มเติมสำหรับประวัติการอนุมัติ'
rows={4}
/>
<div className='flex flex-wrap gap-2'>
@@ -193,7 +193,7 @@ export function ApprovalRequestPanel({
}
>
<Icons.circleCheck className='mr-2 h-4 w-4' />
Approve
</Button>
) : null}
{canReject && canHandleCurrentStep && isPending ? (
@@ -205,7 +205,7 @@ export function ApprovalRequestPanel({
}
>
<Icons.warning className='mr-2 h-4 w-4' />
Reject
</Button>
) : null}
{canReturn && canHandleCurrentStep && isPending ? (
@@ -217,7 +217,7 @@ export function ApprovalRequestPanel({
}
>
<Icons.chevronLeft className='mr-2 h-4 w-4' />
Return
</Button>
) : null}
{canCancelCurrentRequest ? (
@@ -227,36 +227,36 @@ export function ApprovalRequestPanel({
onClick={() => cancelAction.mutate(approval.request.id)}
>
<Icons.close className='mr-2 h-4 w-4' />
Cancel Request
</Button>
) : null}
</div>
{!canHandleCurrentStep && isPending ? (
<div className='text-muted-foreground text-sm'>
Current step is assigned to{' '}
{' '}
<span className='font-medium'>{approval.currentStep?.roleName ?? 'another role'}</span>.
</div>
) : null}
{!isPending ? (
<div className='text-muted-foreground text-sm'>
This request is already {approval.request.status.replaceAll('_', ' ')}.
{approval.request.status.replaceAll('_', ' ')}
</div>
) : null}
{isActing ? (
<div className='text-muted-foreground text-xs'>Saving approval action...</div>
<div className='text-muted-foreground text-xs'>...</div>
) : null}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Timeline</CardTitle>
<CardDescription>Full audit trail of request submission and approval actions.</CardDescription>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className='space-y-4'>
{!approval.timeline.length ? (
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center text-sm'>
No approval actions recorded yet.
</div>
) : (
approval.timeline.map((item, index) => (
@@ -272,7 +272,7 @@ export function ApprovalRequestPanel({
{item.actorName ?? item.actedBy}
</span>
<span className='text-muted-foreground text-xs'>
Step {item.stepNumber}
{item.stepNumber}
</span>
</div>
<div className='text-muted-foreground text-xs'>

View File

@@ -5,6 +5,13 @@ import { Icons } from '@/components/icons';
export function ApprovalStatusBadge({ status }: { status: string }) {
const normalized = status.toLowerCase();
const labelMap: Record<string, string> = {
pending: 'รออนุมัติ',
approved: 'อนุมัติแล้ว',
rejected: 'ไม่อนุมัติ',
returned: 'ตีกลับ',
cancelled: 'ยกเลิก'
};
const variant =
normalized === 'approved'
? 'default'
@@ -25,7 +32,7 @@ export function ApprovalStatusBadge({ status }: { status: string }) {
return (
<Badge variant={variant} className='capitalize'>
<Icon className='h-3 w-3' />
{status.replaceAll('_', ' ')}
{labelMap[normalized] ?? status.replaceAll('_', ' ')}
</Badge>
);
}