Publier Globinours 1.0.0-rc.3
This commit is contained in:
parent
ea8c24d622
commit
9a2b4068da
325 changed files with 38230 additions and 20 deletions
226
app/Views/litter_form.php
Normal file
226
app/Views/litter_form.php
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
$coatColors = [
|
||||
'Bicolore Noir / Blanc' => t('coat.black_white'), 'Bicolore Gris / Blanc' => t('coat.grey_white'), 'Bicolore Roux / Blanc' => t('coat.ginger_white'), 'Bicolore Tigré et Blanc' => t('coat.tabby_white'),
|
||||
'Gris' => t('coat.grey'), 'Roux' => t('coat.ginger'), 'Noir' => t('coat.black'), 'Chocolat' => t('coat.chocolate'), 'Blanc' => t('coat.white'), 'Tricolore / Calico' => t('coat.calico'),
|
||||
'Écaille de tortue' => t('coat.tortoiseshell'), 'Colourpoint / Siamois' => t('coat.colourpoint'), 'Tigré / Tabby' => t('coat.tabby'),
|
||||
];
|
||||
$prefillCount = max(0, min(20, (int)($_GET['count'] ?? 4)));
|
||||
?>
|
||||
<form method="post" action="/litter/save" class="card">
|
||||
<input type="hidden" name="csrf" value="<?= h(Auth::csrf()) ?>">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<h3 class="card-title"><?= h(t('litter.create')) ?></h3>
|
||||
<a href="<?= $sourceAnimalId ? '/animal?id=' . (int)$sourceAnimalId . '#tab-litters' : '/animals' ?>"
|
||||
class="btn btn-ghost-secondary"><?= h(t('common.cancel')) ?></a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input type="hidden" name="source_animal_id" value="<?= (int)$sourceAnimalId ?>">
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label"><?= h(t('litter.mother')) ?> <span
|
||||
class="text-muted"><?= h(t('common.optional_parentheses')) ?></span></label>
|
||||
<select class="form-select" name="mother_id" id="motherId">
|
||||
<option value="">— <?= h(t('litter.unknown_parent')) ?> —</option>
|
||||
<?php foreach ($mothers as $parent): ?>
|
||||
<option value="<?= (int)$parent['id'] ?>"
|
||||
<?= (int)$motherId === (int)$parent['id'] ? 'selected' : '' ?>><?= h($parent['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label"><?= h(t('litter.father')) ?> <span
|
||||
class="text-muted"><?= h(t('common.optional_parentheses')) ?></span></label>
|
||||
<select class="form-select" name="father_id" id="fatherId">
|
||||
<option value="">— <?= h(t('litter.unknown_parent')) ?> —</option>
|
||||
<?php foreach ($fathers as $parent): ?>
|
||||
<option value="<?= (int)$parent['id'] ?>"
|
||||
<?= (int)$fatherId === (int)$parent['id'] ? 'selected' : '' ?>><?= h($parent['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label"><?= h(t('litter.birth_date')) ?> <span
|
||||
class="text-muted"><?= h(t('common.optional_parentheses')) ?></span></label>
|
||||
<input class="form-control" type="date" name="birth_date" placeholder="jj/mm/aaaa">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="kitten_mode" id="kittenMode" value="new">
|
||||
<div class="nav nav-tabs mb-4" role="tablist" aria-label="<?= h(t('litter.kitten_type')) ?>">
|
||||
<button class="nav-link active" type="button" data-kitten-mode="new" aria-selected="true">✨
|
||||
<?= h(t('litter.new_kittens')) ?></button>
|
||||
<button class="nav-link" type="button" data-kitten-mode="existing" aria-selected="false">🔎
|
||||
<?= h(t('litter.existing_cats')) ?></button>
|
||||
</div>
|
||||
|
||||
<div id="newKittensPane">
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="kittenCount"><?= h(t('litter.kitten_count')) ?></label>
|
||||
<input class="form-control" id="kittenCount" type="number" min="1" max="20" step="1"
|
||||
value="<?= max(1, $prefillCount) ?>" required>
|
||||
<div class="form-hint"><?= h(t('litter.create_records')) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<h4 class="mb-0"><?= h(t('litter.new_records')) ?></h4>
|
||||
<div class="text-muted small"><?= h(t('litter.auto_name')) ?></div>
|
||||
</div>
|
||||
<div id="kittens" class="vstack gap-2"></div>
|
||||
</div>
|
||||
|
||||
<div id="existingKittensPane" class="d-none">
|
||||
<div class="mb-3">
|
||||
<h4 class="mb-1"><?= h(t('litter.select_siblings')) ?></h4>
|
||||
<div class="text-muted small mb-3"><?= h(t('litter.no_new_record')) ?></div>
|
||||
<input class="form-control" id="existingKittenSearch" type="search"
|
||||
placeholder="<?= h(t('litter.search_name_code')) ?>" autocomplete="off">
|
||||
</div>
|
||||
<div class="row g-2" id="existingKittenChoices">
|
||||
<?php foreach (($existingKittens ?? []) as $candidate): ?>
|
||||
<div class="col-md-4 existing-kitten-choice"
|
||||
data-search="<?= h(mb_strtolower($candidate['name'] . ' ' . $candidate['internal_code'])) ?>"
|
||||
data-animal-id="<?= (int)$candidate['id'] ?>">
|
||||
<label class="form-select d-flex align-items-center gap-2 h-100" style="cursor:pointer">
|
||||
<input class="form-check-input mt-0 existing-kitten-checkbox" type="checkbox"
|
||||
name="existing_kitten_ids[]" value="<?= (int)$candidate['id'] ?>" disabled>
|
||||
<span class="coat-swatch" style="<?= h(coat_swatch_style($candidate['color'] ?? '')) ?>"></span>
|
||||
<span><strong><?= h($candidate['name']) ?></strong><br><small
|
||||
class="text-muted"><?= h($candidate['internal_code'] ?: t('animal.no_code')) ?></small></span>
|
||||
<?php if (!empty($candidate['archived_at'])): ?><span
|
||||
class="badge bg-secondary-lt ms-auto"><?= h(t('animal.archived_badge')) ?></span><?php endif; ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<button class="btn btn-primary" type="submit"><?= h(t('litter.create_litter')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<template id="kittenTemplate">
|
||||
<div class="card card-sm kitten-row">
|
||||
<div class="card-body row g-2 align-items-end">
|
||||
<div class="col-md-1 fw-bold kitten-number"></div>
|
||||
<div class="col-md-4"><label class="form-label"><?= h(t('litter.name_optional')) ?></label><input
|
||||
class="form-control kitten-name" placeholder="<?= h(t('litter.auto_name_placeholder')) ?>"></div>
|
||||
<div class="col-md-3"><label class="form-label"><?= h(t('animal.coat')) ?></label><select
|
||||
class="form-select kitten-color">
|
||||
<option value=""><?= h(t('common.to_define')) ?></option>
|
||||
<?php foreach ($coatColors as $value => $label): ?><option value="<?= h($value) ?>"><?= h($label) ?>
|
||||
</option><?php endforeach; ?>
|
||||
</select></div>
|
||||
<div class="col-md-2"><label class="form-label"><?= h(t('animal.sex_label')) ?></label><select
|
||||
class="form-select kitten-sex">
|
||||
<option value="U"><?= h(t('common.to_define')) ?></option>
|
||||
<option value="F"><?= h(t('sex.female')) ?></option>
|
||||
<option value="M"><?= h(t('sex.male')) ?></option>
|
||||
</select></div>
|
||||
<div class="col-md-2"><button class="btn btn-outline-danger w-100 remove-kitten"
|
||||
type="button"><?= h(t('common.remove')) ?></button></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
(() => {
|
||||
const list = document.getElementById('kittens');
|
||||
const template = document.getElementById('kittenTemplate');
|
||||
const countInput = document.getElementById('kittenCount');
|
||||
const modeInput = document.getElementById('kittenMode');
|
||||
const newPane = document.getElementById('newKittensPane');
|
||||
const existingPane = document.getElementById('existingKittensPane');
|
||||
|
||||
function renumber() {
|
||||
[...list.children].forEach((row, index) => {
|
||||
row.querySelector('.kitten-number').textContent = 'N° ' + (index + 1);
|
||||
row.querySelector('.kitten-name').name = `kittens[${index}][name]`;
|
||||
row.querySelector('.kitten-color').name = `kittens[${index}][color]`;
|
||||
row.querySelector('.kitten-sex').name = `kittens[${index}][sex]`;
|
||||
});
|
||||
}
|
||||
|
||||
function add() {
|
||||
const row = template.content.firstElementChild.cloneNode(true);
|
||||
row.querySelector('.remove-kitten').addEventListener('click', () => {
|
||||
row.remove();
|
||||
countInput.value = list.children.length;
|
||||
renumber();
|
||||
});
|
||||
list.appendChild(row);
|
||||
renumber();
|
||||
}
|
||||
|
||||
function requestedCount() {
|
||||
const value = parseInt(countInput.value, 10);
|
||||
return Math.max(1, Math.min(20, Number.isNaN(value) ? 1 : value));
|
||||
}
|
||||
|
||||
function hasValues(row) {
|
||||
return row.querySelector('.kitten-name').value !== '' ||
|
||||
row.querySelector('.kitten-color').value !== '' ||
|
||||
row.querySelector('.kitten-sex').value !== 'U';
|
||||
}
|
||||
|
||||
function syncCount(confirmRemoval = false) {
|
||||
const target = requestedCount();
|
||||
if (target < list.children.length) {
|
||||
const removedRows = [...list.children].slice(target);
|
||||
if (confirmRemoval && removedRows.some(hasValues) &&
|
||||
!window.confirm( < ? = json_encode(t('litter.reduce_confirm'), JSON_UNESCAPED_UNICODE |
|
||||
JSON_UNESCAPED_SLASHES) ? > )) {
|
||||
countInput.value = list.children.length;
|
||||
return;
|
||||
}
|
||||
while (list.children.length > target) list.lastElementChild.remove();
|
||||
}
|
||||
while (list.children.length < target) add();
|
||||
countInput.value = target;
|
||||
renumber();
|
||||
}
|
||||
countInput.addEventListener('input', () => {
|
||||
if (requestedCount() > list.children.length) syncCount();
|
||||
});
|
||||
countInput.addEventListener('change', () => syncCount(true));
|
||||
syncCount();
|
||||
const existingSearch = document.getElementById('existingKittenSearch');
|
||||
const mother = document.getElementById('motherId');
|
||||
const father = document.getElementById('fatherId');
|
||||
const syncExistingChoices = () => {
|
||||
const query = existingSearch?.value.trim().toLocaleLowerCase( < ? = json_encode(I18n::locale() ===
|
||||
'fr' ? 'fr' : 'en') ? > ) || '';
|
||||
document.querySelectorAll('.existing-kitten-choice').forEach(choice => {
|
||||
const isParent = choice.dataset.animalId === mother?.value || choice.dataset
|
||||
.animalId === father?.value;
|
||||
choice.classList.toggle('d-none', isParent || (query !== '' && !choice.dataset.search
|
||||
.includes(query)));
|
||||
if (isParent) choice.querySelector('.existing-kitten-checkbox').checked = false;
|
||||
});
|
||||
};
|
||||
existingSearch?.addEventListener('input', syncExistingChoices);
|
||||
mother?.addEventListener('change', syncExistingChoices);
|
||||
father?.addEventListener('change', syncExistingChoices);
|
||||
syncExistingChoices();
|
||||
const setMode = mode => {
|
||||
const existing = mode === 'existing';
|
||||
modeInput.value = existing ? 'existing' : 'new';
|
||||
newPane.classList.toggle('d-none', existing);
|
||||
existingPane.classList.toggle('d-none', !existing);
|
||||
newPane.querySelectorAll('input,select').forEach(input => input.disabled = existing);
|
||||
existingPane.querySelectorAll('input[type="checkbox"]').forEach(input => input.disabled = !
|
||||
existing);
|
||||
document.querySelectorAll('[data-kitten-mode]').forEach(button => {
|
||||
const active = button.dataset.kittenMode === mode;
|
||||
button.classList.toggle('active', active);
|
||||
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||
});
|
||||
};
|
||||
document.querySelectorAll('[data-kitten-mode]').forEach(button => button.addEventListener('click', () =>
|
||||
setMode(button.dataset.kittenMode)));
|
||||
setMode('new');
|
||||
})();
|
||||
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue