feat(WEB): 견적 시스템 개선, 엑셀 다운로드, PDF 생성 기능 추가
견적 시스템: - QuoteRegistrationV2: 할인 모달, 거래명세서 모달, vatType 필드 추가 - DiscountModal: 할인율/할인금액 상호 계산 모달 - QuoteTransactionModal: 거래명세서 미리보기 모달 - LocationDetailPanel, LocationListPanel 개선 템플릿 기능: - UniversalListPage: 엑셀 다운로드 기능 추가 (전체/선택 다운로드) - DocumentViewer: PDF 생성 기능 개선 신규 API: - /api/pdf/generate: Puppeteer 기반 PDF 생성 엔드포인트 UI 개선: - 입력 컴포넌트 placeholder 스타일 개선 (opacity 50%) - 각종 리스트 컴포넌트 정렬/필터링 개선 패키지 추가: - html2canvas, jspdf, puppeteer, dom-to-image-more Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,9 +17,9 @@ import {
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
AlertCircle,
|
||||
Download,
|
||||
Eye,
|
||||
} from 'lucide-react';
|
||||
import type { ExcelColumn } from '@/lib/utils/excel-download';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { TableCell, TableRow } from '@/components/ui/table';
|
||||
@@ -37,7 +37,7 @@ import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
|
||||
import { getStocks, getStockStats, getStockStatsByType } from './actions';
|
||||
import { ITEM_TYPE_LABELS, ITEM_TYPE_STYLES, STOCK_STATUS_LABELS } from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import type { StockItem, StockStats, ItemType } from './types';
|
||||
import type { StockItem, StockStats, ItemType, StockStatusType } from './types';
|
||||
|
||||
// 페이지당 항목 수
|
||||
const ITEMS_PER_PAGE = 20;
|
||||
@@ -80,10 +80,42 @@ export function StockStatusList() {
|
||||
[router]
|
||||
);
|
||||
|
||||
// ===== 엑셀 다운로드 =====
|
||||
const handleExcelDownload = useCallback(() => {
|
||||
console.log('엑셀 다운로드');
|
||||
// TODO: 엑셀 다운로드 기능 구현
|
||||
// ===== 엑셀 컬럼 정의 =====
|
||||
const excelColumns: ExcelColumn<StockItem>[] = useMemo(() => [
|
||||
{ header: '품목코드', key: 'itemCode' },
|
||||
{ header: '품목명', key: 'itemName' },
|
||||
{ header: '품목유형', key: 'itemType', transform: (value) => ITEM_TYPE_LABELS[value as ItemType] || String(value) },
|
||||
{ header: '단위', key: 'unit' },
|
||||
{ header: '재고량', key: 'stockQty' },
|
||||
{ header: '안전재고', key: 'safetyStock' },
|
||||
{ header: 'LOT수', key: 'lotCount' },
|
||||
{ header: 'LOT경과일', key: 'lotDaysElapsed' },
|
||||
{ header: '상태', key: 'status', transform: (value) => value ? STOCK_STATUS_LABELS[value as StockStatusType] : '-' },
|
||||
{ header: '위치', key: 'location' },
|
||||
], []);
|
||||
|
||||
// ===== API 응답 매핑 함수 =====
|
||||
const mapStockResponse = useCallback((result: unknown): StockItem[] => {
|
||||
const data = result as { data?: { data?: Record<string, unknown>[] } };
|
||||
const rawItems = data.data?.data ?? [];
|
||||
return rawItems.map((item: Record<string, unknown>) => {
|
||||
const stock = item.stock as Record<string, unknown> | null;
|
||||
const hasStock = !!stock;
|
||||
return {
|
||||
id: String(item.id ?? ''),
|
||||
itemCode: (item.code ?? '') as string,
|
||||
itemName: (item.name ?? '') as string,
|
||||
itemType: (item.item_type ?? 'RM') as ItemType,
|
||||
unit: (item.unit ?? 'EA') as string,
|
||||
stockQty: hasStock ? (parseFloat(String(stock?.stock_qty)) || 0) : 0,
|
||||
safetyStock: hasStock ? (parseFloat(String(stock?.safety_stock)) || 0) : 0,
|
||||
lotCount: hasStock ? (Number(stock?.lot_count) || 0) : 0,
|
||||
lotDaysElapsed: hasStock ? (Number(stock?.days_elapsed) || 0) : 0,
|
||||
status: hasStock ? (stock?.status as StockStatusType | null) : null,
|
||||
location: hasStock ? ((stock?.location as string) || '-') : '-',
|
||||
hasStock,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
// ===== 통계 카드 =====
|
||||
@@ -236,13 +268,24 @@ export function StockStatusList() {
|
||||
// 테이블 푸터
|
||||
tableFooter,
|
||||
|
||||
// 헤더 액션 (엑셀 다운로드)
|
||||
headerActions: () => (
|
||||
<Button variant="outline" onClick={handleExcelDownload}>
|
||||
<Download className="w-4 h-4 mr-1.5" />
|
||||
엑셀 다운로드
|
||||
</Button>
|
||||
),
|
||||
// 엑셀 다운로드 설정
|
||||
excelDownload: {
|
||||
columns: excelColumns,
|
||||
filename: '재고현황',
|
||||
sheetName: '재고',
|
||||
fetchAllUrl: '/api/proxy/stocks',
|
||||
fetchAllParams: ({ activeTab, searchValue }) => {
|
||||
const params: Record<string, string> = {};
|
||||
if (activeTab && activeTab !== 'all') {
|
||||
params.item_type = activeTab;
|
||||
}
|
||||
if (searchValue) {
|
||||
params.search = searchValue;
|
||||
}
|
||||
return params;
|
||||
},
|
||||
mapResponse: mapStockResponse,
|
||||
},
|
||||
|
||||
// 테이블 행 렌더링
|
||||
renderTableRow: (
|
||||
@@ -363,7 +406,7 @@ export function StockStatusList() {
|
||||
);
|
||||
},
|
||||
}),
|
||||
[tabs, stats, tableFooter, handleRowClick, handleExcelDownload]
|
||||
[tabs, stats, tableFooter, handleRowClick, excelColumns, mapStockResponse]
|
||||
);
|
||||
|
||||
return <UniversalListPage config={config} />;
|
||||
|
||||
Reference in New Issue
Block a user