o2o-negosium-original/negodata/front/src/pages/partners.tsx
Mina Choi 87524bf6e8 [feat] negodata/front: 프론트 초기 구축 (Vite+React, orval API 클라이언트, 견적/상품/협력사 페이지)
- 라우팅 react-router v7, 서버상태 TanStack Query
- orval 자동 생성 API 클라이언트(src/api/generated)
- 견적/상품/협력사/견적설정 페이지, shadcn UI, 토큰(tokens.css)
- 실행: 루트 docker compose (front :3001), README 그에 맞게 정리
- AI Studio 스캐폴드 잔재 제거(metadata.json, 중복 DESIGN_TOKENS.md), 타이틀 NegoData, vite.config 죽은코드 제거

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:18:08 +09:00

131 lines
4.2 KiB
TypeScript

import { useState } from 'react';
import { Plus, Upload } from 'lucide-react';
import { showToast } from '@/lib/notify';
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 { usePartners } from '@/features/partners/hooks/usePartners';
import { usePartnerFilters } from '@/features/partners/hooks/usePartnerFilters';
import { PartnerTable } from '@/features/partners/components/PartnerTable';
import { PartnerFormSheet } from '@/features/partners/components/PartnerFormSheet';
import { ExcelUploadModal } from '@/features/partners/components/ExcelUploadModal';
import { prioritiesList, type Partner } from '@/features/partners/types';
export default function PartnersPage() {
const { partners, createPartner, updatePartner, deletePartner, bulkCreate } = usePartners();
const {
search,
setSearch,
priorityFilter,
setPriorityFilter,
page,
setPage,
paginated,
totalPages,
totalCount,
itemsPerPage,
} = usePartnerFilters(partners);
// 모달 제어 + 폼 컨텍스트
const [openModal, setOpenModal] = useState<null | 'form' | 'excel'>(null);
const [formMode, setFormMode] = useState<'create' | 'edit'>('create');
const [editing, setEditing] = useState<Partner | null>(null);
const openCreate = () => {
setFormMode('create');
setEditing(null);
setOpenModal('form');
};
const openEdit = (part: Partner) => {
setFormMode('edit');
setEditing(part);
setOpenModal('form');
};
const handleDeletePartner = async (id: string, partnerName: string) => {
if (confirm(`[${partnerName}] 파트너사를 협력사 목록에서 삭제하시겠습니까?`)) {
try {
await deletePartner(id);
showToast('협력사 정보가 명부에서 제거되었습니다.', 'info');
} catch (err) {
showToast(err instanceof Error ? err.message : '협력사 삭제 실패', 'error');
}
}
};
return (
<PageContainer>
<PageToolbar
actions={
<>
<Button variant="outline" onClick={() => setOpenModal('excel')}>
<Upload />
엑셀 일괄 업로드
</Button>
<Button onClick={openCreate}>
<Plus />
신규 협력사 등록
</Button>
</>
}
>
<SearchInput
id="partner-search"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="협력사명, 코드 또는 담당자명으로 추적 검색..."
/>
<Select value={priorityFilter} onValueChange={(v) => setPriorityFilter(v as string)}>
<SelectTrigger id="partner-priority-filter" className="w-full sm:w-48">
<SelectValue>
{(value) => (value === 'ALL' ? '우선순위 가중치 (전체)' : `우선도: ${value}`)}
</SelectValue>
</SelectTrigger>
<SelectContent>
{prioritiesList.map((prio) => (
<SelectItem key={prio} value={prio}>
{prio === 'ALL' ? '우선순위 가중치 (전체)' : `우선도: ${prio}`}
</SelectItem>
))}
</SelectContent>
</Select>
</PageToolbar>
<PartnerTable
data={paginated}
onRowClick={openEdit}
page={page}
totalPages={totalPages}
totalCount={totalCount}
pageSize={itemsPerPage}
onPageChange={setPage}
/>
{openModal === 'form' && (
<PartnerFormSheet
key={`${formMode}-${editing?.supplier_id ?? 'new'}`}
open
mode={formMode}
partner={editing}
onCreate={createPartner}
onUpdate={updatePartner}
onDelete={handleDeletePartner}
onClose={() => setOpenModal(null)}
/>
)}
{openModal === 'excel' && (
<ExcelUploadModal
open
partners={partners}
onConfirm={bulkCreate}
onClose={() => setOpenModal(null)}
/>
)}
</PageContainer>
);
}