185 lines
4.6 KiB
Svelte
185 lines
4.6 KiB
Svelte
<script lang="ts">
|
|
import type { Cabin } from "$lib/types";
|
|
import * as db from "$lib/db";
|
|
|
|
interface Props {
|
|
cabins: Cabin[];
|
|
onclose: () => void;
|
|
onchanged: () => void;
|
|
}
|
|
|
|
let { cabins, onclose, onchanged }: Props = $props();
|
|
|
|
let newName = $state("");
|
|
let newCapacity = $state<number | null>(null);
|
|
let error = $state<string | null>(null);
|
|
|
|
function showModal(el: HTMLDialogElement) {
|
|
el.showModal();
|
|
}
|
|
|
|
async function add(event: SubmitEvent) {
|
|
event.preventDefault();
|
|
error = null;
|
|
const name = newName.trim();
|
|
if (!name) return;
|
|
try {
|
|
await db.createCabin(
|
|
name,
|
|
newCapacity == null || Number.isNaN(newCapacity) ? null : newCapacity
|
|
);
|
|
newName = "";
|
|
newCapacity = null;
|
|
onchanged();
|
|
} catch (e) {
|
|
error = `Nie udało się dodać domku: ${e}`;
|
|
}
|
|
}
|
|
|
|
async function persist(cabin: Cabin) {
|
|
error = null;
|
|
if (!cabin.name.trim()) return;
|
|
try {
|
|
await db.updateCabin({ ...cabin, name: cabin.name.trim() });
|
|
onchanged();
|
|
} catch (e) {
|
|
error = `Nie udało się zapisać zmian: ${e}`;
|
|
}
|
|
}
|
|
|
|
async function move(index: number, dir: -1 | 1) {
|
|
const other = index + dir;
|
|
if (other < 0 || other >= cabins.length) return;
|
|
const a = cabins[index];
|
|
const b = cabins[other];
|
|
// sort_order może się powtarzać — nadaj jednoznaczne wartości wg pozycji na liście
|
|
try {
|
|
await db.updateCabin({ ...a, sort_order: other + 1 });
|
|
await db.updateCabin({ ...b, sort_order: index + 1 });
|
|
onchanged();
|
|
} catch (e) {
|
|
error = `Nie udało się zmienić kolejności: ${e}`;
|
|
}
|
|
}
|
|
|
|
async function remove(cabin: Cabin) {
|
|
if (!confirm(`Usunąć domek „${cabin.name}"? Usunie to również wszystkie jego rezerwacje.`)) {
|
|
return;
|
|
}
|
|
try {
|
|
await db.deleteCabin(cabin.id);
|
|
onchanged();
|
|
} catch (e) {
|
|
error = `Nie udało się usunąć domku: ${e}`;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<dialog {@attach showModal} {onclose}>
|
|
<h2>Domki</h2>
|
|
|
|
{#if cabins.length === 0}
|
|
<p class="hint">Nie ma jeszcze żadnych domków — dodaj pierwszy poniżej.</p>
|
|
{:else}
|
|
<div class="list">
|
|
<span class="head">Nazwa</span>
|
|
<span class="head">Pojemność</span>
|
|
<span class="head"></span>
|
|
{#each cabins as cabin, i (cabin.id)}
|
|
<input
|
|
type="text"
|
|
bind:value={cabin.name}
|
|
onchange={() => persist(cabin)}
|
|
aria-label="Nazwa domku"
|
|
/>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
bind:value={cabin.capacity}
|
|
onchange={() => persist(cabin)}
|
|
aria-label="Pojemność domku"
|
|
placeholder="—"
|
|
/>
|
|
<span class="row-actions">
|
|
<button type="button" class="btn sm" onclick={() => move(i, -1)} disabled={i === 0} aria-label="Przesuń wyżej">↑</button>
|
|
<button type="button" class="btn sm" onclick={() => move(i, 1)} disabled={i === cabins.length - 1} aria-label="Przesuń niżej">↓</button>
|
|
<button type="button" class="btn sm danger" onclick={() => remove(cabin)}>Usuń</button>
|
|
</span>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<form class="add" onsubmit={add}>
|
|
<input type="text" bind:value={newName} placeholder="Nazwa nowego domku" required />
|
|
<input type="number" min="1" bind:value={newCapacity} placeholder="Osób" />
|
|
<button type="submit" class="btn primary">Dodaj</button>
|
|
</form>
|
|
|
|
{#if error}
|
|
<p class="error">{error}</p>
|
|
{/if}
|
|
|
|
<div class="actions">
|
|
<button type="button" class="btn" onclick={onclose}>Zamknij</button>
|
|
</div>
|
|
</dialog>
|
|
|
|
<style>
|
|
dialog {
|
|
width: min(480px, calc(100vw - 32px));
|
|
padding: 20px;
|
|
}
|
|
h2 {
|
|
margin: 0 0 14px;
|
|
font-size: 17px;
|
|
}
|
|
.hint {
|
|
font-size: 13px;
|
|
color: var(--muted);
|
|
}
|
|
.list {
|
|
display: grid;
|
|
grid-template-columns: 1fr 80px auto;
|
|
gap: 6px 8px;
|
|
align-items: center;
|
|
max-height: 320px;
|
|
overflow-y: auto;
|
|
}
|
|
.head {
|
|
font-size: 11px;
|
|
color: var(--muted);
|
|
}
|
|
input {
|
|
font: inherit;
|
|
font-size: 14px;
|
|
color: var(--text);
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 6px 8px;
|
|
min-width: 0;
|
|
}
|
|
.row-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
.add {
|
|
display: grid;
|
|
grid-template-columns: 1fr 80px auto;
|
|
gap: 8px;
|
|
margin-top: 14px;
|
|
padding-top: 14px;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
.error {
|
|
margin: 10px 0 0;
|
|
font-size: 13px;
|
|
color: var(--danger);
|
|
}
|
|
.actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
margin-top: 16px;
|
|
}
|
|
</style>
|