Init
This commit is contained in:
214
src/lib/components/CabinsDialog.svelte
Normal file
214
src/lib/components/CabinsDialog.svelte
Normal file
@@ -0,0 +1,214 @@
|
||||
<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" onclick={() => move(i, -1)} disabled={i === 0} aria-label="Przesuń wyżej">↑</button>
|
||||
<button type="button" onclick={() => move(i, 1)} disabled={i === cabins.length - 1} aria-label="Przesuń niżej">↓</button>
|
||||
<button type="button" class="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="primary">Dodaj</button>
|
||||
</form>
|
||||
|
||||
{#if error}
|
||||
<p class="error">{error}</p>
|
||||
{/if}
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" onclick={onclose}>Zamknij</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
width: min(480px, calc(100vw - 32px));
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
dialog::backdrop {
|
||||
background: rgb(0 0 0 / 0.35);
|
||||
}
|
||||
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;
|
||||
}
|
||||
button {
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
padding: 5px 9px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
button.danger {
|
||||
color: var(--danger);
|
||||
border-color: var(--danger);
|
||||
}
|
||||
button.primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
.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>
|
||||
Reference in New Issue
Block a user