v1.1.3 Kalendarz: podgląd wydarzenia i wyrównanie dat
All checks were successful
Build and Push Docker Images / build-and-push (push) Successful in 1m31s

Kliknięcie w istniejące wydarzenie pokazuje teraz podgląd (godziny,
lokalizacja, uczestnicy, notatki) z przyciskami Edytuj/Usuń zamiast
od razu otwierać formularz edycji. Wyrównano też pola "Początek" i
"Koniec" w formularzu wydarzenia, które przez niespójną wysokość
etykiet renderowały się na różnych wysokościach.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 15:30:01 +02:00
parent ce631feb5b
commit 75f826bfc9
3 changed files with 108 additions and 2 deletions

View File

@@ -0,0 +1,84 @@
import { useDeleteEvent } from '../api/queries.js';
import { useConfirm } from './ConfirmDialogProvider.jsx';
import { useTranslation } from '../i18n/I18nContext.jsx';
import Icon from './Icon.jsx';
function dateOnly(value) {
return value ? value.slice(0, 10) : '';
}
function timeOnly(value) {
return value && value.length > 10 ? value.slice(11, 16) : null;
}
export default function EventDetailModal({ event, onClose, onEdit }) {
const deleteEvent = useDeleteEvent();
const confirmDialog = useConfirm();
const { t } = useTranslation();
const startTime = timeOnly(event.start_at);
const endTime = timeOnly(event.end_at);
const multiDay = dateOnly(event.start_at) !== dateOnly(event.end_at);
const dayLabel = multiDay ? `${dateOnly(event.start_at)} ${dateOnly(event.end_at)}` : dateOnly(event.start_at);
const timeLabel = event.all_day
? `${t('calendar.allDay')} · ${dayLabel}`
: `${dayLabel} · ${startTime} ${endTime}`;
async function handleDelete() {
const ok = await confirmDialog({
title: t('eventFormModal.deleteConfirmTitle'),
message: t('eventFormModal.deleteConfirmMessage'),
confirmLabel: t('common.delete'),
});
if (!ok) return;
await deleteEvent.mutateAsync(event.id);
onClose();
}
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-sheet" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h3>{event.title}</h3>
<button className="icon-btn" onClick={onClose} aria-label={t('common.close')}>
<Icon name="close" />
</button>
</div>
<div className="event-detail-body">
<div className="event-detail-row">
<Icon name={event.all_day ? 'event' : 'schedule'} />
<span>{timeLabel}</span>
</div>
{event.location && (
<div className="event-detail-row">
<Icon name="location_on" />
<span>{event.location}</span>
</div>
)}
{event.attendees.length > 0 && (
<div className="event-detail-row">
<Icon name="group" />
<span>{event.attendees.map((a) => a.name).join(', ')}</span>
</div>
)}
{event.notes && (
<div className="event-detail-row">
<Icon name="notes" />
<span>{event.notes}</span>
</div>
)}
</div>
<div className="modal-actions">
<button type="button" className="btn-danger" onClick={handleDelete} disabled={deleteEvent.isPending}>
{t('common.delete')}
</button>
<button type="button" className="btn-primary" onClick={onEdit}>
{t('common.edit')}
</button>
</div>
</div>
</div>
);
}

View File

@@ -5,6 +5,7 @@ import { useAuth } from '../auth/AuthContext.jsx';
import { useTranslation } from '../i18n/I18nContext.jsx'; import { useTranslation } from '../i18n/I18nContext.jsx';
import EventListItem from '../components/EventListItem.jsx'; import EventListItem from '../components/EventListItem.jsx';
import EventFormModal from '../components/EventFormModal.jsx'; import EventFormModal from '../components/EventFormModal.jsx';
import EventDetailModal from '../components/EventDetailModal.jsx';
import Icon from '../components/Icon.jsx'; import Icon from '../components/Icon.jsx';
const WEEKDAY_LABELS = { const WEEKDAY_LABELS = {
@@ -48,6 +49,7 @@ export default function Calendar() {
return new Date(now.getFullYear(), now.getMonth(), 1); return new Date(now.getFullYear(), now.getMonth(), 1);
}); });
const [selectedDate, setSelectedDate] = useState(todayStr()); const [selectedDate, setSelectedDate] = useState(todayStr());
const [viewingEvent, setViewingEvent] = useState(null);
const [editingEvent, setEditingEvent] = useState(null); const [editingEvent, setEditingEvent] = useState(null);
const [showCreate, setShowCreate] = useState(false); const [showCreate, setShowCreate] = useState(false);
@@ -139,7 +141,7 @@ export default function Calendar() {
{isLoading && <p>{t('common.loading')}</p>} {isLoading && <p>{t('common.loading')}</p>}
{!isLoading && selectedEvents.length === 0 && <p className="empty-state">{t('calendar.emptyState')}</p>} {!isLoading && selectedEvents.length === 0 && <p className="empty-state">{t('calendar.emptyState')}</p>}
{selectedEvents.map((event) => ( {selectedEvents.map((event) => (
<EventListItem key={event.id} event={event} onClick={() => setEditingEvent(event)} /> <EventListItem key={event.id} event={event} onClick={() => setViewingEvent(event)} />
))} ))}
</div> </div>
@@ -152,13 +154,24 @@ export default function Calendar() {
/> />
)} )}
{viewingEvent && !editingEvent && (
<EventDetailModal
event={viewingEvent}
onClose={() => setViewingEvent(null)}
onEdit={() => setEditingEvent(viewingEvent)}
/>
)}
{editingEvent && ( {editingEvent && (
<EventFormModal <EventFormModal
event={editingEvent} event={editingEvent}
members={members} members={members}
currentUserId={user?.id} currentUserId={user?.id}
defaultDate={selectedDate} defaultDate={selectedDate}
onClose={() => setEditingEvent(null)} onClose={() => {
setEditingEvent(null);
setViewingEvent(null);
}}
/> />
)} )}
</div> </div>

View File

@@ -415,6 +415,11 @@ input, select, button, textarea { font-family: inherit; font-size: 1rem; color:
} }
.split-slider-amounts { display: flex; gap: 10px; } .split-slider-amounts { display: flex; gap: 10px; }
.split-slider-amounts .field { flex: 1; } .split-slider-amounts .field { flex: 1; }
.split-slider-amounts .field label {
height: 1.2em;
line-height: 1.2;
white-space: nowrap;
}
/* History */ /* History */
.filters { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; } .filters { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
@@ -615,6 +620,10 @@ input, select, button, textarea { font-family: inherit; font-size: 1rem; color:
.modal-actions { display: flex; gap: 10px; } .modal-actions { display: flex; gap: 10px; }
.modal-actions button { flex: 1; } .modal-actions button { flex: 1; }
.event-detail-body { display: flex; flex-direction: column; gap: 14px; margin-bottom: 20px; }
.event-detail-row { display: flex; align-items: flex-start; gap: 10px; color: var(--text); }
.event-detail-row .material-symbols-outlined { color: var(--muted); font-size: 1.2rem; flex-shrink: 0; }
.modal-overlay { .modal-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;