refactor(WEB): 회계/견적/설정/생산 등 전반적 코드 개선 및 공통화 2차
- 회계 모듈 전면 개선: 청구/입금/출금/매입/매출/세금계산서/일반전표/거래처원장 등 - 견적 모듈 금액 포맷/할인/수식/미리보기 등 코드 정리 - 설정 모듈: 계정관리/직급/직책/권한 상세 간소화 - 생산 모듈: 작업지시서/작업자화면/검수 문서 코드 정리 - UniversalListPage 엑셀 다운로드 및 필터 기능 확장 - 대시보드/게시판/수주 등 날짜 유틸 공통화 적용 - claudedocs 문서 인덱스 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,10 +19,12 @@ 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 { ContentSkeleton } from '@/components/ui/skeleton';
|
||||
import { toast } from 'sonner';
|
||||
import { formatAmountWon as formatCurrency } from '@/lib/utils/amount';
|
||||
import type { Card as CardType, CardFormData, CardStatus } from './types';
|
||||
import {
|
||||
CARD_COMPANIES,
|
||||
@@ -40,10 +42,6 @@ import {
|
||||
getApprovalFormUrl,
|
||||
} from './actions';
|
||||
|
||||
function formatCurrency(value: number): string {
|
||||
return value.toLocaleString('ko-KR') + '원';
|
||||
}
|
||||
|
||||
function formatExpiryDate(value: string): string {
|
||||
if (value && value.length === 4) {
|
||||
return `${value.slice(0, 2)}/${value.slice(2)}`;
|
||||
@@ -71,7 +69,6 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [mode, setMode] = useState(initialMode);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isLoadingApproval, setIsLoadingApproval] = useState(false);
|
||||
const [employees, setEmployees] = useState<Array<{ id: string; label: string }>>([]);
|
||||
@@ -154,16 +151,11 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
if (!card?.id) return;
|
||||
const result = await deleteCard(card.id);
|
||||
if (result.success) {
|
||||
toast.success('카드가 삭제되었습니다.');
|
||||
router.push('/ko/hr/card-management');
|
||||
} else {
|
||||
toast.error(result.error || '카드 삭제에 실패했습니다.');
|
||||
}
|
||||
};
|
||||
const deleteDialog = useDeleteDialog({
|
||||
onDelete: async (id) => deleteCard(id),
|
||||
onSuccess: () => router.push('/ko/hr/card-management'),
|
||||
entityName: '카드',
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
if (isCreateMode) {
|
||||
@@ -352,7 +344,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
|
||||
|
||||
{/* 하단 버튼 */}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => setShowDeleteDialog(true)} className="text-destructive hover:bg-destructive hover:text-destructive-foreground">
|
||||
<Button variant="outline" onClick={() => deleteDialog.single.open(card!.id)} className="text-destructive hover:bg-destructive hover:text-destructive-foreground">
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
삭제
|
||||
</Button>
|
||||
@@ -364,8 +356,8 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
|
||||
</div>
|
||||
|
||||
<DeleteConfirmDialog
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={setShowDeleteDialog}
|
||||
open={deleteDialog.single.isOpen}
|
||||
onOpenChange={deleteDialog.single.onOpenChange}
|
||||
description={
|
||||
<>
|
||||
카드를 정말 삭제하시겠습니까?
|
||||
@@ -375,7 +367,8 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onConfirm={deleteDialog.single.confirm}
|
||||
loading={deleteDialog.isPending}
|
||||
/>
|
||||
</PageLayout>
|
||||
);
|
||||
|
||||
@@ -41,12 +41,9 @@ import {
|
||||
CARD_STATUS_COLORS,
|
||||
getCardCompanyLabel,
|
||||
} from './types';
|
||||
import { formatAmountWon as formatCurrency } from '@/lib/utils/amount';
|
||||
import { getCards, getCardStats } from './actions';
|
||||
|
||||
function formatCurrency(value: number): string {
|
||||
return value.toLocaleString('ko-KR') + '원';
|
||||
}
|
||||
|
||||
export function CardManagement() {
|
||||
const router = useRouter();
|
||||
const itemsPerPage = 20;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
USER_ROLE_LABELS,
|
||||
USER_ACCOUNT_STATUS_LABELS,
|
||||
} from './types';
|
||||
import { formatNumber } from '@/lib/utils/amount';
|
||||
|
||||
interface EmployeeDetailProps {
|
||||
employee: Employee;
|
||||
@@ -87,7 +88,7 @@ export function EmployeeDetail({ employee, onEdit, onDelete }: EmployeeDetailPro
|
||||
{employee.salary && (
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-muted-foreground">연봉</dt>
|
||||
<dd className="text-sm mt-1">{employee.salary.toLocaleString()}원</dd>
|
||||
<dd className="text-sm mt-1">{formatNumber(employee.salary)}원</dd>
|
||||
</div>
|
||||
)}
|
||||
{employee.bankAccount && (
|
||||
|
||||
@@ -255,17 +255,17 @@ export function EmployeeManagement() {
|
||||
// 테이블 컬럼 정의
|
||||
const tableColumns = useMemo(() => [
|
||||
{ key: 'rowNumber', label: '번호', className: 'w-[60px] text-center' },
|
||||
{ key: 'employeeCode', label: '사원코드', className: 'min-w-[100px]' },
|
||||
{ key: 'department', label: '부서', className: 'min-w-[100px]' },
|
||||
{ key: 'position', label: '직책', className: 'min-w-[100px]' },
|
||||
{ key: 'name', label: '이름', className: 'min-w-[80px]' },
|
||||
{ key: 'rank', label: '직급', className: 'min-w-[80px]' },
|
||||
{ key: 'phone', label: '휴대폰', className: 'min-w-[120px]' },
|
||||
{ key: 'email', label: '이메일', className: 'min-w-[150px]' },
|
||||
{ key: 'hireDate', label: '입사일', className: 'min-w-[100px]' },
|
||||
{ key: 'status', label: '상태', className: 'min-w-[80px]' },
|
||||
{ key: 'userId', label: '사용자아이디', className: 'min-w-[100px]' },
|
||||
{ key: 'userRole', label: '권한', className: 'min-w-[80px]' },
|
||||
{ key: 'employeeCode', label: '사원코드', className: 'min-w-[100px]', sortable: true },
|
||||
{ key: 'department', label: '부서', className: 'min-w-[100px]', sortable: true },
|
||||
{ key: 'position', label: '직책', className: 'min-w-[100px]', sortable: true },
|
||||
{ key: 'name', label: '이름', className: 'min-w-[80px]', sortable: true },
|
||||
{ key: 'rank', label: '직급', className: 'min-w-[80px]', sortable: true },
|
||||
{ key: 'phone', label: '휴대폰', className: 'min-w-[120px]', sortable: true },
|
||||
{ key: 'email', label: '이메일', className: 'min-w-[150px]', sortable: true },
|
||||
{ key: 'hireDate', label: '입사일', className: 'min-w-[100px]', sortable: true },
|
||||
{ key: 'status', label: '상태', className: 'min-w-[80px]', sortable: true },
|
||||
{ key: 'userId', label: '사용자아이디', className: 'min-w-[100px]', sortable: true },
|
||||
{ key: 'userRole', label: '권한', className: 'min-w-[80px]', sortable: true },
|
||||
{ key: 'actions', label: '작업', className: 'w-[100px] text-right' },
|
||||
], []);
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
} from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { toast } from 'sonner';
|
||||
import { formatDate } from '@/lib/utils/date';
|
||||
|
||||
// ===== Mock 데이터 생성 (request 탭용 - 신청 현황은 leaves API 사용 예정) =====
|
||||
|
||||
@@ -188,7 +189,7 @@ export function VacationManagement() {
|
||||
position: item.jobTitle || '-', // job_title_label → 직책
|
||||
rank: item.rank || '-', // json_extra.rank → 직급
|
||||
vacationType: item.grantType as VacationType,
|
||||
grantDate: item.grantDate.split('T')[0],
|
||||
grantDate: formatDate(item.grantDate),
|
||||
grantDays: item.grantDays,
|
||||
reason: item.reason || undefined,
|
||||
createdAt: item.createdAt,
|
||||
@@ -235,7 +236,7 @@ export function VacationManagement() {
|
||||
endDate: item.endDate,
|
||||
vacationDays: item.days,
|
||||
status: item.status as RequestStatus,
|
||||
requestDate: item.createdAt.split('T')[0],
|
||||
requestDate: formatDate(item.createdAt),
|
||||
createdAt: item.createdAt,
|
||||
updatedAt: item.updatedAt,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user