- Add inline time editing in log list (click on time to edit) - Add edit button with pencil icon for each log entry - Modify AddLogModal to support both create and edit modes - Wire up updateLog from useLogs hook in Dashboard Co-Authored-By: Claude <noreply@anthropic.com>
164 lines
6.2 KiB
TypeScript
164 lines
6.2 KiB
TypeScript
import { useState } from 'react';
|
||
import { format, startOfMonth, endOfMonth, addMonths, subMonths } from 'date-fns';
|
||
import { ru } from 'date-fns/locale';
|
||
import { useLogs, type TimeLog } 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 [editingLog, setEditingLog] = useState<TimeLog | null>(null);
|
||
|
||
const startDate = format(startOfMonth(currentDate), 'yyyy-MM-dd');
|
||
const endDate = format(endOfMonth(currentDate), 'yyyy-MM-dd');
|
||
|
||
const { logs, isLoading, createLog, updateLog, 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);
|
||
}
|
||
};
|
||
|
||
const handleEdit = (log: TimeLog) => {
|
||
setEditingLog(log);
|
||
setIsAddModalOpen(true);
|
||
};
|
||
|
||
const handleUpdateTime = async (id: string, time: string) => {
|
||
await updateLog({ id, data: { time } });
|
||
};
|
||
|
||
const handleSave = async (data: { date: string; time: string; description: string }) => {
|
||
if (editingLog) {
|
||
await updateLog({ id: editingLog.id, data });
|
||
} else {
|
||
await createLog(data);
|
||
}
|
||
};
|
||
|
||
const handleCloseModal = () => {
|
||
setIsAddModalOpen(false);
|
||
setEditingLog(null);
|
||
};
|
||
|
||
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}
|
||
onEdit={handleEdit}
|
||
onUpdateTime={handleUpdateTime}
|
||
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={handleCloseModal}
|
||
onSave={handleSave}
|
||
editingLog={editingLog}
|
||
/>
|
||
<ReportModal
|
||
isOpen={isReportModalOpen}
|
||
onClose={() => setIsReportModalOpen(false)}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|