feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링

- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-25 22:30:06 +09:00
parent 1675bcbedf
commit 8f9507a665
86 changed files with 856 additions and 685 deletions

View File

@@ -10,7 +10,8 @@ import { DynamicBoardEditForm } from '@/components/board/DynamicBoard/DynamicBoa
import { useState, useEffect, useCallback, Suspense } from 'react';
import { DetailPageSkeleton } from '@/components/ui/skeleton';
import { format } from 'date-fns';
import { ArrowLeft, Pencil, Trash2, MessageSquare, Eye } from 'lucide-react';
import { ArrowLeft, Edit, Trash2, MessageSquare, Eye } from 'lucide-react';
import { useMenuStore } from '@/stores/menuStore';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -100,6 +101,7 @@ function DetailModeRouter() {
// 실제 상세 컴포넌트 (자체 hooks 사용)
function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; postId: string }) {
const router = useRouter();
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
// 게시판 정보
const [boardName, setBoardName] = useState<string>('게시판');
@@ -261,6 +263,7 @@ function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; p
icon={MessageSquare}
/>
<div className="pb-24">
{/* 게시글 상세 */}
<Card className="mb-6">
<CardHeader>
@@ -282,9 +285,9 @@ function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; p
</div>
</div>
{isAuthor && (
<div className="flex items-center gap-2">
<div className="hidden md:flex items-center gap-2">
<Button variant="outline" size="sm" onClick={handleEdit}>
<Pencil className="h-4 w-4 mr-1" />
<Edit className="h-4 w-4 mr-1" />
</Button>
<Button variant="destructive" size="sm" onClick={() => setShowDeleteDialog(true)}>
@@ -400,13 +403,33 @@ function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; p
</div>
</CardContent>
</Card>
</div>
{/* 하단 버튼 */}
<div className="mt-6 flex justify-start">
<Button variant="outline" onClick={handleBack}>
<ArrowLeft className="h-4 w-4 mr-2" />
{/* 하단 액션 버튼 (sticky) */}
<div
className={`fixed bottom-4 left-4 right-4 px-4 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:px-6 md:right-[48px] ${sidebarCollapsed ? 'md:left-[156px]' : 'md:left-[316px]'} flex items-center justify-between`}
>
<Button variant="outline" onClick={handleBack} size="sm" className="md:size-default">
<ArrowLeft className="w-4 h-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
{isAuthor && (
<div className="flex items-center gap-1 md:gap-2">
<Button
variant="outline"
onClick={() => setShowDeleteDialog(true)}
size="sm"
className="text-destructive hover:bg-destructive hover:text-destructive-foreground md:size-default"
>
<Trash2 className="w-4 h-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
<Button onClick={handleEdit} size="sm" className="md:size-default">
<Edit className="w-4 h-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
</div>
)}
</div>
{/* 게시글 삭제 확인 다이얼로그 */}

View File

@@ -223,7 +223,7 @@ function DynamicBoardListContent({ boardCode }: { boardCode: string }) {
const handleRowClick = useCallback(
(item: BoardPost) => {
router.push(`/ko/boards/${boardCode}/${item.id}`);
router.push(`/ko/boards/${boardCode}/${item.id}?mode=view`);
},
[router, boardCode]
);

View File

@@ -1,14 +0,0 @@
'use client';
import { use } from 'react';
import { PriceDistributionDetail } from '@/components/pricing-distribution';
export default function PriceDistributionEditPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = use(params);
return <PriceDistributionDetail id={id} mode="edit" />;
}

View File

@@ -10,5 +10,5 @@ export default function PriceDistributionDetailPage({
}) {
const { id } = use(params);
return <PriceDistributionDetail id={id} mode="view" />;
return <PriceDistributionDetail id={id} />;
}

View File

@@ -199,7 +199,7 @@ export default function OrderEditPage() {
}, [orderId, router]);
const handleCancel = () => {
router.push(`/sales/order-management-sales/${orderId}`);
router.push(`/sales/order-management-sales/${orderId}?mode=view`);
};
// onSubmit wrapper for IntegratedDetailTemplate
@@ -247,7 +247,7 @@ export default function OrderEditPage() {
if (result.success) {
toast.success("수주가 수정되었습니다.");
router.push(`/sales/order-management-sales/${orderId}`);
router.push(`/sales/order-management-sales/${orderId}?mode=view`);
return { success: true };
} else {
toast.error(result.error || "수주 수정에 실패했습니다.");

View File

@@ -395,11 +395,11 @@ export default function ProductionOrderCreatePage() {
}, [fetchData]);
const handleCancel = () => {
router.push(`/ko/sales/order-management-sales/${orderId}`);
router.push(`/ko/sales/order-management-sales/${orderId}?mode=view`);
};
const handleBackToDetail = () => {
router.push(`/ko/sales/order-management-sales/${orderId}`);
router.push(`/ko/sales/order-management-sales/${orderId}?mode=view`);
};
const handleConfirm = async () => {
@@ -467,7 +467,7 @@ export default function ProductionOrderCreatePage() {
const handleSuccessDialogClose = () => {
setShowSuccessDialog(false);
// 수주 상세 페이지로 이동 (상태가 변경되었으므로)
router.push(`/ko/sales/order-management-sales/${orderId}`);
router.push(`/ko/sales/order-management-sales/${orderId}?mode=view`);
};
// 선택된 우선순위 설정 가져오기

View File

@@ -318,11 +318,11 @@ export default function ProductionOrdersListPage() {
};
const handleRowClick = (item: ProductionOrder) => {
router.push(`/sales/order-management-sales/production-orders/${item.id}`);
router.push(`/sales/order-management-sales/production-orders/${item.id}?mode=view`);
};
const handleView = (item: ProductionOrder) => {
router.push(`/sales/order-management-sales/production-orders/${item.id}`);
router.push(`/sales/order-management-sales/production-orders/${item.id}?mode=view`);
};
// 개별 삭제 다이얼로그 열기

View File

@@ -118,7 +118,7 @@ export default function QuoteDetailPage() {
// 수주등록 페이지로 이동 핸들러
const handleOrderRegister = useCallback(() => {
router.push(`/sales/order-management-sales/new?quoteId=${quoteId}`);
router.push(`/sales/order-management-sales/new?mode=new&quoteId=${quoteId}`);
}, [router, quoteId]);
// 기존 수주 보기 핸들러 (이미 수주가 있는 경우)
@@ -161,7 +161,7 @@ export default function QuoteDetailPage() {
if (isEditMode) {
// edit 모드에서 저장 후 view 모드로 전환
router.push(`/sales/quote-management/${quoteId}`);
router.push(`/sales/quote-management/${quoteId}?mode=view`);
} else {
// view 모드에서 최종저장 후 페이지 새로고침 (데이터 갱신)
router.refresh();

View File

@@ -45,7 +45,7 @@ export default function QuoteNewPage() {
// 저장 후 상세 페이지로 이동
if (result.data?.id) {
router.push(`/sales/quote-management/${result.data.id}`);
router.push(`/sales/quote-management/${result.data.id}?mode=view`);
}
} catch (error) {
console.error('[QuoteNewPage] 저장 오류:', error);