f047_Edit-der-Namen #15

Closed
jared wants to merge 11 commits from f047_Edit-der-Namen into development
4 changed files with 262 additions and 183 deletions
Showing only changes of commit f335f7a13f - Show all commits

View File

@@ -1,126 +1,101 @@
<script lang="ts"> <script lang="ts">
import Edit from '$lib/icons/Edit.svelte'; import Edit from '$lib/icons/Edit.svelte';
import Trash from '$lib/icons/Trash.svelte'; import Trash from '$lib/icons/Trash.svelte';
import { validateInput } from '$lib/helper/error-utils'; import { tick } from 'svelte';
interface ListItem { interface ListItem {
name: string; name: string;
token?: string; token?: string;
// add other properties as needed // add other properties as needed
} }
let {
list,
editedName = $bindable(),
currentName,
mina marked this conversation as resolved Outdated
Outdated
Review

Der Kommentar könnte m.M.n. raus

Der Kommentar könnte m.M.n. raus
onSave = () => {},
onDelete = () => {}
} = $props();
type Variant = '' | 'casename' | 'crimename'; let names = list.map((l: ListItem) => l.name);
let localName = $state(currentName);
mina marked this conversation as resolved Outdated
Outdated
Review

Bitte Console.log entfernen.

Bitte Console.log entfernen.
//let names = list;
let wasCancelled = $state(false);
export let inputValue: string = ''; // Automatisch berechneter Fehler
export let variant: Variant = ''; let error: string = $derived(validateName(localName));
mina marked this conversation as resolved Outdated
Outdated
Review

Typo editSart to editStart

Typo editSart to editS**t**art
export let existings: ListItem[];
export let id: number;
export let editable: boolean = true;
export let editing: boolean;
//CustomEvents: // Manuell steuerbarer Fehlerstatus
export let editStart: (payload: { let manualError = $state('');
id: number;
inputValue: string;
variant: Variant;
editing: boolean;
}) => void;
export let save: (payload: { newValue: string; oldValue: string; variant: Variant }) => void; console.log('EditableItem: Beginn', names, editedName);
jared marked this conversation as resolved Outdated
Outdated
Review

The signature '(): EventDispatcher<{ editSart: {}; save: {}; delete: {}; cancel: void; }>' of 'createEventDispatcher' is deprecated.

Mittlerweile wird ein Event mittels Callback function dispatched. Gucke ggf. noch einmal in die Docs.

Zum Beispiel:

<script type="ts"> const props: { onClick(): void; onExplode(name: string, age: number): void; } = $props(); </script>

<MyComponent
onClick={() => alert('clicked')}
onExplode={(name, age) => alert(name + ' ' + age)}
/>

The signature '(): EventDispatcher<{ editSart: {}; save: {}; delete: {}; cancel: void; }>' of 'createEventDispatcher' is deprecated. Mittlerweile wird ein Event mittels Callback function dispatched. Gucke ggf. noch einmal in die Docs. Zum Beispiel: <script type="ts"> const props: { onClick(): void; onExplode(name: string, age: number): void; } = $props(); </script> <MyComponent onClick={() => alert('clicked')} onExplode={(name, age) => alert(name + ' ' + age)} />
Outdated
Review

Ich habe es angepasst, es musste einiges verändert werden, bitte schaue es dir nochmal an.

Ich habe es angepasst, es musste einiges verändert werden, bitte schaue es dir nochmal an.
export let deleteItem: (payload: {
inputValue: string;
variant: Variant;
customEvent: Event;
}) => void;
export let cancel: () => void = () => {};
const existingNames = existings.map((item) => item.name);
jared marked this conversation as resolved Outdated
Outdated
Review

Villt die Variable noch besser bennennen. InternalValue oder oldValue, kann halt alles sein...

Villt die Variable noch besser bennennen. InternalValue oder oldValue, kann halt alles sein...
Outdated
Review

Allgemein, weil es eine Komponente ist, die vielseitig einsetzbar. Es geht darum quasi den Startwert? zu speichern, für den Abbruch und den aktuellen Wert, um ein editieren in Gang zu bringen. Man könnte es auch cancelValue und EditValue nennen? Vielleicht besser?

Allgemein, weil es eine Komponente ist, die vielseitig einsetzbar. Es geht darum quasi den Startwert? zu speichern, für den Abbruch und den aktuellen Wert, um ein editieren in Gang zu bringen. Man könnte es auch cancelValue und EditValue nennen? Vielleicht besser?
Outdated
Review

Naja, der Begriff Value bringt halt nicht, weil das quasi immer ein value ist. Daher so explizit bennen wie es geht

Naja, der Begriff Value bringt halt nicht, weil das quasi immer ein value ist. Daher so explizit bennen wie es geht
export { classNames as class }; let isEditing = $state(false);
let inputRef: HTMLInputElement;
let internalValue = inputValue; // Validierungsfunktion
let oldValue = inputValue; function validateName(name: string) {
let showWarning = false; const trimmed = name.trim();
mina marked this conversation as resolved Outdated
Outdated
Review

Wird im weiteren Verlauf nicht verwendet, daher löschen

Wird im weiteren Verlauf nicht verwendet, daher löschen
$: errors = validateInput(oldValue, internalValue, { minLength: 3, existingNames }); if (!trimmed) {
return 'Name darf nicht leer sein.';
function startEdit() {
oldValue = inputValue;
internalValue = inputValue;
editing = true;
editStart({ id, inputValue, variant, editing });
} }
function cancelEdit() { const duplicate = list.some(
internalValue = oldValue; (item: ListItem) => item.name === trimmed && item.name !== currentName
editing = false; );
showWarning = false;
console.log('Abgebrochen'); if (duplicate) return 'Name existiert bereits.';
cancel(); return '';
} }
function saveEdit(ev?: MouseEvent | KeyboardEvent) { // Speichern, wenn gültig und zurück an Eltern
ev?.preventDefault(); function commitIfValid() {
ev?.stopPropagation(); if (!validateName(localName) && !wasCancelled && localName != currentName) {
editedName = localName.trim();
const name = internalValue.trim(); onSave(editedName, currentName); //Eltern benachrichtigen
} else {
if (errors.length !== 0) { resetEdit();
showWarning = true;
console.log('Abgebrochen', errors);
return;
} }
if (!name) {
showWarning = true;
console.log('Abgebrochen', showWarning);
return;
} }
if (existingNames.includes(name) && name !== oldValue.trim()) { // Abbrechen Eingabe zurücksetzen
showWarning = true; function resetEdit() {
console.log('Abgebrochen', showWarning); wasCancelled = false;
return; manualError = '';
inputRef?.blur();
isEditing = false;
} }
editing = false; // Tastatursteuerung
showWarning = false; function handleKeydown(event: KeyboardEvent) {
save({ newValue: internalValue, oldValue, variant }); if (event.key === 'Enter') {
event.preventDefault();
commitIfValid();
} else if (event.key === 'Escape') {
event.preventDefault();
editedName = currentName;
resetEdit();
}
} }
function handleKey(e: KeyboardEvent) { async function startEdit() {
if (e.key === 'Enter') saveEdit(e); isEditing = true;
if (e.key === 'Escape') cancelEdit(); await tick();
inputRef?.focus();
} }
</script> </script>
<div class=""> <div>
<div class="flex gap-x-2">
{#if editing}
<input <input
bind:value={internalValue} bind:this={inputRef}
on:keydown|preventDefault={handleKey} bind:value={localName}
on:blur|preventDefault|stopPropagation={cancelEdit} onblur={commitIfValid}
class="" onkeydown={handleKeydown}
class:bg-red-200={(showWarning && editing) || errors.length !== 0}
/> />
{:else} <button onclick={startEdit}><Edit /></button>
<span>{inputValue}</span> <button onclick={() => onDelete(currentName)}><Trash /></button>
{#if !editing && editable} {#if manualError || error}
<button on:click|preventDefault|stopPropagation={startEdit}> <p style="color: red;">{manualError || error}</p>
<Edit />
</button>
<button
on:click|preventDefault={(e: Event) => {
deleteItem({ inputValue, variant, customEvent: e });
}}
>
<Trash />
</button>
{/if}
{/if}
</div>
{#if editing && errors}
<p class="text-red-600 text-sm mt-1 font-medium">{errors[0]}</p>
{/if} {/if}
</div> </div>

View File

@@ -2,17 +2,23 @@
import Folder from '$lib/icons/Folder.svelte'; import Folder from '$lib/icons/Folder.svelte';
import EditableItem from '$lib/components/EditableItem.svelte'; import EditableItem from '$lib/components/EditableItem.svelte';
export let data;
export let editingId: number;
interface ListItem { interface ListItem {
name: string; name: string;
token?: string; token?: string;
// add other properties as needed // add other properties as needed
Review

Man könnte einen Ordner für type anlegen src/lib/types/...

Man könnte einen Ordner für type anlegen src/lib/types/...
Review

s.u.

s.u.
} }
let { data } = $props();
const caseList: ListItem[] = data.caseList; const caseList: ListItem[] = data.caseList;
//Variabeln für EditableItem
let names: string[] = $state(caseList.map((l) => l.name));
let editedName: string = $state('');
function getNameById(list: ListItem[], id: number) {
return list[id].name;
}
async function delete_item(ev: Event) { async function delete_item(ev: Event) {
let delete_item = window.confirm('Bist du sicher?'); let delete_item = window.confirm('Bist du sicher?');
@@ -42,6 +48,51 @@
} }
} }
} }
async function handleSave(newName: string, oldName: string) {
console.log('Eltern, speichern erfolgreich', newName, oldName);
try {
const res = await fetch(`/api/list/${oldName}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ oldName, newName })
});
if (!res.ok) {
const msg = await res.text();
console.error('❌ Fehler beim Speichern:', msg);
} else {
console.log('✅ Erfolgreich gespeichert:', newName);
}
} catch (err) {
console.error('⚠️ Netzwerkfehler:', err);
}
}
async function handleDelete(name: string) {
try {
const res = await fetch(`/api/list/${name}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name })
});
if (!res.ok) {
const msg = await res.text();
console.error('❌ Fehler beim Löschen:', msg);
} else {
console.log('🗑️ Erfolgreich gelöscht:', name);
// Optional: Lokale Liste aktualisieren
//S items = items.filter((item) => item.name !== name);
}
} catch (err) {
console.error('⚠️ Netzwerkfehler beim Löschen:', err);
}
}
</script> </script>
<div class="-z-10 bg-white"> <div class="-z-10 bg-white">
@@ -52,33 +103,28 @@
<ul role="list" class="divide-y divide-gray-100"> <ul role="list" class="divide-y divide-gray-100">
{#each caseList as item, i} {#each caseList as item, i}
<li> <li>
<a href="/list/{item.name}?token={item.token}" class="flex justify-between gap-x-6 py-5">
<div class="flex gap-x-4"> <div class="flex gap-x-4">
<!-- Ordner --> <!-- Ordner -->
<a
href="/list/{item.name}?token={item.token}"
class="bg-red-500 flex justify-between gap-x-6 py-5"
>
<Folder /> <Folder />
</a>
<div class="min-w-0 flex-auto"> <div class="min-w-0 flex-auto">
<EditableItem <EditableItem
class="" list={caseList}
id={i} bind:editedName={names[i]}
inputValue={item.name} currentName={item.name}
editing={editingId === i} onSave={handleSave}
editStart={() => (editingId = i)} onDelete={handleDelete}
variant="casename"
existings={caseList}
save={(data) => {
console.log('Gespeichert:', data);
}}
deleteItem={(data) => {
console.log('Gelöscht:', data);
delete_item(data.customEvent);
}}
></EditableItem> ></EditableItem>
</div> </div>
<div class="hidden sm:flex sm:flex-col sm:items-end"> <div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">Vorgang</p> <p class="text-sm leading-6 text-gray-900">Vorgang</p>
</div> </div>
</div> </div>
</a>
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@@ -122,17 +122,17 @@
<ul class="divide-y divide-gray-100"> <ul class="divide-y divide-gray-100">
{#each crimesList as item, i} {#each crimesList as item, i}
<li> <li>
<div class=" flex gap-x-4">
<a <a
href="/view/{$page.params.vorgang}/{item.name}?token={token}" href="/view/{$page.params.vorgang}/{item.name}?token={token}"
class=" flex justify-between gap-x-6 py-5" class=" flex justify-between gap-x-6 py-5"
aria-label="zum 3D-modell" aria-label="zum 3D-modell"
> >
<div class=" flex gap-x-4">
<Cube /> <Cube />
</a>
<div class="min-w-0 flex-auto"> <div class="min-w-0 flex-auto">
{#if admin} {#if admin}
<EditableItem <EditableItem
class="bg-lred-500"
id={i} id={i}
inputValue={item.name} inputValue={item.name}
editing={editingId === i} editing={editingId === i}
@@ -164,7 +164,6 @@
</p> </p>
{/if} {/if}
</div> </div>
</a>
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@@ -1,24 +1,83 @@
import { client } from '$lib/minio'; import { client } from '$lib/minio';
import type { RequestHandler } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
export async function DELETE({ params }) {
const vorgang = params.vorgang;
const object_list = await new Promise((resolve, reject) => {
const res = [];
const items_str = client.listObjects('tatort', vorgang, true);
items_str.on('data', (obj) => { // Beispiel-Datenquelle (ersetzen durch echte DB oder Datei)
res.push(obj.name); let mockList = [ "202505-test-se", "Minas-TestVorgang", "Testing-Mina", "xyz-123" ];
});
items_str.on('error', reject); export const GET: RequestHandler = async ({ params }) => {
const { filename } = params;
items_str.on('end', async () => { // TODO: Datei lesen oder Datenbankabfrage
resolve(res); return json({ filename, list: mockList });
}); };
});
await client.removeObjects('tatort', object_list); export const PUT: RequestHandler = async ({ request, params }) => {
const { filename } = params;
const { oldName, newName } = await request.json();
return new Response(null, { status: 204 }); if (!newName || !newName.trim()) {
return new Response('Ungültiger Name', { status: 400 });
} }
const index = mockList.findIndex((name) => name === oldName);
if (index === -1) {
return new Response('Name nicht gefunden', { status: 404 });
}
if (mockList.includes(newName)) {
return new Response('Name existiert bereits', { status: 409 });
}
console.log('📥 PUT-Request empfangen:', mockList);
mockList[index] = newName;
console.log('📄 Datei:', filename);
console.log('🔁 Umbenennen:', oldName, '→', newName, mockList);
// return new Response(JSON.stringify({ success: true }), { status: 200 });
return json({ success: true, updated: newName });
};
export const DELETE: RequestHandler = async ({ request, params }) => {
const { filename } = params;
const { name } = await request.json();
const index = mockList.findIndex((n) => n === name);
if (index === -1) {
return new Response('Name nicht gefunden', { status: 404 });
}
console.log('📥 DELETE-Request empfangen:', mockList);
mockList.splice(index, 1);
console.log('📄 Datei:', filename);
console.log('🔁 gelöscht', mockList);
return json({ success: true, deleted: name });
};
// export async function DELETE({ params }) {
// const vorgang = params.vorgang;
// const object_list = await new Promise((resolve, reject) => {
// const res = [];
// const items_str = client.listObjects('tatort', vorgang, true);
// items_str.on('data', (obj) => {
// res.push(obj.name);
// });
// items_str.on('error', reject);
// items_str.on('end', async () => {
// resolve(res);
// });
// });
// await client.removeObjects('tatort', object_list);
// return new Response(null, { status: 204 });
// }