Files
sam-react-prod/src/components/business/construction/estimates/sections/EstimateDetailTableSection.tsx
권혁성 2465d739fe feat: 견적서 목업 데이터 → API 연동 전환
- EstimateDetailTableSection: 하드코딩된 셀렉트 옵션 → API 데이터 연동
  - 재료/도장/모터/제어기/시공비: getCommonCodeOptions() 사용
  - 공과 품목: getExpenseItemOptions() 사용
- EstimateListClient: 거래처/견적자 필터 API 연동
  - MOCK_PARTNERS → getClientOptions()
  - MOCK_ESTIMATORS → getUserOptions()
- actions.ts: 공통코드/거래처/사용자/공과품목 API 함수 추가
- constants.ts: MOCK_MATERIALS 제거
- EstimateDetailForm: MOCK_MATERIALS import 제거
2026-01-20 16:10:23 +09:00

661 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React from 'react';
import { X, HelpCircle } from 'lucide-react';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import type { EstimateDetailItem } from '../types';
import { formatAmount } from '../utils';
import { calculateItemValuesWithApplied, calculateTotalsWithApplied } from '../hooks/useEstimateCalculations';
import type { CommonCodeOption } from '../actions';
// 계산식 정보
const FORMULA_INFO: Record<string, string> = {
weight: '면적 × 25',
area: '(가로 × 0.16) × (세로 × 0.5)',
steelScreen: '면적 × 47,500',
caulking: '(세로 × 4) × 조정단가',
rail: '(세로 × 0.2) × 조정단가',
bottom: '가로 × 조정단가',
boxReinforce: '가로 × 조정단가',
shaft: '가로 × 조정단가',
unitPrice: '철제스크린 + 코킹 + 레일 + 하장 + 박스보강 + 샤프트 + 도장 + 모터 + 제어기 + 가로시공비 + 세로시공비',
expense: '단가 × 공과율',
cost: '단가 + 공과',
costExecution: '원가 × 수량',
marginCost: '원가 × 마진율(1.03)',
marginCostExecution: '마진원가 × 수량',
expenseExecution: '공과 × 수량',
};
// 계산식 툴팁이 있는 헤더 컴포넌트
function FormulaHeader({ label, formulaKey, className }: { label: string; formulaKey: string; className?: string }) {
const formula = FORMULA_INFO[formulaKey];
if (!formula) {
return <span>{label}</span>;
}
return (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<span className={`inline-flex items-center gap-1 cursor-help ${className || ''}`}>
{label}
<HelpCircle className="h-3.5 w-3.5 text-gray-400 hover:text-gray-600" />
</span>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-xs">
<p className="text-xs font-medium text-gray-700">{label} </p>
<p className="text-xs text-gray-500 mt-1">{formula}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
// appliedPrices 타입 정의
export interface AppliedPrices {
caulking: number;
rail: number;
bottom: number;
boxReinforce: number;
shaft: number;
painting: number;
motor: number;
controller: number;
}
// 옵션 데이터 타입
export interface EstimateDetailOptions {
materials: CommonCodeOption[];
paintings: CommonCodeOption[];
motors: CommonCodeOption[];
controllers: CommonCodeOption[];
widthConstructions: CommonCodeOption[];
heightConstructions: CommonCodeOption[];
}
interface EstimateDetailTableSectionProps {
detailItems: EstimateDetailItem[];
appliedPrices: AppliedPrices | null;
isViewMode: boolean;
options?: EstimateDetailOptions;
onAddItems: (count: number) => void;
onRemoveItem: (id: string) => void;
onRemoveSelected: () => void;
onItemChange: (id: string, field: keyof EstimateDetailItem, value: string | number) => void;
onSelectItem: (id: string, selected: boolean) => void;
onSelectAll: (selected: boolean) => void;
onApplyAdjustedPrice: () => void;
onReset: () => void;
}
// API 데이터 로드 전 기본 옵션 (폴백용)
const DEFAULT_OPTIONS: EstimateDetailOptions = {
materials: [
{ value: 'screen', label: '스크린', code: 'screen' },
{ value: 'slat', label: '슬랫', code: 'slat' },
{ value: 'bending', label: '벤딩', code: 'bending' },
{ value: 'jointbar', label: '조인트바', code: 'jointbar' },
],
paintings: [
{ value: '0', label: '직접입력', code: '0', price: 0 },
{ value: '50000', label: '도장A', code: 'painting_a', price: 50000 },
{ value: '80000', label: '도장B', code: 'painting_b', price: 80000 },
],
motors: [
{ value: '300000', label: '모터 300,000', code: 'motor_300k', price: 300000 },
{ value: '500000', label: '모터 500,000', code: 'motor_500k', price: 500000 },
],
controllers: [
{ value: '150000', label: '제어기 150,000', code: 'ctrl_150k', price: 150000 },
{ value: '250000', label: '제어기 250,000', code: 'ctrl_250k', price: 250000 },
],
widthConstructions: [
{ value: '300000', label: '3.01~4.0M', code: 'w_3_4m', price: 300000 },
{ value: '400000', label: '4.01~5.0M', code: 'w_4_5m', price: 400000 },
{ value: '500000', label: '5.01~6.0M', code: 'w_5_6m', price: 500000 },
{ value: '600000', label: '6.01~7.0M', code: 'w_6_7m', price: 600000 },
],
heightConstructions: [
{ value: '5000', label: '3.51~4.5M', code: 'h_3_4m', price: 5000 },
{ value: '8000', label: '4.51~5.5M', code: 'h_4_5m', price: 8000 },
{ value: '10000', label: '5.51~6.5M', code: 'h_5_6m', price: 10000 },
],
};
export function EstimateDetailTableSection({
detailItems,
appliedPrices,
isViewMode,
options,
onAddItems,
onRemoveItem,
onRemoveSelected,
onItemChange,
onSelectItem,
onSelectAll,
onApplyAdjustedPrice,
onReset,
}: EstimateDetailTableSectionProps) {
// API 옵션이 없으면 기본 옵션 사용
const opts = options || DEFAULT_OPTIONS;
const selectedCount = detailItems.filter((item) => (item as unknown as { selected?: boolean }).selected).length;
const allSelected = detailItems.length > 0 && detailItems.every((item) => (item as unknown as { selected?: boolean }).selected);
const totals = calculateTotalsWithApplied(detailItems, appliedPrices);
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-4">
<div className="flex items-center gap-4">
<CardTitle className="text-lg whitespace-nowrap"> </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}
>
</Button>
<Button
type="button"
variant="default"
size="sm"
className="bg-blue-500 hover:bg-blue-600"
onClick={onApplyAdjustedPrice}
>
</Button>
</div>
)}
</div>
{!isViewMode && (
<div className="flex items-center gap-2">
<Input
type="number"
min={1}
defaultValue={1}
className="w-16 text-center"
id="detail-add-count"
/>
<Button
type="button"
variant="default"
size="sm"
className="bg-gray-900 hover:bg-gray-800"
onClick={() => {
const countInput = document.getElementById('detail-add-count') as HTMLInputElement;
const count = Math.max(1, parseInt(countInput?.value || '1', 10));
onAddItems(count);
}}
>
</Button>
{/* TODO: 견적 상세 기획서 수정 후 초기화 버튼 및 테이블 항목/데이터 재작업 필요
<Button type="button" variant="outline" size="sm" onClick={onReset}>
초기화
</Button>
*/}
</div>
)}
</CardHeader>
<CardContent>
<div className="overflow-x-auto max-h-[600px]">
<Table>
<TableHeader className="sticky top-0 bg-white z-10">
<TableRow className="bg-gray-100">
{!isViewMode && (
<TableHead className="w-[40px] text-center sticky left-0 bg-gray-100 z-20">
<input
type="checkbox"
className="h-4 w-4 rounded border-gray-300"
checked={allSelected}
onChange={(e) => onSelectAll(e.target.checked)}
/>
</TableHead>
)}
<TableHead className="w-[100px] text-center"></TableHead>
<TableHead className="w-[80px] text-center"></TableHead>
<TableHead className="w-[70px] text-right"></TableHead>
<TableHead className="w-[70px] text-right"></TableHead>
<TableHead className="w-[70px] text-right">
<FormulaHeader label="무게" formulaKey="weight" />
</TableHead>
<TableHead className="w-[70px] text-right">
<FormulaHeader label="면적" formulaKey="area" />
</TableHead>
<TableHead className="w-[90px] text-right">
<FormulaHeader label="철제,스크린" formulaKey="steelScreen" />
</TableHead>
<TableHead className="w-[80px] text-right">
<FormulaHeader label="코킹" formulaKey="caulking" />
</TableHead>
<TableHead className="w-[80px] text-right">
<FormulaHeader label="레일" formulaKey="rail" />
</TableHead>
<TableHead className="w-[80px] text-right">
<FormulaHeader label="하장" formulaKey="bottom" />
</TableHead>
<TableHead className="w-[90px] text-right">
<FormulaHeader label="박스+보강" formulaKey="boxReinforce" />
</TableHead>
<TableHead className="w-[80px] text-right">
<FormulaHeader label="샤프트" formulaKey="shaft" />
</TableHead>
<TableHead className="w-[80px] text-center"></TableHead>
<TableHead className="w-[80px] text-center"></TableHead>
<TableHead className="w-[80px] text-center"></TableHead>
<TableHead className="w-[100px] text-center"></TableHead>
<TableHead className="w-[100px] text-center"></TableHead>
<TableHead className="w-[90px] text-right">
<FormulaHeader label="단가" formulaKey="unitPrice" />
</TableHead>
<TableHead className="w-[70px] text-right"></TableHead>
<TableHead className="w-[80px] text-right">
<FormulaHeader label="공과" formulaKey="expense" />
</TableHead>
<TableHead className="w-[50px] text-right"></TableHead>
<TableHead className="w-[90px] text-right">
<FormulaHeader label="원가" formulaKey="cost" />
</TableHead>
<TableHead className="w-[90px] text-right">
<FormulaHeader label="원가실행" formulaKey="costExecution" />
</TableHead>
<TableHead className="w-[90px] text-right">
<FormulaHeader label="마진원가" formulaKey="marginCost" />
</TableHead>
<TableHead className="w-[100px] text-right">
<FormulaHeader label="마진원가실행" formulaKey="marginCostExecution" />
</TableHead>
<TableHead className="w-[90px] text-right">
<FormulaHeader label="공과실행" formulaKey="expenseExecution" />
</TableHead>
{!isViewMode && <TableHead className="w-[50px] text-center"></TableHead>}
</TableRow>
</TableHeader>
<TableBody>
{detailItems.length === 0 ? (
<TableRow>
<TableCell
colSpan={isViewMode ? 27 : 29}
className="text-center text-gray-500 py-8"
>
.
</TableCell>
</TableRow>
) : (
<>
{detailItems.map((item) => {
const values = calculateItemValuesWithApplied(item, appliedPrices);
return (
<TableRow key={item.id}>
{!isViewMode && (
<TableCell className="text-center sticky left-0 bg-white">
<input
type="checkbox"
className="h-4 w-4 rounded border-gray-300"
checked={(item as unknown as { selected?: boolean }).selected || false}
onChange={(e) => onSelectItem(item.id, e.target.checked)}
/>
</TableCell>
)}
{/* 01: 명칭 */}
<TableCell>
<Input
value={item.name ?? ''}
onChange={(e) => onItemChange(item.id, 'name', e.target.value)}
disabled={isViewMode}
className={`w-full min-w-[80px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 02: 제품 */}
<TableCell>
<Select
value={item.material ?? ''}
onValueChange={(val) => onItemChange(item.id, 'material', val)}
disabled={isViewMode}
>
<SelectTrigger className={`w-full min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}>
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{opts.materials.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</TableCell>
{/* 03: 가로 */}
<TableCell>
<Input
type="number"
step="0.01"
value={item.width ?? 0}
onChange={(e) => onItemChange(item.id, 'width', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[60px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 04: 세로 */}
<TableCell>
<Input
type="number"
step="0.01"
value={item.height ?? 0}
onChange={(e) => onItemChange(item.id, 'height', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[60px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 05: 무게 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
step="0.01"
value={values.weight.toFixed(2)}
onChange={(e) => onItemChange(item.id, 'calcWeight', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[60px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 06: 면적 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
step="0.01"
value={values.area.toFixed(2)}
onChange={(e) => onItemChange(item.id, 'calcArea', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[60px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 07: 철제,스크린 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
value={values.steelScreen}
onChange={(e) => onItemChange(item.id, 'calcSteelScreen', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[80px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 08: 코킹 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
value={values.caulking}
onChange={(e) => onItemChange(item.id, 'calcCaulking', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 09: 레일 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
value={values.rail}
onChange={(e) => onItemChange(item.id, 'calcRail', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 10: 하장 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
value={values.bottom}
onChange={(e) => onItemChange(item.id, 'calcBottom', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 11: 박스+보강 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
value={values.boxReinforce}
onChange={(e) => onItemChange(item.id, 'calcBoxReinforce', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[80px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 12: 샤프트 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
value={values.shaft}
onChange={(e) => onItemChange(item.id, 'calcShaft', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 13: 도장 */}
<TableCell>
<Select
value={String(item.coating || '')}
onValueChange={(val) => onItemChange(item.id, 'coating', Number(val))}
disabled={isViewMode}
>
<SelectTrigger className={`w-full min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}>
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{opts.paintings.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</TableCell>
{/* 14: 모터 */}
<TableCell>
<Select
value={String(item.mounting || '300000')}
onValueChange={(val) => onItemChange(item.id, 'mounting', Number(val))}
disabled={isViewMode}
>
<SelectTrigger className={`w-full min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}>
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{opts.motors.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</TableCell>
{/* 15: 제어기 */}
<TableCell>
<Select
value={String(item.controller || '')}
onValueChange={(val) => onItemChange(item.id, 'controller', Number(val))}
disabled={isViewMode}
>
<SelectTrigger className={`w-full min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}>
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{opts.controllers.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</TableCell>
{/* 16: 가로시공비 */}
<TableCell>
<Select
value={String(item.widthConstruction || '')}
onValueChange={(val) => onItemChange(item.id, 'widthConstruction', Number(val))}
disabled={isViewMode}
>
<SelectTrigger className={`w-full min-w-[90px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}>
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{opts.widthConstructions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</TableCell>
{/* 17: 세로시공비 */}
<TableCell>
<Select
value={String(item.heightConstruction || '')}
onValueChange={(val) => onItemChange(item.id, 'heightConstruction', Number(val))}
disabled={isViewMode}
>
<SelectTrigger className={`w-full min-w-[90px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}>
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{opts.heightConstructions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</TableCell>
{/* 18: 단가 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
value={values.unitPrice}
onChange={(e) => onItemChange(item.id, 'calcUnitPrice', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[80px] font-medium ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 19: 공과율 */}
<TableCell>
<Input
type="number"
step="0.01"
value={item.expense ?? 0}
onChange={(e) => onItemChange(item.id, 'expense', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[60px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 20: 공과 (인풋, 계산값 표시 + 수정 가능) */}
<TableCell>
<Input
type="number"
value={values.expense}
onChange={(e) => onItemChange(item.id, 'calcExpense', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[70px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 21: 수량 */}
<TableCell>
<Input
type="number"
min={1}
value={item.quantity ?? 0}
onChange={(e) => onItemChange(item.id, 'quantity', Number(e.target.value))}
disabled={isViewMode}
className={`text-right min-w-[40px] ${isViewMode ? 'bg-gray-50' : 'bg-white'}`}
/>
</TableCell>
{/* 22: 원가 */}
<TableCell className="text-right bg-gray-50">{formatAmount(values.cost)}</TableCell>
{/* 23: 원가실행 */}
<TableCell className="text-right bg-gray-50">{formatAmount(values.costExecution)}</TableCell>
{/* 24: 마진원가 */}
<TableCell className="text-right bg-gray-50">{formatAmount(values.marginCost)}</TableCell>
{/* 25: 마진원가실행 */}
<TableCell className="text-right bg-gray-50 font-medium">{formatAmount(values.marginCostExecution)}</TableCell>
{/* 26: 공과실행 */}
<TableCell className="text-right bg-gray-50">{formatAmount(values.expenseExecution)}</TableCell>
{!isViewMode && (
<TableCell className="text-center">
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8 text-red-500 hover:text-red-600"
onClick={() => onRemoveItem(item.id)}
>
<X className="h-4 w-4" />
</Button>
</TableCell>
)}
</TableRow>
);
})}
{/* 합계 행 */}
{detailItems.length > 0 && (
<TableRow className="bg-orange-50 font-medium border-t-2 border-orange-300">
{!isViewMode && <TableCell className="sticky left-0 bg-orange-50"></TableCell>}
<TableCell colSpan={4} className="text-center font-bold"></TableCell>
<TableCell className="text-right">{totals.weight.toFixed(2)}</TableCell>
<TableCell className="text-right">{totals.area.toFixed(2)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.steelScreen)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.caulking)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.rail)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.bottom)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.boxReinforce)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.shaft)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.painting)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.motor)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.controller)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.widthConstruction)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.heightConstruction)}</TableCell>
<TableCell className="text-right font-bold">{formatAmount(totals.unitPrice)}</TableCell>
<TableCell className="text-right">-</TableCell>
<TableCell className="text-right">{formatAmount(totals.expense)}</TableCell>
<TableCell className="text-right">{totals.quantity}</TableCell>
<TableCell className="text-right">{formatAmount(totals.cost)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.costExecution)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.marginCost)}</TableCell>
<TableCell className="text-right font-bold">{formatAmount(totals.marginCostExecution)}</TableCell>
<TableCell className="text-right">{formatAmount(totals.expenseExecution)}</TableCell>
{!isViewMode && <TableCell></TableCell>}
</TableRow>
)}
</>
)}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
);
}