Versión 1.0
This commit is contained in:
716
src/pages/Sessions.js
Normal file
716
src/pages/Sessions.js
Normal file
@@ -0,0 +1,716 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import sessionService from '../services/sessionService';
|
||||
|
||||
const Sessions = () => {
|
||||
const { isAuthenticated, sessionData, refreshSessions, user, makeSessionActive, updateCurrentSessionEntry } = useAuth();
|
||||
const [editingSession, setEditingSession] = useState(null);
|
||||
const [creatingNewSession, setCreatingNewSession] = useState(false);
|
||||
const [formError, setFormError] = useState('');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [editForm, setEditForm] = useState({
|
||||
date: '',
|
||||
dateDisplay: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
pauses: [],
|
||||
makeActive: false // New field for making session active
|
||||
});
|
||||
const nativeDatePickerRef = useRef(null);
|
||||
const parseDateValue = (value, fallbackDate) => {
|
||||
if (!value) return null;
|
||||
if (value instanceof Date) return value;
|
||||
if (typeof value === 'string') {
|
||||
if (/^\d{2}:\d{2}(:\d{2})?$/.test(value) && fallbackDate) {
|
||||
const [h, m, s = '0'] = value.split(':').map(Number);
|
||||
const d = new Date(fallbackDate);
|
||||
d.setHours(h, m, s, 0);
|
||||
return d;
|
||||
}
|
||||
const d = new Date(value);
|
||||
if (!Number.isNaN(d.getTime())) return d;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const formatDateTime = (value, fallbackDate = null) => {
|
||||
const date = parseDateValue(value, fallbackDate);
|
||||
if (!date) return 'N/A';
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const year = date.getFullYear();
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
const formatDateForInput = (date) => {
|
||||
if (!date) return '';
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
const formatDateForDisplay = (dateValue) => {
|
||||
if (!dateValue) return '';
|
||||
const [year, month, day] = dateValue.split('-');
|
||||
if (!year || !month || !day) return '';
|
||||
return `${day}/${month}/${year}`;
|
||||
};
|
||||
|
||||
const parseDateDisplay = (value) => {
|
||||
if (!value) return '';
|
||||
const match = value.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
|
||||
if (!match) return '';
|
||||
const [, day, month, year] = match;
|
||||
const paddedDay = String(day).padStart(2, '0');
|
||||
const paddedMonth = String(month).padStart(2, '0');
|
||||
const date = new Date(`${year}-${paddedMonth}-${paddedDay}T00:00:00`);
|
||||
if (Number.isNaN(date.getTime())) return '';
|
||||
return `${year}-${paddedMonth}-${paddedDay}`;
|
||||
};
|
||||
|
||||
const normalizeTimeInput = (value) => {
|
||||
if (!value) return '';
|
||||
const trimmed = value.trim().toUpperCase().replace(/\s+/g, '');
|
||||
|
||||
const twentyFourMatch = trimmed.match(/^([01]?\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/);
|
||||
if (twentyFourMatch) {
|
||||
return `${String(Number(twentyFourMatch[1])).padStart(2, '0')}:${twentyFourMatch[2]}`;
|
||||
}
|
||||
|
||||
const twelveHourMatch = trimmed.match(/^(0?[1-9]|1[0-2]):([0-5]\d)(:[0-5]\d)?(AM|PM)$/);
|
||||
if (!twelveHourMatch) return '';
|
||||
let hour = Number(twelveHourMatch[1]);
|
||||
const minute = twelveHourMatch[2];
|
||||
const suffix = twelveHourMatch[4];
|
||||
|
||||
if (suffix === 'AM' && hour === 12) hour = 0;
|
||||
if (suffix === 'PM' && hour !== 12) hour += 12;
|
||||
return `${String(hour).padStart(2, '0')}:${minute}`;
|
||||
};
|
||||
|
||||
const formatDateOnly = (date) => {
|
||||
if (!date) return '';
|
||||
return new Date(date).toLocaleDateString('en-GB', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
const formatTimeForInput = (date) => {
|
||||
if (!date) return '';
|
||||
const d = new Date(date);
|
||||
return d.toTimeString().slice(0, 5); // HH:mm
|
||||
};
|
||||
|
||||
const formatDuration = (milliseconds) => {
|
||||
if (!milliseconds) return '00:00:00';
|
||||
|
||||
const totalSeconds = Math.floor(milliseconds / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const calculateWorkTime = (session) => {
|
||||
// Calculate total pause time
|
||||
let totalPauseTime = 0;
|
||||
if (session.pauses) {
|
||||
session.pauses.forEach(pause => {
|
||||
if (pause.end) {
|
||||
totalPauseTime += new Date(pause.end) - new Date(pause.start);
|
||||
} else if (session.endTime) {
|
||||
// If session has ended but pause hasn't, count pause until session end
|
||||
totalPauseTime += new Date(session.endTime) - new Date(pause.start);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Work time is total duration minus pause time
|
||||
return session.duration - totalPauseTime;
|
||||
};
|
||||
|
||||
const startEditing = (session) => {
|
||||
setFormError('');
|
||||
setEditingSession(session);
|
||||
setEditForm({
|
||||
id: session.id,
|
||||
date: formatDateForInput(session.startTime),
|
||||
dateDisplay: formatDateForDisplay(formatDateForInput(session.startTime)),
|
||||
startTime: formatTimeForInput(session.startTime),
|
||||
endTime: session.endTime ? formatTimeForInput(session.endTime) : '',
|
||||
pauses: (session.pauses || []).map((pause) => ({
|
||||
start: formatTimeForInput(pause.start),
|
||||
end: pause.end ? formatTimeForInput(pause.end) : ''
|
||||
})),
|
||||
makeActive: false
|
||||
});
|
||||
};
|
||||
|
||||
const startEditingCurrent = (session) => {
|
||||
setFormError('');
|
||||
setEditingSession('current');
|
||||
setEditForm({
|
||||
id: 'current',
|
||||
date: formatDateForInput(session.startTime),
|
||||
dateDisplay: formatDateForDisplay(formatDateForInput(session.startTime)),
|
||||
startTime: formatTimeForInput(session.startTime),
|
||||
endTime: session.endTime ? formatTimeForInput(session.endTime) : '',
|
||||
pauses: (session.pauses || []).map((pause) => ({
|
||||
start: formatTimeForInput(pause.start),
|
||||
end: pause.end ? formatTimeForInput(pause.end) : ''
|
||||
})),
|
||||
makeActive: false
|
||||
});
|
||||
};
|
||||
|
||||
const startCreating = () => {
|
||||
setFormError('');
|
||||
setCreatingNewSession(true);
|
||||
setEditingSession('new');
|
||||
// Set default to today with default times
|
||||
const today = formatDateForInput(new Date());
|
||||
|
||||
setEditForm({
|
||||
date: today,
|
||||
dateDisplay: formatDateForDisplay(today),
|
||||
startTime: '09:00',
|
||||
endTime: '17:00',
|
||||
pauses: [],
|
||||
makeActive: false
|
||||
});
|
||||
};
|
||||
|
||||
const cancelEditing = () => {
|
||||
setFormError('');
|
||||
setEditingSession(null);
|
||||
setCreatingNewSession(false);
|
||||
setEditForm({
|
||||
date: '',
|
||||
dateDisplay: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
pauses: [],
|
||||
makeActive: false
|
||||
});
|
||||
};
|
||||
|
||||
const combineDateAndTime = (date, time) => {
|
||||
if (!date || !time) return null;
|
||||
const normalizedTime = normalizeTimeInput(time);
|
||||
if (!normalizedTime) return null;
|
||||
return new Date(`${date}T${normalizedTime}`);
|
||||
};
|
||||
|
||||
const withTimeout = async (promise, timeoutMs, timeoutMessage) => {
|
||||
let timerId;
|
||||
try {
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
timerId = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
|
||||
});
|
||||
return await Promise.race([promise, timeoutPromise]);
|
||||
} finally {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
};
|
||||
|
||||
const getResolvedFormDate = () => {
|
||||
if (editForm.date) return editForm.date;
|
||||
return parseDateDisplay(editForm.dateDisplay);
|
||||
};
|
||||
|
||||
const saveChanges = async () => {
|
||||
if (isSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
setFormError('');
|
||||
if (!user?.id) {
|
||||
setFormError('Your user session is not ready yet. Please reload and try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedDate = getResolvedFormDate();
|
||||
if (!resolvedDate) {
|
||||
setFormError('Please provide a valid date (dd/mm/yyyy).');
|
||||
return;
|
||||
}
|
||||
|
||||
const toActiveSessionShape = (record) => ({
|
||||
id: record.id,
|
||||
startTime: new Date(record.start_time ?? record.startTime),
|
||||
endTime: record.end_time ? new Date(record.end_time) : null,
|
||||
duration: record.duration_ms ?? record.duration ?? 0,
|
||||
userId: record.user_id ?? record.userId ?? user?.id,
|
||||
pauses: record.pauses || []
|
||||
});
|
||||
const normalizePauses = () =>
|
||||
(editForm.pauses || [])
|
||||
.map((pause) => {
|
||||
if (!pause?.start) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const start = combineDateAndTime(resolvedDate, pause.start);
|
||||
if (!start) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const end = pause.end ? combineDateAndTime(resolvedDate, pause.end) : null;
|
||||
return {
|
||||
start: start.toISOString(),
|
||||
end: end ? end.toISOString() : null
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
const normalizePausesForCurrent = () =>
|
||||
(editForm.pauses || [])
|
||||
.map((pause) => {
|
||||
if (!pause?.start) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const start = combineDateAndTime(resolvedDate, pause.start);
|
||||
if (!start) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const end = pause.end ? combineDateAndTime(resolvedDate, pause.end) : null;
|
||||
return {
|
||||
start,
|
||||
end
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (creatingNewSession) {
|
||||
// Create new session with date + time combination
|
||||
const startDateTime = combineDateAndTime(resolvedDate, editForm.startTime);
|
||||
const endDateTime = editForm.makeActive ? null : combineDateAndTime(resolvedDate, editForm.endTime);
|
||||
|
||||
if (!startDateTime || (!editForm.makeActive && !endDateTime)) {
|
||||
setFormError('Please provide valid start/end times in HH:mm format.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editForm.makeActive && endDateTime <= startDateTime) {
|
||||
setFormError('End time must be later than start time.');
|
||||
return;
|
||||
}
|
||||
|
||||
const newSessionData = {
|
||||
userId: user.id,
|
||||
startTime: startDateTime.toISOString(),
|
||||
endTime: endDateTime ? endDateTime.toISOString() : null,
|
||||
pauses: normalizePauses()
|
||||
};
|
||||
|
||||
const result = await withTimeout(
|
||||
sessionService.createSession(newSessionData),
|
||||
15000,
|
||||
'Request timed out while creating session. Check DB/network and try again.'
|
||||
);
|
||||
if (result.success) {
|
||||
await withTimeout(
|
||||
refreshSessions(),
|
||||
15000,
|
||||
'Request timed out while refreshing sessions after create.'
|
||||
);
|
||||
if (editForm.makeActive && result.data) {
|
||||
makeSessionActive(toActiveSessionShape(result.data));
|
||||
}
|
||||
}
|
||||
} else if (editingSession === 'current') {
|
||||
const startDateTime = combineDateAndTime(resolvedDate, editForm.startTime);
|
||||
if (!startDateTime) {
|
||||
setFormError('Please provide a valid start time in HH:mm format.');
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedPausesForDb = normalizePauses();
|
||||
updateCurrentSessionEntry({
|
||||
startTime: startDateTime,
|
||||
endTime: null,
|
||||
pauses: normalizePausesForCurrent()
|
||||
});
|
||||
|
||||
if (sessionData.currentTimeEntry?.id && sessionData.currentTimeEntry.id !== 'current') {
|
||||
await withTimeout(
|
||||
sessionService.updateSession(sessionData.currentTimeEntry.id, {
|
||||
start_time: startDateTime.toISOString(),
|
||||
end_time: null,
|
||||
pauses: normalizedPausesForDb
|
||||
}),
|
||||
15000,
|
||||
'Request timed out while updating current session.'
|
||||
);
|
||||
await withTimeout(
|
||||
refreshSessions(),
|
||||
15000,
|
||||
'Request timed out while refreshing sessions after current-session update.'
|
||||
);
|
||||
}
|
||||
|
||||
cancelEditing();
|
||||
return;
|
||||
} else if (editingSession && editingSession.id) {
|
||||
// Update existing session
|
||||
const startDateTime = combineDateAndTime(resolvedDate, editForm.startTime);
|
||||
const endDateTime = editForm.makeActive ? null : combineDateAndTime(resolvedDate, editForm.endTime);
|
||||
|
||||
if (startDateTime && endDateTime && endDateTime <= startDateTime) {
|
||||
setFormError('End time must be later than start time.');
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
start_time: startDateTime ? startDateTime.toISOString() : undefined,
|
||||
end_time: endDateTime ? endDateTime.toISOString() : null,
|
||||
pauses: normalizePauses()
|
||||
};
|
||||
|
||||
const result = await withTimeout(
|
||||
sessionService.updateSession(editForm.id, updateData),
|
||||
15000,
|
||||
'Request timed out while updating session.'
|
||||
);
|
||||
if (result.success) {
|
||||
await withTimeout(
|
||||
refreshSessions(),
|
||||
15000,
|
||||
'Request timed out while refreshing sessions after update.'
|
||||
);
|
||||
if (editForm.makeActive && result.data) {
|
||||
makeSessionActive(toActiveSessionShape(result.data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel editing after save
|
||||
cancelEditing();
|
||||
} catch (error) {
|
||||
console.error('Error saving session:', error);
|
||||
setFormError(error?.message ? `Could not save session: ${error.message}` : 'Could not save session. Please verify date/time values and try again.');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSession = async () => {
|
||||
if (!editingSession || editingSession === 'current' || creatingNewSession || !editForm.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldDelete = window.confirm('Are you sure you want to delete this session?');
|
||||
if (!shouldDelete) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await sessionService.deleteSession(editForm.id);
|
||||
await refreshSessions();
|
||||
cancelEditing();
|
||||
} catch (error) {
|
||||
console.error('Error deleting session:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const addPause = () => {
|
||||
setEditForm(prev => ({
|
||||
...prev,
|
||||
pauses: [...prev.pauses, { start: '', end: '' }]
|
||||
}));
|
||||
};
|
||||
|
||||
const updatePause = (index, field, value) => {
|
||||
setEditForm(prev => {
|
||||
const newPauses = [...prev.pauses];
|
||||
newPauses[index] = { ...newPauses[index], [field]: value };
|
||||
return { ...prev, pauses: newPauses };
|
||||
});
|
||||
};
|
||||
|
||||
const removePause = (index) => {
|
||||
setEditForm(prev => ({
|
||||
...prev,
|
||||
pauses: prev.pauses.filter((_, i) => i !== index)
|
||||
}));
|
||||
};
|
||||
|
||||
// Check if end time is in the future
|
||||
const isEndTimeInFuture = () => {
|
||||
if (!editForm.date || !editForm.endTime) return false;
|
||||
const endDateTime = combineDateAndTime(editForm.date, editForm.endTime);
|
||||
return endDateTime && endDateTime > new Date();
|
||||
};
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sessions-container">
|
||||
<header className="sessions-header">
|
||||
<div>
|
||||
<p className="eyebrow">History</p>
|
||||
<h1>Session History</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{(editingSession || creatingNewSession) && (
|
||||
<div className="edit-session-form">
|
||||
<h2>{creatingNewSession ? 'Create New Session' : editingSession === 'current' ? 'Edit Current Session' : 'Edit Session'}</h2>
|
||||
{formError && <div className="error-message">{formError}</div>}
|
||||
|
||||
<div className="form-group">
|
||||
<label>Date:</label>
|
||||
<div className="date-input-row">
|
||||
<input
|
||||
type="text"
|
||||
value={editForm.dateDisplay}
|
||||
placeholder="dd/mm/yyyy"
|
||||
onChange={(e) => {
|
||||
const nextDisplay = e.target.value;
|
||||
const parsedDate = parseDateDisplay(nextDisplay);
|
||||
setEditForm({
|
||||
...editForm,
|
||||
dateDisplay: nextDisplay,
|
||||
date: nextDisplay === '' ? '' : parsedDate || editForm.date
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="date-picker-button"
|
||||
aria-label="Open calendar"
|
||||
title="Open calendar"
|
||||
onClick={() => {
|
||||
const picker = nativeDatePickerRef.current;
|
||||
if (!picker) return;
|
||||
if (picker.showPicker) {
|
||||
picker.showPicker();
|
||||
} else {
|
||||
picker.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
📅
|
||||
</button>
|
||||
<input
|
||||
ref={nativeDatePickerRef}
|
||||
type="date"
|
||||
lang="en-GB"
|
||||
className="native-date-picker-hidden"
|
||||
value={editForm.date}
|
||||
onChange={(e) => {
|
||||
const nextDate = e.target.value;
|
||||
setEditForm({
|
||||
...editForm,
|
||||
date: nextDate,
|
||||
dateDisplay: formatDateForDisplay(nextDate)
|
||||
});
|
||||
}}
|
||||
aria-label="Select date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Start Time:</label>
|
||||
<input
|
||||
type="time"
|
||||
value={editForm.startTime}
|
||||
step="60"
|
||||
onChange={(e) => setEditForm({...editForm, startTime: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!editForm.makeActive && (
|
||||
<div className="form-group">
|
||||
<label>End Time:</label>
|
||||
<input
|
||||
type="time"
|
||||
value={editForm.endTime}
|
||||
step="60"
|
||||
onChange={(e) => setEditForm({...editForm, endTime: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show "Make Active" checkbox if end time is in the future */}
|
||||
{(isEndTimeInFuture() || creatingNewSession) && (
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.makeActive}
|
||||
onChange={(e) => setEditForm({...editForm, makeActive: e.target.checked})}
|
||||
/>
|
||||
Make this session active now
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="pauses-section">
|
||||
<h3>Pauses</h3>
|
||||
{editForm.pauses.map((pause, index) => (
|
||||
<div key={index} className="pause-edit-row">
|
||||
<input
|
||||
type="time"
|
||||
value={pause.start}
|
||||
step="60"
|
||||
onChange={(e) => updatePause(index, 'start', e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="time"
|
||||
value={pause.end}
|
||||
step="60"
|
||||
onChange={(e) => updatePause(index, 'end', e.target.value)}
|
||||
/>
|
||||
<button
|
||||
onClick={() => removePause(index)}
|
||||
className="remove-pause-btn"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={addPause} className="add-pause-btn">
|
||||
Add Pause
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="form-actions">
|
||||
<button onClick={saveChanges} className="save-button" disabled={isSaving}>
|
||||
{isSaving ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
{editingSession && editingSession !== 'current' && !creatingNewSession && (
|
||||
<button onClick={deleteSession} className="delete-button" disabled={isSaving}>
|
||||
Delete Session
|
||||
</button>
|
||||
)}
|
||||
<button onClick={cancelEditing} className="cancel-button" disabled={isSaving}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="current-session">
|
||||
<h2>Current Session</h2>
|
||||
{sessionData.currentTimeEntry ? (
|
||||
<div className="session-item current">
|
||||
<div className="session-info">
|
||||
<div><strong>Start Time:</strong> {formatDateTime(sessionData.currentTimeEntry.startTime)}</div>
|
||||
<div><strong>Status:</strong> <span className="active-status">Active</span></div>
|
||||
{sessionData.currentTimeEntry.pauses && sessionData.currentTimeEntry.pauses.length > 0 && (
|
||||
<div>
|
||||
<strong>Pauses:</strong>
|
||||
{sessionData.currentTimeEntry.pauses.map((pause, index) => (
|
||||
<div key={index} className="pause-history-item">
|
||||
{formatDateTime(pause.start, sessionData.currentTimeEntry.startTime)} -{' '}
|
||||
{pause.end
|
||||
? formatDateTime(pause.end, sessionData.currentTimeEntry.startTime)
|
||||
: 'Currently paused'}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => startEditingCurrent(sessionData.currentTimeEntry)}
|
||||
className="edit-session-button"
|
||||
>
|
||||
Edit Current Session
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="empty-text">No active session</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="past-sessions">
|
||||
<div className="past-sessions-header">
|
||||
<h2>Past Sessions</h2>
|
||||
<button onClick={startCreating} className="nav-button">
|
||||
Create New Session
|
||||
</button>
|
||||
</div>
|
||||
{sessionData.sessions.filter((session) => session.endTime).length > 0 ? (
|
||||
<div className="sessions-grid">
|
||||
{[...sessionData.sessions]
|
||||
.filter((session) => session.endTime)
|
||||
.reverse()
|
||||
.map((session) => (
|
||||
<div key={session.id} className="session-item past">
|
||||
{editingSession === session ? (
|
||||
// Editing view would go here
|
||||
<div>Editing...</div>
|
||||
) : (
|
||||
// Display view
|
||||
<div className="session-info">
|
||||
<div className="session-card-header">
|
||||
<span className="session-date">{formatDateOnly(session.startTime)}</span>
|
||||
<span className="session-tag">Completed</span>
|
||||
</div>
|
||||
|
||||
<div className="session-row">
|
||||
<strong>Start Time:</strong> {formatDateTime(session.startTime)}
|
||||
</div>
|
||||
<div className="session-row">
|
||||
<strong>End Time:</strong> {formatDateTime(session.endTime)}
|
||||
</div>
|
||||
|
||||
<div className="session-stats-grid">
|
||||
<div className="session-stat">
|
||||
<span className="session-stat-label">Total Duration</span>
|
||||
<span className="session-stat-value">{formatDuration(session.duration)}</span>
|
||||
</div>
|
||||
<div className="session-stat">
|
||||
<span className="session-stat-label">Pause Time</span>
|
||||
<span className="session-stat-value">
|
||||
{formatDuration(
|
||||
session.pauses?.reduce((total, pause) => {
|
||||
const start = parseDateValue(pause.start, session.startTime);
|
||||
const end = parseDateValue(pause.end, session.startTime);
|
||||
if (!start || !end) return total;
|
||||
const pauseDuration = end - start;
|
||||
return total + (Number.isFinite(pauseDuration) ? Math.max(pauseDuration, 0) : 0);
|
||||
}, 0)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="session-stat">
|
||||
<span className="session-stat-label">Work Time</span>
|
||||
<span className="session-stat-value">{formatDuration(calculateWorkTime(session))}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => startEditing(session)}
|
||||
className="edit-session-button"
|
||||
>
|
||||
Edit Session
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="empty-text">No past sessions found</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sessions;
|
||||
Reference in New Issue
Block a user