From 8bc4b90fe93f0a9991cb1b6614a54157cab41b79 Mon Sep 17 00:00:00 2001 From: byeongcheolryu Date: Sat, 10 Jan 2026 11:07:17 +0900 Subject: [PATCH] =?UTF-8?q?chore(WEB):=20QMS=20=ED=92=88=EC=A7=88=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20Day=20=ED=83=AD=20=EB=B0=8F=20=EB=A3=A8=ED=8A=B8=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DayTabs 컴포넌트 리팩토링 - RouteList 기능 확장 및 UI 개선 - 목업 데이터 구조 조정 - 페이지 레이아웃 개선 Co-Authored-By: Claude Opus 4.5 --- .../quality/qms/components/DayTabs.tsx | 46 +++++++------- .../quality/qms/components/RouteList.tsx | 61 ++++++++++++++----- .../(protected)/quality/qms/mockData.ts | 26 ++++---- .../[locale]/(protected)/quality/qms/page.tsx | 44 ++++++++++--- .../[locale]/(protected)/quality/qms/types.ts | 2 +- 5 files changed, 119 insertions(+), 60 deletions(-) diff --git a/src/app/[locale]/(protected)/quality/qms/components/DayTabs.tsx b/src/app/[locale]/(protected)/quality/qms/components/DayTabs.tsx index d7945c1d..312ff3d3 100644 --- a/src/app/[locale]/(protected)/quality/qms/components/DayTabs.tsx +++ b/src/app/[locale]/(protected)/quality/qms/components/DayTabs.tsx @@ -71,28 +71,32 @@ export function DayTabs({ activeDay, onDayChange, day1Progress, day2Progress }: - {/* 전체 진행률 - 한줄 표시 */} -
- 전체 심사 진행률 - - {/* 전체 프로그레스 바 */} -
+ {/* 진행률 - 3줄 표시 */} +
+ {/* 전체 심사 진행률 */} +
+ 전체 심사
- {overallPercentage}% + + {totalCompleted}/{totalItems} +
- {/* 구분선 */} -
- {/* 1일차 진행률 */} -
- 1일차 -
+
+ 1일차: 기준/매뉴얼 +
- {day1Percentage}% + {day1Progress.completed}/{day1Progress.total}
{/* 2일차 진행률 */} -
- 2일차 -
+
+ 2일차: 로트추적 +
- {day2Percentage}% + {day2Progress.completed}/{day2Progress.total}
diff --git a/src/app/[locale]/(protected)/quality/qms/components/RouteList.tsx b/src/app/[locale]/(protected)/quality/qms/components/RouteList.tsx index 32b373cc..e436d620 100644 --- a/src/app/[locale]/(protected)/quality/qms/components/RouteList.tsx +++ b/src/app/[locale]/(protected)/quality/qms/components/RouteList.tsx @@ -1,17 +1,19 @@ "use client"; import React, { useState } from 'react'; -import { ChevronDown, ChevronUp, MapPin } from 'lucide-react'; +import { ChevronDown, ChevronUp, MapPin, Check } from 'lucide-react'; import { RouteItem } from '../types'; +import { cn } from '@/lib/utils'; interface RouteListProps { routes: RouteItem[]; selectedId: string | null; onSelect: (route: RouteItem) => void; + onToggleItem: (routeId: string, itemId: string, isCompleted: boolean) => void; reportCode: string | null; } -export const RouteList = ({ routes, selectedId, onSelect, reportCode }: RouteListProps) => { +export const RouteList = ({ routes, selectedId, onSelect, onToggleItem, reportCode }: RouteListProps) => { const [expandedId, setExpandedId] = useState(null); const handleClick = (route: RouteItem) => { @@ -19,6 +21,11 @@ export const RouteList = ({ routes, selectedId, onSelect, reportCode }: RouteLis setExpandedId(expandedId === route.id ? null : route.id); }; + const handleToggle = (e: React.MouseEvent, routeId: string, itemId: string, currentState: boolean) => { + e.stopPropagation(); + onToggleItem(routeId, itemId, !currentState); + }; + return (

@@ -37,6 +44,8 @@ export const RouteList = ({ routes, selectedId, onSelect, reportCode }: RouteLis routes.map((route) => { const isSelected = selectedId === route.id; const isExpanded = expandedId === route.id; + const completedCount = route.subItems.filter(item => item.isCompleted).length; + const totalCount = route.subItems.length; return (
@@ -52,6 +61,16 @@ export const RouteList = ({ routes, selectedId, onSelect, reportCode }: RouteLis

{route.code}

+ {totalCount > 0 && ( + + {completedCount}/{totalCount} + + )}

수주일: {route.date}

현장: {route.site}

@@ -75,23 +94,35 @@ export const RouteList = ({ routes, selectedId, onSelect, reportCode }: RouteLis {route.subItems.map((item) => (
-
-
{item.name}
+
+
+ {item.name} +
{item.location}
- handleToggle(e, route.id, item.id, item.isCompleted)} + className={cn( + "flex items-center gap-1 text-[10px] font-bold px-2 py-1 rounded border transition-all", + item.isCompleted + ? "text-green-600 border-green-300 bg-green-100 hover:bg-green-200" + : "text-gray-500 border-gray-300 bg-white hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300" + )} > - {item.status} - + + {item.isCompleted ? '완료' : '확인'} +
))}
diff --git a/src/app/[locale]/(protected)/quality/qms/mockData.ts b/src/app/[locale]/(protected)/quality/qms/mockData.ts index c0a2b985..cd98a852 100644 --- a/src/app/[locale]/(protected)/quality/qms/mockData.ts +++ b/src/app/[locale]/(protected)/quality/qms/mockData.ts @@ -146,7 +146,7 @@ export const MOCK_REPORTS: InspectionReport[] = [ ]; // 수주루트 목록 (reportId로 연결) -export const MOCK_ROUTES: Record = { +export const MOCK_ROUTES_INITIAL: Record = { '1': [ { id: '1-1', @@ -155,13 +155,13 @@ export const MOCK_ROUTES: Record = { site: '강남 아파트 A동', locationCount: 7, subItems: [ - { id: '1-1-1', name: 'KD-SS-240924-19-01', location: '101동 501호', status: '합격' }, - { id: '1-1-2', name: 'KD-SS-240924-19-02', location: '101동 502호', status: '합격' }, - { id: '1-1-3', name: 'KD-SS-240924-19-03', location: '101동 503호', status: '합격' }, - { id: '1-1-4', name: 'KD-SS-240924-19-04', location: '101동 601호', status: '합격' }, - { id: '1-1-5', name: 'KD-SS-240924-19-05', location: '101동 602호', status: '합격' }, - { id: '1-1-6', name: 'KD-SS-240924-19-06', location: '101동 603호', status: '합격' }, - { id: '1-1-7', name: 'KD-SS-240924-19-07', location: '102동 501호', status: '합격' }, + { id: '1-1-1', name: 'KD-SS-240924-19-01', location: '101동 501호', isCompleted: true }, + { id: '1-1-2', name: 'KD-SS-240924-19-02', location: '101동 502호', isCompleted: true }, + { id: '1-1-3', name: 'KD-SS-240924-19-03', location: '101동 503호', isCompleted: true }, + { id: '1-1-4', name: 'KD-SS-240924-19-04', location: '101동 601호', isCompleted: true }, + { id: '1-1-5', name: 'KD-SS-240924-19-05', location: '101동 602호', isCompleted: true }, + { id: '1-1-6', name: 'KD-SS-240924-19-06', location: '101동 603호', isCompleted: false }, + { id: '1-1-7', name: 'KD-SS-240924-19-07', location: '102동 501호', isCompleted: false }, ], }, { @@ -171,8 +171,8 @@ export const MOCK_ROUTES: Record = { site: '강남 아파트 B동', locationCount: 7, subItems: [ - { id: '1-2-1', name: 'KD-SS-241024-15-01', location: '103동 501호', status: '합격' }, - { id: '1-2-2', name: 'KD-SS-241024-15-02', location: '103동 502호', status: '대기' }, + { id: '1-2-1', name: 'KD-SS-241024-15-01', location: '103동 501호', isCompleted: true }, + { id: '1-2-2', name: 'KD-SS-241024-15-02', location: '103동 502호', isCompleted: false }, ], }, ], @@ -184,8 +184,8 @@ export const MOCK_ROUTES: Record = { site: '서초 오피스텔 본관', locationCount: 8, subItems: [ - { id: '2-1-1', name: 'SC-AP-241101-01-01', location: '1층 로비', status: '합격' }, - { id: '2-1-2', name: 'SC-AP-241101-01-02', location: '2층 사무실', status: '합격' }, + { id: '2-1-1', name: 'SC-AP-241101-01-01', location: '1층 로비', isCompleted: true }, + { id: '2-1-2', name: 'SC-AP-241101-01-02', location: '2층 사무실', isCompleted: false }, ], }, ], @@ -197,7 +197,7 @@ export const MOCK_ROUTES: Record = { site: '송파 주상복합 A타워', locationCount: 10, subItems: [ - { id: '3-1-1', name: 'SP-CW-240801-01-01', location: '1층 외벽', status: '합격' }, + { id: '3-1-1', name: 'SP-CW-240801-01-01', location: '1층 외벽', isCompleted: false }, ], }, { diff --git a/src/app/[locale]/(protected)/quality/qms/page.tsx b/src/app/[locale]/(protected)/quality/qms/page.tsx index ad1bda51..3f6ba24a 100644 --- a/src/app/[locale]/(protected)/quality/qms/page.tsx +++ b/src/app/[locale]/(protected)/quality/qms/page.tsx @@ -15,7 +15,7 @@ import { AuditSettingsPanel, SettingsButton, type AuditDisplaySettings } from '. import { InspectionReport, RouteItem, Document, DocumentItem, ChecklistCategory } from './types'; import { MOCK_REPORTS, - MOCK_ROUTES, + MOCK_ROUTES_INITIAL, MOCK_DOCUMENTS, DEFAULT_DOCUMENTS, MOCK_DAY1_CATEGORIES, @@ -55,6 +55,9 @@ export default function QualityInspectionPage() { const [selectedReport, setSelectedReport] = useState(null); const [selectedRoute, setSelectedRoute] = useState(null); + // 2일차 루트 데이터 상태 (완료 토글용) + const [routesData, setRoutesData] = useState>(MOCK_ROUTES_INITIAL); + // 모달 상태 const [modalOpen, setModalOpen] = useState(false); const [selectedDoc, setSelectedDoc] = useState(null); @@ -70,20 +73,20 @@ export default function QualityInspectionPage() { return { completed, total }; }, [day1Categories]); - // ===== 2일차 진행률 계산 (로트 추적 완료 기준) ===== + // ===== 2일차 진행률 계산 (개소별 완료 기준) ===== const day2Progress = useMemo(() => { let completed = 0; let total = 0; - Object.values(MOCK_ROUTES).forEach(routes => { + Object.values(routesData).forEach(routes => { routes.forEach(route => { - total++; - const allPassed = route.subItems.length > 0 && - route.subItems.every(item => item.status === '합격'); - if (allPassed) completed++; + route.subItems.forEach(item => { + total++; + if (item.isCompleted) completed++; + }); }); }); return { completed, total }; - }, []); + }, [routesData]); // ===== 1일차 필터링된 카테고리 (완료 항목 숨기기 옵션) ===== const filteredDay1Categories = useMemo(() => { @@ -165,8 +168,8 @@ export default function QualityInspectionPage() { const currentRoutes = useMemo(() => { if (!selectedReport) return []; - return MOCK_ROUTES[selectedReport.id] || []; - }, [selectedReport]); + return routesData[selectedReport.id] || []; + }, [selectedReport, routesData]); const currentDocuments = useMemo(() => { if (!selectedRoute) return DEFAULT_DOCUMENTS; @@ -204,6 +207,26 @@ export default function QualityInspectionPage() { setSearchTerm(term); }; + // ===== 2일차 개소별 완료 토글 ===== + const handleToggleItem = useCallback((routeId: string, itemId: string, isCompleted: boolean) => { + setRoutesData(prev => { + const newData = { ...prev }; + for (const reportId of Object.keys(newData)) { + newData[reportId] = newData[reportId].map(route => { + if (route.id !== routeId) return route; + return { + ...route, + subItems: route.subItems.map(item => { + if (item.id !== itemId) return item; + return { ...item, isCompleted }; + }), + }; + }); + } + return newData; + }); + }, []); + return (
{/* 헤더 (설정 버튼 포함) */} @@ -316,6 +339,7 @@ export default function QualityInspectionPage() { routes={currentRoutes} selectedId={selectedRoute?.id || null} onSelect={handleRouteSelect} + onToggleItem={handleToggleItem} reportCode={selectedReport?.code || null} />
diff --git a/src/app/[locale]/(protected)/quality/qms/types.ts b/src/app/[locale]/(protected)/quality/qms/types.ts index a8fabc3f..d3da2363 100644 --- a/src/app/[locale]/(protected)/quality/qms/types.ts +++ b/src/app/[locale]/(protected)/quality/qms/types.ts @@ -23,7 +23,7 @@ export interface UnitInspection { id: string; name: string; // e.g., KD-SS-240924-19-01 location: string; // e.g., 101동 501호 - status: '합격' | '불합격' | '대기'; + isCompleted: boolean; // 확인 완료 여부 } export interface Document {