Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 3 additions & 46 deletions src/app/companies/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,36 +36,13 @@ export default function CompaniesPage() {
router.push('/employees/add');
};

const handleExportData = () => {
// 데이터를 CSV 형식으로 변환
if (!company) return;

const companyData = [
['회사명', '주소', '연락처', '이메일', '웹사이트', '설명'],
[company.name, company.address, company.phone, company.email, company.website, company.description]
];

// CSV 문자열 생성
const csvContent = companyData.map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',')).join('\n');

// Blob 생성 및 다운로드
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', `${company.name}_정보.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};

return (
<div className={`p-6 md:p-8 ${currentTheme.background} min-h-screen`}>
<div className="max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-8">
<PageHeader
title="업체 정보"
description="위카 주식회사 정보와 직원 목록을 관리합니다"
description="회사 정보와 직원 목록을 관리합니다"
/>

<div className="hidden md:flex space-x-2">
Expand All @@ -78,20 +55,10 @@ export default function CompaniesPage() {
</svg>
새 직원 추가
</button>

<button
onClick={handleExportData}
className={`px-4 py-2 rounded-lg border ${currentTheme.border} ${currentTheme.cardBg} text-sm font-medium flex items-center hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors`}
>
<svg className="w-4 h-4 mr-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
내보내기
</button>
</div>
</div>

<div className="mb-10">
<div>
{isLoading ? (
<div className={`mb-8 p-8 rounded-2xl ${currentTheme.cardBg} shadow-lg border ${currentTheme.border} flex justify-center`}>
<span className={`${currentTheme.text}`}>회사 정보를 불러오는 중...</span>
Expand All @@ -116,7 +83,7 @@ export default function CompaniesPage() {
)}
</div>

<div className="md:flex md:justify-between md:items-center mb-6">
<div className="mt-8">
<h2 className={`text-2xl font-bold ${currentTheme.text}`}>인력 관리</h2>

<div className="mt-4 md:mt-0 flex md:hidden space-x-2">
Expand All @@ -129,16 +96,6 @@ export default function CompaniesPage() {
</svg>
추가
</button>

<button
onClick={handleExportData}
className={`flex-grow px-4 py-2 rounded-lg border ${currentTheme.border} ${currentTheme.cardBg} text-sm font-medium flex items-center justify-center`}
>
<svg className="w-4 h-4 mr-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
내보내기
</button>
</div>
</div>

Expand Down
6 changes: 6 additions & 0 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ export default function DashboardPage() {
},
ticks: {
color: currentTheme.mode === 'dark' ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.7)',
callback: (value) => `${Number(value).toLocaleString()} km`
},
},
x: {
Expand All @@ -474,6 +475,11 @@ export default function DashboardPage() {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: (context) => `${Number(context.raw).toLocaleString()} km`
}
}
},
}}
/>
Expand Down
42 changes: 0 additions & 42 deletions src/app/logs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { VehicleLogList } from "@/components/logs/VehicleLogList";
import { VehicleLogFilter } from "@/components/logs/VehicleLogFilter";
import { VehicleLogFilter as FilterType, VehicleLog, DriveType } from "@/types/logs";
import { useTheme } from "@/contexts/ThemeContext";
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import { downloadExcel } from "@/lib/utils";
import VehicleLogDetailSlidePanel from "@/components/logs/VehicleLogDetailSlidePanel";
import { useCarLogsStore } from "@/lib/carLogsStore";

Expand Down Expand Up @@ -59,10 +57,6 @@ export default function LogsPage() {
}, 300);
};

const handleExportExcel = (logs: VehicleLog[]) => {
downloadExcel(logs, 'vehicle-logs');
};

const handleDeleteLog = (id: string) => {
console.log(`삭제할 운행 기록 ID: ${id}`);

Expand All @@ -88,41 +82,6 @@ export default function LogsPage() {
title="운행일지"
description="차량 운행 기록을 관리하고 조회할 수 있습니다."
/>
<button
onClick={() => {
// VehicleLogList 컴포넌트에서 filteredLogs를 가져올 수 없으므로
// carLogs를 VehicleLog 형식으로 변환해서 전달
const mappedLogs = carLogs.map(log => {
// API 응답의 driveType을 변환
let driveType: DriveType = 'UNCLASSIFIED';
if (log.driveType === 'COMMUTE' || log.driveType === 'BUSINESS' || log.driveType === 'PERSONAL') {
driveType = log.driveType as DriveType;
}

return {
id: log.logId.toString(),
vehicleNumber: log.mdn || '',
startTime: log.onTime || '',
endTime: log.offTime || '',
startMileage: log.onMileage || 0,
endMileage: log.offMileage || 0,
totalDistance: log.totalMileage !== null && log.totalMileage !== undefined
? log.totalMileage
: (log.offMileage || 0) - (log.onMileage || 0),
driveType: driveType,
driver: log.driver ? { id: '1', name: log.driver } : null,
note: log.description || null,
createdAt: log.onTime || '',
updatedAt: log.offTime || ''
};
});
handleExportExcel(mappedLogs);
}}
className={`flex items-center px-3 py-1.5 rounded-lg text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-all duration-200 shadow-sm`}
>
<ArrowDownTrayIcon className="h-4 w-4 mr-1.5" />
<span>Excel 내보내기</span>
</button>
</div>

<div className="mt-4">
Expand All @@ -136,7 +95,6 @@ export default function LogsPage() {
<div className="mt-4">
<VehicleLogList
filter={currentFilter as FilterType}
onExport={handleExportExcel}
onLogSelect={handleLogSelect}
isSlideOpen={isSlidePanelOpen}
onCloseSlide={handleCloseSlidePanel}
Expand Down
2 changes: 2 additions & 0 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
MapIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChartBarIcon,
} from "@heroicons/react/24/outline";
import { useTheme, themes } from "@/contexts/ThemeContext";
import { useAuthStore } from "@/lib/authStore";
Expand All @@ -25,6 +26,7 @@ const navigation = [
{ name: "차량", href: "/vehicles", icon: TruckIcon },
{ name: "운행일지", href: "/logs", icon: ClipboardDocumentListIcon },
{ name: "실시간 관제", href: "/monitoring", icon: MapIcon },
{ name: "분석", href: "/statistics", icon: ChartBarIcon },
{ name: "회사", href: "/companies", icon: BuildingOfficeIcon },
];

Expand Down
6 changes: 2 additions & 4 deletions src/components/logs/VehicleLogList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ import { useCarLogsStore } from '@/lib/carLogsStore';
interface VehicleLogListProps {
filter: VehicleLogFilter;
searchTerm?: string;
onExport: (logs: VehicleLog[]) => void;
onLogSelect?: (log: VehicleLog) => void;
onLogSelect: (log: VehicleLog) => void;
isSlideOpen?: boolean;
onCloseSlide?: () => void;
selectedLog?: VehicleLog | null;
selectedLog: VehicleLog | null;
isLoading?: boolean;
}

export function VehicleLogList({
filter,
searchTerm = '',
onExport,
onLogSelect,
isSlideOpen = false,
onCloseSlide,
Expand Down
32 changes: 12 additions & 20 deletions src/lib/carLogsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,37 +97,29 @@ export const useCarLogsStore = create<CarLogsState>((set, get) => ({
const endDate = params?.endDate !== undefined ? params.endDate : currentFilter.endDate;
const driveType = params?.driveType !== undefined ? params.driveType : currentFilter.driveType;

const requestBody: Record<string, any> = {};
// 쿼리 파라미터 구성
const queryParams = new URLSearchParams();
queryParams.append('page', page.toString());
queryParams.append('size', size.toString());

if (vehicleNumber) {
requestBody.mdn = vehicleNumber;
queryParams.append('mdn', vehicleNumber);
}

if (startDate) {
const formattedStartDate = new Date(startDate);
formattedStartDate.setHours(0, 0, 0, 0);

// 한국 시간대로 변환 (UTC+9)
const koreaTimeString = formatToKoreaTime(formattedStartDate, true);
requestBody.startTime = koreaTimeString;
queryParams.append('from', startDate);
}

if (endDate) {
const formattedEndDate = new Date(endDate);
formattedEndDate.setHours(23, 59, 59, 999);

// 한국 시간대로 변환 (UTC+9)
const koreaTimeString = formatToKoreaTime(formattedEndDate, false);
requestBody.endTime = koreaTimeString;
queryParams.append('to', endDate);
}

if (driveType) {
requestBody.driveType = driveType;
queryParams.append('driveType', driveType);
}

const data = await fetchApi<{data: any, message: string, statusCode: number}>(`/api/carLogs?page=${page}&size=${size}`, undefined, {
method: 'POST',
body: JSON.stringify(requestBody)
const data = await fetchApi<{data: any, message: string, statusCode: number}>(`/api/carLogs?${queryParams.toString()}`, undefined, {
method: 'GET'
});

// 새로운 API 응답 형식 처리 (data 필드에 실제 데이터가 있음)
Expand Down Expand Up @@ -180,14 +172,14 @@ export const useCarLogsStore = create<CarLogsState>((set, get) => ({
method: 'GET'
});

// 새로운 API 응답 형식 처리 (data 필드에 실제 데이터가 있음)
// 새로운 API 응답 형식 처리
const data = response.data || response;

set({
stats: {
totalMileage: data.totalMileage || 0,
carLogsCount: data.carLogsCount || "0",
monthlyMileages: data.monthlyMileages || []
monthlyMileages: Array.isArray(data) ? data : []
},
isLoading: false
});
Expand Down