- 공통 템플릿 타입 수정 (IntegratedDetailTemplate, UniversalListPage) - 페이지(app/[locale]) 타입 호환성 수정 (80개) - 재고/자재 모듈 타입 수정 (StockStatus, ReceivingManagement) - 생산 모듈 타입 수정 (WorkOrders, WorkerScreen, WorkResults) - 주문/출고 모듈 타입 수정 (ShipmentManagement, Orders) - 견적/단가 모듈 타입 수정 (Quotes, Pricing) - 건설 모듈 타입 수정 (49개, 17개 하위 모듈) - HR 모듈 타입 수정 (CardManagement, VacationManagement 등) - 설정 모듈 타입 수정 (PermissionManagement, AccountManagement 등) - 게시판 모듈 타입 수정 (BoardManagement, BoardList 등) - 회계 모듈 타입 수정 (VendorManagement, BadDebtCollection 등) - 기타 모듈 타입 수정 (CEODashboard, clients, vehicle 등) - 유틸/훅/API 타입 수정 (hooks, contexts, lib) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
196 lines
6.9 KiB
TypeScript
196 lines
6.9 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { QuantityInput } from '@/components/ui/quantity-input';
|
|
import { CurrencyInput } from '@/components/ui/currency-input';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table';
|
|
import type { ExpenseItem } from '../types';
|
|
import { formatAmount } from '../utils';
|
|
|
|
// 공과 옵션 타입
|
|
interface ExpenseOption {
|
|
value: string;
|
|
label: string;
|
|
}
|
|
|
|
interface ExpenseDetailSectionProps {
|
|
expenseItems: ExpenseItem[];
|
|
expenseOptions: ExpenseOption[]; // 공과 품목 옵션 (Items API에서 조회)
|
|
isViewMode: boolean;
|
|
onAddItems: (count: number) => void;
|
|
onRemoveSelected: () => void;
|
|
onItemChange: (id: string, field: keyof ExpenseItem, value: string | number) => void;
|
|
onSelectItem: (id: string, selected: boolean) => void;
|
|
onSelectAll: (selected: boolean) => void;
|
|
}
|
|
|
|
export function ExpenseDetailSection({
|
|
expenseItems,
|
|
expenseOptions,
|
|
isViewMode,
|
|
onAddItems,
|
|
onRemoveSelected,
|
|
onItemChange,
|
|
onSelectItem,
|
|
onSelectAll,
|
|
}: ExpenseDetailSectionProps) {
|
|
const [expenseAddCount, setExpenseAddCount] = useState<number | undefined>(1);
|
|
const selectedCount = expenseItems.filter((item) => item.selected).length;
|
|
const allSelected = expenseItems.length > 0 && expenseItems.every((item) => item.selected);
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
|
<div className="flex items-center gap-4">
|
|
<CardTitle className="text-lg">공과 상세</CardTitle>
|
|
{!isViewMode && (
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm text-gray-600">{selectedCount}건 선택</span>
|
|
<Button
|
|
type="button"
|
|
variant="default"
|
|
size="sm"
|
|
className="bg-gray-900 hover:bg-gray-800"
|
|
onClick={onRemoveSelected}
|
|
disabled={selectedCount === 0}
|
|
>
|
|
삭제
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
{!isViewMode && (
|
|
<div className="flex items-center gap-2">
|
|
<QuantityInput
|
|
min={1}
|
|
value={expenseAddCount}
|
|
onChange={(val) => setExpenseAddCount(val)}
|
|
className="w-16 text-center"
|
|
id="expense-add-count"
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="default"
|
|
size="sm"
|
|
className="bg-gray-900 hover:bg-gray-800"
|
|
onClick={() => {
|
|
const count = Math.max(1, expenseAddCount ?? 1);
|
|
onAddItems(count);
|
|
}}
|
|
>
|
|
추가
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="overflow-x-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow className="bg-gray-100">
|
|
{!isViewMode && (
|
|
<TableHead className="w-[50px] text-center">
|
|
<input
|
|
type="checkbox"
|
|
className="h-4 w-4 rounded border-gray-300"
|
|
checked={allSelected}
|
|
onChange={(e) => onSelectAll(e.target.checked)}
|
|
/>
|
|
</TableHead>
|
|
)}
|
|
<TableHead className="text-center">공과</TableHead>
|
|
<TableHead className="text-right">금액</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{expenseItems.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell
|
|
colSpan={isViewMode ? 2 : 3}
|
|
className="text-center text-gray-500 py-8"
|
|
>
|
|
등록된 공과가 없습니다.
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
expenseItems.map((item) => (
|
|
<TableRow key={item.id}>
|
|
{!isViewMode && (
|
|
<TableCell className="text-center">
|
|
<input
|
|
type="checkbox"
|
|
className="h-4 w-4 rounded border-gray-300"
|
|
checked={item.selected || false}
|
|
onChange={(e) => onSelectItem(item.id, e.target.checked)}
|
|
/>
|
|
</TableCell>
|
|
)}
|
|
<TableCell>
|
|
<Select
|
|
value={item.name}
|
|
onValueChange={(val) => onItemChange(item.id, 'name', val)}
|
|
disabled={isViewMode}
|
|
>
|
|
<SelectTrigger className={isViewMode ? 'bg-gray-50' : 'bg-white'}>
|
|
<SelectValue placeholder="공과 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{expenseOptions.length === 0 ? (
|
|
<SelectItem value="_empty" disabled>
|
|
등록된 공과 품목이 없습니다
|
|
</SelectItem>
|
|
) : (
|
|
expenseOptions.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))
|
|
)}
|
|
</SelectContent>
|
|
</Select>
|
|
</TableCell>
|
|
<TableCell>
|
|
<CurrencyInput
|
|
value={item.amount}
|
|
onChange={(value) => onItemChange(item.id, 'amount', value ?? 0)}
|
|
disabled={isViewMode}
|
|
className={`text-right ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
{/* 합계 행 */}
|
|
{expenseItems.length > 0 && (
|
|
<TableRow className="bg-gray-50 font-medium">
|
|
<TableCell colSpan={isViewMode ? 1 : 2} className="text-center">
|
|
합계
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
{formatAmount(expenseItems.reduce((sum, item) => sum + item.amount, 0))}
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |