upd inventory

This commit is contained in:
beo3000 2026-03-06 19:48:52 +01:00
parent aa07fe016e
commit fb47aacd83
6 changed files with 79 additions and 17 deletions

View File

@ -1 +1 @@
1.2.28
1.2.31

View File

@ -1072,6 +1072,15 @@ export async function softDeleteAssetImage(id: string): Promise<void> {
}
}
export async function reorderAssetImages(orderedIds: string[]): Promise<void> {
await db.transaction('rw', db.assetImages, async () => {
for (let i = 0; i < orderedIds.length; i++) {
const ai = await db.assetImages.get(orderedIds[i]);
if (ai) await db.assetImages.update(orderedIds[i], { sortOrder: i, updatedAt: now(), version: ai.version + 1 });
}
});
}
// --- AssetPersons ---
export async function addAssetPerson(assetId: string, personId: string): Promise<AssetPerson> {

View File

@ -353,13 +353,13 @@ async function doSync(since: Date | null): Promise<void> {
if (_syncInProgress) return;
_syncInProgress = true;
syncStatus.set('syncing');
const syncStartedAt = new Date(); // record before push to avoid missing entities created during sync
try {
await pushAll(since);
const sinceWithBuffer = since ? new Date(since.getTime() - 60_000) : null;
const serverTimestamp = await pullAndMerge(sinceWithBuffer);
const syncTime = new Date(serverTimestamp);
lastSyncAt.set(syncTime);
localStorage.setItem('lastSyncAt', syncTime.toISOString());
await pullAndMerge(sinceWithBuffer);
lastSyncAt.set(syncStartedAt);
localStorage.setItem('lastSyncAt', syncStartedAt.toISOString());
syncStatus.set('idle');
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);

View File

@ -4,14 +4,14 @@
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import {
updateAsset, softDeleteAsset, getAssetImages, addAssetImage, softDeleteAssetImage,
updateAsset, softDeleteAsset, addAssetImage, softDeleteAssetImage, reorderAssetImages,
getAssetPersons, addAssetPerson, removeAssetPerson, getFamilyPersons, getRooms,
} from '$lib/db/repositories';
import { getAccessToken } from '$lib/auth/authStore';
import { getImageUrl } from '$lib/db/imageStore';
import MarkdownEditor from '$lib/components/MarkdownEditor.svelte';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import { ChevronLeft, Package, Plus, Trash2, ScanSearch, Sparkles } from 'lucide-svelte';
import { ChevronLeft, Package, Plus, Trash2, ScanSearch, Sparkles, Star } from 'lucide-svelte';
import DarkSelect from '$lib/components/DarkSelect.svelte';
import LabelConfirm from '$lib/components/LabelConfirm.svelte';
import { recognizePhoto, enrichAsset } from '$lib/services/visionService';
@ -52,10 +52,18 @@
images = [];
persons = [];
db.assets.get(id).then(a => { asset = a; });
getAssetImages(id).then(r => { images = r; loadImageUrls(r); });
getAssetPersons(id).then(r => { persons = r; });
getFamilyPersons().then(r => { familyPersons = r; });
getRooms().then(r => { rooms = r; });
// Live subscription: update images whenever assetImages table changes
const sub = liveQuery(() =>
db.assetImages.where('assetId').equals(id).filter(ai => !ai.deletedAt).sortBy('sortOrder')
).subscribe(result => { images = result; });
return () => sub.unsubscribe();
});
$effect(() => {
loadImageUrls(images);
});
$effect(() => {
@ -154,13 +162,9 @@
if (!res.ok) throw new Error('Upload failed');
const { id } = await res.json() as { id: string };
await addAssetImage(assetId, id);
// If no cover, set this as cover
if (!asset?.coverImageId) {
await save({ coverImageId: id });
}
const newImages = await getAssetImages(assetId);
images = newImages;
await loadImageUrls(newImages);
} finally {
uploading = false;
}
@ -178,9 +182,6 @@
if (asset?.coverImageId === img.imageId) {
await save({ coverImageId: null });
}
const newImages = await getAssetImages(assetId);
images = newImages;
await loadImageUrls(newImages);
}
async function setCover(img: AssetImage) {
@ -204,6 +205,36 @@
return meta?.abbreviation ?? meta?.fullName ?? p.name.replace(/^Person /, '');
}
let dragSrcId = $state<string | null>(null);
let dragOverId = $state<string | null>(null);
function onDragStart(e: DragEvent, id: string) {
dragSrcId = id;
e.dataTransfer!.effectAllowed = 'move';
}
function onDragOver(e: DragEvent, id: string) {
e.preventDefault();
dragOverId = id;
}
function onDragEnd() {
dragSrcId = null;
dragOverId = null;
}
async function onDrop(e: DragEvent, targetId: string) {
e.preventDefault();
if (!dragSrcId || dragSrcId === targetId) { onDragEnd(); return; }
const ids = images.map(i => i.id);
const srcIdx = ids.indexOf(dragSrcId);
const tgtIdx = ids.indexOf(targetId);
ids.splice(srcIdx, 1);
ids.splice(tgtIdx, 0, dragSrcId);
onDragEnd();
await reorderAssetImages(ids);
}
let notesValue = $state('');
let notesInit = $state(false);
$effect(() => {
@ -245,10 +276,21 @@
{#if images.length > 0}
<div class="mb-4 flex gap-2 overflow-x-auto pb-1">
{#each images as img (img.id)}
<div class="relative flex-shrink-0 group">
{@const isCover = img.imageId === asset.coverImageId}
{@const isDragOver = dragOverId === img.id && dragSrcId !== img.id}
<div
class="relative flex-shrink-0 group {isDragOver ? 'opacity-50' : ''}"
draggable="true"
ondragstart={(e) => onDragStart(e, img.id)}
ondragover={(e) => onDragOver(e, img.id)}
ondrop={(e) => onDrop(e, img.id)}
ondragend={onDragEnd}
role="listitem"
>
<button
class="h-16 w-16 overflow-hidden rounded-lg border-2 {img.imageId === asset.coverImageId ? 'border-accent' : 'border-transparent'} hover:border-accent/50"
class="h-16 w-16 overflow-hidden rounded-lg border-2 {isCover ? 'border-accent' : 'border-transparent'} hover:border-accent/50 cursor-grab active:cursor-grabbing"
onclick={() => setCover(img)}
title={isCover ? 'Titelbild' : 'Als Titelbild setzen'}
>
{#if imageUrls.get(img.id)}
<img src={imageUrls.get(img.id)} alt="" class="h-full w-full object-cover" />
@ -256,6 +298,17 @@
<div class="h-full w-full bg-white/10"></div>
{/if}
</button>
<!-- Cover star badge -->
{#if isCover}
<span class="absolute left-0.5 top-0.5 pointer-events-none">
<Star size={13} class="text-accent fill-accent drop-shadow" />
</span>
{:else}
<span class="absolute left-0.5 top-0.5 pointer-events-none hidden group-hover:block">
<Star size={13} class="text-white/60" />
</span>
{/if}
<!-- Delete button -->
<button
class="absolute -right-1 -top-1 hidden group-hover:flex h-5 w-5 items-center justify-center rounded-full bg-danger text-white"
onclick={() => deleteImage(img)}

Binary file not shown.

Binary file not shown.