Publier Globinours 1.0.0-rc.3

This commit is contained in:
Alexandre NOEL 2026-09-03 12:39:15 +02:00
commit 9a2b4068da
325 changed files with 38230 additions and 20 deletions

View file

@ -0,0 +1,459 @@
<?php
// Sous-vue de la fiche animale ; les variables sont préparées par AnimalsController::show().
?>
<!-- IDENTITÉ -->
<div class="tab-pane active show" id="tab-ident">
<?php if ($canViewMedical && !empty($death)): ?>
<?php
$deathCauseLabels = array_combine(array_keys(DeathController::CAUSES), array_map(static fn ($key) => DeathController::causeLabel($key), array_keys(DeathController::CAUSES)));
$deathPlaceLabels = array_combine(array_keys(DeathController::PLACES), array_map(static fn ($key) => DeathController::placeLabel($key), array_keys(DeathController::PLACES)));
$deathDispositionLabels = array_combine(array_keys(DeathController::DISPOSITIONS), array_map(static fn ($key) => DeathController::dispositionLabel($key), array_keys(DeathController::DISPOSITIONS)));
$deathDate = DateTimeImmutable::createFromFormat('Y-m-d', (string)$death['deceased_date']);
$deathDateLabel = !empty($death['death_year']) ? t('animal.year', ['year' => (int)$death['death_year']]) : ($deathDate ? $deathDate->format('d/m/Y') : t('animal.date_unknown'));
?>
<div class="card border-dark mb-3 overflow-hidden">
<div class="card-status-start bg-dark"></div>
<div class="card-header d-flex align-items-center justify-content-between">
<div>
<h3 class="card-title mb-1">🌈 <?= h(t('animal.death_recorded')) ?></h3>
<div class="text-muted small"><?= h($deathDateLabel) ?> ·
<?= h($deathCauseLabels[$death['cause_code']] ?? $death['cause_code']) ?></div>
</div><a class="btn btn-outline-dark btn-sm"
href="/animal/death?id=<?= (int)$animal['id'] ?>"><?= h(t('common.edit')) ?></a>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<div class="subheader"><?= h(t('animal.statistics')) ?></div><span
class="badge <?= !empty($death['occurred_in_care']) ? 'bg-dark' : 'bg-secondary-lt' ?>"><?= h(t(!empty($death['occurred_in_care']) ? 'animal.in_shelter_figures' : 'animal.outside_care')) ?></span>
</div>
<div class="col-md-3">
<div class="subheader"><?= h(t('death.place')) ?></div>
<strong><?= h($deathPlaceLabels[$death['place_type']] ?? $death['place_type']) ?></strong><?php if ($death['place_details']): ?>
<div class="small text-muted"><?= h($death['place_details']) ?></div><?php endif; ?>
</div>
<div class="col-md-3">
<div class="subheader"><?= h(t('death.care_body')) ?></div>
<strong><?= h($deathDispositionLabels[$death['body_disposition']] ?? $death['body_disposition']) ?></strong><?php if ($death['crematorium_name']): ?>
<div class="small text-muted"><?= h($death['crematorium_name']) ?></div><?php endif; ?>
</div>
<div class="col-md-3">
<div class="subheader"><?= h(t('animal.context')) ?></div>
<?php if (!empty($death['euthanized'])): ?><span
class="badge bg-purple-lt"><?= h(t('animal.euthanasia')) ?></span><?php else: ?><span
class="text-muted"><?= h(t('animal.natural_death_short')) ?></span><?php endif; ?><?php if ($death['veterinarian_name']): ?>
<div class="small text-muted"><?= h($death['veterinarian_name']) ?></div><?php endif; ?>
</div>
<?php if ($death['cause_details'] || $death['notes']): ?><div class="col-12 small">
<?php if ($death['cause_details']): ?><strong><?= h(t('animal.cause')) ?> :</strong>
<?= nl2br(h($death['cause_details'])) ?><?php endif; ?><?php if ($death['notes']): ?><div
class="mt-1"><strong><?= h(t('common.notes')) ?> :</strong> <?= nl2br(h($death['notes'])) ?>
</div><?php endif; ?></div><?php endif; ?>
<?php if (!empty($death['death_age_value'])): ?><div class="col-12 small">
<strong><?= h(t('animal.death_age')) ?></strong> <?= (int)$death['death_age_value'] ?> <?= h(match($death['death_age_unit']) {
'day' => t('animal.age_days'),'week' => t('animal.age_weeks'),'month' => t('animal.age_months'),'year' => t('animal.age_years'),default => ''
}) ?></div><?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if (!empty($bondedGroup)): ?>
<div class="card border-purple mb-3">
<div class="card-status-start bg-purple"></div>
<div class="card-header d-flex align-items-center justify-content-between">
<div>
<h3 class="card-title mb-1">🔗 <?= h($bondedGroup['name'] ?: t('animal.bonded_cats')) ?></h3>
<div class="text-muted small"><?= h(t('animal.bonded_help')) ?></div>
</div>
<form method="post" action="/bonded/dissolve"
onsubmit="return confirm(<?= h(json_encode(t('animal.bonded_dissolve_confirm'), JSON_HEX_APOS | JSON_HEX_QUOT)) ?>);">
<input type="hidden" name="group_id" value="<?= (int)$bondedGroup['id'] ?>">
<input type="hidden" name="animal_id" value="<?= (int)$animal['id'] ?>">
<button class="btn btn-outline-secondary btn-sm"
type="submit"><?= h(t('animal.dissolve_group')) ?></button>
</form>
</div>
<div class="card-body">
<?php if (!empty($bondedGroup['notes'])): ?><p class="text-muted mb-3">
<?= nl2br(h($bondedGroup['notes'])) ?></p><?php endif; ?>
<div class="d-flex flex-wrap gap-2">
<?php foreach ($bondedMembers as $member): ?>
<a class="btn <?= (int)$member['id'] === (int)$animal['id'] ? 'btn-purple' : 'btn-outline-purple' ?>"
href="/animal?id=<?= (int)$member['id'] ?>">
<span class="coat-swatch me-1"
style="<?= h(coat_swatch_style($member['color'] ?? '')) ?>"></span><?= h($member['name']) ?>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php
$lifeProfileMeta = [
'yes' => [t('common.yes'),'bg-green-lt text-green','✓'],
'no' => [t('common.no'),'bg-red-lt text-red','✕'],
'unknown' => [t('common.unknown'),'bg-secondary-lt text-secondary','—'],
];
?>
<div class="card mt-3">
<div class="card-header">
<div>
<h3 class="card-title mb-1"><?= h(t('animal.form.intake')) ?></h3>
<div class="text-muted small"><?= h(t('animal.intake_origin_help')) ?></div>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-6 col-lg-3">
<div class="subheader"><?= h(t('common.date')) ?></div>
<strong><?= h(!empty($animal['intake_date']) ? date('d/m/Y', strtotime((string)$animal['intake_date'])) : '—') ?></strong>
</div>
<div class="col-6 col-lg-3">
<div class="subheader"><?= h(t('animal.intake_mode')) ?></div>
<strong><?= h($intakeTypes[$animal['intake_type'] ?? 'unknown'] ?? t('common.not_provided')) ?></strong>
</div>
<div class="col-6 col-lg-3">
<div class="subheader"><?= h(t('animal.reason')) ?></div>
<strong><?= h($intakeReasons[$animal['intake_reason'] ?? 'unknown'] ?? t('common.not_provided')) ?></strong>
</div>
<div class="col-6 col-lg-3">
<div class="subheader"><?= h(t('animal.origin_city')) ?></div>
<strong><?= h(($animal['rescue_location_name'] ?? '') ?: '—') ?></strong>
</div>
<div class="col-12 col-lg-6">
<div class="subheader"><?= h(t('animal.form.depositor')) ?></div><?php if ($depositor): ?><a
class="fw-bold"
href="/directory/edit?id=<?= (int)$depositor['id'] ?>"><?= h($depositor['name']) ?></a><?php if (!empty($depositor['city'])): ?><span
class="text-muted"> · <?= h($depositor['city']) ?></span><?php endif; ?><?php else: ?><span
class="text-muted"><?= h(t('animal.form.no_depositor')) ?></span><?php endif; ?>
</div>
<?php if (!empty($animal['intake_circumstances'])): ?><div class="col-12">
<div class="subheader"><?= h(t('animal.form.circumstances')) ?></div>
<div><?= nl2br(h($animal['intake_circumstances'])) ?></div>
</div><?php endif; ?>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<div>
<h3 class="card-title mb-1"><?= h(t('animal.form.life_profile')) ?></h3>
<div class="text-muted small"><?= h(t('animal.life_profile_help')) ?></div>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<?php foreach ([
'compatibility_dogs' => ['🐶',t('animals.dogs')],
'compatibility_cats' => ['🐱',t('animals.cats')],
'compatibility_children' => ['🧒',t('animals.children')],
'house_trained' => ['🧼',t('animals.cleanliness')],
] as $field => $profile): [$profileLabel,$profileClass,$profileSymbol] = $lifeProfileMeta[$animal[$field] ?? 'unknown'] ?? $lifeProfileMeta['unknown']; ?>
<div class="col-6 col-lg-3">
<div class="border rounded p-3 h-100 d-flex align-items-center gap-3"><span class="fs-2"
aria-hidden="true"><?= $profile[0] ?></span>
<div>
<div class="text-muted small"><?= h($profile[1]) ?></div><span
class="badge <?= h($profileClass) ?>"><?= h($profileSymbol . ' ' . $profileLabel) ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php require __DIR__ . '/_expense_summary.php'; ?>
<div class="card mt-3">
<div class="card-header">
<h3 class="card-title m-0"><?= h(t('common.notes')) ?></h3>
</div>
<div class="card-body"><?= $animal['notes'] ? nl2br(h($animal['notes'])) : '—' ?></div>
</div>
<?php
$rescueAddr = trim((string)($animal['rescue_address'] ?? ''));
$rescueLat = isset($animal['rescue_lat']) && $animal['rescue_lat'] !== '' ? (float)$animal['rescue_lat'] : null;
$rescueLng = isset($animal['rescue_lng']) && $animal['rescue_lng'] !== '' ? (float)$animal['rescue_lng'] : null;
$currentAddr = trim((string)($animal['current_address'] ?? '5 RUE NOTRE DAME 29260 LESNEVEN'));
$currentLat = isset($animal['current_lat']) && $animal['current_lat'] !== '' ? (float)$animal['current_lat'] : null;
$currentLng = isset($animal['current_lng']) && $animal['current_lng'] !== '' ? (float)$animal['current_lng'] : null;
$hasRescue = ($rescueLat !== null && $rescueLng !== null);
$hasCurrent = ($currentLat !== null && $currentLng !== null);
?>
<div class="card mt-3">
<div class="card-header d-flex align-items-center justify-content-between">
<h3 class="card-title m-0"><?= h(t('animal.location')) ?></h3>
<div class="d-flex gap-2">
<span class="badge bg-azure-lt"><?= h(t('animal.rescue')) ?></span>
<span class="badge bg-lime-lt"><?= h(t('animal.current')) ?></span>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<div class="text-muted mb-1"><?= h(t('animal.rescue_place')) ?></div>
<div class="fw-bold"><?= $rescueAddr !== '' ? h($rescueAddr) : '—' ?></div>
<?php if (!$hasRescue): ?>
<div class="text-muted small mt-1"><?= h(t('animal.coordinates_missing')) ?></div>
<?php endif; ?>
</div>
<div class="col-md-6">
<div class="text-muted mb-1"><?= h(t('animal.form.current_location')) ?></div>
<div class="fw-bold"><?= h($currentAddr) ?></div>
<?php if (!$hasCurrent): ?>
<div class="text-muted small mt-1"><?= h(t('animal.coordinates_missing')) ?></div>
<?php endif; ?>
</div>
</div>
<?php if ($animal['status'] === 'adopte'): ?>
<div class="alert alert-success mt-3">
<strong><?= h(t('animal.adopted_by')) ?> :</strong><br>
<?= htmlspecialchars($adoption['adopter_name'] . ' - ' . $adoption['adopter_address'] . ' ' . $adoption['adopter_postal_code'] . ' ' . $adoption['adopter_city'] . ' - (' . $adoption['adopter_phone'] . ' - ' . $adoption['adopter_email'] . ')') ?>
</div>
<?php endif; ?>
<div class="mt-3" id="mapJourney"
style="height:320px;border-radius:10px;overflow:hidden; border:1px solid #ccc"></div>
<?php
function haversine_km($lat1, $lon1, $lat2, $lon2)
{
$earthRadius = 6371; // km
$dLat = deg2rad($lat2 - $lat1);
$dLon = deg2rad($lon2 - $lon1);
$a = sin($dLat / 2) * sin($dLat / 2) +
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
sin($dLon / 2) * sin($dLon / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
return $earthRadius * $c;
}
$distanceKm = null;
if ($hasRescue && $hasCurrent) {
$distanceKm = haversine_km(
$rescueLat,
$rescueLng,
$currentLat,
$currentLng,
);
}
?>
<?php if ($distanceKm !== null): ?>
<div class="mt-3 text-muted small">
<?= h(t('animal.distance_story', ['name' => $animal['name'],'distance' => number_format($distanceKm, 2)])) ?><br>
<em><?= h(t('animal.distance_caption')) ?></em>
</div>
<?php endif; ?>
</div>
</div>
<?php
/** @var array|null $icad Structure = ['cached'=>..., 'stale'=>bool] comme ton endpoint */
$cache = $icad['cached'] ?? null;
$data = $cache['data'] ?? null;
$status = $cache['status'] ?? 'empty';
$fetched = $cache['fetched_at'] ?? '';
$icadUrl = $data['icad_url'] ?? null;
$badge = match ($status) {
'ok' => 'bg-success-lt',
'error' => 'bg-danger-lt',
default => 'bg-muted-lt',
};
$label = match ($status) {
'ok' => 'i-CAD : OK',
'error' => 'i-CAD : Erreur',
default => t('animal.icad_not_checked'),
};
$err = (string)($cache['error'] ?? '');
?>
<div class="card mt-3">
<div class="card-header d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center gap-2">
<span class="badge <?= $badge ?>"><?= h($label) ?></span>
<?php if ($fetched): ?>
<span class="text-muted small"><?= h(t('animal.last_sync', ['date' => $fetched])) ?></span>
<?php endif; ?>
</div>
<div class="d-flex gap-2">
<?php if ($icadUrl): ?>
<a class="btn btn-sm btn-outline-primary" href="<?= h($icadUrl) ?>" target="_blank" rel="noopener">
<?= h(t('animal.open_icad')) ?>
</a>
<?php endif; ?>
<button class="btn btn-sm btn-outline-secondary" type="button" id="btn-icad-refresh"
data-animal-id="<?= (int)$animal['id'] ?>">
<?= h(t('common.refresh')) ?>
</button>
</div>
</div>
<div class="card-body" id="icad-card-body">
<?php if ($status === 'error'): ?>
<div class="alert alert-danger">
<strong><?= h(t('animal.icad_error')) ?></strong><br>
<?= h($err) ?>
</div>
<?php elseif ($status !== 'ok' || !$data): ?>
<div class="text-muted"><?= h(t('animal.no_icad')) ?></div>
<?php else: ?>
<div class="row g-3">
<div class="col-md-6">
<div class="datagrid">
<div class="datagrid-item">
<div class="datagrid-title"><?= h(t('animal.icad_name')) ?></div>
<div class="datagrid-content"><?= h($data['name'] ?? '') ?></div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Puce</div>
<div class="datagrid-content"><?= h($data['insert'] ?? '') ?></div>
</div>
<div class="datagrid-item">
<div class="datagrid-title"><?= h(t('animal.sex')) ?></div>
<div class="datagrid-content"><?= h($data['sex'] ?? '') ?></div>
</div>
<div class="datagrid-item">
<div class="datagrid-title"><?= h(t('animal.birth')) ?></div>
<div class="datagrid-content"><?= h($data['birth_date'] ?? '') ?></div>
</div>
<div class="datagrid-item">
<div class="datagrid-title"><?= h(t('animal.appearance')) ?></div>
<div class="datagrid-content"><?= h($data['appearance'] ?? '') ?></div>
</div>
</div>
<?php
$holder =
$data['holder'] ??
$data['owner'] ??
$data['detenteur'] ??
null;
// Si ton parse aplatit au lieu d'imbriquer :
if (!$holder && (
isset($data['holder_name']) || isset($data['holder_phone']) || isset($data['holder_email'])
)) {
$holder = [
'name' => $data['holder_name'] ?? '',
'phone' => $data['holder_phone'] ?? '',
'email' => $data['holder_email'] ?? '',
'type' => $data['holder_type'] ?? '',
'address' => $data['holder_address'] ?? '',
];
}
?>
<?php if (is_array($holder) && array_filter($holder, fn ($v) => trim((string)$v) !== '')): ?>
<hr class="my-3">
<div class="row g-3">
<div class="col-md-12">
<div class="d-flex align-items-center justify-content-between mb-2">
<div class="fw-bold"><?= h(t('animal.holder')) ?></div>
<span class="text-muted small"><span
class="badge bg-danger-lt"><?= h(t('animal.do_not_share')) ?></span></span>
</div>
<div class="datagrid">
<?php if (!empty($holder['type'])): ?>
<div class="datagrid-item">
<div class="datagrid-title">Type</div>
<div class="datagrid-content"><?= h($holder['type']) ?></div>
</div>
<?php endif; ?>
<?php if (!empty($holder['name'])): ?>
<div class="datagrid-item">
<div class="datagrid-title"><?= h(t('common.name')) ?></div>
<div class="datagrid-content"><?= h($holder['name']) ?></div>
</div>
<?php endif; ?>
<?php if (!empty($holder['phone'])): ?>
<div class="datagrid-item">
<div class="datagrid-title"><?= h(t('common.phone')) ?></div>
<div class="datagrid-content"><?= h($holder['phone']) ?></div>
</div>
<?php endif; ?>
<?php if (!empty($holder['email'])): ?>
<div class="datagrid-item">
<div class="datagrid-title"><?= h(t('common.email')) ?></div>
<div class="datagrid-content"><?= h($holder['email']) ?></div>
</div>
<?php endif; ?>
<?php if (!empty($holder['address'])): ?>
<div class="datagrid-item" style="grid-column: 1 / -1;">
<div class="datagrid-title"><?= h(t('common.address')) ?></div>
<div class="datagrid-content"><?= nl2br(h($holder['address'])) ?></div>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
</div>
<div class="col-md-6">
<div class="text-muted mb-2"><?= h(t('animal.icad_history')) ?></div>
<?php $events = $data['events'] ?? []; ?>
<?php if (empty($events)): ?>
<div class="text-muted"><?= h(t('animal.no_event')) ?></div>
<?php else: ?>
<ul class="list list-timeline">
<?php foreach ($events as $e): ?>
<li>
<div class="list-timeline-icon bg-blue-lt">
<i class="ti ti-history"></i>
</div>
<div class="list-timeline-content">
<div class="fw-bold"><?= h($e['label'] ?? '') ?></div>
<div class="text-muted small">
<?= h(t('common.date')) ?> : <?= h($e['date'] ?? '') ?> —
<?= h(t('animal.recorded')) ?> : <?= h($e['recorded_at'] ?? '') ?>
</div>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>