From 8f9507a665da8c8c1e91d6c06a1cbb5fe0968215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Wed, 25 Feb 2026 22:30:06 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=8B=A4=EC=A4=91=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20UI=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선 - FormField, TabChip, Select 등 공통 컴포넌트 개선 - 가격배분 edit 페이지 제거 및 상세 페이지 통합 - 체크리스트, 근태, 급여, 권한 관리 등 폼 개선 Co-Authored-By: Claude Opus 4.6 --- .../boards/[boardCode]/[postId]/page.tsx | 39 ++++- .../(protected)/boards/[boardCode]/page.tsx | 2 +- .../price-distribution/[id]/edit/page.tsx | 14 -- .../price-distribution/[id]/page.tsx | 2 +- .../order-management-sales/[id]/edit/page.tsx | 4 +- .../[id]/production-order/page.tsx | 6 +- .../production-orders/page.tsx | 4 +- .../sales/quote-management/[id]/page.tsx | 4 +- .../sales/quote-management/new/page.tsx | 2 +- .../VendorLedger/VendorLedgerDetail.tsx | 4 +- .../accounting/VendorLedger/index.tsx | 2 +- src/components/atoms/TabChip.tsx | 16 +- src/components/auth/LoginPage.tsx | 4 +- src/components/board/BoardDetail/index.tsx | 14 +- .../board/BoardManagement/BoardDetail.tsx | 52 +++--- .../BoardManagement/BoardDetailClientV2.tsx | 5 +- .../board/BoardManagement/BoardForm.tsx | 31 ++-- .../board/CommentSection/CommentItem.tsx | 6 +- .../DynamicBoard/DynamicBoardCreateForm.tsx | 38 ++--- .../DynamicBoard/DynamicBoardEditForm.tsx | 40 ++--- .../contract/ContractDetailForm.tsx | 2 +- .../contract/modals/ContractDocumentModal.tsx | 2 +- .../hooks/useOrderDetailForm.ts | 4 +- .../ProgressBillingDetailForm.tsx | 2 +- .../hooks/useProgressBillingDetailForm.ts | 4 +- .../checklist-management/ChecklistDetail.tsx | 38 +++-- .../checklist-management/ChecklistForm.tsx | 6 +- .../ChecklistListClient.tsx | 2 +- .../checklist-management/ItemDetail.tsx | 18 +-- .../checklist-management/ItemForm.tsx | 151 +++++++++++++----- src/components/clients/ClientDetail.tsx | 4 +- .../EventManagement/EventDetail.tsx | 7 +- .../InquiryManagement/InquiryDetail.tsx | 55 +++---- .../NoticeManagement/NoticeDetail.tsx | 7 +- .../AttendanceInfoDialog.tsx | 56 +++---- .../AttendanceManagement/ReasonInfoDialog.tsx | 22 +-- .../hr/AttendanceManagement/index.tsx | 2 +- .../hr/CalendarManagement/index.tsx | 2 +- .../hr/CardManagement/CardDetail.tsx | 79 +++++---- src/components/hr/CardManagement/index.tsx | 3 +- .../DepartmentToolbar.tsx | 11 +- .../DepartmentManagement/DepartmentTree.tsx | 1 - .../DepartmentTreeItem.tsx | 72 +++++---- .../hr/EmployeeManagement/EmployeeForm.tsx | 77 ++++----- .../SalaryManagement/SalaryDetailDialog.tsx | 14 +- src/components/hr/SalaryManagement/index.tsx | 1 + .../VacationGrantDialog.tsx | 2 +- .../VacationRequestDialog.tsx | 2 +- .../hr/VacationManagement/index.tsx | 6 +- src/components/molecules/FormField.tsx | 5 +- src/components/organisms/MobileCard.tsx | 8 +- .../PriceDistributionDetail.tsx | 50 +++--- .../PriceDistributionList.tsx | 4 +- .../process-management/ProcessDetail.tsx | 4 +- .../process-management/ProcessForm.tsx | 4 +- .../process-management/StepDetail.tsx | 4 +- .../process-management/StepForm.tsx | 6 +- .../production/WorkOrders/WorkOrderCreate.tsx | 4 +- .../quotes/QuoteManagementClient.tsx | 4 +- .../settings/AccountInfoManagement/index.tsx | 4 +- .../AccountManagement/AccountDetail.tsx | 61 ++++--- .../AccountManagement/AccountDetailForm.tsx | 43 ++--- .../settings/AccountManagement/index.tsx | 1 + .../AttendanceSettingsManagement/index.tsx | 30 ++-- .../BarobillIntegration/SignupModal.tsx | 8 +- .../settings/BarobillIntegration/index.tsx | 4 +- .../settings/LeavePolicyManagement/index.tsx | 6 +- .../ItemSettingsDialog.tsx | 2 +- .../settings/NotificationSettings/index.tsx | 72 +++++---- .../PaymentHistoryClient.tsx | 1 + .../PaymentHistoryManagement/index.tsx | 1 + .../PermissionDetailClient.tsx | 4 +- .../settings/PermissionManagement/index.tsx | 30 ++-- .../settings/RankManagement/index.tsx | 70 +++++--- .../SubscriptionManagement.tsx | 50 +++--- .../settings/TitleManagement/index.tsx | 70 +++++--- .../settings/WorkScheduleManagement/index.tsx | 24 +-- .../templates/IntegratedListTemplateV2.tsx | 3 +- .../templates/UniversalListPage/types.ts | 1 + src/components/ui/select.tsx | 4 +- .../ForkliftDetail/index.tsx | 2 +- .../vehicle-management/ForkliftList/index.tsx | 4 +- .../VehicleDetail/index.tsx | 2 +- .../vehicle-management/VehicleList/index.tsx | 4 +- .../VehicleLogDetail/index.tsx | 2 +- .../VehicleLogList/index.tsx | 4 +- 86 files changed, 856 insertions(+), 685 deletions(-) delete mode 100644 src/app/[locale]/(protected)/master-data/price-distribution/[id]/edit/page.tsx diff --git a/src/app/[locale]/(protected)/boards/[boardCode]/[postId]/page.tsx b/src/app/[locale]/(protected)/boards/[boardCode]/[postId]/page.tsx index a10f1a67..e8f9a3b4 100644 --- a/src/app/[locale]/(protected)/boards/[boardCode]/[postId]/page.tsx +++ b/src/app/[locale]/(protected)/boards/[boardCode]/[postId]/page.tsx @@ -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('게시판'); @@ -261,6 +263,7 @@ function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; p icon={MessageSquare} /> +
{/* 게시글 상세 */} @@ -282,9 +285,9 @@ function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; p
{isAuthor && ( -
+
+
- {/* 하단 버튼 */} -
- + {isAuthor && ( +
+ + +
+ )}
{/* 게시글 삭제 확인 다이얼로그 */} diff --git a/src/app/[locale]/(protected)/boards/[boardCode]/page.tsx b/src/app/[locale]/(protected)/boards/[boardCode]/page.tsx index 0db94d31..ab47180d 100644 --- a/src/app/[locale]/(protected)/boards/[boardCode]/page.tsx +++ b/src/app/[locale]/(protected)/boards/[boardCode]/page.tsx @@ -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] ); diff --git a/src/app/[locale]/(protected)/master-data/price-distribution/[id]/edit/page.tsx b/src/app/[locale]/(protected)/master-data/price-distribution/[id]/edit/page.tsx deleted file mode 100644 index 6c915a30..00000000 --- a/src/app/[locale]/(protected)/master-data/price-distribution/[id]/edit/page.tsx +++ /dev/null @@ -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 ; -} diff --git a/src/app/[locale]/(protected)/master-data/price-distribution/[id]/page.tsx b/src/app/[locale]/(protected)/master-data/price-distribution/[id]/page.tsx index f1aae77d..8b08a669 100644 --- a/src/app/[locale]/(protected)/master-data/price-distribution/[id]/page.tsx +++ b/src/app/[locale]/(protected)/master-data/price-distribution/[id]/page.tsx @@ -10,5 +10,5 @@ export default function PriceDistributionDetailPage({ }) { const { id } = use(params); - return ; + return ; } diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx index e5fd0b19..4fd0d94f 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx @@ -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 || "수주 수정에 실패했습니다."); diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx index ddba66f0..d9276708 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx @@ -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`); }; // 선택된 우선순위 설정 가져오기 diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx index a94bb01f..dce9c551 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/production-orders/page.tsx @@ -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`); }; // 개별 삭제 다이얼로그 열기 diff --git a/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx b/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx index c218c28c..e94ee20b 100644 --- a/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx +++ b/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx @@ -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"eId=${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(); diff --git a/src/app/[locale]/(protected)/sales/quote-management/new/page.tsx b/src/app/[locale]/(protected)/sales/quote-management/new/page.tsx index 45d66c48..09691075 100644 --- a/src/app/[locale]/(protected)/sales/quote-management/new/page.tsx +++ b/src/app/[locale]/(protected)/sales/quote-management/new/page.tsx @@ -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); diff --git a/src/components/accounting/VendorLedger/VendorLedgerDetail.tsx b/src/components/accounting/VendorLedger/VendorLedgerDetail.tsx index a3a32045..b1c9fe16 100644 --- a/src/components/accounting/VendorLedger/VendorLedgerDetail.tsx +++ b/src/components/accounting/VendorLedger/VendorLedgerDetail.tsx @@ -8,7 +8,7 @@ import { useState, useMemo, useCallback, useEffect } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { format, startOfMonth, endOfMonth } from 'date-fns'; -import { Download, Pencil } from 'lucide-react'; +import { Download, Edit } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { ContentSkeleton } from '@/components/ui/skeleton'; import { Card, CardContent } from '@/components/ui/card'; @@ -343,7 +343,7 @@ export function VendorLedgerDetail({ className="h-8 w-8" onClick={() => handleEditTransaction(entry)} > - + )} diff --git a/src/components/accounting/VendorLedger/index.tsx b/src/components/accounting/VendorLedger/index.tsx index 06c03614..629c986e 100644 --- a/src/components/accounting/VendorLedger/index.tsx +++ b/src/components/accounting/VendorLedger/index.tsx @@ -138,7 +138,7 @@ export function VendorLedger({ if (startDate) params.set('start_date', startDate); if (endDate) params.set('end_date', endDate); const queryString = params.toString(); - router.push(`/ko/accounting/vendor-ledger/${item.id}${queryString ? `?${queryString}` : ''}`); + router.push(`/ko/accounting/vendor-ledger/${item.id}?mode=view${queryString ? `&${queryString}` : ''}`); }, [router, startDate, endDate] ); diff --git a/src/components/atoms/TabChip.tsx b/src/components/atoms/TabChip.tsx index e9327f95..049a23a6 100644 --- a/src/components/atoms/TabChip.tsx +++ b/src/components/atoms/TabChip.tsx @@ -42,25 +42,19 @@ export function TabChip({ -
- {onDelete && ( - - )} - {onEdit && ( - - )} -
+ + + {/* 하단 액션 버튼 (sticky) */} +
+ +
+ {onDelete && ( + + )} + {onEdit && ( + + )}
diff --git a/src/components/board/BoardManagement/BoardDetailClientV2.tsx b/src/components/board/BoardManagement/BoardDetailClientV2.tsx index f490f2c4..c313fbfb 100644 --- a/src/components/board/BoardManagement/BoardDetailClientV2.tsx +++ b/src/components/board/BoardManagement/BoardDetailClientV2.tsx @@ -93,9 +93,10 @@ export function BoardDetailClientV2({ boardId, initialMode }: BoardDetailClientV // URL 쿼리 변경 감지 useEffect(() => { - if (!isNewMode && modeFromQuery === 'edit') { + if (isNewMode) return; + if (modeFromQuery === 'edit') { setMode('edit'); - } else if (!isNewMode && !modeFromQuery) { + } else { setMode('view'); } }, [modeFromQuery, isNewMode]); diff --git a/src/components/board/BoardManagement/BoardForm.tsx b/src/components/board/BoardManagement/BoardForm.tsx index a11b6331..77d51d3f 100644 --- a/src/components/board/BoardManagement/BoardForm.tsx +++ b/src/components/board/BoardManagement/BoardForm.tsx @@ -17,7 +17,8 @@ import { SelectValue, } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; -import { ClipboardList, ArrowLeft, Save } from 'lucide-react'; +import { ClipboardList, ArrowLeft, Save, X } from 'lucide-react'; +import { useMenuStore } from '@/stores/menuStore'; import type { Board, BoardFormData, BoardTarget, BoardStatus } from './types'; import { BOARD_TARGETS, BOARD_STATUS_LABELS } from './types'; @@ -62,6 +63,7 @@ const getCurrentDateTime = (): string => { export function BoardForm({ mode, board, onSubmit }: BoardFormProps) { const router = useRouter(); + const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed); const [formData, setFormData] = useState({ target: 'all', targetName: '', @@ -129,7 +131,7 @@ export function BoardForm({ mode, board, onSubmit }: BoardFormProps) { icon={ClipboardList} /> -
+ {/* 게시판 정보 */} @@ -254,18 +256,21 @@ export function BoardForm({ mode, board, onSubmit }: BoardFormProps) { - {/* 버튼 영역 */} -
- - -
+ + {/* 하단 액션 버튼 (sticky) */} +
+ + +
); } \ No newline at end of file diff --git a/src/components/board/CommentSection/CommentItem.tsx b/src/components/board/CommentSection/CommentItem.tsx index c1154bf5..32c08770 100644 --- a/src/components/board/CommentSection/CommentItem.tsx +++ b/src/components/board/CommentSection/CommentItem.tsx @@ -12,7 +12,7 @@ import { useState, useCallback, memo } from 'react'; import { format } from 'date-fns'; -import { User, Pencil, Trash2 } from 'lucide-react'; +import { User, Edit, Trash2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; @@ -90,7 +90,7 @@ export const CommentItem = memo(function CommentItem({ {/* 댓글 내용 */}
{/* 작성자 정보 + 날짜 + 버튼 */} -
+
{authorInfo} @@ -107,7 +107,7 @@ export const CommentItem = memo(function CommentItem({ className="h-7 px-2 text-gray-500 hover:text-gray-700" onClick={handleEditClick} > - + 수정 - -
+ + {/* 하단 액션 버튼 (sticky) */} +
+ + +
); } diff --git a/src/components/board/DynamicBoard/DynamicBoardEditForm.tsx b/src/components/board/DynamicBoard/DynamicBoardEditForm.tsx index f0a88322..b8f90662 100644 --- a/src/components/board/DynamicBoard/DynamicBoardEditForm.tsx +++ b/src/components/board/DynamicBoard/DynamicBoardEditForm.tsx @@ -7,7 +7,7 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { ArrowLeft, Save, MessageSquare } from 'lucide-react'; +import { ArrowLeft, X, Save, MessageSquare } from 'lucide-react'; import { DetailPageSkeleton } from '@/components/ui/skeleton'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; @@ -17,6 +17,7 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Checkbox } from '@/components/ui/checkbox'; +import { useMenuStore } from '@/stores/menuStore'; import { getDynamicBoardPost, updateDynamicBoardPost } from '@/components/board/DynamicBoard/actions'; import { getBoardByCode } from '@/components/board/BoardManagement/actions'; import type { PostApiData } from '@/components/customer-center/shared/types'; @@ -59,6 +60,7 @@ interface DynamicBoardEditFormProps { export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditFormProps) { const router = useRouter(); + const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed); // 게시판 정보 const [boardName, setBoardName] = useState('게시판'); @@ -134,7 +136,7 @@ export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditForm }); if (result.success) { - router.push(`/ko/boards/${boardCode}/${postId}`); + router.push(`/ko/boards/${boardCode}/${postId}?mode=view`); } else { setError(result.error || '게시글 수정에 실패했습니다.'); setIsSubmitting(false); @@ -143,7 +145,7 @@ export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditForm // 취소 const handleCancel = () => { - router.push(`/ko/boards/${boardCode}/${postId}`); + router.push(`/ko/boards/${boardCode}/${postId}?mode=view`); }; // 로딩 상태 @@ -178,7 +180,7 @@ export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditForm icon={MessageSquare} /> -
+ 게시글 수정 @@ -230,23 +232,21 @@ export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditForm - {/* 버튼 영역 */} -
- - -
+ + {/* 하단 액션 버튼 (sticky) */} +
+ + +
); } diff --git a/src/components/business/construction/contract/ContractDetailForm.tsx b/src/components/business/construction/contract/ContractDetailForm.tsx index 73ebaf10..eb1abf87 100644 --- a/src/components/business/construction/contract/ContractDetailForm.tsx +++ b/src/components/business/construction/contract/ContractDetailForm.tsx @@ -92,7 +92,7 @@ export default function ContractDetailForm({ // 변경 계약서 생성 핸들러 const handleCreateChangeContract = useCallback(() => { - router.push(`/ko/construction/project/contract/create?baseContractId=${contractId}`); + router.push(`/ko/construction/project/contract/create?mode=new&baseContractId=${contractId}`); }, [router, contractId]); // 폼 필드 변경 diff --git a/src/components/business/construction/contract/modals/ContractDocumentModal.tsx b/src/components/business/construction/contract/modals/ContractDocumentModal.tsx index 1d07a0aa..07200fbd 100644 --- a/src/components/business/construction/contract/modals/ContractDocumentModal.tsx +++ b/src/components/business/construction/contract/modals/ContractDocumentModal.tsx @@ -28,7 +28,7 @@ export function ContractDocumentModal({ // 수정 const handleEdit = () => { onOpenChange(false); - router.push(`/ko/construction/project/contract/${contract.id}/edit`); + router.push(`/ko/construction/project/contract/${contract.id}?mode=edit`); }; // 상신 (전자결재) diff --git a/src/components/business/construction/order-management/hooks/useOrderDetailForm.ts b/src/components/business/construction/order-management/hooks/useOrderDetailForm.ts index 7c14dd05..d9ca5705 100644 --- a/src/components/business/construction/order-management/hooks/useOrderDetailForm.ts +++ b/src/components/business/construction/order-management/hooks/useOrderDetailForm.ts @@ -162,7 +162,7 @@ export function useOrderDetailForm({ }, [router, orderId]); const handleCancel = useCallback(() => { - router.push(`/ko/construction/order/order-management/${orderId}`); + router.push(`/ko/construction/order/order-management/${orderId}?mode=view`); }, [router, orderId]); // ============================================ @@ -192,7 +192,7 @@ export function useOrderDetailForm({ if (result.success) { toast.success('수정이 완료되었습니다.'); setShowSaveDialog(false); - router.push(`/ko/construction/order/order-management/${orderId}`); + router.push(`/ko/construction/order/order-management/${orderId}?mode=view`); router.refresh(); } else { toast.error(result.error || '저장에 실패했습니다.'); diff --git a/src/components/business/construction/progress-billing/ProgressBillingDetailForm.tsx b/src/components/business/construction/progress-billing/ProgressBillingDetailForm.tsx index 7775ecbf..8164eaa8 100644 --- a/src/components/business/construction/progress-billing/ProgressBillingDetailForm.tsx +++ b/src/components/business/construction/progress-billing/ProgressBillingDetailForm.tsx @@ -77,7 +77,7 @@ export default function ProgressBillingDetailForm({ // TODO: API 호출 await new Promise((resolve) => setTimeout(resolve, 500)); toast.success('저장되었습니다.'); - router.push('/ko/construction/billing/progress-billing-management/' + billingId); + router.push('/ko/construction/billing/progress-billing-management/' + billingId + '?mode=view'); return { success: true }; } catch (error) { console.error('Save failed:', error); diff --git a/src/components/business/construction/progress-billing/hooks/useProgressBillingDetailForm.ts b/src/components/business/construction/progress-billing/hooks/useProgressBillingDetailForm.ts index cb334d00..31ba87b5 100644 --- a/src/components/business/construction/progress-billing/hooks/useProgressBillingDetailForm.ts +++ b/src/components/business/construction/progress-billing/hooks/useProgressBillingDetailForm.ts @@ -67,7 +67,7 @@ export function useProgressBillingDetailForm({ }, [router, billingId]); const handleCancel = useCallback(() => { - router.push('/construction/billing/progress-billing-management/' + billingId); + router.push('/construction/billing/progress-billing-management/' + billingId + '?mode=view'); }, [router, billingId]); // Form handlers @@ -92,7 +92,7 @@ export function useProgressBillingDetailForm({ // TODO: API 호출 await new Promise((resolve) => setTimeout(resolve, 500)); setShowSaveDialog(false); - router.push('/construction/billing/progress-billing-management/' + billingId); + router.push('/construction/billing/progress-billing-management/' + billingId + '?mode=view'); } catch (error) { console.error('Save failed:', error); } finally { diff --git a/src/components/checklist-management/ChecklistDetail.tsx b/src/components/checklist-management/ChecklistDetail.tsx index 90ed4aab..cf7f9101 100644 --- a/src/components/checklist-management/ChecklistDetail.tsx +++ b/src/components/checklist-management/ChecklistDetail.tsx @@ -67,11 +67,11 @@ export function ChecklistDetail({ checklist }: ChecklistDetailProps) { }; const handleAddItem = () => { - router.push(`/ko/master-data/checklist-management/${checklist.id}/items/new`); + router.push(`/ko/master-data/checklist-management/${checklist.id}/items/new?mode=new`); }; const handleItemClick = (itemId: string) => { - router.push(`/ko/master-data/checklist-management/${checklist.id}/items/${itemId}`); + router.push(`/ko/master-data/checklist-management/${checklist.id}/items/${itemId}?mode=view`); }; const handleDelete = async () => { @@ -195,16 +195,14 @@ export function ChecklistDetail({ checklist }: ChecklistDetailProps) { {/* 항목 테이블 */} -
- - 항목 목록 - {!isItemsLoading && ( - - 총 {items.length}건 - - )} - - @@ -219,26 +217,26 @@ export function ChecklistDetail({ checklist }: ChecklistDetailProps) {
) : (
- +
- - - - - - diff --git a/src/components/checklist-management/ChecklistForm.tsx b/src/components/checklist-management/ChecklistForm.tsx index ed91209e..dc7bb6dc 100644 --- a/src/components/checklist-management/ChecklistForm.tsx +++ b/src/components/checklist-management/ChecklistForm.tsx @@ -78,7 +78,7 @@ export function ChecklistForm({ mode, initialData }: ChecklistFormProps) { const result = await updateChecklist(initialData.id, formData); if (result.success) { toast.success('점검표가 수정되었습니다.'); - router.push(`/ko/master-data/checklist-management/${initialData.id}`); + router.push(`/ko/master-data/checklist-management/${initialData.id}?mode=view`); return { success: true }; } else { toast.error(result.error || '수정에 실패했습니다.'); @@ -88,7 +88,7 @@ export function ChecklistForm({ mode, initialData }: ChecklistFormProps) { const result = await createChecklist(formData); if (result.success && result.data) { toast.success('점검표가 등록되었습니다.'); - router.push(`/ko/master-data/checklist-management/${result.data.id}`); + router.push(`/ko/master-data/checklist-management/${result.data.id}?mode=view`); return { success: true }; } else { toast.error(result.error || '등록에 실패했습니다.'); @@ -105,7 +105,7 @@ export function ChecklistForm({ mode, initialData }: ChecklistFormProps) { const handleCancel = () => { if (isEdit && initialData?.id) { - router.push(`/ko/master-data/checklist-management/${initialData.id}`); + router.push(`/ko/master-data/checklist-management/${initialData.id}?mode=view`); } else { router.push('/ko/master-data/checklist-management'); } diff --git a/src/components/checklist-management/ChecklistListClient.tsx b/src/components/checklist-management/ChecklistListClient.tsx index 8d1c490b..e3a1d946 100644 --- a/src/components/checklist-management/ChecklistListClient.tsx +++ b/src/components/checklist-management/ChecklistListClient.tsx @@ -89,7 +89,7 @@ export default function ChecklistListClient() { // ===== 핸들러 ===== const handleRowClick = useCallback( (checklist: Checklist) => { - router.push(`/ko/master-data/checklist-management/${checklist.id}`); + router.push(`/ko/master-data/checklist-management/${checklist.id}?mode=view`); }, [router] ); diff --git a/src/components/checklist-management/ItemDetail.tsx b/src/components/checklist-management/ItemDetail.tsx index 2e359454..8e5ad7e9 100644 --- a/src/components/checklist-management/ItemDetail.tsx +++ b/src/components/checklist-management/ItemDetail.tsx @@ -52,7 +52,7 @@ export function ItemDetail({ item, checklistId }: ItemDetailProps) { }; const handleBack = () => { - router.push(`/ko/master-data/checklist-management/${checklistId}`); + router.push(`/ko/master-data/checklist-management/${checklistId}?mode=view`); }; const handleDelete = async () => { @@ -61,7 +61,7 @@ export function ItemDetail({ item, checklistId }: ItemDetailProps) { const result = await deleteChecklistItem(checklistId, item.id); if (result.success) { toast.success('항목이 삭제되었습니다.'); - router.push(`/ko/master-data/checklist-management/${checklistId}`); + router.push(`/ko/master-data/checklist-management/${checklistId}?mode=view`); } else { toast.error(result.error || '삭제에 실패했습니다.'); } @@ -121,23 +121,23 @@ export function ItemDetail({ item, checklistId }: ItemDetailProps) { ) : (
-
- + + No. + 항목 번호 + 순서 + 항목명 + 문서 + 사용
+
- - - - - diff --git a/src/components/checklist-management/ItemForm.tsx b/src/components/checklist-management/ItemForm.tsx index 76009f4c..1ed23021 100644 --- a/src/components/checklist-management/ItemForm.tsx +++ b/src/components/checklist-management/ItemForm.tsx @@ -8,13 +8,15 @@ * - 문서 정보: 순서, 문서 번호, 문서, 개정, 시행일 + 행 추가/삭제 */ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useRef } from 'react'; import { useRouter } from 'next/navigation'; import { Plus, Trash2, GripVertical } from 'lucide-react'; +import { ReorderButtons } from '@/components/molecules'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; +import { DatePicker } from '@/components/ui/date-picker'; import { Select, SelectContent, @@ -80,6 +82,65 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) { return []; }); + // 문서 드래그&드롭 상태 + const [docDragIndex, setDocDragIndex] = useState(null); + const [docDragOverIndex, setDocDragOverIndex] = useState(null); + const docDragNodeRef = useRef(null); + + const handleDocDragStart = useCallback( + (e: React.DragEvent, index: number) => { + setDocDragIndex(index); + docDragNodeRef.current = e.currentTarget; + e.dataTransfer.effectAllowed = 'move'; + requestAnimationFrame(() => { + if (docDragNodeRef.current) docDragNodeRef.current.style.opacity = '0.4'; + }); + }, + [] + ); + + const handleDocDragOver = useCallback( + (e: React.DragEvent, index: number) => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + setDocDragOverIndex(index); + }, + [] + ); + + const handleDocDragEnd = useCallback(() => { + if (docDragNodeRef.current) docDragNodeRef.current.style.opacity = '1'; + setDocDragIndex(null); + setDocDragOverIndex(null); + docDragNodeRef.current = null; + }, []); + + const handleDocDrop = useCallback( + (e: React.DragEvent, dropIndex: number) => { + e.preventDefault(); + if (docDragIndex === null || docDragIndex === dropIndex) return; + + setDocuments((prev) => { + const updated = [...prev]; + const [moved] = updated.splice(docDragIndex, 1); + updated.splice(dropIndex, 0, moved); + return updated.map((doc, i) => ({ ...doc, order: i + 1 })); + }); + + handleDocDragEnd(); + }, + [docDragIndex, handleDocDragEnd] + ); + + const handleMoveDocument = useCallback((fromIndex: number, toIndex: number) => { + setDocuments((prev) => { + const updated = [...prev]; + const [moved] = updated.splice(fromIndex, 1); + updated.splice(toIndex, 0, moved); + return updated.map((doc, i) => ({ ...doc, order: i + 1 })); + }); + }, []); + const handleAddDocument = () => { setDocuments((prev) => [ ...prev, @@ -129,7 +190,7 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) { if (result.success) { toast.success('항목이 수정되었습니다.'); router.push( - `/ko/master-data/checklist-management/${checklistId}/items/${initialData.id}` + `/ko/master-data/checklist-management/${checklistId}/items/${initialData.id}?mode=view` ); return { success: true }; } else { @@ -140,7 +201,7 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) { const result = await createChecklistItem(checklistId, formData); if (result.success && result.data) { toast.success('항목이 등록되었습니다.'); - router.push(`/ko/master-data/checklist-management/${checklistId}`); + router.push(`/ko/master-data/checklist-management/${checklistId}?mode=view`); return { success: true }; } else { toast.error(result.error || '등록에 실패했습니다.'); @@ -156,10 +217,10 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) { const handleCancel = () => { if (isEdit && initialData?.id) { router.push( - `/ko/master-data/checklist-management/${checklistId}/items/${initialData.id}` + `/ko/master-data/checklist-management/${checklistId}/items/${initialData.id}?mode=view` ); } else { - router.push(`/ko/master-data/checklist-management/${checklistId}`); + router.push(`/ko/master-data/checklist-management/${checklistId}?mode=view`); } }; @@ -218,14 +279,12 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) { {/* 문서 정보 */} -
- - 문서 정보 - - 총 {documents.length}건 - - - @@ -238,40 +297,61 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) {
) : (
-
- + + 순서 + 문서 번호 + 문서 + 개정 + 시행일
+
- - - - - - {documents.map((doc, index) => ( - - handleDocDragStart(e, index)} + onDragOver={(e) => handleDocDragOver(e, index)} + onDragEnd={handleDocDragEnd} + onDrop={(e) => handleDocDrop(e, index)} + className={`border-b transition-colors ${ + docDragOverIndex === index && docDragIndex !== index + ? 'border-t-2 border-t-primary' + : '' + }`} + > + - - - - - -
- + + 순서 + 문서 번호 + 문서 + 개정 + 시행일 + 삭제
- +
+
+ + handleMoveDocument(index, index - 1)} + onMoveDown={() => handleMoveDocument(index, index + 1)} + isFirst={index === 0} + isLast={index === documents.length - 1} + size="xs" + /> +
+ {doc.order} + @@ -281,7 +361,7 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) { className="h-8" /> + @@ -291,7 +371,7 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) { className="h-8" /> + @@ -301,17 +381,16 @@ export function ItemForm({ mode, checklistId, initialData }: ItemFormProps) { className="h-8" /> - + - handleDocumentChange(index, 'effectiveDate', e.target.value) + onChange={(date) => + handleDocumentChange(index, 'effectiveDate', date) } - className="h-8" + size="sm" /> + diff --git a/src/components/customer-center/EventManagement/EventDetail.tsx b/src/components/customer-center/EventManagement/EventDetail.tsx index ee6b7f74..cd75dc3d 100644 --- a/src/components/customer-center/EventManagement/EventDetail.tsx +++ b/src/components/customer-center/EventManagement/EventDetail.tsx @@ -33,11 +33,12 @@ export function EventDetail({ event }: EventDetailProps) { {/* 메타 정보: 작성자 | 기간 | 조회수 */} -
+
{event.author} - | + | {event.startDate} ~ {event.endDate} - 조회수 {event.viewCount} + | + 조회수 {event.viewCount}
{/* 이미지 영역 */} diff --git a/src/components/customer-center/InquiryManagement/InquiryDetail.tsx b/src/components/customer-center/InquiryManagement/InquiryDetail.tsx index 4ff7e78d..7cd45ae5 100644 --- a/src/components/customer-center/InquiryManagement/InquiryDetail.tsx +++ b/src/components/customer-center/InquiryManagement/InquiryDetail.tsx @@ -267,34 +267,35 @@ export function InquiryDetail({ )}
-
-
+
+ {/* 모바일: 세로 배치 / 데스크탑: 가로 배치 */} +
{comment.authorName} -
- {comment.authorId === currentUserId && editingCommentId !== comment.id && ( - <> - - - - )} - - {format(new Date(comment.createdAt), 'yyyy-MM-dd HH:mm')} - -
+ + {format(new Date(comment.createdAt), 'yyyy-MM-dd HH:mm')} + + {comment.authorId === currentUserId && editingCommentId !== comment.id && ( +
+ + +
+ )}
{editingCommentId === comment.id ? ( diff --git a/src/components/customer-center/NoticeManagement/NoticeDetail.tsx b/src/components/customer-center/NoticeManagement/NoticeDetail.tsx index 9e6ede84..f65fbe85 100644 --- a/src/components/customer-center/NoticeManagement/NoticeDetail.tsx +++ b/src/components/customer-center/NoticeManagement/NoticeDetail.tsx @@ -33,11 +33,12 @@ export function NoticeDetail({ notice }: NoticeDetailProps) {
{/* 메타 정보: 작성자 | 날짜 | 조회수 */} -
+
{notice.author} - | + | {notice.createdAt} - 조회수 {notice.viewCount} + | + 조회수 {notice.viewCount}
{/* 이미지 영역 */} diff --git a/src/components/hr/AttendanceManagement/AttendanceInfoDialog.tsx b/src/components/hr/AttendanceManagement/AttendanceInfoDialog.tsx index 97ecbd8f..80e25776 100644 --- a/src/components/hr/AttendanceManagement/AttendanceInfoDialog.tsx +++ b/src/components/hr/AttendanceManagement/AttendanceInfoDialog.tsx @@ -94,20 +94,20 @@ export function AttendanceInfoDialog({ return ( - + {title} -
+
{/* 대상 선택 */} -
- +
+ handleChange('checkInHour', value)} > - + @@ -154,7 +154,7 @@ export function AttendanceInfoDialog({ value={formData.checkInMinute} onValueChange={(value) => handleChange('checkInMinute', value)} > - + @@ -169,14 +169,14 @@ export function AttendanceInfoDialog({
{/* 퇴근 시간 */} -
- -
+
+ +
handleChange('nightOvertimeHours', value)} > - + @@ -228,7 +228,7 @@ export function AttendanceInfoDialog({ value={formData.nightOvertimeMinutes} onValueChange={(value) => handleChange('nightOvertimeMinutes', value)} > - + @@ -243,14 +243,14 @@ export function AttendanceInfoDialog({
{/* 주말 연장 시간 */} -
- -
+
+ +
handleChange('employeeId', value)} > - + @@ -87,24 +87,24 @@ export function ReasonInfoDialog({
{/* 기준일 */} -
- +
+ handleChange('baseDate', date)} - className="w-[200px]" + className="flex-1 min-w-0" align="end" />
{/* 유형 선택 */} -
- +
+ +
{visibleColumns.map((col) => ( diff --git a/src/components/hr/CardManagement/CardDetail.tsx b/src/components/hr/CardManagement/CardDetail.tsx index d9084515..0828d7cd 100644 --- a/src/components/hr/CardManagement/CardDetail.tsx +++ b/src/components/hr/CardManagement/CardDetail.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { CreditCard, Save, Trash2, X, Edit, Loader2, ExternalLink } from 'lucide-react'; +import { CreditCard, Save, Trash2, X, Edit, Loader2, ExternalLink, ArrowLeft } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { CardNumberInput } from '@/components/ui/card-number-input'; import { formatCardNumber } from '@/lib/formatters'; @@ -33,6 +33,7 @@ import { deleteCard, getActiveEmployees, } from './actions'; +import { useMenuStore } from '@/stores/menuStore'; function formatExpiryDate(value: string): string { if (value && value.length === 4) { @@ -102,6 +103,7 @@ interface CardDetailProps { export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailProps) { const router = useRouter(); const searchParams = useSearchParams(); + const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed); const [mode, setMode] = useState(initialMode); const [isSaving, setIsSaving] = useState(false); const [employees, setEmployees] = useState>([]); @@ -234,14 +236,14 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro -
+
{/* 기본 정보 */} 기본 정보 -
+
카드사
{getCardCompanyLabel(card?.cardCompany || '')}
@@ -306,7 +308,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro 사용자 정보 -
+
부서
{card?.user?.departmentName || '-'}
@@ -346,15 +348,27 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro - {/* 하단 버튼 */} -
-
+ + {/* 하단 버튼 (sticky) */} +
+ +
+ -
@@ -387,7 +401,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro icon={CreditCard} /> -
+
{/* 기본 정보 */} @@ -395,7 +409,7 @@ export function CardDetail({ card, mode: initialMode, isLoading }: CardDetailPro {/* Row 1: 카드사 | 종류 | 카드번호 | 카드명 */} -
+
{/* Row 2: 카드 별칭 | 유효기간 | CSV | 결제일 */} -
+
{/* Row 3: 총 한도 | 사용 금액 | 잔여한도 | 상태 */} -
+
사용자 정보 -
+
- {/* 하단 버튼 */} -
- - -
+
+ + {/* 하단 버튼 (sticky) */} +
+ +
); diff --git a/src/components/hr/CardManagement/index.tsx b/src/components/hr/CardManagement/index.tsx index 3d00550b..59abca3e 100644 --- a/src/components/hr/CardManagement/index.tsx +++ b/src/components/hr/CardManagement/index.tsx @@ -75,7 +75,7 @@ export function CardManagement() { // ===== 핸들러 ===== const handleRowClick = useCallback((item: CardType) => { - router.push(`/ko/hr/card-management/${item.id}`); + router.push(`/ko/hr/card-management/${item.id}?mode=view`); }, [router]); const handleCreate = useCallback(() => { @@ -263,6 +263,7 @@ export function CardManagement() { {CARD_STATUS_LABELS[item.status]} } + showCheckbox={false} isSelected={false} onToggleSelection={() => {}} onClick={() => handleRowClick(item)} diff --git a/src/components/hr/DepartmentManagement/DepartmentToolbar.tsx b/src/components/hr/DepartmentManagement/DepartmentToolbar.tsx index ee0d3e57..5f32fa0b 100644 --- a/src/components/hr/DepartmentManagement/DepartmentToolbar.tsx +++ b/src/components/hr/DepartmentManagement/DepartmentToolbar.tsx @@ -30,16 +30,13 @@ export function DepartmentToolbar({
{/* 선택 카운트 + 버튼 */} -
+
전체 {totalCount}건 + {selectedCount > 0 && ( + <> / {selectedCount}개 선택 + )} - {selectedCount > 0 && ( - <> - / - {selectedCount}개 항목 선택됨 - - )} {selectedCount > 0 && (
-
작업
{/* 트리 아이템 목록 */} diff --git a/src/components/hr/DepartmentManagement/DepartmentTreeItem.tsx b/src/components/hr/DepartmentManagement/DepartmentTreeItem.tsx index 8a29a07d..c05b0042 100644 --- a/src/components/hr/DepartmentManagement/DepartmentTreeItem.tsx +++ b/src/components/hr/DepartmentManagement/DepartmentTreeItem.tsx @@ -3,7 +3,7 @@ import { memo } from 'react'; import { Checkbox } from '@/components/ui/checkbox'; import { Button } from '@/components/ui/button'; -import { ChevronRight, ChevronDown, Plus, Pencil, Trash2 } from 'lucide-react'; +import { ChevronRight, ChevronDown, Plus, SquarePen, Trash2 } from 'lucide-react'; import type { DepartmentTreeItemProps } from './types'; /** @@ -33,15 +33,15 @@ export const DepartmentTreeItem = memo(function DepartmentTreeItem({ <> {/* 현재 행 */}
-
+
{/* 펼침/접힘 버튼 */}
- {/* 작업 버튼 (호버 시 표시) */} -
- - - -
+ {/* 작업 버튼 (선택 시 부서명 아래에 표시, 데스크톱: 호버 시에도 표시) */} + {isSelected && ( +
+ + + +
+ )}
{/* 하위 부서 (재귀) */} diff --git a/src/components/hr/EmployeeManagement/EmployeeForm.tsx b/src/components/hr/EmployeeManagement/EmployeeForm.tsx index 97c03d17..370ca0e0 100644 --- a/src/components/hr/EmployeeManagement/EmployeeForm.tsx +++ b/src/components/hr/EmployeeManagement/EmployeeForm.tsx @@ -569,7 +569,7 @@ export function EmployeeForm({ {/* 프로필 사진 + 사원코드/성별 */} -
+
{/* 프로필 사진 영역 */} {fieldSettings.showProfileImage && (
@@ -783,47 +783,50 @@ export function EmployeeForm({
{formData.departmentPositions.map((dp) => (
- - +
+ + +
{!isViewMode && ( )} diff --git a/src/components/pricing-distribution/PriceDistributionList.tsx b/src/components/pricing-distribution/PriceDistributionList.tsx index 8106b2a7..c6d45537 100644 --- a/src/components/pricing-distribution/PriceDistributionList.tsx +++ b/src/components/pricing-distribution/PriceDistributionList.tsx @@ -110,7 +110,7 @@ export function PriceDistributionList() { if (result.success && result.data) { toast.success('단가배포가 등록되었습니다.'); setShowRegisterDialog(false); - router.push(`/master-data/price-distribution/${result.data.id}`); + router.push(`/master-data/price-distribution/${result.data.id}?mode=view`); } else { toast.error(result.error || '등록에 실패했습니다.'); } @@ -123,7 +123,7 @@ export function PriceDistributionList() { // 행 클릭 → 상세 const handleRowClick = (item: PriceDistributionListItem) => { - router.push(`/master-data/price-distribution/${item.id}`); + router.push(`/master-data/price-distribution/${item.id}?mode=view`); }; // 상태 필터 설정 diff --git a/src/components/process-management/ProcessDetail.tsx b/src/components/process-management/ProcessDetail.tsx index 2c3bffaf..24923ee9 100644 --- a/src/components/process-management/ProcessDetail.tsx +++ b/src/components/process-management/ProcessDetail.tsx @@ -96,11 +96,11 @@ export function ProcessDetail({ process, onProcessUpdate }: ProcessDetailProps) }; const handleAddStep = () => { - router.push(`/ko/master-data/process-management/${process.id}/steps/new`); + router.push(`/ko/master-data/process-management/${process.id}/steps/new?mode=new`); }; const handleStepClick = (stepId: string) => { - router.push(`/ko/master-data/process-management/${process.id}/steps/${stepId}`); + router.push(`/ko/master-data/process-management/${process.id}/steps/${stepId}?mode=view`); }; // ===== 드래그&드롭 (HTML5 네이티브) ===== diff --git a/src/components/process-management/ProcessForm.tsx b/src/components/process-management/ProcessForm.tsx index b5125085..d1a63cb7 100644 --- a/src/components/process-management/ProcessForm.tsx +++ b/src/components/process-management/ProcessForm.tsx @@ -246,14 +246,14 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) { // 단계 상세 이동 const handleStepClick = (stepId: string) => { if (isEdit && initialData?.id) { - router.push(`/ko/master-data/process-management/${initialData.id}/steps/${stepId}`); + router.push(`/ko/master-data/process-management/${initialData.id}/steps/${stepId}?mode=view`); } }; // 단계 등록 이동 const handleAddStep = () => { if (isEdit && initialData?.id) { - router.push(`/ko/master-data/process-management/${initialData.id}/steps/new`); + router.push(`/ko/master-data/process-management/${initialData.id}/steps/new?mode=new`); } else { toast.info('공정을 먼저 등록한 후 단계를 추가할 수 있습니다.'); } diff --git a/src/components/process-management/StepDetail.tsx b/src/components/process-management/StepDetail.tsx index 4831ee58..3825a835 100644 --- a/src/components/process-management/StepDetail.tsx +++ b/src/components/process-management/StepDetail.tsx @@ -53,7 +53,7 @@ export function StepDetail({ step, processId }: StepDetailProps) { }; const handleBack = () => { - router.push(`/ko/master-data/process-management/${processId}`); + router.push(`/ko/master-data/process-management/${processId}?mode=view`); }; const handleDelete = async () => { @@ -62,7 +62,7 @@ export function StepDetail({ step, processId }: StepDetailProps) { const result = await deleteProcessStep(processId, step.id); if (result.success) { toast.success('단계가 삭제되었습니다.'); - router.push(`/ko/master-data/process-management/${processId}`); + router.push(`/ko/master-data/process-management/${processId}?mode=view`); } else { toast.error(result.error || '삭제에 실패했습니다.'); } diff --git a/src/components/process-management/StepForm.tsx b/src/components/process-management/StepForm.tsx index cd951dd4..27d45d07 100644 --- a/src/components/process-management/StepForm.tsx +++ b/src/components/process-management/StepForm.tsx @@ -145,7 +145,7 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) { const result = await updateProcessStep(processId, initialData.id, stepData); if (result.success) { toast.success('단계가 수정되었습니다.'); - router.push(`/ko/master-data/process-management/${processId}/steps/${initialData.id}`); + router.push(`/ko/master-data/process-management/${processId}/steps/${initialData.id}?mode=view`); return { success: true }; } else { toast.error(result.error || '수정에 실패했습니다.'); @@ -155,7 +155,7 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) { const result = await createProcessStep(processId, stepData); if (result.success) { toast.success('단계가 등록되었습니다.'); - router.push(`/ko/master-data/process-management/${processId}`); + router.push(`/ko/master-data/process-management/${processId}?mode=view`); return { success: true }; } else { toast.error(result.error || '등록에 실패했습니다.'); @@ -171,7 +171,7 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) { }; const handleCancel = () => { - router.push(`/ko/master-data/process-management/${processId}`); + router.push(`/ko/master-data/process-management/${processId}?mode=view`); }; const renderFormContent = useCallback( diff --git a/src/components/production/WorkOrders/WorkOrderCreate.tsx b/src/components/production/WorkOrders/WorkOrderCreate.tsx index 70213d80..7475f0e6 100644 --- a/src/components/production/WorkOrders/WorkOrderCreate.tsx +++ b/src/components/production/WorkOrders/WorkOrderCreate.tsx @@ -8,7 +8,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/navigation'; -import { ArrowLeft, FileText, X, Edit2, Loader2, Plus, Search, Trash2 } from 'lucide-react'; +import { ArrowLeft, FileText, X, Edit, Loader2, Plus, Search, Trash2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { DatePicker } from '@/components/ui/date-picker'; @@ -416,7 +416,7 @@ export function WorkOrderCreate() { 해제
diff --git a/src/components/quotes/QuoteManagementClient.tsx b/src/components/quotes/QuoteManagementClient.tsx index 8559dc15..d6c2a9e1 100644 --- a/src/components/quotes/QuoteManagementClient.tsx +++ b/src/components/quotes/QuoteManagementClient.tsx @@ -95,7 +95,7 @@ export function QuoteManagementClient({ // ===== 핸들러 ===== const handleView = useCallback((quote: Quote) => { - router.push(`/sales/quote-management/${quote.id}`); + router.push(`/sales/quote-management/${quote.id}?mode=view`); }, [router]); const handleEdit = useCallback((quote: Quote) => { @@ -354,7 +354,7 @@ export function QuoteManagementClient({ headerActions: () => (
+ + {/* 하단 액션 버튼 (sticky) */} +
+ +
+ + -
- - -
@@ -330,17 +338,18 @@ export function AccountDetail({ account, mode: initialMode }: AccountDetailProps - {/* 버튼 영역 */} -
- - -
+
+ + {/* 하단 액션 버튼 (sticky) */} +
+ +
); diff --git a/src/components/settings/AccountManagement/AccountDetailForm.tsx b/src/components/settings/AccountManagement/AccountDetailForm.tsx index e00f644e..a6d419fb 100644 --- a/src/components/settings/AccountManagement/AccountDetailForm.tsx +++ b/src/components/settings/AccountManagement/AccountDetailForm.tsx @@ -12,7 +12,7 @@ import { useState, useCallback, useMemo } from 'react'; import { useRouter } from 'next/navigation'; -import { Landmark, Save, Trash2, ArrowLeft } from 'lucide-react'; +import { Landmark, Save, Trash2, ArrowLeft, Edit } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -20,6 +20,7 @@ import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import { FormField } from '@/components/molecules/FormField'; +import { useMenuStore } from '@/stores/menuStore'; import type { Account, AccountCategory, AccountFormData } from './types'; import { ACCOUNT_CATEGORY_OPTIONS, @@ -98,6 +99,7 @@ export function AccountDetailForm({ isLoading, }: AccountDetailFormProps) { const router = useRouter(); + const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed); const [mode, setMode] = useState(initialMode); const [formData, setFormData] = useState(() => getInitialFormData(initialData)); const [isSaving, setIsSaving] = useState(false); @@ -216,7 +218,7 @@ export function AccountDetailForm({ icon={Landmark} /> -
+
{/* ===== 기본 정보 ===== */} @@ -321,26 +323,30 @@ export function AccountDetailForm({ )} - {/* ===== 하단 버튼 ===== */} -
- -
+
{isViewMode ? ( <> {onDelete && ( )} - + ) : ( <> @@ -348,15 +354,16 @@ export function AccountDetailForm({ )} - )} diff --git a/src/components/settings/AccountManagement/index.tsx b/src/components/settings/AccountManagement/index.tsx index f3827391..b8f7a1b3 100644 --- a/src/components/settings/AccountManagement/index.tsx +++ b/src/components/settings/AccountManagement/index.tsx @@ -344,6 +344,7 @@ export function AccountManagement() { {ACCOUNT_STATUS_LABELS[item.status]} } + showCheckbox={false} isSelected={false} onToggleSelection={() => {}} onClick={() => handleRowClick(item)} diff --git a/src/components/settings/AttendanceSettingsManagement/index.tsx b/src/components/settings/AttendanceSettingsManagement/index.tsx index 1c069d98..fc02cf19 100644 --- a/src/components/settings/AttendanceSettingsManagement/index.tsx +++ b/src/components/settings/AttendanceSettingsManagement/index.tsx @@ -181,12 +181,10 @@ export function AttendanceSettingsManagement() { GPS 출퇴근 - + {/* GPS 출퇴근 사용 + 연동 부서 */} -
-
- GPS 출퇴근 -
+
+ GPS 출퇴근 -
+
연동 부서
{/* 출퇴근 허용 반경 */} -
-
- 출퇴근 허용 반경 -
+
+ 출퇴근 허용 반경 - onChange({ ...item, soundType: value }) - } - disabled={isDisabled} - > - - - - - {SOUND_OPTIONS.map((option) => ( - - {option.label} - - ))} - - - +
+ 알림 소리 선택 +
+ + +
{/* 추가 알림 선택 */} -
- 추가 알림 선택 +
+ 추가 알림 선택