diff --git a/src/components/business/construction/order-management/OrderDetailForm.tsx b/src/components/business/construction/order-management/OrderDetailForm.tsx index 166402dd..c497074c 100644 --- a/src/components/business/construction/order-management/OrderDetailForm.tsx +++ b/src/components/business/construction/order-management/OrderDetailForm.tsx @@ -121,17 +121,18 @@ export default function OrderDetailForm({ }, [orderId, router]); // 커스텀 헤더 액션 (view 모드에서 발주서 보기, 복제 버튼) + // 모바일: 아이콘만, sm 이상: 아이콘 + 텍스트 const customHeaderActions = useMemo(() => { if (!isViewMode) return null; return ( <> - - ); diff --git a/src/components/common/ScheduleCalendar/CalendarHeader.tsx b/src/components/common/ScheduleCalendar/CalendarHeader.tsx index 5ca6d7b4..35c148bb 100644 --- a/src/components/common/ScheduleCalendar/CalendarHeader.tsx +++ b/src/components/common/ScheduleCalendar/CalendarHeader.tsx @@ -26,24 +26,75 @@ export function CalendarHeader({ { value: 'month', label: '월' }, ]; - return ( -
- {/* PC: 타이틀 + 네비게이션 | 뷰전환 + 필터 (한 줄) */} - {/* 모바일: 타이틀 / 네비게이션 + 뷰전환 / 필터 (세 줄) */} + // 뷰 전환 버튼 렌더링 (재사용) + const renderViewTabs = (className?: string) => ( +
+ {views.map((v) => ( + + ))} +
+ ); - {/* 1줄(모바일) / 좌측(PC): 타이틀 */} + return ( +
+ {/* 모바일 전용: 타이틀 */} {titleSlot && (
{titleSlot}
)} - {/* 2줄(모바일) / 전체(PC): 네비게이션 + 뷰전환 + 필터 */} -
- {/* 좌측: (PC에서만 타이틀) + 네비게이션 */} + {/* 모바일: 네비게이션 (1줄) + 뷰전환 (2줄) */} +
+ {/* 1줄: 네비게이션 < 년월 > */} +
+ + + + {formatYearMonth(currentDate)} + + + +
+ + {/* 2줄: 뷰 전환 탭 */} +
+ {renderViewTabs()} +
+
+ + {/* PC: 타이틀 + 네비게이션 | 뷰전환 + 필터 (한 줄) */} +
+ {/* 좌측: 타이틀 + 네비게이션 */}
{titleSlot && ( - + {titleSlot} )} @@ -70,54 +121,19 @@ export function CalendarHeader({
- - {/* 모바일: 뷰 전환 탭 (네비게이션 옆) */} -
- {views.map((v) => ( - - ))} -
- {/* 우측(PC만): 뷰 전환 + 필터 */} -
-
- {views.map((v) => ( - - ))} -
+ {/* 우측: 뷰 전환 + 필터 */} +
+ {renderViewTabs('px-4 py-1.5')} {filterSlot &&
{filterSlot}
}
- {/* 3줄(모바일만): 필터 */} + {/* 모바일 전용: 필터 */} {filterSlot && (
{filterSlot}
)}
); -} \ No newline at end of file +} diff --git a/src/components/organisms/MobileCard.tsx b/src/components/organisms/MobileCard.tsx index 9e442f84..9fb6304c 100644 --- a/src/components/organisms/MobileCard.tsx +++ b/src/components/organisms/MobileCard.tsx @@ -301,7 +301,7 @@ export function MobileCard({ )} {/* 제목 */} -

+

{title}

diff --git a/src/components/quotes/LocationDetailPanel.tsx b/src/components/quotes/LocationDetailPanel.tsx index 9fa57d1f..36835710 100644 --- a/src/components/quotes/LocationDetailPanel.tsx +++ b/src/components/quotes/LocationDetailPanel.tsx @@ -34,6 +34,7 @@ import { } from "../ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; import { ItemSearchModal } from "./ItemSearchModal"; +import { LocationEditModal } from "./LocationEditModal"; import type { LocationItem } from "./QuoteRegistrationV2"; import type { FinishedGoods } from "./actions"; @@ -117,6 +118,7 @@ export function LocationDetailPanel({ const [activeTab, setActiveTab] = useState("body"); const [itemSearchOpen, setItemSearchOpen] = useState(false); + const [editModalOpen, setEditModalOpen] = useState(false); // --------------------------------------------------------------------------- // 계산된 값 @@ -257,7 +259,14 @@ export function LocationDetailPanel({ className="w-24 h-8 text-center font-bold" /> {!disabled && ( - 수정 + )}
@@ -613,6 +622,16 @@ export function LocationDetailPanel({ }} tabLabel={DETAIL_TABS.find((t) => t.value === activeTab)?.label} /> + + {/* 개소 정보 수정 모달 */} + { + onUpdateLocation(locationId, updates); + }} + /> ); } \ No newline at end of file diff --git a/src/components/quotes/LocationEditModal.tsx b/src/components/quotes/LocationEditModal.tsx new file mode 100644 index 00000000..2832fdc2 --- /dev/null +++ b/src/components/quotes/LocationEditModal.tsx @@ -0,0 +1,282 @@ +/** + * 개소 정보 수정 모달 + * + * 발주 개소 목록에서 수정 버튼 클릭 시 표시 + * - 개소 정보: 층, 부호 + * - 오픈사이즈: 가로, 세로 + * - 필수 설정: 가이드레일, 전원, 제어기 + */ + +"use client"; + +import { useState, useEffect } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "../ui/dialog"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { NumberInput } from "../ui/number-input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; + +import type { LocationItem } from "./QuoteRegistrationV2"; + +// ============================================================================= +// 상수 +// ============================================================================= + +// 가이드레일 설치 유형 +const GUIDE_RAIL_TYPES = [ + { value: "wall", label: "벽면형" }, + { value: "floor", label: "측면형" }, +]; + +// 모터 전원 +const MOTOR_POWERS = [ + { value: "single", label: "단상(220V)" }, + { value: "three", label: "삼상(380V)" }, +]; + +// 연동제어기 +const CONTROLLERS = [ + { value: "basic", label: "단독" }, + { value: "smart", label: "연동" }, + { value: "premium", label: "매립형-뒷박스포함" }, +]; + +// ============================================================================= +// Props +// ============================================================================= + +interface LocationEditModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + location: LocationItem | null; + onSave: (locationId: string, updates: Partial) => void; +} + +// ============================================================================= +// 컴포넌트 +// ============================================================================= + +export function LocationEditModal({ + open, + onOpenChange, + location, + onSave, +}: LocationEditModalProps) { + // --------------------------------------------------------------------------- + // 상태 + // --------------------------------------------------------------------------- + const [formData, setFormData] = useState({ + floor: "", + code: "", + openWidth: 0, + openHeight: 0, + guideRailType: "wall", + motorPower: "single", + controller: "basic", + }); + + // location 변경 시 폼 데이터 초기화 + useEffect(() => { + if (location) { + setFormData({ + floor: location.floor, + code: location.code, + openWidth: location.openWidth, + openHeight: location.openHeight, + guideRailType: location.guideRailType, + motorPower: location.motorPower, + controller: location.controller, + }); + } + }, [location]); + + // --------------------------------------------------------------------------- + // 핸들러 + // --------------------------------------------------------------------------- + + const handleFieldChange = (field: string, value: string | number) => { + setFormData((prev) => ({ ...prev, [field]: value })); + }; + + const handleSave = () => { + if (!location) return; + + onSave(location.id, { + floor: formData.floor, + code: formData.code, + openWidth: formData.openWidth, + openHeight: formData.openHeight, + guideRailType: formData.guideRailType, + motorPower: formData.motorPower, + controller: formData.controller, + }); + + onOpenChange(false); + }; + + const handleCancel = () => { + onOpenChange(false); + }; + + // --------------------------------------------------------------------------- + // 렌더링 + // --------------------------------------------------------------------------- + + if (!location) return null; + + return ( + + + + 개소 정보 수정 + + +
+ {/* 개소 정보 */} +
+

개소 정보

+
+
+ + handleFieldChange("floor", e.target.value)} + placeholder="1층" + /> +
+
+ + handleFieldChange("code", e.target.value)} + placeholder="FSS-01" + /> +
+
+
+ + {/* 오픈사이즈 */} +
+

오픈사이즈

+
+
+ + handleFieldChange("openWidth", value ?? 0)} + placeholder="5000" + /> +
+
+ + handleFieldChange("openHeight", value ?? 0)} + placeholder="3000" + /> +
+
+

+ ※ 제작사이즈는 오픈사이즈의 280mm를 더한 값으로 자동 계산됩니다. +

+
+ + {/* 필수 설정 */} +
+

필수 설정

+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + {/* 버튼 */} +
+ + +
+
+
+ ); +} diff --git a/src/components/quotes/LocationListPanel.tsx b/src/components/quotes/LocationListPanel.tsx index c76b6f7d..79bee6a0 100644 --- a/src/components/quotes/LocationListPanel.tsx +++ b/src/components/quotes/LocationListPanel.tsx @@ -9,7 +9,7 @@ "use client"; import { useState, useCallback } from "react"; -import { Plus, Upload, Download, Trash2 } from "lucide-react"; +import { Plus, Upload, Download, Trash2, Pencil } from "lucide-react"; import { toast } from "sonner"; import { Button } from "../ui/button"; @@ -32,6 +32,7 @@ import { TableRow, } from "../ui/table"; import { DeleteConfirmDialog } from "../ui/confirm-dialog"; +import { LocationEditModal } from "./LocationEditModal"; import type { LocationItem } from "./QuoteRegistrationV2"; import type { FinishedGoods } from "./actions"; @@ -70,6 +71,7 @@ interface LocationListPanelProps { onSelectLocation: (id: string) => void; onAddLocation: (location: Omit) => void; onDeleteLocation: (id: string) => void; + onUpdateLocation: (locationId: string, updates: Partial) => void; onExcelUpload: (locations: Omit[]) => void; finishedGoods: FinishedGoods[]; disabled?: boolean; @@ -85,6 +87,7 @@ export function LocationListPanel({ onSelectLocation, onAddLocation, onDeleteLocation, + onUpdateLocation, onExcelUpload, finishedGoods, disabled = false, @@ -109,6 +112,9 @@ export function LocationListPanel({ // 삭제 확인 다이얼로그 const [deleteTarget, setDeleteTarget] = useState(null); + // 수정 모달 + const [editTarget, setEditTarget] = useState(null); + // --------------------------------------------------------------------------- // 핸들러 // --------------------------------------------------------------------------- @@ -346,17 +352,30 @@ export function LocationListPanel({ {loc.quantity} {!disabled && ( - +
+ + +
)}
@@ -521,6 +540,18 @@ export function LocationListPanel({ title="개소 삭제" description="선택한 개소를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다." /> + + {/* 개소 정보 수정 모달 */} + !open && setEditTarget(null)} + location={editTarget} + onSave={(locationId, updates) => { + onUpdateLocation(locationId, updates); + setEditTarget(null); + toast.success("개소 정보가 수정되었습니다."); + }} + /> ); } \ No newline at end of file diff --git a/src/components/quotes/QuoteRegistrationV2.tsx b/src/components/quotes/QuoteRegistrationV2.tsx index 0fe54277..0d05c134 100644 --- a/src/components/quotes/QuoteRegistrationV2.tsx +++ b/src/components/quotes/QuoteRegistrationV2.tsx @@ -703,6 +703,7 @@ export function QuoteRegistrationV2({ onSelectLocation={setSelectedLocationId} onAddLocation={handleAddLocation} onDeleteLocation={handleDeleteLocation} + onUpdateLocation={handleUpdateLocation} onExcelUpload={handleExcelUpload} finishedGoods={finishedGoods} disabled={isViewMode} diff --git a/src/components/templates/IntegratedDetailTemplate/components/DetailActions.tsx b/src/components/templates/IntegratedDetailTemplate/components/DetailActions.tsx index 1777136d..bb3168f4 100644 --- a/src/components/templates/IntegratedDetailTemplate/components/DetailActions.tsx +++ b/src/components/templates/IntegratedDetailTemplate/components/DetailActions.tsx @@ -103,33 +103,35 @@ export function DetailActions({ const actualSubmitLabel = submitLabel || (isCreateMode ? '등록' : '저장'); // Fixed 스타일: 화면 하단에 고정 (사이드바 상태에 따라 동적 계산) - // 사이드바 펼침: w-64(256px), 접힘: w-24(96px), 차이: 160px + // 모바일: 좌우 여백 16px (left-4 right-4) + // 태블릿/데스크탑: 사이드바 펼침(w-64=256px), 접힘(w-24=96px) + 여백 고려 const stickyStyles = sticky - ? `fixed bottom-6 ${sidebarCollapsed ? 'left-[156px]' : 'left-[316px]'} right-[48px] px-6 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300` + ? `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]'}` : ''; // 공통 레이아웃: 왼쪽(뒤로) | 오른쪽(액션들) + // 모바일: 아이콘만, 태블릿 이상: 아이콘 + 텍스트 return ( -
+
{/* 왼쪽: 목록으로 (view) 또는 취소 (edit/create) */} {isViewMode ? ( showBack && onBack ? ( - ) : (
) ) : ( - )} {/* 오른쪽: 추가액션 + 삭제 + 수정/저장/등록 */} -
+
{extraActions} {/* 삭제 버튼: view, edit 모드에서 표시 (create는 삭제할 대상 없음) */} @@ -138,26 +140,27 @@ export function DetailActions({ variant="outline" onClick={onDelete} disabled={isSubmitting} - className="text-destructive hover:bg-destructive hover:text-destructive-foreground" + size="sm" + className="text-destructive hover:bg-destructive hover:text-destructive-foreground md:size-default" > - - {deleteLabel} + + {deleteLabel} )} {/* 수정 버튼: view 모드에서만 */} {isViewMode && canEdit && showEdit && onEdit && ( - )} {/* 저장/등록 버튼: edit, create 모드에서만 */} {!isViewMode && showSave && onSubmit && ( - )}