first commit
This commit is contained in:
133
frontend/src/components/Dashboard.tsx
Normal file
133
frontend/src/components/Dashboard.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import { useState } from 'react';
|
||||
import { format, startOfMonth, endOfMonth, addMonths, subMonths } from 'date-fns';
|
||||
import { ru } from 'date-fns/locale';
|
||||
import { useLogs } from '../hooks/useLogs';
|
||||
import { LogList } from './LogList';
|
||||
import { AddLogModal } from './AddLogModal';
|
||||
import { ReportModal } from './ReportModal';
|
||||
|
||||
interface Props {
|
||||
username: string;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
export function Dashboard({ username, onLogout }: Props) {
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
|
||||
|
||||
const startDate = format(startOfMonth(currentDate), 'yyyy-MM-dd');
|
||||
const endDate = format(endOfMonth(currentDate), 'yyyy-MM-dd');
|
||||
|
||||
const { logs, isLoading, createLog, deleteLog } = useLogs(startDate, endDate);
|
||||
|
||||
const totalMinutes = logs.reduce((sum, log) => sum + log.minutes, 0);
|
||||
const totalHours = Math.floor(totalMinutes / 60);
|
||||
const totalMins = totalMinutes % 60;
|
||||
|
||||
const handlePrevMonth = () => setCurrentDate(subMonths(currentDate, 1));
|
||||
const handleNextMonth = () => setCurrentDate(addMonths(currentDate, 1));
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (window.confirm('Удалить эту запись?')) {
|
||||
await deleteLog(id);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<header className="bg-white shadow-sm">
|
||||
<div className="max-w-4xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold text-gray-800">TimeTracker</h1>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-gray-600 text-sm">{username}</span>
|
||||
<button
|
||||
onClick={onLogout}
|
||||
className="text-gray-500 hover:text-gray-700 text-sm"
|
||||
>
|
||||
Выйти
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-4xl mx-auto px-4 py-6">
|
||||
{/* Month navigation */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<button
|
||||
onClick={handlePrevMonth}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-600" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<h2 className="text-lg font-semibold text-gray-700 capitalize">
|
||||
{format(currentDate, 'LLLL yyyy', { locale: ru })}
|
||||
</h2>
|
||||
<button
|
||||
onClick={handleNextMonth}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-600" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="bg-white rounded-lg shadow-sm border p-4 mb-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Всего за месяц</p>
|
||||
<p className="text-2xl font-bold text-blue-600">
|
||||
{totalHours}:{String(totalMins).padStart(2, '0')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm text-gray-500">Записей</p>
|
||||
<p className="text-2xl font-bold text-gray-700">{logs.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Logs */}
|
||||
<LogList logs={logs} onDelete={handleDelete} isLoading={isLoading} />
|
||||
</main>
|
||||
|
||||
{/* Floating buttons */}
|
||||
<div className="fixed bottom-6 right-6 flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => setIsReportModalOpen(true)}
|
||||
className="w-14 h-14 bg-green-600 text-white rounded-full shadow-lg hover:bg-green-700 transition-colors flex items-center justify-center"
|
||||
title="Создать отчёт"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsAddModalOpen(true)}
|
||||
className="w-14 h-14 bg-blue-600 text-white rounded-full shadow-lg hover:bg-blue-700 transition-colors flex items-center justify-center"
|
||||
title="Добавить запись"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
<AddLogModal
|
||||
isOpen={isAddModalOpen}
|
||||
onClose={() => setIsAddModalOpen(false)}
|
||||
onSave={createLog}
|
||||
/>
|
||||
<ReportModal
|
||||
isOpen={isReportModalOpen}
|
||||
onClose={() => setIsReportModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user