415 lines
18 KiB
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>
|