o2o-negosium-original/negodata/front/src/pages/quotation.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

142 lines
5.1 KiB
TypeScript

import { useState } from 'react';
import { Settings, Plus } from 'lucide-react';
import { PageContainer } from '@/components/layout/PageContainer';
import { PageToolbar, SearchInput } from '@/components/layout/PageToolbar';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { useQuotations } from '@/features/quotations/hooks/useQuotations';
import { useQuotationFilters } from '@/features/quotations/hooks/useQuotationFilters';
import { QuotationTable } from '@/features/quotations/components/QuotationTable';
import { QuotationDetailDrawer } from '@/features/quotations/components/QuotationDetailDrawer';
import { CreateQuotationWizard } from '@/features/quotations/components/CreateQuotationWizard';
import { QuotationSettingsModal } from '@/features/quotations/components/QuotationSettingsModal';
import { QUOTATION_STATUS_FILTERS } from '@/features/quotations/types';
export default function QuotationPage() {
const {
products,
partners,
cards,
quotations,
quotationSettings,
chatSessions,
stopNegotiation,
addSetting,
deleteSetting,
createQuotation,
} = useQuotations();
const { search, setSearch, statusFilter, setStatusFilter, typeFilter, setTypeFilter, filtered } =
useQuotationFilters(quotations);
const [detailId, setDetailId] = useState<string | null>(null);
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const activeQuotation = quotations.find((e) => e.id === detailId) ?? null;
return (
<PageContainer>
<PageToolbar
actions={
<>
<button
id="quotation-settings-btn"
onClick={() => setIsSettingsOpen(true)}
className="flex items-center gap-2 px-3 py-2.5 bg-muted text-foreground border border-border text-xs font-semibold rounded hover:bg-muted-foreground/10 cursor-pointer transition-colors"
>
<Settings size={14} />
<span>견적 세팅</span>
</button>
<button
id="quotation-create-btn"
onClick={() => setIsCreateOpen(true)}
className="flex items-center gap-2 px-4 py-2.5 bg-primary text-primary-foreground text-xs font-bold rounded hover:opacity-95 cursor-pointer transition-colors animate-pulse"
>
<Plus size={15} />
<span>신규 협상견적 발의</span>
</button>
</>
}
>
<SearchInput
id="quotation-search"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="견적명 또는 견적 번호로 실시간 서치..."
/>
<div className="grid grid-cols-2 gap-2">
<Select value={statusFilter} onValueChange={(v) => setStatusFilter(v as string)}>
<SelectTrigger id="quotation-status-filter" className="font-bold">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체 견적상태</SelectItem>
{QUOTATION_STATUS_FILTERS.map((s) => (
<SelectItem key={s} value={s}>{s}</SelectItem>
))}
</SelectContent>
</Select>
<Select value={typeFilter} onValueChange={(v) => setTypeFilter(v as string)}>
<SelectTrigger id="quotation-type-filter">
<SelectValue>
{(value) => (value === 'ALL' ? '전체 유형' : value === 'RE_NEGOTIATION' ? '재협상' : '재견적')}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체 유형</SelectItem>
<SelectItem value="RE_NEGOTIATION">재협상</SelectItem>
<SelectItem value="RE_ESTIMATE">재견적</SelectItem>
</SelectContent>
</Select>
</div>
</PageToolbar>
<QuotationTable
data={filtered}
products={products}
onOpenDetail={setDetailId}
onStop={stopNegotiation}
/>
{activeQuotation && (
<QuotationDetailDrawer
key={activeQuotation.id}
estimate={activeQuotation}
products={products}
partners={partners}
cards={cards}
quotationSettings={quotationSettings}
sessions={chatSessions[activeQuotation.id ?? ''] ?? []}
onStop={stopNegotiation}
onClose={() => setDetailId(null)}
/>
)}
{isCreateOpen && (
<CreateQuotationWizard
open
products={products}
partners={partners}
cards={cards}
quotationSettings={quotationSettings}
onCreate={createQuotation}
onClose={() => setIsCreateOpen(false)}
/>
)}
{isSettingsOpen && (
<QuotationSettingsModal
open
settings={quotationSettings}
onAdd={addSetting}
onDelete={deleteSetting}
onClose={() => setIsSettingsOpen(false)}
/>
)}
</PageContainer>
);
}