feat(WEB): 차량 관리 기능 추가 및 CEO 대시보드 Enhanced 섹션 적용

차량 관리 (신규):
- VehicleList/VehicleDetail: 차량 목록/상세
- ForkliftList/ForkliftDetail: 지게차 목록/상세
- VehicleLogList/VehicleLogDetail: 운행일지 목록/상세
- 관련 페이지 라우트 추가 (/vehicle-management/*)

CEO 대시보드:
- Enhanced 섹션 컴포넌트 적용 (아이콘 + 컬러 테마)
- EnhancedStatusBoardSection, EnhancedDailyReportSection, EnhancedMonthlyExpenseSection
- TodayIssueSection 개선

IntegratedDetailTemplate:
- FieldInput, FieldRenderer 기능 확장

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-28 14:53:20 +09:00
parent 805063c686
commit e5f0f5da61
43 changed files with 5165 additions and 135 deletions

View File

@@ -0,0 +1,52 @@
'use client';
/**
* 지게차 수정 페이지
*/
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { ForkliftDetail } from '@/components/vehicle-management/ForkliftDetail';
import { getForkliftById } from '@/components/vehicle-management/ForkliftList/actions';
import type { Forklift } from '@/components/vehicle-management/types';
export default function ForkliftEditPage() {
const params = useParams();
const id = params.id as string;
const [data, setData] = useState<Forklift | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) return;
getForkliftById(id)
.then((result) => {
if (result.success && result.data) {
setData(result.data);
} else {
setError(result.error || '데이터를 불러올 수 없습니다.');
}
})
.finally(() => setIsLoading(false));
}, [id]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
if (error || !data) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-red-500">{error || '지게차를 찾을 수 없습니다.'}</div>
</div>
);
}
return <ForkliftDetail mode="edit" initialData={data} id={id} />;
}

View File

@@ -0,0 +1,52 @@
'use client';
/**
* 지게차 상세 페이지
*/
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { ForkliftDetail } from '@/components/vehicle-management/ForkliftDetail';
import { getForkliftById } from '@/components/vehicle-management/ForkliftList/actions';
import type { Forklift } from '@/components/vehicle-management/types';
export default function ForkliftDetailPage() {
const params = useParams();
const id = params.id as string;
const [data, setData] = useState<Forklift | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) return;
getForkliftById(id)
.then((result) => {
if (result.success && result.data) {
setData(result.data);
} else {
setError(result.error || '데이터를 불러올 수 없습니다.');
}
})
.finally(() => setIsLoading(false));
}, [id]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
if (error || !data) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-red-500">{error || '지게차를 찾을 수 없습니다.'}</div>
</div>
);
}
return <ForkliftDetail mode="view" initialData={data} id={id} />;
}

View File

@@ -0,0 +1,11 @@
'use client';
/**
* 지게차 등록 페이지
*/
import { ForkliftDetail } from '@/components/vehicle-management/ForkliftDetail';
export default function ForkliftNewPage() {
return <ForkliftDetail mode="create" />;
}

View File

@@ -0,0 +1,35 @@
'use client';
/**
* 지게차 관리 리스트 페이지
*/
import { useEffect, useState } from 'react';
import { ForkliftList } from '@/components/vehicle-management/ForkliftList';
import { getForklifts } from '@/components/vehicle-management/ForkliftList/actions';
import type { Forklift } from '@/components/vehicle-management/types';
export default function ForkliftPage() {
const [data, setData] = useState<Forklift[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
getForklifts()
.then((result) => {
if (result.success) {
setData(result.data);
}
})
.finally(() => setIsLoading(false));
}, []);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
return <ForkliftList initialData={data} />;
}

View File

@@ -0,0 +1,52 @@
'use client';
/**
* 차량일지 수정 페이지
*/
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { VehicleLogDetail } from '@/components/vehicle-management/VehicleLogDetail';
import { getVehicleLogById } from '@/components/vehicle-management/VehicleLogList/actions';
import type { VehicleLog } from '@/components/vehicle-management/types';
export default function VehicleLogEditPage() {
const params = useParams();
const id = params.id as string;
const [data, setData] = useState<VehicleLog | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) return;
getVehicleLogById(id)
.then((result) => {
if (result.success && result.data) {
setData(result.data);
} else {
setError(result.error || '데이터를 불러올 수 없습니다.');
}
})
.finally(() => setIsLoading(false));
}, [id]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
if (error || !data) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-red-500">{error || '차량일지를 찾을 수 없습니다.'}</div>
</div>
);
}
return <VehicleLogDetail mode="edit" initialData={data} id={id} />;
}

View File

@@ -0,0 +1,52 @@
'use client';
/**
* 차량일지 상세 페이지
*/
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { VehicleLogDetail } from '@/components/vehicle-management/VehicleLogDetail';
import { getVehicleLogById } from '@/components/vehicle-management/VehicleLogList/actions';
import type { VehicleLog } from '@/components/vehicle-management/types';
export default function VehicleLogDetailPage() {
const params = useParams();
const id = params.id as string;
const [data, setData] = useState<VehicleLog | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) return;
getVehicleLogById(id)
.then((result) => {
if (result.success && result.data) {
setData(result.data);
} else {
setError(result.error || '데이터를 불러올 수 없습니다.');
}
})
.finally(() => setIsLoading(false));
}, [id]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
if (error || !data) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-red-500">{error || '차량일지를 찾을 수 없습니다.'}</div>
</div>
);
}
return <VehicleLogDetail mode="view" initialData={data} id={id} />;
}

View File

@@ -0,0 +1,11 @@
'use client';
/**
* 차량일지 등록 페이지
*/
import { VehicleLogDetail } from '@/components/vehicle-management/VehicleLogDetail';
export default function VehicleLogNewPage() {
return <VehicleLogDetail mode="create" />;
}

View File

@@ -0,0 +1,35 @@
'use client';
/**
* 차량일지/월간사진기록 리스트 페이지
*/
import { useEffect, useState } from 'react';
import { VehicleLogList } from '@/components/vehicle-management/VehicleLogList';
import { getVehicleLogs } from '@/components/vehicle-management/VehicleLogList/actions';
import type { VehicleLog } from '@/components/vehicle-management/types';
export default function VehicleLogPage() {
const [data, setData] = useState<VehicleLog[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
getVehicleLogs()
.then((result) => {
if (result.success) {
setData(result.data);
}
})
.finally(() => setIsLoading(false));
}, []);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
return <VehicleLogList initialData={data} />;
}

View File

@@ -0,0 +1,52 @@
'use client';
/**
* 차량 수정 페이지
*/
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { VehicleDetail } from '@/components/vehicle-management/VehicleDetail';
import { getVehicleById } from '@/components/vehicle-management/VehicleList/actions';
import type { Vehicle } from '@/components/vehicle-management/types';
export default function VehicleEditPage() {
const params = useParams();
const id = params.id as string;
const [data, setData] = useState<Vehicle | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) return;
getVehicleById(id)
.then((result) => {
if (result.success && result.data) {
setData(result.data);
} else {
setError(result.error || '데이터를 불러올 수 없습니다.');
}
})
.finally(() => setIsLoading(false));
}, [id]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
if (error || !data) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-red-500">{error || '차량을 찾을 수 없습니다.'}</div>
</div>
);
}
return <VehicleDetail mode="edit" initialData={data} id={id} />;
}

View File

@@ -0,0 +1,52 @@
'use client';
/**
* 차량 상세 페이지
*/
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { VehicleDetail } from '@/components/vehicle-management/VehicleDetail';
import { getVehicleById } from '@/components/vehicle-management/VehicleList/actions';
import type { Vehicle } from '@/components/vehicle-management/types';
export default function VehicleDetailPage() {
const params = useParams();
const id = params.id as string;
const [data, setData] = useState<Vehicle | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) return;
getVehicleById(id)
.then((result) => {
if (result.success && result.data) {
setData(result.data);
} else {
setError(result.error || '데이터를 불러올 수 없습니다.');
}
})
.finally(() => setIsLoading(false));
}, [id]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
if (error || !data) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-red-500">{error || '차량을 찾을 수 없습니다.'}</div>
</div>
);
}
return <VehicleDetail mode="view" initialData={data} id={id} />;
}

View File

@@ -0,0 +1,11 @@
'use client';
/**
* 차량 등록 페이지
*/
import { VehicleDetail } from '@/components/vehicle-management/VehicleDetail';
export default function VehicleNewPage() {
return <VehicleDetail mode="create" />;
}

View File

@@ -0,0 +1,35 @@
'use client';
/**
* 차량 관리 리스트 페이지
*/
import { useEffect, useState } from 'react';
import { VehicleList } from '@/components/vehicle-management/VehicleList';
import { getVehicles } from '@/components/vehicle-management/VehicleList/actions';
import type { Vehicle } from '@/components/vehicle-management/types';
export default function VehiclePage() {
const [data, setData] = useState<Vehicle[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
getVehicles()
.then((result) => {
if (result.success) {
setData(result.data);
}
})
.finally(() => setIsLoading(false));
}, []);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-muted-foreground"> ...</div>
</div>
);
}
return <VehicleList initialData={data} />;
}