From 37f0e57b16961521477518335c2619c938e8d212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Tue, 17 Mar 2026 18:30:49 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[quality]=20=EC=84=A4=EB=B9=84=EC=A0=90?= =?UTF-8?q?=EA=B2=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quality/EquipmentInspection/index.tsx | 114 ++++++++++++++---- 1 file changed, 93 insertions(+), 21 deletions(-) diff --git a/src/components/quality/EquipmentInspection/index.tsx b/src/components/quality/EquipmentInspection/index.tsx index 13cbd84c..1ebadc6a 100644 --- a/src/components/quality/EquipmentInspection/index.tsx +++ b/src/components/quality/EquipmentInspection/index.tsx @@ -7,7 +7,7 @@ * 셀 클릭으로 ○/X/△ 토글 */ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; import { useSearchParams } from 'next/navigation'; import { Loader2 } from 'lucide-react'; import { toast } from 'sonner'; @@ -20,7 +20,6 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; import { @@ -67,13 +66,15 @@ interface GridData { nonWorkingDays: string[]; } +const YEAR_OPTIONS = Array.from({ length: 10 }, (_, i) => 2021 + i); +const MONTH_OPTIONS = Array.from({ length: 12 }, (_, i) => i + 1); + export function EquipmentInspectionGrid() { const searchParams = useSearchParams(); const [cycle, setCycle] = useState('daily'); - const [period, setPeriod] = useState(() => { - const now = new Date(); - return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; - }); + const [year, setYear] = useState(() => new Date().getFullYear()); + const [month, setMonth] = useState(() => new Date().getMonth() + 1); + const period = `${year}-${String(month).padStart(2, '0')}`; const [lineFilter, setLineFilter] = useState('all'); const [equipmentFilter, setEquipmentFilter] = useState(() => { if (typeof window === 'undefined') return 'all'; @@ -85,6 +86,40 @@ export function EquipmentInspectionGrid() { const [isLoading, setIsLoading] = useState(true); const [showReset, setShowReset] = useState(false); + // 드래그 스크롤 + const scrollRef = useRef(null); + const isDragging = useRef(false); + const startX = useRef(0); + const scrollLeft = useRef(0); + + const handleMouseDown = useCallback((e: React.MouseEvent) => { + const el = scrollRef.current; + if (!el) return; + isDragging.current = true; + startX.current = e.pageX - el.offsetLeft; + scrollLeft.current = el.scrollLeft; + el.style.cursor = 'grabbing'; + el.style.userSelect = 'none'; + }, []); + + const handleMouseMove = useCallback((e: React.MouseEvent) => { + if (!isDragging.current) return; + const el = scrollRef.current; + if (!el) return; + e.preventDefault(); + const x = e.pageX - el.offsetLeft; + el.scrollLeft = scrollLeft.current - (x - startX.current); + }, []); + + const handleMouseUp = useCallback(() => { + isDragging.current = false; + const el = scrollRef.current; + if (el) { + el.style.cursor = 'grab'; + el.style.userSelect = ''; + } + }, []); + // 옵션 로드 useEffect(() => { getEquipmentOptions().then((r) => { @@ -139,13 +174,19 @@ export function EquipmentInspectionGrid() { : Array.isArray(rawLabels) ? rawLabels : []; // 주말(토/일) 계산 const [y, m] = period.split('-').map(Number); - const weekends: string[] = []; + const weekends = new Set(); const daysInMonth = new Date(y, m, 0).getDate(); for (let d = 1; d <= daysInMonth; d++) { const dow = new Date(y, m - 1, d).getDay(); - if (dow === 0 || dow === 6) weekends.push(String(d)); + if (dow === 0 || dow === 6) weekends.add(String(d)); } - setGridData({ rows, labels, nonWorkingDays: weekends }); + // API에서 받은 임시휴일 추가 (형식: "2026-03-17" → 일자 "17"으로 변환) + const apiHolidays: string[] = apiItems.length > 0 ? (apiItems[0].non_working_days ?? []) : []; + for (const dateStr of apiHolidays) { + const day = String(Number(dateStr.split('-')[2])); + weekends.add(day); + } + setGridData({ rows, labels, nonWorkingDays: Array.from(weekends) }); } else { setGridData({ rows: [], labels: [], nonWorkingDays: [] }); } @@ -187,7 +228,12 @@ export function EquipmentInspectionGrid() { return { ...prev, rows: newRows }; }); } else { - toast.error(result.error || '점검 결과 변경에 실패했습니다.'); + const errorMsg = result.error?.includes('non_working_day') + ? '비근무일에는 점검을 입력할 수 없습니다.' + : result.error?.includes('no_inspect_permission') + ? '담당자만 점검을 입력할 수 있습니다.' + : result.error || '점검 결과 변경에 실패했습니다.'; + toast.error(errorMsg); } }, [cycle, period]); @@ -243,12 +289,28 @@ export function EquipmentInspectionGrid() {
- { - if (v) setPeriod(v.substring(0, 7)); - }} - /> +
+ + +
@@ -353,7 +415,14 @@ export function EquipmentInspectionGrid() { boxShadow: '4px 0 6px -2px rgba(0,0,0,0.08)', }); return ( -
+
@@ -419,10 +488,13 @@ export function EquipmentInspectionGrid() { borderRight: `1px solid ${bc}`, borderBottom: `1px solid ${bc}`, ...(isNonWorking ? { background: '#fef2f2' } : {}), }} - onClick={() => - row.canInspect && - handleCellClick(row.equipment.id, template.id, label) - } + onClick={() => { + if (!row.canInspect) { + toast.error('담당자만 점검을 입력할 수 있습니다.'); + return; + } + handleCellClick(row.equipment.id, template.id, label); + }} > {getResultSymbol(result)}