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
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:
84
frontend/src/components/EventDetailModal.jsx
Normal file
84
frontend/src/components/EventDetailModal.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user