UI and backend logic make ´NameItemEditor´ reusable to be able to use with Vorgang
265 lines
6.7 KiB
Svelte
265 lines
6.7 KiB
Svelte
<script lang="ts">
|
||
import ExpandableForm from '$lib/components/ExpandableForm.svelte';
|
||
import Trash from '$lib/icons/Trash.svelte';
|
||
import Folder from '$lib/icons/Folder.svelte';
|
||
import EmptyList from '$lib/components/EmptyList.svelte';
|
||
import NameItemEditor from '$lib/components/NameItemEditor.svelte';
|
||
import Alert from '$lib/components/Alert.svelte';
|
||
import Button from '$lib/components/Button.svelte';
|
||
import Modal from '$lib/components/Modal/Modal.svelte';
|
||
import ModalTitle from '$lib/components/Modal/ModalTitle.svelte';
|
||
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
|
||
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
|
||
import { API_ROUTES, ROUTE_NAMES } from '../../index.js';
|
||
import { invalidateAll } from '$app/navigation';
|
||
|
||
let { data, form } = $props();
|
||
|
||
let vorgangList = $state(data.vorgangList);
|
||
|
||
// same as `vorgangList` but with one different property to be used
|
||
// with ´NameItemEditor`
|
||
const derivedList = $derived.by(
|
||
() => {
|
||
return vorgangList.map(
|
||
({ vorgangName, ...rest }) => (
|
||
{
|
||
name: vorgangName,
|
||
...rest
|
||
}
|
||
)
|
||
)
|
||
}
|
||
);
|
||
|
||
let isEmptyList = vorgangList.length === 0;
|
||
|
||
let vorgangName = $state('');
|
||
let vorgangPIN = $state('');
|
||
let errorMsg = $state('');
|
||
|
||
// reset input fields when submission successful
|
||
$effect(() => {
|
||
if (form?.token) {
|
||
vorgangName = '';
|
||
vorgangPIN = '';
|
||
errorMsg = '';
|
||
}
|
||
});
|
||
|
||
async function submitVorgang(ev: Event) {
|
||
const isValid = inputValid(vorgangName, vorgangPIN);
|
||
if (!isValid) {
|
||
ev.preventDefault();
|
||
return;
|
||
}
|
||
|
||
// continue form action on server
|
||
}
|
||
|
||
/**
|
||
* Check for required fields
|
||
* @param vorgangName
|
||
* @param vorgangPIN
|
||
* @returns {boolean} Indicates whether input is valid
|
||
*/
|
||
function inputValid(vorgangName, vorgangPIN) {
|
||
if (!(vorgangName || vorgangPIN)) {
|
||
errorMsg = 'Bitte beide Felder ausfüllen.';
|
||
return false;
|
||
} else if (!vorgangName) {
|
||
errorMsg = 'Bitte einen Vorgangsnamen vergeben.';
|
||
return false;
|
||
} else if (!vorgangPIN) {
|
||
errorMsg = 'Bitte einen Vorgangs-PIN eingeben.';
|
||
return false;
|
||
}
|
||
|
||
const existing = vorgangList.some((vorg) => vorg.vorgangName === vorgangName);
|
||
if (existing) {
|
||
errorMsg = 'Der Name existiert bereits.';
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
async function deleteVorgang(vorgangToken: string) {
|
||
let delete_item = window.confirm('Bist du sicher?');
|
||
|
||
if (delete_item) {
|
||
let url = API_ROUTES.VORGANG(vorgangToken);
|
||
|
||
try {
|
||
const response = await fetch(url, { method: 'DELETE' });
|
||
if (response.status == 204) {
|
||
setTimeout(() => {
|
||
window.location.reload();
|
||
}, 500);
|
||
}
|
||
} catch (error) {
|
||
if (error instanceof Error) {
|
||
console.log(error.message);
|
||
} else {
|
||
console.log(error);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//Variablen für Modal
|
||
let open = $state(false);
|
||
let inProgress = $state(false);
|
||
let isError = $state(false);
|
||
|
||
async function handleSave(newName: string, oldName: string, vorgangToken: string) {
|
||
open = true;
|
||
inProgress = true;
|
||
isError = false;
|
||
try {
|
||
const res = await fetch(API_ROUTES.VORGANG(vorgangToken), {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ vorgangToken, oldName, newName })
|
||
});
|
||
|
||
if (!res.ok) {
|
||
throw new Error('Fehler beim Speichern');
|
||
}
|
||
await invalidateAll();
|
||
vorgangList = data.vorgangList;
|
||
open = false;
|
||
} catch (err) {
|
||
console.error('⚠️ Netzwerkfehler beim Speichern', err);
|
||
isError = true;
|
||
} finally {
|
||
inProgress = false;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
async function closeModal() {
|
||
open = false;
|
||
isError = false;
|
||
}
|
||
|
||
</script>
|
||
|
||
<div class="-z-10 bg-white">
|
||
<div class="flex flex-col items-center justify-center w-full">
|
||
<h1 class="text-xl">Liste der Vorgänge</h1>
|
||
</div>
|
||
<div class="mx-auto flex justify-center max-w-7xl h-full">
|
||
<ul role="list" class="divide-y divide-gray-100">
|
||
{#if isEmptyList}
|
||
<EmptyList></EmptyList>
|
||
{:else}
|
||
{#each vorgangList as vorgangItem}
|
||
<li data-testid="test-list-item">
|
||
<div class="flex items-center justify-center gap-3">
|
||
<a
|
||
href="{ROUTE_NAMES.VORGANG(vorgangItem.vorgangToken)}"
|
||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-gray-50 transition text-center"
|
||
>
|
||
<Folder class="w-6 h-6 text-gray-600" />
|
||
</a>
|
||
<NameItemEditor
|
||
list={derivedList}
|
||
currentName={vorgangItem.vorgangName}
|
||
vorgangToken={vorgangItem.vorgangToken}
|
||
onSave={handleSave}
|
||
onDelete={deleteVorgang}
|
||
/>
|
||
</div>
|
||
</li>
|
||
{/each}
|
||
{/if}
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<ExpandableForm>
|
||
<form class="flex flex-col items-center" method="POST">
|
||
<div class="flex flex-col sm:flex-row sm:space-x-4 w-full max-w-lg">
|
||
<div class="flex-1">
|
||
<label for="vorgang" class="block text-sm font-medium leading-6 text-gray-900">
|
||
<span class="flex"> Vorgangsname </span>
|
||
</label>
|
||
<div class="mt-2">
|
||
<div
|
||
class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"
|
||
>
|
||
<input
|
||
required
|
||
bind:value={vorgangName}
|
||
type="text"
|
||
name="vorgang"
|
||
id="vorgang"
|
||
class="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex-1 mt-4 sm:mt-0">
|
||
<label for="pin" class="block text-sm font-medium leading-6 text-gray-900">
|
||
<span class="flex"> PIN </span>
|
||
</label>
|
||
<div class="mt-2">
|
||
<div
|
||
class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"
|
||
>
|
||
<input
|
||
required
|
||
type="password"
|
||
bind:value={vorgangPIN}
|
||
name="pin"
|
||
id="pin"
|
||
class="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{#if errorMsg}
|
||
<p>{errorMsg}</p>
|
||
{/if}
|
||
{#if form?.message}
|
||
<p>{form.message}</p>
|
||
{/if}
|
||
<button
|
||
type="submit"
|
||
on:click={submitVorgang}
|
||
class="mt-4 bg-indigo-600 text-white px-6 py-2 rounded hover:bg-indigo-700 transition"
|
||
>
|
||
Neuen Vorgang hinzufügen
|
||
</button>
|
||
</form>
|
||
|
||
</ExpandableForm>
|
||
|
||
<Modal {open}
|
||
><ModalTitle>Umbenennen</ModalTitle><ModalContent>
|
||
{#if inProgress}
|
||
<p class="py-2 mb-1">Vorgang läuft...</p>
|
||
{:else if isError}
|
||
<Alert class="w-full" type="error">Fehler beim Umbenennen</Alert>
|
||
{:else}
|
||
<Alert class="w-full">Umbenennen erfolgreich</Alert>
|
||
{/if}
|
||
</ModalContent>
|
||
<ModalFooter><Button disabled={inProgress} on:click={closeModal}>Ok</Button></ModalFooter>
|
||
</Modal>
|
||
|
||
<style>
|
||
ul {
|
||
min-width: 24rem;
|
||
}
|
||
</style>
|
||
|