프로필 모달에서 차량/운전자 정보 섹션 제거
Build & Deploy to K8s / build-and-deploy (push) Failing after 6m29s

ProfileModal 의 isDriver 토글 섹션 + 새 차량 등록 모달 + DriverInfo
/DriverFormData 타입 + 관련 props 모두 제거. 호출부(AppLayout) 와
useProfile 훅의 driver/vehicle 상태 관리 로직도 함께 정리.

차량/운전자 메뉴 화면(components/vehicle/*) 과 대시보드 위젯
(VehicleListWidget, DriverManagementWidget 등) 은 그대로 유지.

3 files changed, 525 deletions(-).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Johngreen
2026-04-28 07:52:38 +09:00
parent 57c6c1b472
commit 2b954db854
3 changed files with 4 additions and 525 deletions
-28
View File
@@ -414,20 +414,6 @@ function AppLayoutInner({ children }: AppLayoutProps) {
selectImage,
removeImage,
saveProfile,
isDriver,
hasVehicle,
driverInfo,
driverFormData,
updateDriverFormData,
handleDriverStatusChange,
handleDriverAccountDelete,
handleDeleteVehicle,
openVehicleRegisterModal,
closeVehicleRegisterModal,
isVehicleRegisterModalOpen,
newVehicleData,
updateNewVehicleData,
handleRegisterVehicle,
} = useProfile(user, refreshUserData, refreshMenus);
const tabMode = useTabStore((s) => s.mode);
@@ -1177,20 +1163,6 @@ function AppLayoutInner({ children }: AppLayoutProps) {
isSaving={isSaving}
departments={departments}
alertModal={alertModal}
isDriver={isDriver}
hasVehicle={hasVehicle}
driverInfo={driverInfo}
driverFormData={driverFormData}
onDriverFormChange={updateDriverFormData}
onDriverStatusChange={handleDriverStatusChange}
onDriverAccountDelete={handleDriverAccountDelete}
onDeleteVehicle={handleDeleteVehicle}
onOpenVehicleRegisterModal={openVehicleRegisterModal}
isVehicleRegisterModalOpen={isVehicleRegisterModalOpen}
newVehicleData={newVehicleData}
onCloseVehicleRegisterModal={closeVehicleRegisterModal}
onNewVehicleDataChange={updateNewVehicleData}
onRegisterVehicle={handleRegisterVehicle}
onClose={closeProfileModal}
onFormChange={updateFormData}
onImageSelect={selectImage}
+1 -258
View File
@@ -4,18 +4,14 @@ import {
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Camera, X, Car, Wrench, Clock, Plus, Trash2 } from "lucide-react";
import { Camera, X } from "lucide-react";
import { ProfileFormData } from "@/types/profile";
import { Separator } from "@/components/ui/separator";
import { VehicleRegisterData } from "@/lib/api/driver";
import { apiClient } from "@/lib/api/client";
// 언어 정보 타입
@@ -25,16 +21,6 @@ interface LanguageInfo {
langNative: string;
}
// 운전자 정보 타입
export interface DriverInfo {
vehicleNumber: string;
vehicleType: string | null;
licenseNumber: string;
phoneNumber: string;
vehicleStatus: string | null;
branchName: string | null;
}
// 알림 모달 컴포넌트
interface AlertModalProps {
isOpen: boolean;
@@ -75,15 +61,6 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
);
}
// 운전자 폼 데이터 타입
export interface DriverFormData {
vehicleNumber: string;
vehicleType: string;
licenseNumber: string;
phoneNumber: string;
branchName: string;
}
interface ProfileModalProps {
isOpen: boolean;
user: any;
@@ -100,23 +77,6 @@ interface ProfileModalProps {
message: string;
type: "success" | "error" | "info";
};
// 운전자 관련 props (선택적)
isDriver?: boolean;
hasVehicle?: boolean;
driverInfo?: DriverInfo | null;
driverFormData?: DriverFormData;
onDriverFormChange?: (field: keyof DriverFormData, value: string) => void;
onDriverStatusChange?: (status: "off" | "maintenance") => void;
onDriverAccountDelete?: () => void;
// 차량 삭제/등록 관련 props
onDeleteVehicle?: () => void;
onOpenVehicleRegisterModal?: () => void;
// 새 차량 등록 모달 관련 props
isVehicleRegisterModalOpen?: boolean;
newVehicleData?: VehicleRegisterData;
onCloseVehicleRegisterModal?: () => void;
onNewVehicleDataChange?: (field: keyof VehicleRegisterData, value: string) => void;
onRegisterVehicle?: () => void;
onClose: () => void;
onFormChange: (field: keyof ProfileFormData, value: string) => void;
onImageSelect: (event: React.ChangeEvent<HTMLInputElement>) => void;
@@ -136,20 +96,6 @@ export function ProfileModal({
isSaving,
departments,
alertModal,
isDriver = false,
hasVehicle = false,
driverInfo,
driverFormData,
onDriverFormChange,
onDriverStatusChange,
onDriverAccountDelete,
onDeleteVehicle,
onOpenVehicleRegisterModal,
isVehicleRegisterModalOpen = false,
newVehicleData,
onCloseVehicleRegisterModal,
onNewVehicleDataChange,
onRegisterVehicle,
onClose,
onFormChange,
onImageSelect,
@@ -197,21 +143,6 @@ export function ProfileModal({
}
}, [isOpen]);
// 차량 상태 한글 변환
const getStatusLabel = (status: string | null) => {
switch (status) {
case "off":
return "대기";
case "active":
return "운행중";
case "inactive":
return "공차";
case "maintenance":
return "정비";
default:
return status || "-";
}
};
return (
<>
<Dialog open={isOpen} onOpenChange={onClose}>
@@ -356,151 +287,6 @@ export function ProfileModal({
</div>
</div>
{/* 운전자 정보 섹션 (공차중계 사용자만) */}
{isDriver && (
<>
<Separator className="my-4" />
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Car className="h-5 w-5 text-primary" />
<h3 className="text-sm font-semibold">/ </h3>
</div>
{/* 차량 유무에 따른 버튼 표시 */}
{hasVehicle ? (
<Button
type="button"
variant="destructive"
size="sm"
onClick={onDeleteVehicle}
className="flex items-center gap-1"
>
<Trash2 className="h-3 w-3" />
</Button>
) : (
<Button
type="button"
variant="default"
size="sm"
onClick={onOpenVehicleRegisterModal}
className="flex items-center gap-1"
>
<Plus className="h-3 w-3" />
</Button>
)}
</div>
{/* 운전자 정보 (항상 수정 가능) */}
{driverFormData && onDriverFormChange && (
<>
{/* 차량 정보 - 차량이 있을 때만 수정 가능 */}
{hasVehicle ? (
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="vehicleNumber"></Label>
<Input
id="vehicleNumber"
value={driverFormData.vehicleNumber}
onChange={(e) => onDriverFormChange("vehicleNumber", e.target.value)}
placeholder="12가1234"
/>
</div>
<div className="space-y-2">
<Label htmlFor="vehicleType"></Label>
<Input
id="vehicleType"
value={driverFormData.vehicleType}
onChange={(e) => onDriverFormChange("vehicleType", e.target.value)}
placeholder="1톤 카고"
/>
</div>
</div>
) : (
/* 차량이 없는 경우: 안내 메시지 */
<div className="text-center py-4 text-muted-foreground border rounded-md bg-muted/30">
<Car className="h-8 w-8 mx-auto mb-2 opacity-30" />
<p className="text-sm"> .</p>
<p className="text-xs mt-1"> .</p>
</div>
)}
{/* 운전자 개인 정보 - 항상 수정 가능 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="driverPhone"></Label>
<Input
id="driverPhone"
value={driverFormData.phoneNumber}
onChange={(e) => onDriverFormChange("phoneNumber", e.target.value)}
placeholder="010-1234-5678"
/>
</div>
<div className="space-y-2">
<Label htmlFor="licenseNumber"></Label>
<Input
id="licenseNumber"
value={driverFormData.licenseNumber}
onChange={(e) => onDriverFormChange("licenseNumber", e.target.value)}
placeholder="12-34-567890-12"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="branchName"> </Label>
<Input
id="branchName"
value={driverFormData.branchName}
onChange={(e) => onDriverFormChange("branchName", e.target.value)}
placeholder="서울 본점"
/>
</div>
{/* 차량 상태 - 차량이 있을 때만 표시 */}
{hasVehicle && driverInfo && onDriverStatusChange && (
<div className="space-y-2">
<Label> </Label>
<div className="flex items-center gap-4">
<span className="text-sm font-medium px-3 py-1 rounded-full bg-muted">
{getStatusLabel(driverInfo.vehicleStatus)}
</span>
<div className="flex gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => onDriverStatusChange("off")}
disabled={driverInfo.vehicleStatus === "off"}
className="flex items-center gap-1"
>
<Clock className="h-3 w-3" />
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => onDriverStatusChange("maintenance")}
disabled={driverInfo.vehicleStatus === "maintenance"}
className="flex items-center gap-1"
>
<Wrench className="h-3 w-3" />
</Button>
</div>
</div>
<p className="text-xs text-muted-foreground">
* /
</p>
</div>
)}
</>
)}
</div>
</>
)}
</div>
<DialogFooter>
@@ -523,49 +309,6 @@ export function ProfileModal({
type={alertModal.type}
/>
{/* 새 차량 등록 모달 */}
{isVehicleRegisterModalOpen && newVehicleData && onNewVehicleDataChange && onRegisterVehicle && onCloseVehicleRegisterModal && (
<Dialog open={isVehicleRegisterModalOpen} onOpenChange={onCloseVehicleRegisterModal}>
<DialogContent className="sm:max-w-[400px]">
<DialogHeader>
<DialogTitle> </DialogTitle>
<DialogDescription>
.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="space-y-2">
<Label htmlFor="newVehicleNumber"> *</Label>
<Input
id="newVehicleNumber"
value={newVehicleData.vehicleNumber}
onChange={(e) => onNewVehicleDataChange("vehicleNumber", e.target.value)}
placeholder="12가1234"
/>
</div>
<div className="space-y-2">
<Label htmlFor="newVehicleType"></Label>
<Input
id="newVehicleType"
value={newVehicleData.vehicleType || ""}
onChange={(e) => onNewVehicleDataChange("vehicleType", e.target.value)}
placeholder="1톤 카고"
/>
</div>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={onCloseVehicleRegisterModal}>
</Button>
<Button type="button" onClick={onRegisterVehicle}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
</>
);
}
+3 -239
View File
@@ -1,20 +1,9 @@
"use client";
import { useState, useCallback, useEffect } from "react";
import { useState, useCallback } from "react";
import { ProfileFormData, ProfileModalState } from "@/types/profile";
import { LAYOUT_CONFIG, MESSAGES } from "@/constants/layout";
import { apiCall } from "@/lib/api/client";
import {
getDriverProfile,
updateDriverProfile,
updateDriverStatus,
deleteDriverAccount,
deleteDriverVehicle,
registerDriverVehicle,
DriverProfile,
VehicleRegisterData,
} from "@/lib/api/driver";
import { DriverInfo, DriverFormData } from "@/components/layout/ProfileModal";
// 알림 모달 상태 타입
interface AlertModalState {
@@ -59,26 +48,6 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
}>
>([]);
// 운전자 정보 상태
const [isDriver, setIsDriver] = useState(false);
const [hasVehicle, setHasVehicle] = useState(false); // 차량 보유 여부
const [driverInfo, setDriverInfo] = useState<DriverInfo | null>(null);
const [driverFormData, setDriverFormData] = useState<DriverFormData>({
vehicleNumber: "",
vehicleType: "",
licenseNumber: "",
phoneNumber: "",
branchName: "",
});
// 새 차량 등록 모달 상태
const [isVehicleRegisterModalOpen, setIsVehicleRegisterModalOpen] = useState(false);
const [newVehicleData, setNewVehicleData] = useState<VehicleRegisterData>({
vehicleNumber: "",
vehicleType: "",
branchName: "",
});
// 알림 모달 표시 함수
const showAlert = useCallback((title: string, message: string, type: "success" | "error" | "info" = "info") => {
setAlertModal({
@@ -106,41 +75,6 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
}
}, []);
// 운전자 정보 로드 함수
const loadDriverInfo = useCallback(async () => {
try {
const response = await getDriverProfile();
if (response.success && response.data) {
setIsDriver(true);
// 차량 보유 여부 확인
const vehicleExists = !!response.data.vehicleNumber;
setHasVehicle(vehicleExists);
setDriverInfo({
vehicleNumber: response.data.vehicleNumber,
vehicleType: response.data.vehicleType,
licenseNumber: response.data.licenseNumber,
phoneNumber: response.data.phoneNumber,
vehicleStatus: response.data.vehicleStatus,
branchName: response.data.branchName,
});
setDriverFormData({
vehicleNumber: response.data.vehicleNumber || "",
vehicleType: response.data.vehicleType || "",
licenseNumber: response.data.licenseNumber || "",
phoneNumber: response.data.phoneNumber || "",
branchName: response.data.branchName || "",
});
} else {
setIsDriver(false);
setHasVehicle(false);
setDriverInfo(null);
}
} catch (error) {
console.error("운전자 정보 로드 실패:", error);
setIsDriver(false);
}
}, []);
/**
* 프로필 모달 열기
*/
@@ -148,8 +82,6 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
if (user) {
// 부서 목록 로드
loadDepartments();
// 운전자 정보 로드
loadDriverInfo();
setModalState((prev) => ({
...prev,
@@ -166,7 +98,7 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
isSaving: false,
}));
}
}, [user, loadDepartments, loadDriverInfo]);
}, [user, loadDepartments]);
/**
* 프로필 모달 닫기
@@ -193,138 +125,6 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
}));
}, []);
/**
* 운전자 폼 데이터 변경
*/
const updateDriverFormData = useCallback((field: keyof DriverFormData, value: string) => {
setDriverFormData((prev) => ({
...prev,
[field]: value,
}));
}, []);
/**
* 차량 상태 변경 (대기/정비)
*/
const handleDriverStatusChange = useCallback(
async (status: "off" | "maintenance") => {
try {
const response = await updateDriverStatus(status);
if (response.success) {
showAlert("상태 변경", response.message || "차량 상태가 변경되었습니다.", "success");
// 운전자 정보 새로고침
await loadDriverInfo();
} else {
showAlert("상태 변경 실패", response.message || "상태 변경에 실패했습니다.", "error");
}
} catch (error) {
console.error("차량 상태 변경 실패:", error);
showAlert("오류", "상태 변경 중 오류가 발생했습니다.", "error");
}
},
[showAlert, loadDriverInfo]
);
/**
* 회원 탈퇴
*/
const handleDriverAccountDelete = useCallback(async () => {
if (!confirm("정말로 탈퇴하시겠습니까?\n차량 정보가 함께 삭제되며, 이 작업은 되돌릴 수 없습니다.")) {
return;
}
try {
const response = await deleteDriverAccount();
if (response.success) {
showAlert("탈퇴 완료", "회원 탈퇴가 완료되었습니다.", "success");
// 로그아웃 처리
window.location.href = "/login";
} else {
showAlert("탈퇴 실패", response.message || "회원 탈퇴에 실패했습니다.", "error");
}
} catch (error) {
console.error("회원 탈퇴 실패:", error);
showAlert("오류", "회원 탈퇴 중 오류가 발생했습니다.", "error");
}
}, [showAlert]);
/**
* 차량 삭제
*/
const handleDeleteVehicle = useCallback(async () => {
if (!confirm("이 차량을 더 이상 사용하지 않습니까?\n차량 정보가 삭제됩니다.")) {
return;
}
try {
const response = await deleteDriverVehicle();
if (response.success) {
showAlert("삭제 완료", "차량이 삭제되었습니다.", "success");
// 운전자 정보 새로고침
await loadDriverInfo();
} else {
showAlert("삭제 실패", response.message || "차량 삭제에 실패했습니다.", "error");
}
} catch (error) {
console.error("차량 삭제 실패:", error);
showAlert("오류", "차량 삭제 중 오류가 발생했습니다.", "error");
}
}, [showAlert, loadDriverInfo]);
/**
* 새 차량 등록 모달 열기
*/
const openVehicleRegisterModal = useCallback(() => {
setNewVehicleData({
vehicleNumber: "",
vehicleType: "",
branchName: driverFormData.branchName || "", // 기존 소속 지점 유지
});
setIsVehicleRegisterModalOpen(true);
}, [driverFormData.branchName]);
/**
* 새 차량 등록 모달 닫기
*/
const closeVehicleRegisterModal = useCallback(() => {
setIsVehicleRegisterModalOpen(false);
}, []);
/**
* 새 차량 데이터 변경
*/
const updateNewVehicleData = useCallback((field: keyof VehicleRegisterData, value: string) => {
setNewVehicleData((prev) => ({
...prev,
[field]: value,
}));
}, []);
/**
* 새 차량 등록 처리
*/
const handleRegisterVehicle = useCallback(async () => {
if (!newVehicleData.vehicleNumber) {
showAlert("입력 오류", "차량번호는 필수입니다.", "error");
return;
}
try {
const response = await registerDriverVehicle(newVehicleData);
if (response.success) {
showAlert("등록 완료", "차량이 등록되었습니다.", "success");
setIsVehicleRegisterModalOpen(false);
// 운전자 정보 새로고침
await loadDriverInfo();
} else {
showAlert("등록 실패", response.message || "차량 등록에 실패했습니다.", "error");
}
} catch (error) {
console.error("차량 등록 실패:", error);
showAlert("오류", "차량 등록 중 오류가 발생했습니다.", "error");
}
}, [newVehicleData, showAlert, loadDriverInfo]);
/**
* 이미지 선택 처리
*/
@@ -429,22 +229,6 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
// API 호출 (JWT 토큰 자동 포함)
const response = await apiCall("PUT", "/admin/profile", updateData);
// 운전자 정보도 저장 (운전자인 경우)
if (isDriver) {
const driverResponse = await updateDriverProfile({
userName: modalState.form_data.user_name,
phoneNumber: driverFormData.phoneNumber,
licenseNumber: driverFormData.licenseNumber,
vehicleNumber: driverFormData.vehicleNumber,
vehicleType: driverFormData.vehicleType,
branchName: driverFormData.branchName,
});
if (!driverResponse.success) {
console.warn("운전자 정보 저장 실패:", driverResponse.message);
}
}
if (response.success || (response as any).result) {
// locale이 변경된 경우 전역 변수와 localStorage 업데이트
const localeChanged = modalState.form_data.locale && modalState.form_data.locale !== user.locale;
@@ -481,7 +265,7 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
} finally {
setModalState((prev) => ({ ...prev, isSaving: false }));
}
}, [user, modalState.selected_file, modalState.selected_image, modalState.form_data, refreshUserData, showAlert, isDriver, driverFormData]);
}, [user, modalState.selected_file, modalState.selected_image, modalState.form_data, refreshUserData, showAlert]);
return {
// 상태
@@ -495,16 +279,6 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
alertModal,
closeAlert,
// 운전자 관련 상태
isDriver,
hasVehicle,
driverInfo,
driverFormData,
// 새 차량 등록 모달 상태
isVehicleRegisterModalOpen,
newVehicleData,
// 액션
openProfileModal,
closeProfileModal,
@@ -512,15 +286,5 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
selectImage,
removeImage,
saveProfile,
// 운전자 관련 액션
updateDriverFormData,
handleDriverStatusChange,
handleDriverAccountDelete,
handleDeleteVehicle,
openVehicleRegisterModal,
closeVehicleRegisterModal,
updateNewVehicleData,
handleRegisterVehicle,
};
};