refactor(WEB): 견적관리 목록 페이지 V2 URL 패턴 적용
- 목록 페이지에서 V1 등록 로직 제거 - ?mode=new → /new 리다이렉트 처리 (V1 호환) - QuoteManagementClient 링크 V2 패턴으로 변경 - 견적 등록: ?mode=new → /new - 상세 보기: ?mode=view 제거 (기본 view) - DevToolbar test URL → V2 URL로 업데이트
This commit is contained in:
@@ -1,15 +1,19 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 견적관리 페이지 (Client Component)
|
* 견적관리 목록 페이지
|
||||||
|
*
|
||||||
|
* V2 URL 패턴:
|
||||||
|
* - 목록: /sales/quote-management
|
||||||
|
* - 등록: /sales/quote-management/new
|
||||||
|
* - 상세: /sales/quote-management/[id]
|
||||||
|
* - 수정: /sales/quote-management/[id]?mode=edit
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation';
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import { QuoteManagementClient } from '@/components/quotes/QuoteManagementClient';
|
import { QuoteManagementClient } from '@/components/quotes/QuoteManagementClient';
|
||||||
import { QuoteRegistration, QuoteFormData } from '@/components/quotes/QuoteRegistration';
|
import { getQuotes } from '@/components/quotes';
|
||||||
import { getQuotes, createQuote, transformFormDataToApi } from '@/components/quotes';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
const DEFAULT_PAGINATION = {
|
const DEFAULT_PAGINATION = {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
@@ -26,48 +30,29 @@ export default function QuoteManagementPage() {
|
|||||||
const [data, setData] = useState<Awaited<ReturnType<typeof getQuotes>>['data']>([]);
|
const [data, setData] = useState<Awaited<ReturnType<typeof getQuotes>>['data']>([]);
|
||||||
const [pagination, setPagination] = useState(DEFAULT_PAGINATION);
|
const [pagination, setPagination] = useState(DEFAULT_PAGINATION);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
|
||||||
|
|
||||||
|
// V1 호환: ?mode=new → /new 리다이렉트
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode !== 'new') {
|
if (mode === 'new') {
|
||||||
getQuotes({ perPage: 100 })
|
router.replace('/sales/quote-management/new');
|
||||||
.then(result => {
|
return;
|
||||||
setData(result.data);
|
|
||||||
setPagination(result.pagination);
|
|
||||||
})
|
|
||||||
.finally(() => setIsLoading(false));
|
|
||||||
} else {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
}, [mode]);
|
|
||||||
|
|
||||||
|
getQuotes({ perPage: 100 })
|
||||||
|
.then(result => {
|
||||||
|
setData(result.data);
|
||||||
|
setPagination(result.pagination);
|
||||||
|
})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
}, [mode, router]);
|
||||||
|
|
||||||
|
// 리다이렉트 중이면 로딩 표시
|
||||||
if (mode === 'new') {
|
if (mode === 'new') {
|
||||||
const handleBack = () => {
|
return (
|
||||||
router.push('/sales/quote-management');
|
<div className="flex items-center justify-center min-h-[400px]">
|
||||||
};
|
<div className="text-muted-foreground">리다이렉트 중...</div>
|
||||||
|
</div>
|
||||||
const handleSave = async (formData: QuoteFormData): Promise<{ success: boolean; error?: string }> => {
|
);
|
||||||
if (isSaving) return { success: false, error: '저장 중입니다.' };
|
|
||||||
setIsSaving(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const apiData = transformFormDataToApi(formData);
|
|
||||||
const result = await createQuote(apiData as any);
|
|
||||||
|
|
||||||
if (result.success && result.data) {
|
|
||||||
router.push(`/sales/quote-management/${result.data.id}`);
|
|
||||||
return { success: true };
|
|
||||||
} else {
|
|
||||||
return { success: false, error: result.error || '견적 등록에 실패했습니다.' };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: '견적 등록에 실패했습니다.' };
|
|
||||||
} finally {
|
|
||||||
setIsSaving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <QuoteRegistration onBack={handleBack} onSave={handleSave} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -42,11 +42,9 @@ import { useDevFillContext, type DevFillPageType } from './DevFillContext';
|
|||||||
|
|
||||||
// 페이지 경로와 타입 매핑 (pathname만으로 매칭되는 패턴)
|
// 페이지 경로와 타입 매핑 (pathname만으로 매칭되는 패턴)
|
||||||
const PAGE_PATTERNS: { pattern: RegExp; type: DevFillPageType; label: string }[] = [
|
const PAGE_PATTERNS: { pattern: RegExp; type: DevFillPageType; label: string }[] = [
|
||||||
// 판매/생산 플로우
|
// 판매/생산 플로우 (V2 URL 패턴 적용)
|
||||||
{ pattern: /\/quote-management\/test-new/, type: 'quoteV2', label: '견적V2' },
|
{ pattern: /\/quote-management\/new$/, type: 'quoteV2', label: '견적V2' },
|
||||||
{ pattern: /\/quote-management\/test\/\d+/, type: 'quoteV2', label: '견적V2' },
|
{ pattern: /\/quote-management\/\d+$/, type: 'quoteV2', label: '견적V2' },
|
||||||
{ pattern: /\/quote-management\/new/, type: 'quote', label: '견적' },
|
|
||||||
{ pattern: /\/quote-management\/\d+\/edit/, type: 'quote', label: '견적' },
|
|
||||||
{ pattern: /\/order-management-sales\/new/, type: 'order', label: '수주' },
|
{ pattern: /\/order-management-sales\/new/, type: 'order', label: '수주' },
|
||||||
{ pattern: /\/order-management-sales\/\d+\/edit/, type: 'order', label: '수주' },
|
{ pattern: /\/order-management-sales\/\d+\/edit/, type: 'order', label: '수주' },
|
||||||
{ pattern: /\/work-orders\/create/, type: 'workOrder', label: '작업지시' },
|
{ pattern: /\/work-orders\/create/, type: 'workOrder', label: '작업지시' },
|
||||||
@@ -73,8 +71,7 @@ const MODE_NEW_PAGES: { pattern: RegExp; type: DevFillPageType; label: string }[
|
|||||||
|
|
||||||
// 플로우 단계 정의
|
// 플로우 단계 정의
|
||||||
const FLOW_STEPS: { type: DevFillPageType; label: string; icon: typeof FileText; path: string }[] = [
|
const FLOW_STEPS: { type: DevFillPageType; label: string; icon: typeof FileText; path: string }[] = [
|
||||||
{ type: 'quoteV2', label: '견적V2', icon: FileText, path: '/sales/quote-management/test-new' },
|
{ type: 'quoteV2', label: '견적', icon: FileText, path: '/sales/quote-management/new' },
|
||||||
{ type: 'quote', label: '견적', icon: FileText, path: '/sales/quote-management/new' },
|
|
||||||
{ type: 'order', label: '수주', icon: ClipboardList, path: '/sales/order-management-sales?mode=new' },
|
{ type: 'order', label: '수주', icon: ClipboardList, path: '/sales/order-management-sales?mode=new' },
|
||||||
{ type: 'workOrder', label: '작업지시', icon: Wrench, path: '/production/work-orders/create' },
|
{ type: 'workOrder', label: '작업지시', icon: Wrench, path: '/production/work-orders/create' },
|
||||||
{ type: 'workOrderComplete', label: '완료', icon: CheckCircle2, path: '' }, // 상세 페이지에서 처리
|
{ type: 'workOrderComplete', label: '완료', icon: CheckCircle2, path: '' }, // 상세 페이지에서 처리
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export function QuoteManagementClient({
|
|||||||
|
|
||||||
// ===== 핸들러 =====
|
// ===== 핸들러 =====
|
||||||
const handleView = useCallback((quote: Quote) => {
|
const handleView = useCallback((quote: Quote) => {
|
||||||
router.push(`/sales/quote-management/${quote.id}?mode=view`);
|
router.push(`/sales/quote-management/${quote.id}`);
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
const handleEdit = useCallback((quote: Quote) => {
|
const handleEdit = useCallback((quote: Quote) => {
|
||||||
@@ -358,7 +358,7 @@ export function QuoteManagementClient({
|
|||||||
headerActions: () => (
|
headerActions: () => (
|
||||||
<Button
|
<Button
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
onClick={() => router.push('/sales/quote-management?mode=new')}
|
onClick={() => router.push('/sales/quote-management/new')}
|
||||||
>
|
>
|
||||||
<FileText className="w-4 h-4 mr-2" />
|
<FileText className="w-4 h-4 mr-2" />
|
||||||
견적 등록
|
견적 등록
|
||||||
|
|||||||
Reference in New Issue
Block a user