561 lines
32 KiB
PHP
561 lines
32 KiB
PHP
<?php
|
||
$first = $cursor->modify('first day of this month');
|
||
$offset = (int)$first->format('N') - 1;
|
||
$days = (int)$cursor->format('t');
|
||
$trailing = (7 - (($offset + $days) % 7)) % 7;
|
||
$weekStart = $weekCursor->modify('monday this week');
|
||
$weekEnd = $weekStart->modify('+6 days');
|
||
$byDay = [];
|
||
foreach ($items as $item) {
|
||
$byDay[substr($item['instance_starts_at'], 0, 10)][] = $item;
|
||
}$edit = $editItem ?? [];
|
||
$editStart = !empty($edit['starts_at']) ? new DateTimeImmutable($edit['starts_at']) : null;
|
||
$colors = ['appointment' => 'blue','veterinary' => 'red','transport' => 'orange','pre_adoption' => 'purple','task' => 'green','other' => 'secondary','vaccine' => 'cyan','deworming' => 'yellow','treatment_start' => 'teal','treatment_end' => 'pink'];
|
||
$sourceTabs = ['vaccine' => 'tab-vac','deworming' => 'tab-med','treatment_start' => 'tab-trt','treatment_end' => 'tab-trt'];
|
||
$legend = ['organization' => ['appointment' => '#206bc4','veterinary' => '#d63939','transport' => '#f76707','pre_adoption' => '#ae3ec9','task' => '#2fb344'],'medical' => ['vaccine' => '#17a2b8','deworming' => '#f59f00','treatment_start' => '#0ca678','treatment_end' => '#d6336c']];
|
||
?>
|
||
<style>
|
||
.agenda-calendar {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, minmax(120px, 1fr));
|
||
min-width: 735px;
|
||
border-radius: 12px;
|
||
overflow: hidden
|
||
}
|
||
|
||
.agenda-weekday {
|
||
padding: .65rem;
|
||
background: var(--tblr-bg-surface-secondary);
|
||
font-weight: 700;
|
||
text-align: center
|
||
}
|
||
|
||
.agenda-day {
|
||
min-height: 128px;
|
||
padding: .5rem;
|
||
border-top: 1px solid var(--tblr-border-color);
|
||
border-right: 1px solid var(--tblr-border-color);
|
||
background: var(--tblr-bg-surface)
|
||
}
|
||
|
||
.agenda-day:nth-child(7n) {
|
||
border-right: 0
|
||
}
|
||
|
||
.agenda-day.muted {
|
||
background: rgba(98, 105, 118, .04)
|
||
}
|
||
|
||
.agenda-number {
|
||
font-weight: 700;
|
||
font-size: .8rem
|
||
}
|
||
|
||
.agenda-event {
|
||
display: block;
|
||
margin-top: .3rem;
|
||
padding: .3rem .4rem;
|
||
border-radius: 6px;
|
||
background: rgba(32, 107, 196, .09);
|
||
border-left: 3px solid var(--tblr-primary);
|
||
font-size: .68rem;
|
||
color: inherit;
|
||
text-decoration: none
|
||
}
|
||
|
||
.agenda-event strong {
|
||
display: block;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis
|
||
}
|
||
|
||
.agenda-event.red {
|
||
border-color: #d63939;
|
||
background: rgba(214, 57, 57, .09)
|
||
}
|
||
|
||
.agenda-event.orange {
|
||
border-color: #f76707;
|
||
background: rgba(247, 103, 7, .09)
|
||
}
|
||
|
||
.agenda-event.purple {
|
||
border-color: #ae3ec9;
|
||
background: rgba(174, 62, 201, .09)
|
||
}
|
||
|
||
.agenda-event.green {
|
||
border-color: #2fb344;
|
||
background: rgba(47, 179, 68, .09)
|
||
}
|
||
|
||
.agenda-event.cyan {
|
||
border-color: #17a2b8;
|
||
background: rgba(23, 162, 184, .1)
|
||
}
|
||
|
||
.agenda-event.yellow {
|
||
border-color: #f59f00;
|
||
background: rgba(245, 159, 0, .11)
|
||
}
|
||
|
||
.agenda-event.teal {
|
||
border-color: #0ca678;
|
||
background: rgba(12, 166, 120, .1)
|
||
}
|
||
|
||
.agenda-event.pink {
|
||
border-color: #d6336c;
|
||
background: rgba(214, 51, 108, .1)
|
||
}
|
||
|
||
.agenda-legend {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: .65rem;
|
||
padding: .7rem;
|
||
background: rgba(98, 105, 118, .045);
|
||
border-radius: 10px
|
||
}
|
||
|
||
.agenda-legend-group {
|
||
display: flex;
|
||
flex: 1 1 360px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: .45rem
|
||
}
|
||
|
||
.agenda-legend-title {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: .35rem;
|
||
margin-right: .15rem;
|
||
font-size: .72rem;
|
||
font-weight: 700;
|
||
color: var(--tblr-secondary);
|
||
text-transform: uppercase;
|
||
letter-spacing: .025em
|
||
}
|
||
|
||
.agenda-legend-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: .38rem;
|
||
padding: .28rem .55rem;
|
||
border-radius: 999px;
|
||
background: var(--tblr-bg-surface);
|
||
box-shadow: 0 1px 2px rgba(24, 36, 51, .08);
|
||
font-size: .72rem;
|
||
color: var(--tblr-body-color);
|
||
white-space: nowrap
|
||
}
|
||
|
||
.agenda-legend-dot {
|
||
width: .55rem;
|
||
height: .55rem;
|
||
border-radius: 50%;
|
||
flex: 0 0 auto;
|
||
box-shadow: 0 0 0 2px rgba(255, 255, 255, .8)
|
||
}
|
||
|
||
@media(max-width:900px) {
|
||
.agenda-calendar {
|
||
grid-template-columns: repeat(7, minmax(105px, 1fr))
|
||
}
|
||
|
||
.agenda-legend-group {
|
||
flex-basis: 100%
|
||
}
|
||
}
|
||
|
||
</style>
|
||
<style>
|
||
.agenda-calendar-week .agenda-day {
|
||
min-height: 360px
|
||
}
|
||
|
||
.agenda-day.agenda-day-clickable {
|
||
position: relative;
|
||
cursor: pointer;
|
||
transition: background-color .15s ease
|
||
}
|
||
|
||
.agenda-day.agenda-day-clickable:hover {
|
||
background-color: rgba(32, 107, 196, .055)
|
||
}
|
||
|
||
.agenda-day.agenda-day-clickable::after {
|
||
content: '+';
|
||
position: absolute;
|
||
right: .45rem;
|
||
top: .35rem;
|
||
width: 1.35rem;
|
||
height: 1.35rem;
|
||
display: grid;
|
||
place-items: center;
|
||
border-radius: 50%;
|
||
color: var(--tblr-primary);
|
||
background: rgba(32, 107, 196, .1);
|
||
font-weight: 700;
|
||
opacity: 0;
|
||
transform: scale(.85);
|
||
transition: .15s ease
|
||
}
|
||
|
||
.agenda-day.agenda-day-clickable:hover::after,
|
||
.agenda-day.agenda-day-clickable:focus-visible::after {
|
||
opacity: 1;
|
||
transform: scale(1)
|
||
}
|
||
|
||
.agenda-day.agenda-day-clickable:focus-visible {
|
||
outline: 2px solid var(--tblr-primary);
|
||
outline-offset: -2px
|
||
}
|
||
|
||
</style>
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||
<div class="text-muted"><?= h(t('agenda.intro')) ?></div>
|
||
<?php if (PermissionService::can('agenda', 'edit')): ?><button class="btn btn-primary" id="agendaAddButton"
|
||
data-bs-toggle="modal" data-bs-target="#agendaModal">+ <?= h(t('agenda.add')) ?></button><?php endif; ?>
|
||
</div>
|
||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||
<div class="btn-group" role="group" aria-label="<?= h(t('agenda.view')) ?>"><a
|
||
class="btn btn-sm <?= $viewMode === 'month' ? 'btn-primary' : 'btn-outline-secondary' ?>"
|
||
href="/agenda?view=month&month=<?= h(date('Y-m')) ?>">▦ <?= h(t('agenda.month_view')) ?></a><a
|
||
class="btn btn-sm <?= $viewMode === 'week' ? 'btn-primary' : 'btn-outline-secondary' ?>"
|
||
href="/agenda?view=week&date=<?= h(date('Y-m-d')) ?>">▥ <?= h(t('agenda.week_view')) ?></a></div>
|
||
<div class="d-flex flex-wrap gap-2"><a class="btn btn-sm btn-outline-secondary"
|
||
href="/agenda?view=<?= h($viewMode) ?><?= $viewMode === 'week' ? '&date=' . h(date('Y-m-d')) : '&month=' . h(date('Y-m')) ?>"><?= h(t('agenda.today')) ?></a><a
|
||
class="btn btn-sm btn-outline-primary"
|
||
href="/agenda/export.ics?view=<?= h($viewMode) ?><?= $viewMode === 'week' ? '&date=' . h($weekCursor->format('Y-m-d')) : '&month=' . h($cursor->format('Y-m')) ?>">⇩
|
||
<?= h(t('agenda.export_ics')) ?></a></div>
|
||
</div>
|
||
<form class="card card-body mb-3 py-2" method="get" action="/agenda" id="agendaFilters"><input type="hidden" name="view"
|
||
value="<?= h($viewMode) ?>"><?php if ($viewMode === 'week'): ?><input type="hidden" name="date"
|
||
value="<?= h($weekCursor->format('Y-m-d')) ?>"><?php else: ?><input type="hidden" name="month"
|
||
value="<?= h($cursor->format('Y-m')) ?>"><?php endif; ?><div class="row g-2 align-items-end">
|
||
<div class="col-sm-6 col-lg-2"><label class="form-label small mb-1"><?= h(t('agenda.scope')) ?></label><select
|
||
class="form-select form-select-sm" name="scope" onchange="this.form.submit()">
|
||
<option value="all"><?= h(t('agenda.scope_all')) ?></option>
|
||
<option value="mine" <?= $filters['scope'] === 'mine' ? 'selected' : '' ?>>
|
||
<?= h(t('agenda.scope_mine')) ?></option>
|
||
</select></div>
|
||
<div class="col-sm-6 col-lg-2"><label
|
||
class="form-label small mb-1"><?= h(t('agenda.assigned')) ?></label><select
|
||
class="form-select form-select-sm" name="assigned_user_id" onchange="this.form.submit()">
|
||
<option value="">—</option><?php foreach ($users as $u): ?><option value="<?= (int)$u['id'] ?>"
|
||
<?= $filters['assigned_user_id'] == (int)$u['id'] ? 'selected' : '' ?>><?= h($u['name']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select></div>
|
||
<div class="col-sm-6 col-lg-2"><label
|
||
class="form-label small mb-1"><?= h(t('agenda.volunteer')) ?></label><select
|
||
class="form-select form-select-sm" name="volunteer_contact_id" onchange="this.form.submit()">
|
||
<option value="">—</option><?php foreach ($volunteers as $v): ?><option value="<?= (int)$v['id'] ?>"
|
||
<?= $filters['volunteer_contact_id'] == (int)$v['id'] ? 'selected' : '' ?>><?= h($v['name']) ?>
|
||
</option><?php endforeach; ?>
|
||
</select></div>
|
||
<div class="col-sm-6 col-lg-2"><label class="form-label small mb-1"><?= h(t('agenda.type')) ?></label><select
|
||
class="form-select form-select-sm" name="category" onchange="this.form.submit()">
|
||
<option value=""><?= h(t('common.all')) ?></option><?php foreach (array_keys($colors) as $type): ?>
|
||
<option value="<?= h($type) ?>" <?= $filters['category'] === $type ? 'selected' : '' ?>>
|
||
<?= h(t('agenda.type_' . $type)) ?></option><?php endforeach; ?>
|
||
</select></div>
|
||
<div class="col-sm-8 col-lg-3"><label class="form-label small mb-1"><?= h(t('common.search')) ?></label><input
|
||
class="form-control form-control-sm" name="q" value="<?= h($filters['q']) ?>"
|
||
placeholder="<?= h(t('agenda.search_placeholder')) ?>"></div>
|
||
<div class="col-sm-4 col-lg-1 d-flex gap-1"><button class="btn btn-sm btn-primary flex-fill"
|
||
title="<?= h(t('common.search')) ?>">⌕</button><a class="btn btn-sm btn-outline-secondary"
|
||
href="/agenda?view=<?= h($viewMode) ?>" title="<?= h(t('common.reset')) ?>">×</a></div>
|
||
<div class="col-12"><input type="hidden" name="show_medical" value="0"><label
|
||
class="form-check form-switch mb-0"><input class="form-check-input" type="checkbox" name="show_medical"
|
||
value="1" <?= $filters['show_medical'] ? 'checked' : '' ?> onchange="this.form.submit()"><span
|
||
class="form-check-label small"><?= h(t('agenda.show_medical')) ?></span></label></div>
|
||
</div>
|
||
</form>
|
||
<div class="agenda-legend mb-3" aria-label="<?= h(t('agenda.legend')) ?>">
|
||
<?php foreach ($legend as $group => $entries): ?><div class="agenda-legend-group"><span
|
||
class="agenda-legend-title"><?= $group === 'medical' ? '❤' : '📅' ?>
|
||
<?= h(t('agenda.legend_' . $group)) ?></span><?php foreach ($entries as $type => $color): ?><span
|
||
class="agenda-legend-item"><span class="agenda-legend-dot"
|
||
style="background:<?= h($color) ?>"></span><?= h(t('agenda.type_' . $type)) ?></span><?php endforeach; ?>
|
||
</div><?php endforeach; ?></div>
|
||
<details class="card mb-3">
|
||
<summary class="card-header cursor-pointer"><span class="card-title">🔄
|
||
<?= h(t('agenda.subscription')) ?></span><span
|
||
class="ms-auto badge <?= $feedEnabled ? 'bg-green-lt' : 'bg-secondary-lt' ?>"><?= h(t($feedEnabled ? 'agenda.feed_active' : 'agenda.feed_inactive')) ?></span>
|
||
</summary>
|
||
<div class="card-body">
|
||
<p class="text-muted"><?= h(t('agenda.subscription_help')) ?></p><?php if ($feedUrl): ?><div
|
||
class="alert alert-success"><strong><?= h(t('agenda.copy_now')) ?></strong>
|
||
<div class="input-group mt-2"><input class="form-control font-monospace" id="agendaFeedUrl" readonly
|
||
value="<?= h($feedUrl) ?>"><button class="btn btn-outline-success" type="button"
|
||
onclick="navigator.clipboard.writeText(document.getElementById('agendaFeedUrl').value)"><?= h(t('common.copy')) ?></button>
|
||
</div>
|
||
</div><?php elseif ($feedEnabled): ?><div class="alert alert-info"><?= h(t('agenda.feed_hidden')) ?></div>
|
||
<?php endif; ?><div class="d-flex flex-wrap gap-2">
|
||
<form method="post" action="/agenda/feed/regenerate"><input type="hidden" name="csrf"
|
||
value="<?= h(Auth::csrf()) ?>"><button
|
||
class="btn btn-outline-primary"><?= h($feedEnabled ? t('agenda.regenerate_feed') : t('agenda.create_feed')) ?></button>
|
||
</form><?php if ($feedEnabled): ?><form method="post" action="/agenda/feed/revoke"
|
||
onsubmit="return confirm(<?= h(json_encode(t('agenda.revoke_confirm'), JSON_HEX_APOS | JSON_HEX_QUOT)) ?>)">
|
||
<input type="hidden" name="csrf" value="<?= h(Auth::csrf()) ?>"><button
|
||
class="btn btn-outline-danger"><?= h(t('agenda.revoke_feed')) ?></button></form><?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
<template id="agendaVolunteerField">
|
||
<div class="col-md-4 agenda-volunteer-field"><label
|
||
class="form-label"><?= h(t('agenda.volunteers')) ?></label><select class="form-select"
|
||
name="volunteer_contact_ids[]" multiple size="4"><?php $selectedVolunteers = array_map('intval', array_filter(explode(',', (string)($edit['volunteer_ids'] ?? ''))));
|
||
foreach ($volunteers as $v): ?><option value="<?= (int)$v['id'] ?>"
|
||
<?= in_array((int)$v['id'], $selectedVolunteers, true) ? 'selected' : '' ?>><?= h($v['name']) ?>
|
||
</option><?php endforeach; ?></select>
|
||
<div class="form-hint"><?= h(t('agenda.volunteers_help')) ?></div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const assigned = document.querySelector('#agendaModal select[name="assigned_user_id"]');
|
||
const template = document.getElementById('agendaVolunteerField');
|
||
if (assigned && template && !document.querySelector('#agendaModal .agenda-volunteer-field')) assigned
|
||
.closest('[class*="col-md-"]')?.after(template.content.cloneNode(true));
|
||
const form = document.getElementById('agendaFilters');
|
||
if (form) {
|
||
const params = new URLSearchParams(new FormData(form));
|
||
['view', 'date', 'month'].forEach(key => params.delete(key));
|
||
document.querySelectorAll('a[href^="/agenda?"] , a[href^="/agenda/export.ics?"]').forEach(link => {
|
||
if (link.title === < ? = json_encode(t('common.reset')) ? > ) return;
|
||
const url = new URL(link.href, location.origin);
|
||
params.forEach((value, key) => url.searchParams.set(key, value));
|
||
link.href = url.pathname + '?' + url.searchParams.toString();
|
||
});
|
||
}
|
||
const modal = document.getElementById('agendaModal');
|
||
const addButton = document.getElementById('agendaAddButton');
|
||
if (modal && addButton && < ? = (!$edit && PermissionService::can('agenda', 'edit')) ? 'true' :
|
||
'false' ? > ) {
|
||
const month = < ? = json_encode($cursor - > format('Y-m')) ? > ;
|
||
const weekDates = < ? = json_encode(array_map(static fn($i) => $weekStart - > modify('+'.$i.
|
||
' days') - > format('Y-m-d'), range(0, 6))) ? > ;
|
||
document.querySelectorAll('.agenda-calendar .agenda-day:not(.muted)').forEach((cell, index) => {
|
||
const number = cell.querySelector('.agenda-number')?.textContent.trim();
|
||
const date = < ? = $viewMode === 'week' ? 'weekDates[index]' :
|
||
'month+\'-\'+String(parseInt(number,10)).padStart(2,\'0\')' ? > ;
|
||
if (!date) return;
|
||
cell.classList.add('agenda-day-clickable');
|
||
cell.tabIndex = 0;
|
||
cell.setAttribute('role', 'button');
|
||
cell.setAttribute('aria-label', < ? = json_encode(t('agenda.add_on_date')) ? > +' ' +
|
||
date.split('-').reverse().join('/'));
|
||
const open = event => {
|
||
if (event.target.closest('.agenda-event,button,a,input,select,textarea,label'))
|
||
return;
|
||
modal.querySelector('input[name="date"]').value = date;
|
||
addButton.click();
|
||
};
|
||
cell.addEventListener('click', open);
|
||
cell.addEventListener('keydown', event => {
|
||
if (event.key === 'Enter' || event.key === ' ') {
|
||
event.preventDefault();
|
||
open(event);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
});
|
||
|
||
</script>
|
||
<?php if ($status): ?><div class="alert <?= $status === 'conflict' ? 'alert-warning' : 'alert-success' ?>">
|
||
<?= $status === 'conflict' ? '⚠️' : '✅' ?>
|
||
<?= h(t($status === 'conflict' ? 'agenda.conflict_saved' : 'agenda.saved')) ?></div>
|
||
<?php endif; ?><?php if ($error): ?><div class="alert alert-danger"><?= h($error) ?></div><?php endif; ?>
|
||
<?php if ($viewMode === 'month'): ?><div class="card mb-3">
|
||
<div class="card-header d-flex justify-content-between"><a class="btn btn-sm btn-outline-secondary"
|
||
href="/agenda?view=month&month=<?= h($cursor->modify('-1 month')->format('Y-m')) ?>">←</a>
|
||
<h3 class="card-title text-capitalize">
|
||
<?= h(I18n::locale() === 'fr' ? ['janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre'][(int)$cursor->format('n') - 1] . ' ' . $cursor->format('Y') : $cursor->format('F Y')) ?>
|
||
</h3><a class="btn btn-sm btn-outline-secondary"
|
||
href="/agenda?view=month&month=<?= h($cursor->modify('+1 month')->format('Y-m')) ?>">→</a>
|
||
</div>
|
||
<div class="card-body p-0 overflow-auto">
|
||
<div class="agenda-calendar">
|
||
<?php foreach ([t('agenda.mon'),t('agenda.tue'),t('agenda.wed'),t('agenda.thu'),t('agenda.fri'),t('agenda.sat'),t('agenda.sun')] as $label): ?>
|
||
<div class="agenda-weekday"><?= h($label) ?></div>
|
||
<?php endforeach; ?><?php for ($i = 0;$i < $offset;$i++): ?><div class="agenda-day muted"></div>
|
||
<?php endfor; ?><?php for ($day = 1;$day <= $days;$day++): $date = $cursor->format('Y-m-') . str_pad((string)$day, 2, '0', STR_PAD_LEFT); ?>
|
||
<div class="agenda-day <?= $date === date('Y-m-d') ? 'bg-yellow-lt' : '' ?>">
|
||
<div class="agenda-number"><?= $day ?></div>
|
||
<?php foreach ($byDay[$date] ?? [] as $item): $source = $item['source_type'] ?? 'agenda';
|
||
$isLinkedReminder = $source !== 'agenda';
|
||
$eventHref = $isLinkedReminder ? '/animal?id=' . (int)$item['animal_id'] . '#' . ($sourceTabs[$source] ?? 'tab-med') : '/agenda?view=month&month=' . h($cursor->format('Y-m')) . '&edit=' . (int)$item['id']; ?><a
|
||
class="agenda-event <?= h($colors[$item['item_type']] ?? 'secondary') ?>" href="<?= $eventHref ?>"
|
||
title="<?= h($item['title']) ?>"><strong><?= $item['all_day'] ? '' : h(substr($item['instance_starts_at'], 11, 5) . ' ') ?><?= h($item['title']) ?></strong><?php if ($item['animal_name']): ?>🐾
|
||
<?= h($item['animal_name']) ?><?php endif; ?></a><?php endforeach; ?>
|
||
</div><?php endfor; ?><?php for ($i = 0;$i < $trailing;$i++): ?><div class="agenda-day muted"></div>
|
||
<?php endfor; ?></div>
|
||
</div>
|
||
</div><?php else: ?><div class="card mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center"><a
|
||
class="btn btn-sm btn-outline-secondary"
|
||
href="/agenda?view=week&date=<?= h($weekStart->modify('-7 days')->format('Y-m-d')) ?>">←</a>
|
||
<h3 class="card-title">
|
||
<?= h(t('agenda.week_range', ['start' => $weekStart->format('d/m/Y'),'end' => $weekEnd->format('d/m/Y')])) ?>
|
||
</h3><a class="btn btn-sm btn-outline-secondary"
|
||
href="/agenda?view=week&date=<?= h($weekStart->modify('+7 days')->format('Y-m-d')) ?>">→</a>
|
||
</div>
|
||
<div class="card-body p-0 overflow-auto">
|
||
<div class="agenda-calendar agenda-calendar-week"><?php $weekLabels = [t('agenda.mon'),t('agenda.tue'),t('agenda.wed'),t('agenda.thu'),t('agenda.fri'),t('agenda.sat'),t('agenda.sun')];
|
||
foreach ($weekLabels as $i => $label):$weekDate = $weekStart->modify('+' . $i . ' days'); ?><div
|
||
class="agenda-weekday"><span class="d-block"><?= h($label) ?></span><span
|
||
class="small fw-normal"><?= h($weekDate->format('d/m')) ?></span></div><?php endforeach; ?><?php for ($i = 0;$i < 7;$i++):$weekDate = $weekStart->modify('+' . $i . ' days');
|
||
$date = $weekDate->format('Y-m-d'); ?><div
|
||
class="agenda-day <?= $date === date('Y-m-d') ? 'bg-yellow-lt' : '' ?>">
|
||
<div class="agenda-number"><?= h($weekDate->format('d/m')) ?></div>
|
||
<?php foreach ($byDay[$date] ?? [] as $item):$source = $item['source_type'] ?? 'agenda';
|
||
$isLinkedReminder = $source !== 'agenda';
|
||
$eventHref = $isLinkedReminder ? '/animal?id=' . (int)$item['animal_id'] . '#' . ($sourceTabs[$source] ?? 'tab-med') : '/agenda?view=week&date=' . h($weekStart->format('Y-m-d')) . '&edit=' . (int)$item['id']; ?><a
|
||
class="agenda-event <?= h($colors[$item['item_type']] ?? 'secondary') ?>" href="<?= $eventHref ?>"
|
||
title="<?= h($item['title']) ?>"><strong><?= $item['all_day'] ? '' : h(substr($item['instance_starts_at'], 11, 5) . ' ') ?><?= h($item['title']) ?></strong><?php if ($item['animal_name']): ?>🐾
|
||
<?= h($item['animal_name']) ?><?php endif; ?></a><?php endforeach; ?>
|
||
</div><?php endfor; ?></div>
|
||
</div>
|
||
</div><?php endif; ?>
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><?= h(t('agenda.upcoming')) ?></h3>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-vcenter card-table">
|
||
<thead>
|
||
<tr>
|
||
<th><?= h(t('common.date')) ?></th>
|
||
<th><?= h(t('agenda.event')) ?></th>
|
||
<th><?= h(t('animal.animal')) ?></th>
|
||
<th><?= h(t('agenda.assigned')) ?></th>
|
||
<th><?= h(t('agenda.status')) ?></th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><?php foreach ($upcoming as $item): $source = $item['source_type'] ?? 'agenda';
|
||
$isLinkedReminder = $source !== 'agenda';
|
||
$animalHref = '/animal?id=' . (int)$item['animal_id'] . '#' . ($sourceTabs[$source] ?? 'tab-med'); ?><tr>
|
||
<td><?= h(date('d/m/Y', strtotime($item['instance_starts_at']))) ?><div class="small text-muted">
|
||
<?= $item['all_day'] ? h(t('agenda.all_day')) : h(substr($item['instance_starts_at'], 11, 5)) ?>
|
||
</div>
|
||
</td>
|
||
<td><strong><?= h($item['title']) ?></strong>
|
||
<div class="small text-muted">
|
||
<?= h(t('agenda.type_' . $item['item_type'])) ?><?= $item['location'] ? ' · ' . h($item['location']) : '' ?>
|
||
</div>
|
||
</td>
|
||
<td><?= $item['animal_name'] ? '<a href="' . h($animalHref) . '">' . h($item['animal_name']) . '</a>' : '—' ?>
|
||
</td>
|
||
<td><?= h($item['assigned_name'] ?: '—') ?></td>
|
||
<td><span
|
||
class="badge bg-<?= h($item['status'] === 'completed' ? 'green' : 'blue') ?>-lt"><?= h(t('agenda.status_' . $item['status'])) ?></span>
|
||
</td>
|
||
<td class="text-end"><?php if ($isLinkedReminder): ?><a class="btn btn-sm btn-outline-secondary"
|
||
href="<?= h($animalHref) ?>"><?= h(t('agenda.open_animal')) ?></a><?php elseif (PermissionService::can('agenda', 'edit')): ?>
|
||
<div class="d-flex gap-1 justify-content-end"><a class="btn btn-sm btn-outline-secondary"
|
||
href="/agenda?edit=<?= (int)$item['id'] ?>"><?= h(t('common.edit')) ?></a><?php if ($item['status'] === 'planned'): ?>
|
||
<form method="post" action="/agenda/status"><input type="hidden" name="id"
|
||
value="<?= (int)$item['id'] ?>"><input type="hidden" name="status"
|
||
value="completed"><button class="btn btn-sm btn-outline-success">✓</button></form>
|
||
<?php endif; ?></div><?php endif; ?></td>
|
||
</tr><?php endforeach; ?><?php if (!$upcoming): ?><tr>
|
||
<td colspan="6" class="text-center text-muted py-4"><?= h(t('agenda.none')) ?></td>
|
||
</tr><?php endif; ?></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<?php if (PermissionService::can('agenda', 'edit')): ?><div class="modal modal-blur fade <?= $edit ? 'show' : '' ?>"
|
||
id="agendaModal" tabindex="-1"
|
||
<?= $edit ? 'style="display:block" aria-modal="true" role="dialog"' : 'aria-hidden="true"' ?>>
|
||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||
<form class="modal-content" method="post" action="/agenda/save"><input type="hidden" name="id"
|
||
value="<?= (int)($edit['id'] ?? 0) ?>">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title"><?= h($edit ? t('agenda.edit') : t('agenda.add')) ?></h5><a class="btn-close"
|
||
href="/agenda?month=<?= h($cursor->format('Y-m')) ?>" aria-label="<?= h(t('common.close')) ?>"></a>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-4"><label class="form-label"><?= h(t('agenda.type')) ?></label><select
|
||
class="form-select" name="item_type"><?php foreach (AgendaService::TYPES as $type): ?>
|
||
<option value="<?= h($type) ?>"
|
||
<?= ($edit['item_type'] ?? 'task') === $type ? 'selected' : '' ?>>
|
||
<?= h(t('agenda.type_' . $type)) ?></option><?php endforeach; ?></select></div>
|
||
<div class="col-md-8"><label class="form-label"><?= h(t('common.title')) ?></label><input
|
||
class="form-control" name="title" required maxlength="180"
|
||
value="<?= h($edit['title'] ?? '') ?>"></div>
|
||
<div class="col-md-3"><label class="form-label"><?= h(t('common.date')) ?></label><input
|
||
class="form-control" type="date" name="date" required
|
||
value="<?= h($editStart ? $editStart->format('Y-m-d') : date('Y-m-d')) ?>"></div>
|
||
<div class="col-md-3"><label class="form-label"><?= h(t('agenda.time')) ?></label><input
|
||
class="form-control" type="time" name="time"
|
||
value="<?= h($editStart ? $editStart->format('H:i') : '09:00') ?>"></div>
|
||
<div class="col-md-3"><label class="form-label"><?= h(t('agenda.end_date')) ?></label><input
|
||
class="form-control" type="date" name="end_date"
|
||
value="<?= h(!empty($edit['ends_at']) ? substr($edit['ends_at'], 0, 10) : '') ?>"></div>
|
||
<div class="col-md-3"><label class="form-label"><?= h(t('agenda.end_time')) ?></label><input
|
||
class="form-control" type="time" name="end_time"
|
||
value="<?= h(!empty($edit['ends_at']) ? substr($edit['ends_at'], 11, 5) : '') ?>"></div>
|
||
<div class="col-12"><label class="form-check"><input class="form-check-input" type="checkbox"
|
||
name="all_day" <?= !empty($edit['all_day']) ? 'checked' : '' ?>><span
|
||
class="form-check-label"><?= h(t('agenda.all_day')) ?></span></label></div>
|
||
<div class="col-md-4"><label class="form-label"><?= h(t('agenda.priority')) ?></label><select
|
||
class="form-select"
|
||
name="priority"><?php foreach (['low','normal','high','urgent'] as $p): ?><option
|
||
value="<?= $p ?>" <?= ($edit['priority'] ?? 'normal') === $p ? 'selected' : '' ?>>
|
||
<?= h(t('agenda.priority_' . $p)) ?></option><?php endforeach; ?></select></div>
|
||
<div class="col-md-4"><label class="form-label"><?= h(t('agenda.assigned')) ?></label><select
|
||
class="form-select" name="assigned_user_id">
|
||
<option value="">—</option><?php foreach ($users as $u): ?><option
|
||
value="<?= (int)$u['id'] ?>"
|
||
<?= (int)($edit['assigned_user_id'] ?? 0) === (int)$u['id'] ? 'selected' : '' ?>>
|
||
<?= h($u['name']) ?></option><?php endforeach; ?>
|
||
</select></div>
|
||
<div class="col-md-4"><label class="form-label"><?= h(t('agenda.reminder')) ?></label><select
|
||
class="form-select" name="reminder_minutes">
|
||
<option value="">—</option><?php foreach ([0,30,60,1440,2880,10080] as $m): ?><option
|
||
value="<?= $m ?>"
|
||
<?= ($edit['reminder_minutes'] ?? '') !== '' && (int)$edit['reminder_minutes'] === $m ? 'selected' : '' ?>>
|
||
<?= h(t('agenda.reminder_' . $m)) ?></option><?php endforeach; ?>
|
||
</select></div>
|
||
<div class="col-md-6"><label class="form-label"><?= h(t('animal.animal')) ?></label><select
|
||
class="form-select" name="animal_id">
|
||
<option value="">—</option><?php foreach ($animals as $a): ?><option
|
||
value="<?= (int)$a['id'] ?>"
|
||
<?= (int)($edit['animal_id'] ?? 0) === (int)$a['id'] ? 'selected' : '' ?>>
|
||
<?= h($a['name']) ?></option><?php endforeach; ?>
|
||
</select></div>
|
||
<div class="col-md-6"><label class="form-label"><?= h(t('agenda.contact')) ?></label><select
|
||
class="form-select" name="contact_id">
|
||
<option value="">—</option><?php foreach ($contacts as $c): ?><option
|
||
value="<?= (int)$c['id'] ?>"
|
||
<?= (int)($edit['contact_id'] ?? 0) === (int)$c['id'] ? 'selected' : '' ?>>
|
||
<?= h($c['name']) ?></option><?php endforeach; ?>
|
||
</select></div>
|
||
<div class="col-md-6"><label class="form-label"><?= h(t('agenda.location')) ?></label><input
|
||
class="form-control" name="location" value="<?= h($edit['location'] ?? '') ?>"></div>
|
||
<div class="col-md-3"><label class="form-label"><?= h(t('agenda.repeat')) ?></label><select
|
||
class="form-select"
|
||
name="repeat_rule"><?php foreach (['none','daily','weekly','monthly'] as $r): ?><option
|
||
value="<?= $r ?>" <?= ($edit['repeat_rule'] ?? 'none') === $r ? 'selected' : '' ?>>
|
||
<?= h(t('agenda.repeat_' . $r)) ?></option><?php endforeach; ?></select></div>
|
||
<div class="col-md-3"><label class="form-label"><?= h(t('agenda.repeat_until')) ?></label><input
|
||
class="form-control" type="date" name="repeat_until"
|
||
value="<?= h($edit['repeat_until'] ?? '') ?>"></div>
|
||
<div class="col-12"><label class="form-label"><?= h(t('common.description')) ?></label><textarea
|
||
class="form-control" name="description"
|
||
rows="3"><?= h($edit['description'] ?? '') ?></textarea></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer"><a class="btn me-auto" href="/agenda"><?= h(t('common.cancel')) ?></a><button
|
||
class="btn btn-primary"><?= h(t('common.save')) ?></button></div>
|
||
</form>
|
||
</div>
|
||
</div><?php if ($edit): ?><div class="modal-backdrop fade show"></div><?php endif; ?><?php endif; ?>
|