refactor(WEB): 회계/견적/설정/생산 등 전반적 코드 개선 및 공통화 2차
- 회계 모듈 전면 개선: 청구/입금/출금/매입/매출/세금계산서/일반전표/거래처원장 등 - 견적 모듈 금액 포맷/할인/수식/미리보기 등 코드 정리 - 설정 모듈: 계정관리/직급/직책/권한 상세 간소화 - 생산 모듈: 작업지시서/작업자화면/검수 문서 코드 정리 - UniversalListPage 엑셀 다운로드 및 필터 기능 확장 - 대시보드/게시판/수주 등 날짜 유틸 공통화 적용 - claudedocs 문서 인덱스 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 || '-' },
|
||||
]}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user