(
- '협력사_업로드_양식',
- [
- { header: '협력사명', value: (r) => r.name },
- { header: '식별코드', value: (r) => r.code },
- { header: '담당자명', value: (r) => r.managerName },
- { header: '담당자이메일', value: (r) => r.managerEmail },
- { header: '우선순위', value: (r) => r.priority },
- ],
- [{ name: '예시) (주)한빛정밀', code: 'PART-EXAMPLE-001', managerName: '김철수 과장', managerEmail: 'cs.kim@example.com', priority: 'HIGH' }],
- );
- };
-
// 업로드된 CSV를 파싱해 원본 행으로 적재(검증은 자동 파생). 헤더는 양식과 동일해야 함.
const handleFile = async (file: File) => {
const parsed = parseCsv(await file.text());
@@ -243,16 +243,6 @@ export function ExcelUploadModal({ open, partners, onConfirm, onClose }: ExcelUp
>
엑셀 파일 업로드하기
-
-
) : (
diff --git a/negodata/front/src/features/products/components/ExcelUploadModal.tsx b/negodata/front/src/features/products/components/ExcelUploadModal.tsx
index de687cf..c208868 100644
--- a/negodata/front/src/features/products/components/ExcelUploadModal.tsx
+++ b/negodata/front/src/features/products/components/ExcelUploadModal.tsx
@@ -1,5 +1,5 @@
import { useMemo, useRef, useState } from 'react';
-import { Upload, X, FileSpreadsheet, CheckCircle2, Download, Trash2 } from 'lucide-react';
+import { Upload, X, FileSpreadsheet, CheckCircle2, Trash2 } from 'lucide-react';
import type { ReqCreateItem as ItemCreate } from '@/api/generated/model/reqCreateItem';
import { showToast } from '@/lib/notify';
import { downloadExcel, parseCsv, type BulkFailure } from '@/lib/excel';
@@ -22,6 +22,8 @@ type RawRow = {
made_in: string;
price: number;
minPrice: number;
+ purchase_price: number;
+ selling_price: number;
image_url: string;
moq: string;
lead_time: number;
@@ -59,6 +61,8 @@ const UPLOAD_COLUMNS: { header: string; key: keyof RawRow }[] = [
{ header: '원산지', key: 'made_in' },
{ header: '상품 단가', key: 'price' },
{ header: '최저한도', key: 'minPrice' },
+ { header: '매입가', key: 'purchase_price' },
+ { header: '판매가', key: 'selling_price' },
{ header: '이미지URL', key: 'image_url' },
{ header: '최소주문수량', key: 'moq' },
{ header: '리드타임(일)', key: 'lead_time' },
@@ -69,7 +73,7 @@ const UPLOAD_COLUMNS: { header: string; key: keyof RawRow }[] = [
];
// 숫자 입력 컬럼 / 필수 컬럼(헤더에 * 표기)
-const NUMERIC_KEYS = new Set(['price', 'minPrice', 'lead_time']);
+const NUMERIC_KEYS = new Set(['price', 'minPrice', 'purchase_price', 'selling_price', 'lead_time']);
const REQUIRED_KEYS = new Set(['name', 'code', 'price']);
// 양식에 채워 넣는 예시 행(시드 상품과 동일 셋). 다운로드 양식에 그대로 들어간다.
@@ -77,19 +81,28 @@ const EXAMPLE_ROWS: Record[] = [
{
name: '리튬인산철 배터리 모듈', code: 'BAT-LFP-100', model_name: 'LFP-100A',
category: '에너지/배터리', spec: '3.2V 100Ah', manufacturer: '한성에너지', made_in: '대한민국',
- price: 1250000, minPrice: 1037500, image_url: 'https://example.com/img/lfp-100a.jpg',
+ price: 1250000, minPrice: 1037500, purchase_price: 1000000, selling_price: 1250000, image_url: 'https://example.com/img/lfp-100a.jpg',
moq: '10 EA', lead_time: 14, quantity_unit: 'EA', delivery_type: '협력사배송',
vat_yn: 'Y', delivery_fee_yn: 'N',
},
{
name: '산업용 6축 로봇암', code: 'ROB-6AX-22', model_name: 'RX-6A',
category: '자동화설비', spec: '가반하중 12kg', manufacturer: '오토메카', made_in: '일본',
- price: 18900000, minPrice: 16065000, image_url: 'https://example.com/img/rx-6a.jpg',
+ price: 18900000, minPrice: 16065000, purchase_price: 15000000, selling_price: 18900000, image_url: 'https://example.com/img/rx-6a.jpg',
moq: '1 EA', lead_time: 30, quantity_unit: 'EA', delivery_type: '지정택배배송',
vat_yn: 'Y', delivery_fee_yn: 'N',
},
];
+// 업로드 양식(.csv) 다운로드 — 전체 컬럼 헤더 + 예시 행(시드 상품 셋). UPLOAD_COLUMNS 단일 정의 공유. 툴바·모달이 공유한다.
+export function downloadProductTemplate() {
+ downloadExcel>(
+ '상품_업로드_양식',
+ UPLOAD_COLUMNS.map((c) => ({ header: c.header, value: (r) => r[c.key] })),
+ EXAMPLE_ROWS,
+ );
+}
+
type ExcelUploadModalProps = {
open: boolean;
products: Product[]; // 코드 중복 검사용
@@ -149,6 +162,8 @@ function toItemCreate(row: RawRow): ItemCreate {
manufacturer: row.manufacturer || undefined,
made_in: row.made_in || undefined,
price: row.price,
+ purchase_price: row.purchase_price || undefined,
+ selling_price: row.selling_price || undefined,
image_url: row.image_url || undefined,
moq: row.moq || undefined,
lead_time: row.lead_time || undefined,
@@ -187,15 +202,6 @@ export function ExcelUploadModal({ open, products, onConfirm, onClose }: ExcelUp
onClose();
};
- // 업로드 양식(.csv) 다운로드 — 전체 컬럼 헤더 + 예시 행(시드 상품 셋). UPLOAD_COLUMNS 단일 정의 공유.
- const handleDownloadTemplate = () => {
- downloadExcel>(
- '상품_업로드_양식',
- UPLOAD_COLUMNS.map((c) => ({ header: c.header, value: (r) => r[c.key] })),
- EXAMPLE_ROWS,
- );
- };
-
// 업로드된 CSV를 파싱해 원본 행으로 적재(검증은 자동 파생). 헤더는 양식과 동일해야 함.
const handleFile = async (file: File) => {
const parsed = parseCsv(await file.text());
@@ -211,6 +217,8 @@ export function ExcelUploadModal({ open, products, onConfirm, onClose }: ExcelUp
made_in: r['원산지'] ?? '',
price: Number(r['상품 단가']) || 0,
minPrice: Number(r['최저한도']) || 0,
+ purchase_price: Number(r['매입가']) || 0,
+ selling_price: Number(r['판매가']) || 0,
image_url: r['이미지URL'] ?? '',
moq: r['최소주문수량'] ?? '',
lead_time: Number(r['리드타임(일)']) || 0,
@@ -345,16 +353,6 @@ export function ExcelUploadModal({ open, products, onConfirm, onClose }: ExcelUp
>
엑셀 파일 업로드하기
-
-
) : (
diff --git a/negodata/front/src/pages/partners.tsx b/negodata/front/src/pages/partners.tsx
index a2a8a80..013ddf1 100644
--- a/negodata/front/src/pages/partners.tsx
+++ b/negodata/front/src/pages/partners.tsx
@@ -1,4 +1,4 @@
-import { Plus, Upload } from 'lucide-react';
+import { Plus, Upload, Download, FileSpreadsheet, ChevronDown } from 'lucide-react';
import { useOverlayRouter } from '@/lib/useOverlayRouter';
import { showToast } from '@/lib/notify';
import { confirm } from '@/lib/confirm';
@@ -6,12 +6,13 @@ import { PageContainer } from '@/components/layout/PageContainer';
import { PageToolbar, SearchInput } from '@/components/layout/PageToolbar';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '@/components/ui/button';
+import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu';
import { useServerList } from '@/lib/useServerList';
import { usePartners } from '@/features/partners/hooks/usePartners';
import type { ListSuppliersParams } from '@/api/generated/model/listSuppliersParams';
import { PartnerTable } from '@/features/partners/components/PartnerTable';
import { PartnerFormSheet } from '@/features/partners/components/PartnerFormSheet';
-import { ExcelUploadModal } from '@/features/partners/components/ExcelUploadModal';
+import { ExcelUploadModal, downloadPartnerTemplate } from '@/features/partners/components/ExcelUploadModal';
import { prioritiesList, type Partner } from '@/features/partners/types';
export default function PartnersPage() {
@@ -57,10 +58,23 @@ export default function PartnersPage() {
-
+
+ }>
+
+ 엑셀업로드
+
+
+
+ overlay.open('modal', 'excel')}>
+
+ 일괄 업로드
+
+
+
+ 양식 다운로드
+
+
+
-
+
+ }>
+
+ 엑셀업로드
+
+
+
+ overlay.open('modal', 'excel')}>
+
+ 일괄 업로드
+
+
+
+ 양식 다운로드
+
+
+