From 9f947e48230f08d971c1e967c0503d363975c069 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Tue, 23 Jun 2026 16:57:29 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20negodata/front:=20=EA=B2=AC=EC=A0=81?= =?UTF-8?q?=20=EC=9E=AC=EC=83=9D=EC=84=B1=C2=B7=EC=B0=A8=EC=88=98=20?= =?UTF-8?q?=EC=B2=B4=EC=9D=B8=20UI=20+=20=EB=8B=B4=EB=8B=B9=EC=9E=90/?= =?UTF-8?q?=EB=A9=94=EB=AA=A8=20+=20=EC=8A=A4=ED=81=AC=EB=A1=A4=C2=B7?= =?UTF-8?q?=EB=AA=A9=EC=97=85=20=EC=A0=95=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 견적 재생성(다음 견적) 엔드포인트 연동 모달: 연결 공급사+협상단계 표시, 마지막 차수에서만 - 차수 체인 타임라인/견적번호 클릭 필터, 생성 응답 슬림(qt_id/session_count) 반영 - 상세 시트 스크롤 수정(min-h-0)·배경 스크롤 락(useScrollLock)·헤더2:탭1 비율 - 담당자=로그인 유저(store) 자동 전송 + 생성 모달 메모 입력 추가 - 목업/가짜 폴백 제거(담당자·견적명·번호·메모 → '-'), 시드 회사 placeholder UUID→실 UUID - 견적상태 CREATED 칩 pulse 제거 Co-Authored-By: Claude Opus 4.8 --- .../front/src/api/generated/model/index.ts | 5 +- .../api/generated/model/reqCreateQuotation.ts | 1 - ...{asyncJob.ts => reqRegenerateQuotation.ts} | 5 +- .../api/generated/model/resCreateQuotation.ts | 9 +- ...nAsyncJob.ts => resCreateQuotationQtId.ts} | 3 +- .../model/resCreateQuotationQuotation.ts | 9 -- .../src/api/generated/quotation/quotation.ts | 66 ++++++++++ negodata/front/src/components/ui/sheet.tsx | 4 +- .../components/QuotationCreateModal.tsx | 24 +++- .../QuotationDetailSheet/ChatTab.tsx | 4 +- .../DrawerHeaderCards.tsx | 19 +-- .../QuotationDetailSheet/RegenerateModal.tsx | 117 ++++++++++++++++++ .../QuotationDetailSheet/RoundTimeline.tsx | 47 +++++++ .../QuotationDetailSheet/StatusPill.tsx | 6 +- .../components/QuotationDetailSheet/index.tsx | 100 ++++++++++++--- .../quotations/components/QuotationTable.tsx | 31 ++++- .../quotations/hooks/useQuotationChain.ts | 26 ++++ .../quotations/hooks/useQuotations.ts | 40 +++++- .../front/src/features/quotations/types.ts | 58 +++++++-- negodata/front/src/lib/useOverlayRouter.ts | 16 ++- negodata/front/src/lib/useScrollLock.ts | 30 +++++ negodata/front/src/pages/quotation.tsx | 13 +- postgres-init/03-seed-negodata.sql | 18 +-- 23 files changed, 565 insertions(+), 86 deletions(-) rename negodata/front/src/api/generated/model/{asyncJob.ts => reqRegenerateQuotation.ts} (64%) rename negodata/front/src/api/generated/model/{resCreateQuotationAsyncJob.ts => resCreateQuotationQtId.ts} (54%) delete mode 100644 negodata/front/src/api/generated/model/resCreateQuotationQuotation.ts create mode 100644 negodata/front/src/features/quotations/components/QuotationDetailSheet/RegenerateModal.tsx create mode 100644 negodata/front/src/features/quotations/components/QuotationDetailSheet/RoundTimeline.tsx create mode 100644 negodata/front/src/features/quotations/hooks/useQuotationChain.ts create mode 100644 negodata/front/src/lib/useScrollLock.ts diff --git a/negodata/front/src/api/generated/model/index.ts b/negodata/front/src/api/generated/model/index.ts index 987f649..6501c93 100644 --- a/negodata/front/src/api/generated/model/index.ts +++ b/negodata/front/src/api/generated/model/index.ts @@ -5,7 +5,6 @@ * OpenAPI spec version: 0.1.0 */ -export * from './asyncJob'; export * from './bodyUploadItemImageV1ItemImagePost'; export * from './cardData'; export * from './cardDataCondition'; @@ -126,6 +125,7 @@ export * from './reqCreateSupplierManagerEmail'; export * from './reqCreateSupplierManagerName'; export * from './reqCreateSupplierPriority'; export * from './reqLogin'; +export * from './reqRegenerateQuotation'; export * from './reqUpdateCard'; export * from './reqUpdateCardCondition'; export * from './reqUpdateCardEditScript'; @@ -173,9 +173,8 @@ export * from './resCheckCodesMsg'; export * from './resCreateAccount'; export * from './resCreateAccountMsg'; export * from './resCreateQuotation'; -export * from './resCreateQuotationAsyncJob'; export * from './resCreateQuotationMsg'; -export * from './resCreateQuotationQuotation'; +export * from './resCreateQuotationQtId'; export * from './resDeleteCard'; export * from './resDeleteCardMsg'; export * from './resDeleteItem'; diff --git a/negodata/front/src/api/generated/model/reqCreateQuotation.ts b/negodata/front/src/api/generated/model/reqCreateQuotation.ts index bac460b..60dab99 100644 --- a/negodata/front/src/api/generated/model/reqCreateQuotation.ts +++ b/negodata/front/src/api/generated/model/reqCreateQuotation.ts @@ -15,7 +15,6 @@ export interface ReqCreateQuotation { qt_setting_id: string; version_id?: ReqCreateQuotationVersionId; name?: string; - number?: string; type?: number; status?: number; start_time?: ReqCreateQuotationStartTime; diff --git a/negodata/front/src/api/generated/model/asyncJob.ts b/negodata/front/src/api/generated/model/reqRegenerateQuotation.ts similarity index 64% rename from negodata/front/src/api/generated/model/asyncJob.ts rename to negodata/front/src/api/generated/model/reqRegenerateQuotation.ts index b073009..0b07eaa 100644 --- a/negodata/front/src/api/generated/model/asyncJob.ts +++ b/negodata/front/src/api/generated/model/reqRegenerateQuotation.ts @@ -5,7 +5,6 @@ * OpenAPI spec version: 0.1.0 */ -export interface AsyncJob { - status?: string; - message?: string; +export interface ReqRegenerateQuotation { + supplier_ids?: string[]; } diff --git a/negodata/front/src/api/generated/model/resCreateQuotation.ts b/negodata/front/src/api/generated/model/resCreateQuotation.ts index 76ba712..0c65650 100644 --- a/negodata/front/src/api/generated/model/resCreateQuotation.ts +++ b/negodata/front/src/api/generated/model/resCreateQuotation.ts @@ -6,14 +6,11 @@ */ import type { ErrorInfo } from './errorInfo'; import type { ResCreateQuotationMsg } from './resCreateQuotationMsg'; -import type { ResCreateQuotationQuotation } from './resCreateQuotationQuotation'; -import type { SessionData } from './sessionData'; -import type { ResCreateQuotationAsyncJob } from './resCreateQuotationAsyncJob'; +import type { ResCreateQuotationQtId } from './resCreateQuotationQtId'; export interface ResCreateQuotation { result?: ErrorInfo; msg?: ResCreateQuotationMsg; - quotation?: ResCreateQuotationQuotation; - sessions?: SessionData[]; - async_job?: ResCreateQuotationAsyncJob; + qt_id?: ResCreateQuotationQtId; + session_count?: number; } diff --git a/negodata/front/src/api/generated/model/resCreateQuotationAsyncJob.ts b/negodata/front/src/api/generated/model/resCreateQuotationQtId.ts similarity index 54% rename from negodata/front/src/api/generated/model/resCreateQuotationAsyncJob.ts rename to negodata/front/src/api/generated/model/resCreateQuotationQtId.ts index bc335b0..3e174d8 100644 --- a/negodata/front/src/api/generated/model/resCreateQuotationAsyncJob.ts +++ b/negodata/front/src/api/generated/model/resCreateQuotationQtId.ts @@ -4,6 +4,5 @@ * Negodata Api Server * OpenAPI spec version: 0.1.0 */ -import type { AsyncJob } from './asyncJob'; -export type ResCreateQuotationAsyncJob = AsyncJob | null; +export type ResCreateQuotationQtId = string | null; diff --git a/negodata/front/src/api/generated/model/resCreateQuotationQuotation.ts b/negodata/front/src/api/generated/model/resCreateQuotationQuotation.ts deleted file mode 100644 index ce4e28f..0000000 --- a/negodata/front/src/api/generated/model/resCreateQuotationQuotation.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Generated by orval v7.21.0 🍺 - * Do not edit manually. - * Negodata Api Server - * OpenAPI spec version: 0.1.0 - */ -import type { QuotationData } from './quotationData'; - -export type ResCreateQuotationQuotation = QuotationData | null; diff --git a/negodata/front/src/api/generated/quotation/quotation.ts b/negodata/front/src/api/generated/quotation/quotation.ts index bdfb53b..c20a974 100644 --- a/negodata/front/src/api/generated/quotation/quotation.ts +++ b/negodata/front/src/api/generated/quotation/quotation.ts @@ -27,6 +27,7 @@ import type { HTTPValidationError, ListQuotationsParams, ReqCreateQuotation, + ReqRegenerateQuotation, ResCreateQuotation, ResDeleteQuotation, ResQuotation, @@ -265,6 +266,71 @@ export const useStopQuotation = ,signal?: AbortSignal +) => { + + + return customFetch( + {url: `/v1/quotation/regenerate/${qtId}`, method: 'POST', + headers: {'Content-Type': 'application/json', }, + data: reqRegenerateQuotation, signal + }, + options); + } + + + +export const getRegenerateQuotationMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{qtId: string;data: ReqRegenerateQuotation}, TContext>, request?: SecondParameter} +): UseMutationOptions>, TError,{qtId: string;data: ReqRegenerateQuotation}, TContext> => { + +const mutationKey = ['regenerateQuotation']; +const {mutation: mutationOptions, request: requestOptions} = options ? + options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? + options + : {...options, mutation: {...options.mutation, mutationKey}} + : {mutation: { mutationKey, }, request: undefined}; + + + + + const mutationFn: MutationFunction>, {qtId: string;data: ReqRegenerateQuotation}> = (props) => { + const {qtId,data} = props ?? {}; + + return regenerateQuotation(qtId,data,requestOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type RegenerateQuotationMutationResult = NonNullable>> + export type RegenerateQuotationMutationBody = ReqRegenerateQuotation + export type RegenerateQuotationMutationError = void | HTTPValidationError + + /** + * @summary 견적 재생성(다음 라운드) + */ +export const useRegenerateQuotation = (options?: { mutation?:UseMutationOptions>, TError,{qtId: string;data: ReqRegenerateQuotation}, TContext>, request?: SecondParameter} + , queryClient?: QueryClient): UseMutationResult< + Awaited>, + TError, + {qtId: string;data: ReqRegenerateQuotation}, + TContext + > => { + + const mutationOptions = getRegenerateQuotationMutationOptions(options); + + return useMutation(mutationOptions, queryClient); + } + /** * @summary 견적 상태 조회 */ export const getQuotationStatus = ( diff --git a/negodata/front/src/components/ui/sheet.tsx b/negodata/front/src/components/ui/sheet.tsx index db09d3c..5cb05a0 100644 --- a/negodata/front/src/components/ui/sheet.tsx +++ b/negodata/front/src/components/ui/sheet.tsx @@ -20,8 +20,8 @@ export function Sheet({ open, title, onClose, children }: SheetProps) { {/* 백드롭 */}
- {/* 우측 드로어 패널 */} -
+ {/* 우측 드로어 패널 (패널 자체가 세로 스크롤 — justify-* 는 스크롤 시작점을 가려 안 씀) */} +
{/* 헤더 */}
diff --git a/negodata/front/src/features/quotations/components/QuotationCreateModal.tsx b/negodata/front/src/features/quotations/components/QuotationCreateModal.tsx index 4cc3db0..aacfa10 100644 --- a/negodata/front/src/features/quotations/components/QuotationCreateModal.tsx +++ b/negodata/front/src/features/quotations/components/QuotationCreateModal.tsx @@ -9,6 +9,13 @@ import type { CreateQuotationInput } from '../hooks/useQuotations'; import { QuotationType } from '@/api/generated/model'; import { QUOTATION_TYPE_OPTIONS } from '../types'; +// datetime-local 디폴트값: 현재 한국시간(Asia/Seoul)의 'YYYY-MM-DDTHH:mm'. +// sv-SE 로케일이 'YYYY-MM-DD HH:mm:ss' 를 주고, timeZone 명시로 브라우저 TZ 와 무관하게 KST 로 고정한다. +function nowKstLocalInput(): string { + const s = new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Seoul' }); + return s.slice(0, 16).replace(' ', 'T'); +} + type QuotationCreateModalProps = { open: boolean; products: Product[]; @@ -33,9 +40,10 @@ export function QuotationCreateModal({ const [type, setType] = useState(QuotationType.REQUOTE); const [productId, setProductId] = useState(''); const [selectedPartnerIds, setSelectedPartnerIds] = useState([]); - const [dueDate, setDueDate] = useState('2026-06-15T18:00'); + const [dueDate, setDueDate] = useState(nowKstLocalInput); const [settingId, setSettingId] = useState(quotationSettings[0]?.qt_setting_id ?? ''); const [selectedCardIds, setSelectedCardIds] = useState([]); + const [memo, setMemo] = useState(''); const [submitting, setSubmitting] = useState(false); const typeOptions = QUOTATION_TYPE_OPTIONS; @@ -65,6 +73,7 @@ export function QuotationCreateModal({ dueDate, settingId, cardIds: selectedCardIds, + memo, }); if (ok) onClose(); } finally { @@ -277,6 +286,19 @@ export function QuotationCreateModal({ })}
+ +
+ 메모 (선택) +