Globinours/app/Views/care_round.php

415 lines
18 KiB
PHP

<?php
function care_boxes(array $layout): array
{
$top = match($layout['top_layout']) {
'left2' => ['top_ab' => t('care.box_top_left_double'),'top_c' => t('care.box_top_right')],'right2' => ['top_a' => t('care.box_top_left'),'top_bc' => t('care.box_top_right_double')],'merged' => ['top_all' => t('care.box_top_full')],default => ['top_a' => t('care.box_top_left'),'top_b' => t('care.box_top_center'),'top_c' => t('care.box_top_right')]
};
$bottom = $layout['bottom_layout'] === 'merged' ? ['bottom_all' => t('care.box_bottom_full')] : ['bottom_a' => t('care.box_bottom_left'),'bottom_b' => t('care.box_bottom_right')];
return $top + $bottom + ['' => t('care.box_unassigned')];
}
$roomsByCode = [];
foreach ($rooms as $configuredRoom) {
$roomsByCode[$configuredRoom['code']] = $configuredRoom;
}
$byRoom = [];
foreach ($animals as $a) {
$byRoom[ShelterRoomService::resolveAnimal($a, $roomsByCode)][] = $a;
}
$fieldOptions = [
'food' => ['normal' => t('care.food_normal'),'little' => t('care.food_little'),'refused' => t('care.food_refused')],
'water' => ['normal' => t('care.water_normal'),'renewed' => t('care.water_renewed'),'abnormal' => t('care.abnormal')],
'urine' => ['normal' => t('care.urine_normal'),'absent' => t('care.absent_feminine'),'abnormal' => t('care.abnormal')],
'stool' => ['normal' => t('care.stool_normal'),'absent' => t('care.absent_feminine'),'abnormal' => t('care.abnormal')],
'general_state' => ['good' => t('care.state_good'),'watch' => t('care.watch'),'alert' => t('care.alert')],
];
?>
<style>
.care-mode .btn,
.care-mode .form-control {
min-height: 44px
}
.care-room {
border-width: 1px;
border-style: solid;
border-radius: 1rem;
overflow: hidden
}
.care-box-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: .75rem
}
.care-box {
grid-column: span 3;
min-height: 120px;
border: 2px solid rgba(98, 105, 118, .2);
border-radius: .8rem;
padding: .75rem;
background: #fff
}
.care-box.box-top_a,
.care-box.box-top_b,
.care-box.box-top_c {
grid-column: span 2
}
.care-box.box-top_ab,
.care-box.box-top_bc {
grid-column: span 4
}
.care-box.box-top_all,
.care-box.box-bottom_all {
grid-column: span 6
}
.care-collective {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: .65rem
}
.care-collective-title {
grid-column: 1/-1
}
.care-box-complete {
border-color: #2fb344;
background: #f1fbf4
}
.care-cat {
min-width: 0;
border: 1px solid rgba(98, 105, 118, .18);
border-radius: .65rem;
background: #fff
}
.care-cat[open] {
box-shadow: 0 8px 24px rgba(31, 42, 68, .1)
}
.care-cat summary {
list-style: none;
cursor: pointer;
padding: .65rem
}
.care-cat summary::-webkit-details-marker {
display: none
}
.care-cat-photo {
width: 48px;
height: 48px;
object-fit: cover;
border-radius: .6rem
}
.care-checked {
border-color: #2fb344;
background: #f1fbf4
}
.care-choice input {
position: absolute;
opacity: 0
}
.care-choice span {
display: block;
padding: .55rem .7rem;
border: 1px solid #d6d9df;
border-radius: .5rem;
text-align: center
}
.care-choice input:checked+span {
background: #206bc4;
color: #fff;
border-color: #206bc4
}
@media(max-width:800px) {
.care-box {
grid-column: span 6 !important
}
.care-collective {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr))
}
}
@media(max-width:520px) {
.care-collective {
grid-template-columns: 1fr
}
}
.care-room-silver {
border-color: #aeb5bd
}
.care-room-silver>.card-header,
.care-room-silver>.card-body {
background: #edf0f2
}
.care-room-silver .care-cat {
background: #f8f9fa;
border-color: #adb5bd
}
.care-room-twix {
border-color: #f0a35e
}
.care-room-twix>.card-header,
.care-room-twix>.card-body {
background: #fff0df
}
.care-room-twix .care-cat {
background: #fff9f2;
border-color: #f0a35e
}
.care-room-care {
border-color: #e58a8a
}
.care-room-care>.card-header,
.care-room-care>.card-body {
background: #fdeaea
}
.care-room-care .care-box {
background: #fff6f6;
border-color: #e7a0a0
}
.care-room-quarantine {
border-color: #d8bd52
}
.care-room-quarantine>.card-header,
.care-room-quarantine>.card-body {
background: #fff5c9
}
.care-room-quarantine .care-box {
background: #fffbed;
border-color: #ddc76d
}
.care-room-configured {
border-color: var(--room-color)
}
.care-room-configured>.card-header,
.care-room-configured>.card-body {
background: color-mix(in srgb, var(--room-color) 13%, #fff)
}
.care-room-configured .care-box,
.care-room-configured .care-cat {
border-color: color-mix(in srgb, var(--room-color) 65%, #d6d9df)
}
.care-room .care-box.care-box-complete,
.care-room .care-cat.care-checked {
border-color: #2fb344;
background: #f1fbf4
}
.care-round-toolbar {
flex-wrap: nowrap !important
}
.care-round-actions {
display: flex;
align-items: center;
gap: .5rem;
margin-left: auto;
white-space: nowrap
}
.care-round-actions .flatpickr-wrapper,
.care-round-actions input[type="date"],
.care-round-actions .form-control {
width: 160px;
flex: 0 0 160px
}
@media(max-width:900px) {
.care-round-toolbar {
flex-wrap: wrap !important
}
.care-round-actions {
width: 100%;
margin-left: 0;
flex-wrap: wrap
}
.care-round-actions .flatpickr-wrapper {
width: 160px
}
}
</style>
<div class="care-mode">
<div class="card mb-3">
<div class="card-body d-flex align-items-center gap-2 care-round-toolbar">
<strong
class="me-2"><?= h(t('care.round_of', ['period' => t($period === 'morning' ? 'care.morning' : 'care.evening')])) ?></strong>
<a class="btn <?= $period === 'morning' ? 'btn-primary' : 'btn-outline-primary' ?>"
href="/care-round?period=morning&date=<?= h($date) ?>">☀️ <?= h(t('care.morning')) ?></a>
<a class="btn <?= $period === 'evening' ? 'btn-primary' : 'btn-outline-primary' ?>"
href="/care-round?period=evening&date=<?= h($date) ?>">🌙 <?= h(t('care.evening')) ?></a>
<div class="care-round-actions"><input class="form-control" type="date" value="<?= h($date) ?>"
onchange="location.href='/care-round?period=<?= h($period) ?>&date='+this.value">
<button class="btn btn-outline-secondary" type="button"
onclick="document.documentElement.requestFullscreen?.()">⛶ <?= h(t('care.fullscreen')) ?></button>
<a class="btn btn-outline-secondary" href="/care-round/setup">⚙️ <?= h(t('care.configure_boxes')) ?></a>
</div>
</div>
</div>
<?php foreach ($rooms as $room):$roomKey = (string)$room['code'];
$roomLabel = (string)$room['name'];
$boxed = $room['room_type'] === 'boxes'; ?>
<section class="card care-room care-room-configured mb-3" style="--room-color:<?= h($room['color']) ?>"
id="room-<?= h($roomKey) ?>">
<div class="card-header">
<h2 class="card-title"><?= h($roomLabel) ?></h2><span
class="badge bg-secondary-lt ms-2"><?= h(tn('care.cat', 'care.cats', count($byRoom[$roomKey] ?? []))) ?></span>
<?php if (!empty($room['bulk_validation']) && !empty($byRoom[$roomKey])): ?><form method="post"
action="/care-round/save-room" class="ms-auto"
onsubmit="return confirm(<?= h(json_encode(t('care.validate_room_confirm'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?>)">
<input type="hidden" name="round_id" value="<?= (int)$round['id'] ?>"><input type="hidden" name="room"
value="<?= h($roomKey) ?>"><input type="hidden" name="period" value="<?= h($period) ?>"><input
type="hidden" name="date" value="<?= h($date) ?>"><button class="btn btn-success" type="submit">✓
<?= h(t('care.everyone_well')) ?></button></form><?php endif; ?>
</div>
<div class="card-body">
<?php $roomAnimals = $byRoom[$roomKey] ?? [];
$configuredBoxCodes = array_column($room['boxes'], 'code');
$legacyLayout = $boxed && in_array($roomKey, ['care','quarantine'], true) && !array_diff($configuredBoxCodes, ['top_a','top_b','top_c','bottom_a','bottom_b']) && !array_diff(['top_a','top_b','top_c','bottom_a','bottom_b'], $configuredBoxCodes);
$boxes = $boxed ? ($legacyLayout ? care_boxes($layouts[$roomKey] ?? []) : array_column($room['boxes'], 'name', 'code')) : ['room' => $roomLabel];
if ($boxed && !array_key_exists('', $boxes)) {
$boxes += ['' => t('care.box_unassigned')];
} ?>
<div class="<?= $boxed ? 'care-box-grid' : 'row g-2' ?>">
<?php foreach ($boxes as $boxKey => $boxLabel): $knownBoxKeys = array_values(array_filter(array_keys($boxes), static fn ($key) => $key !== ''));
$boxAnimals = array_values(array_filter($roomAnimals, fn ($a) => !$boxed || ($boxKey === '' ? !in_array((string)($a['care_box_key'] ?? ''), $knownBoxKeys, true) : ($a['care_box_key'] ?? '') === $boxKey))); ?>
<?php $boxComplete = $boxAnimals && count(array_filter($boxAnimals, fn ($cat) => isset($observations[(int)$cat['id']]))) === count($boxAnimals); ?>
<div
class="<?= $boxed ? 'care-box box-' . $boxKey : 'col-12 care-collective' ?> <?= $boxComplete ? 'care-box-complete' : '' ?>">
<div class="fw-bold mb-2 <?= !$boxed ? 'care-collective-title' : '' ?>"><?= h($boxLabel) ?> <span
class="text-muted">·
<?= count($boxAnimals) ?></span><?= $boxComplete ? ' <span class="badge bg-green-lt ms-1">' . h(t('care.finished')) . '</span>' : '' ?>
</div>
<?php foreach ($boxAnimals as $a): $obs = $observations[(int)$a['id']] ?? null;
$src = $a['primary_photo'] ? '/media/animals/' . (int)$a['id'] . '/' . rawurlencode($a['primary_photo']) : '/assets/cat_placeholder.svg'; ?>
<details class="care-cat mb-2 <?= $obs ? 'care-checked' : '' ?>" id="animal-<?= (int)$a['id'] ?>">
<summary class="d-flex align-items-center gap-2"><img class="care-cat-photo"
src="<?= h($src) ?>"
alt=""><strong><?= h($a['name']) ?></strong><?php if (!empty($treatmentsByAnimal[(int)$a['id']])): ?><span
class="badge bg-purple-lt" title="<?= h(t('care.treatment_planned')) ?>"
aria-label="<?= h(t('care.treatment_planned')) ?>">💊</span><?php endif; ?><?php if (!empty($unconfiguredTreatments[(int)$a['id']])): ?><span
class="badge bg-yellow-lt" title="<?= h(t('care.treatment_unconfigured')) ?>"
aria-label="<?= h(t('care.treatment_unconfigured')) ?>">💊 ⚠</span><?php endif; ?><span
class="ms-auto badge <?= $obs ? 'bg-green-lt' : 'bg-yellow-lt' ?>"><?= h(t($obs ? 'care.validated' : 'care.todo')) ?></span>
</summary>
<form method="post" action="/care-round/save" enctype="multipart/form-data"
class="p-2 pt-0 care-observation-form"><input type="hidden" name="round_id"
value="<?= (int)$round['id'] ?>"><input type="hidden" name="animal_id"
value="<?= (int)$a['id'] ?>"><input type="hidden" name="period"
value="<?= h($period) ?>"><input type="hidden" name="date" value="<?= h($date) ?>">
<button class="btn btn-success w-100 mb-2 care-all-normal" type="button">✓
<?= h(t('care.all_normal')) ?></button>
<?php foreach ($fieldOptions as $field => $options): ?><div class="row g-1 mb-2">
<?php foreach ($options as $value => $label): ?><label class="col care-choice"><input
type="radio" name="<?= h($field) ?>" value="<?= h($value) ?>"
<?= (($obs[$field] ?? array_key_first($options)) === $value) ? 'checked' : '' ?>><span><?= h($label) ?></span></label><?php endforeach; ?>
</div><?php endforeach; ?>
<?php foreach ($treatmentsByAnimal[(int)$a['id']] ?? [] as $t): ?><div
class="card card-sm bg-purple-lt mb-2">
<div class="card-body py-2"><strong>💊 <?= h($t['medication_name']) ?></strong>
<div class="small mb-2"><?= h($t['dose_text']) ?></div>
<div class="btn-group w-100"><label class="btn btn-outline-success"><input
type="radio" class="btn-check" name="treatments[<?= (int)$t['id'] ?>]"
value="given"
<?= ($administrations[(int)$t['id']] ?? '') === 'given' ? 'checked' : '' ?>>
<?= h(t('care.given')) ?></label><label
class="btn btn-outline-danger"><input type="radio" class="btn-check"
name="treatments[<?= (int)$t['id'] ?>]" value="skipped"
<?= ($administrations[(int)$t['id']] ?? '') === 'skipped' ? 'checked' : '' ?>>
<?= h(t('care.not_given')) ?></label></div>
</div>
</div><?php endforeach; ?>
<div class="card card-sm bg-red-lt mb-2">
<div class="card-body py-2"><label class="form-label fw-bold">📷
<?= h(t('care.anomaly_photo')) ?></label><input
class="form-control care-photo-input mb-2" type="file" name="care_photo"
accept="image/*" capture="environment"><input
class="form-control care-photo-comment" type="text" name="photo_comment"
maxlength="500" placeholder="<?= h(t('care.photo_comment')) ?>">
<div class="form-hint"><?= h(t('care.photo_hint')) ?></div>
</div>
</div>
<textarea class="form-control mb-2" name="comment" rows="2"
placeholder="<?= h(t('care.optional_comment')) ?>"><?= h($obs['comment'] ?? '') ?></textarea><button
class="btn btn-primary w-100"
type="submit"><?= h(t('care.validate_animal', ['name' => $a['name']])) ?></button>
</form>
<?php foreach ($unconfiguredTreatments[(int)$a['id']] ?? [] as $unconfigured): ?><div
class="alert alert-warning m-2 py-2"><strong>⚠
<?= h($unconfigured['medication_name']) ?></strong> <?= h(t('care.no_schedule')) ?><div
class="small mb-2"><?= h($unconfigured['dose_text']) ?></div>
<form method="post" action="/care-round/treatment-schedule" class="d-flex flex-wrap gap-1">
<input type="hidden" name="treatment_id" value="<?= (int)$unconfigured['id'] ?>"><input
type="hidden" name="period" value="<?= h($period) ?>"><button
class="btn btn-sm btn-outline-primary" name="schedule"
value="morning"><?= h(t('care.morning')) ?></button><button
class="btn btn-sm btn-outline-primary" name="schedule"
value="evening"><?= h(t('care.evening')) ?></button><button
class="btn btn-sm btn-primary" name="schedule"
value="both"><?= h(t('care.both')) ?></button><button
class="btn btn-sm btn-outline-secondary" name="schedule"
value="needed"><?= h(t('animal.as_needed')) ?></button></form>
</div><?php endforeach; ?>
</details>
<?php endforeach; ?>
</div><?php endforeach; ?>
<?php if (!$roomAnimals): ?><div class="text-muted"><?= h(t('care.no_animal_room')) ?></div>
<?php endif; ?>
</div>
</div>
</section><?php endforeach; ?>
</div>
<script>
document.querySelectorAll('.care-all-normal').forEach(b => b.addEventListener('click', () => {
const f = b.form;
[
['food', 'normal'],
['water', 'normal'],
['urine', 'normal'],
['stool', 'normal'],
['general_state', 'good']
].forEach(([n, v]) => {
const e = f.querySelector(`[name="${n}"][value="${v}"]`);
if (e) e.checked = true;
});
}));
document.querySelectorAll('.care-photo-input').forEach(input => input.addEventListener('change', () => {
const comment = input.form.querySelector('.care-photo-comment');
comment.required = input.files.length > 0;
if (input.files.length > 0) comment.focus();
}));
</script>