first commit

This commit is contained in:
Vadim Sobinin
2026-02-02 16:14:57 +03:00
commit fc886320e3
48 changed files with 5569 additions and 0 deletions

View 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>
);
}