refactor(WEB): 회계/견적/설정/생산 등 전반적 코드 개선 및 공통화 2차

- 회계 모듈 전면 개선: 청구/입금/출금/매입/매출/세금계산서/일반전표/거래처원장 등
- 견적 모듈 금액 포맷/할인/수식/미리보기 등 코드 정리
- 설정 모듈: 계정관리/직급/직책/권한 상세 간소화
- 생산 모듈: 작업지시서/작업자화면/검수 문서 코드 정리
- UniversalListPage 엑셀 다운로드 및 필터 기능 확장
- 대시보드/게시판/수주 등 날짜 유틸 공통화 적용
- claudedocs 문서 인덱스 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-20 10:45:47 +09:00
parent 71352923c8
commit f344dc7d00
123 changed files with 877 additions and 789 deletions

View File

@@ -6,6 +6,7 @@ import {
Banknote,
List,
} from 'lucide-react';
import { formatNumber } from '@/lib/utils/amount';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { DatePicker } from '@/components/ui/date-picker';
@@ -19,6 +20,7 @@ import {
SelectValue,
} from '@/components/ui/select';
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
import { useDeleteDialog } from '@/hooks/useDeleteDialog';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { toast } from 'sonner';
@@ -51,7 +53,6 @@ export function WithdrawalDetail({ withdrawalId, mode }: WithdrawalDetailProps)
const [note, setNote] = useState('');
const [vendorId, setVendorId] = useState('');
const [withdrawalType, setWithdrawalType] = useState<WithdrawalType>('unset');
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [vendors, setVendors] = useState<{ id: string; name: string }[]>([]);
@@ -145,19 +146,12 @@ export function WithdrawalDetail({ withdrawalId, mode }: WithdrawalDetailProps)
router.push(`/ko/accounting/withdrawals/${withdrawalId}?mode=edit`);
}, [router, withdrawalId]);
// ===== 삭제 핸들러 =====
const handleDelete = useCallback(async () => {
setIsLoading(true);
const result = await deleteWithdrawal(withdrawalId);
if (result.success) {
toast.success('출금 내역이 삭제되었습니다.');
setShowDeleteDialog(false);
router.push('/ko/accounting/withdrawals');
} else {
toast.error(result.error || '삭제에 실패했습니다.');
}
setIsLoading(false);
}, [withdrawalId, router]);
// ===== 삭제 다이얼로그 =====
const deleteDialog = useDeleteDialog({
onDelete: async (id) => deleteWithdrawal(id),
onSuccess: () => router.push('/ko/accounting/withdrawals'),
entityName: '출금',
});
return (
<PageLayout>
@@ -180,8 +174,8 @@ export function WithdrawalDetail({ withdrawalId, mode }: WithdrawalDetailProps)
<Button
variant="outline"
className="text-red-500 border-red-200 hover:bg-red-50"
onClick={() => setShowDeleteDialog(true)}
disabled={isLoading}
onClick={() => deleteDialog.single.open(withdrawalId)}
disabled={deleteDialog.isPending}
>
</Button>
@@ -252,7 +246,7 @@ export function WithdrawalDetail({ withdrawalId, mode }: WithdrawalDetailProps)
<Label htmlFor="withdrawalAmount"></Label>
<Input
id="withdrawalAmount"
value={withdrawalAmount.toLocaleString()}
value={formatNumber(withdrawalAmount)}
readOnly
disabled
className="bg-gray-50"
@@ -314,12 +308,12 @@ export function WithdrawalDetail({ withdrawalId, mode }: WithdrawalDetailProps)
{/* ===== 삭제 확인 다이얼로그 ===== */}
<DeleteConfirmDialog
open={showDeleteDialog}
onOpenChange={setShowDeleteDialog}
onConfirm={handleDelete}
open={deleteDialog.single.isOpen}
onOpenChange={deleteDialog.single.onOpenChange}
onConfirm={deleteDialog.single.confirm}
title="출금 삭제"
description="이 출금 내역을 삭제하시겠습니까? 삭제된 데이터는 복구할 수 없습니다."
loading={isLoading}
loading={deleteDialog.isPending}
/>
</PageLayout>
);

View File

@@ -69,6 +69,7 @@ import {
ACCOUNT_SUBJECT_OPTIONS,
} from './types';
import { deleteWithdrawal, updateWithdrawalTypes, getWithdrawals } from './actions';
import { formatNumber } from '@/lib/utils/amount';
import { toast } from 'sonner';
import { useDateRange } from '@/hooks';
import {
@@ -432,7 +433,7 @@ export function WithdrawalManagement({ initialData, initialPagination }: Withdra
<TableCell className="font-bold"></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell className="text-right font-bold">{tableTotals.totalAmount.toLocaleString()}</TableCell>
<TableCell className="text-right font-bold">{formatNumber(tableTotals.totalAmount)}</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
@@ -441,8 +442,8 @@ export function WithdrawalManagement({ initialData, initialPagination }: Withdra
// Stats 카드
computeStats: (): StatCard[] => [
{ label: '총 출금', value: `${stats.totalWithdrawal.toLocaleString()}`, icon: Banknote, iconColor: 'text-blue-500' },
{ label: '당월 출금', value: `${stats.monthlyWithdrawal.toLocaleString()}`, icon: Banknote, iconColor: 'text-green-500' },
{ label: '총 출금', value: `${formatNumber(stats.totalWithdrawal)}`, icon: Banknote, iconColor: 'text-blue-500' },
{ label: '당월 출금', value: `${formatNumber(stats.monthlyWithdrawal)}`, icon: Banknote, iconColor: 'text-green-500' },
{ label: '거래처 미설정', value: `${stats.vendorUnsetCount}`, icon: Banknote, iconColor: 'text-orange-500' },
{ label: '출금유형 미설정', value: `${stats.withdrawalTypeUnsetCount}`, icon: Banknote, iconColor: 'text-red-500' },
],
@@ -479,7 +480,7 @@ export function WithdrawalManagement({ initialData, initialPagination }: Withdra
{/* 수취인명 */}
<TableCell>{item.recipientName}</TableCell>
{/* 출금금액 */}
<TableCell className="text-right font-medium">{(item.withdrawalAmount ?? 0).toLocaleString()}</TableCell>
<TableCell className="text-right font-medium">{formatNumber(item.withdrawalAmount ?? 0)}</TableCell>
{/* 거래처 */}
<TableCell className={isVendorUnset ? 'text-red-500 font-medium' : ''}>
{item.vendorName || '미설정'}
@@ -517,7 +518,7 @@ export function WithdrawalManagement({ initialData, initialPagination }: Withdra
onClick={() => handleRowClick(item)}
details={[
{ label: '출금일', value: item.withdrawalDate || '-' },
{ label: '출금액', value: `${(item.withdrawalAmount ?? 0).toLocaleString()}` },
{ label: '출금액', value: `${formatNumber(item.withdrawalAmount ?? 0)}` },
{ label: '거래처', value: item.vendorName || '-' },
{ label: '적요', value: item.note || '-' },
]}

View File

@@ -1,4 +1,5 @@
import { Banknote } from 'lucide-react';
import { formatNumber } from '@/lib/utils/amount';
import type { DetailConfig, FieldDefinition } from '@/components/templates/IntegratedDetailTemplate/types';
import type { WithdrawalRecord } from './types';
import { WITHDRAWAL_TYPE_SELECTOR_OPTIONS } from './types';
@@ -117,7 +118,7 @@ export const withdrawalDetailConfig: DetailConfig = {
withdrawalDate: record.withdrawalDate || '',
bankAccountId: record.bankAccountId || '',
recipientName: record.recipientName || '',
withdrawalAmount: record.withdrawalAmount ? record.withdrawalAmount.toLocaleString() : '0',
withdrawalAmount: record.withdrawalAmount ? formatNumber(record.withdrawalAmount) : '0',
note: record.note || '',
vendorId: record.vendorId || '',
withdrawalType: record.withdrawalType || 'unset',