upd inventory
This commit is contained in:
parent
b068c75616
commit
b4ed2055a2
|
|
@ -1 +1 @@
|
||||||
1.2.31
|
1.2.33
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Sofa, BedDouble, Baby, Utensils, Shirt, DoorOpen, ChefHat, Droplets,
|
||||||
|
Monitor, Archive, Home, Car, TreePine, Hammer, Package, Dumbbell,
|
||||||
|
Music, Flame, Box, Armchair, Bike,
|
||||||
|
} from 'lucide-svelte';
|
||||||
|
|
||||||
|
const ICON_MAP: Record<string, unknown> = {
|
||||||
|
'sofa': Sofa,
|
||||||
|
'bed': BedDouble,
|
||||||
|
'baby': Baby,
|
||||||
|
'utensils': Utensils,
|
||||||
|
'shirt': Shirt,
|
||||||
|
'door-open': DoorOpen,
|
||||||
|
'chef-hat': ChefHat,
|
||||||
|
'droplets': Droplets,
|
||||||
|
'monitor': Monitor,
|
||||||
|
'archive': Archive,
|
||||||
|
'home': Home,
|
||||||
|
'car': Car,
|
||||||
|
'tree-pine': TreePine,
|
||||||
|
'hammer': Hammer,
|
||||||
|
'dumbbell': Dumbbell,
|
||||||
|
'music': Music,
|
||||||
|
'flame': Flame,
|
||||||
|
'box': Box,
|
||||||
|
'armchair': Armchair,
|
||||||
|
'bike': Bike,
|
||||||
|
'package': Package,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon: string;
|
||||||
|
color?: string | null;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
let { icon, color = null, size = 28 }: Props = $props();
|
||||||
|
|
||||||
|
const component = $derived(ICON_MAP[icon] ?? Package);
|
||||||
|
const style = $derived(color ? `color: ${color}` : '');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:component this={component} {size} style={style} class={color ? '' : 'text-accent'} />
|
||||||
|
|
@ -944,13 +944,14 @@ export async function getTasksByContext(contextId: string): Promise<Task[]> {
|
||||||
|
|
||||||
// --- Rooms ---
|
// --- Rooms ---
|
||||||
|
|
||||||
export async function createRoom(fields: { name: string; groupType: RoomGroupType; icon: string; sortOrder?: number; userId?: string }): Promise<Room> {
|
export async function createRoom(fields: { name: string; groupType: RoomGroupType; icon: string; color?: string | null; sortOrder?: number; userId?: string }): Promise<Room> {
|
||||||
const room: Room = {
|
const room: Room = {
|
||||||
id: newId(),
|
id: newId(),
|
||||||
userId: fields.userId ?? '',
|
userId: fields.userId ?? '',
|
||||||
name: fields.name,
|
name: fields.name,
|
||||||
groupType: fields.groupType,
|
groupType: fields.groupType,
|
||||||
icon: fields.icon,
|
icon: fields.icon,
|
||||||
|
color: fields.color ?? null,
|
||||||
sortOrder: fields.sortOrder ?? 0,
|
sortOrder: fields.sortOrder ?? 0,
|
||||||
updatedAt: now(),
|
updatedAt: now(),
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
|
@ -965,7 +966,7 @@ export async function getRooms(): Promise<Room[]> {
|
||||||
return db.rooms.filter(r => !r.deletedAt).sortBy('sortOrder');
|
return db.rooms.filter(r => !r.deletedAt).sortBy('sortOrder');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateRoom(id: string, changes: Partial<Pick<Room, 'name' | 'groupType' | 'icon' | 'sortOrder'>>): Promise<void> {
|
export async function updateRoom(id: string, changes: Partial<Pick<Room, 'name' | 'groupType' | 'icon' | 'color' | 'sortOrder'>>): Promise<void> {
|
||||||
const room = await db.rooms.get(id);
|
const room = await db.rooms.get(id);
|
||||||
if (room) {
|
if (room) {
|
||||||
await db.rooms.update(id, { ...changes, updatedAt: now(), version: room.version + 1 });
|
await db.rooms.update(id, { ...changes, updatedAt: now(), version: room.version + 1 });
|
||||||
|
|
@ -1123,22 +1124,22 @@ export async function getFamilyPersons(): Promise<AgendaContext[]> {
|
||||||
|
|
||||||
// --- Seed default rooms ---
|
// --- Seed default rooms ---
|
||||||
|
|
||||||
const DEFAULT_ROOMS: Array<{ name: string; groupType: RoomGroupType; icon: string }> = [
|
const DEFAULT_ROOMS: Array<{ name: string; groupType: RoomGroupType; icon: string; color: string }> = [
|
||||||
{ name: 'Wohnzimmer', groupType: 'living', icon: 'sofa' },
|
{ name: 'Wohnzimmer', groupType: 'living', icon: 'sofa', color: '#818cf8' },
|
||||||
{ name: 'Schlafzimmer', groupType: 'living', icon: 'bed' },
|
{ name: 'Schlafzimmer', groupType: 'living', icon: 'bed', color: '#a78bfa' },
|
||||||
{ name: 'Kinderzimmer', groupType: 'living', icon: 'baby' },
|
{ name: 'Kinderzimmer', groupType: 'living', icon: 'baby', color: '#f472b6' },
|
||||||
{ name: 'Esszimmer', groupType: 'living', icon: 'utensils' },
|
{ name: 'Esszimmer', groupType: 'living', icon: 'utensils', color: '#fb923c' },
|
||||||
{ name: 'Ankleidezimmer', groupType: 'living', icon: 'shirt' },
|
{ name: 'Ankleidezimmer', groupType: 'living', icon: 'shirt', color: '#c084fc' },
|
||||||
{ name: 'Flur/Diele', groupType: 'living', icon: 'door-open' },
|
{ name: 'Flur/Diele', groupType: 'living', icon: 'door-open', color: '#94a3b8' },
|
||||||
{ name: 'Küche', groupType: 'functional', icon: 'chef-hat' },
|
{ name: 'Küche', groupType: 'functional', icon: 'chef-hat', color: '#fbbf24' },
|
||||||
{ name: 'Badezimmer', groupType: 'functional', icon: 'bath' },
|
{ name: 'Badezimmer', groupType: 'functional', icon: 'droplets', color: '#22d3ee' },
|
||||||
{ name: 'WC', groupType: 'functional', icon: 'toilet' },
|
{ name: 'WC', groupType: 'functional', icon: 'droplets', color: '#67e8f9' },
|
||||||
{ name: 'Arbeitszimmer', groupType: 'functional', icon: 'monitor' },
|
{ name: 'Arbeitszimmer', groupType: 'functional', icon: 'monitor', color: '#60a5fa' },
|
||||||
{ name: 'Hauswirtschaft', groupType: 'functional', icon: 'washing-machine' },
|
{ name: 'Hauswirtschaft', groupType: 'functional', icon: 'archive', color: '#94a3b8' },
|
||||||
{ name: 'Keller', groupType: 'outdoor', icon: 'archive' },
|
{ name: 'Keller', groupType: 'outdoor', icon: 'archive', color: '#78716c' },
|
||||||
{ name: 'Dachboden', groupType: 'outdoor', icon: 'home' },
|
{ name: 'Dachboden', groupType: 'outdoor', icon: 'home', color: '#a8a29e' },
|
||||||
{ name: 'Garage', groupType: 'outdoor', icon: 'car' },
|
{ name: 'Garage', groupType: 'outdoor', icon: 'car', color: '#6b7280' },
|
||||||
{ name: 'Garten', groupType: 'outdoor', icon: 'tree' },
|
{ name: 'Garten', groupType: 'outdoor', icon: 'tree-pine', color: '#4ade80' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const SEED_SETTING_KEY = 'inventory.seeded';
|
const SEED_SETTING_KEY = 'inventory.seeded';
|
||||||
|
|
@ -1156,6 +1157,7 @@ export async function seedDefaultRoomsIfNeeded(userId: string): Promise<void> {
|
||||||
name: r.name,
|
name: r.name,
|
||||||
groupType: r.groupType,
|
groupType: r.groupType,
|
||||||
icon: r.icon,
|
icon: r.icon,
|
||||||
|
color: r.color,
|
||||||
sortOrder: i,
|
sortOrder: i,
|
||||||
updatedAt: ts,
|
updatedAt: ts,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
export interface RoomIconDef {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROOM_ICONS: RoomIconDef[] = [
|
||||||
|
{ key: 'sofa', label: 'Sofa' },
|
||||||
|
{ key: 'bed', label: 'Bett' },
|
||||||
|
{ key: 'armchair', label: 'Sessel' },
|
||||||
|
{ key: 'utensils', label: 'Besteck' },
|
||||||
|
{ key: 'chef-hat', label: 'Küche' },
|
||||||
|
{ key: 'droplets', label: 'Wasser' },
|
||||||
|
{ key: 'monitor', label: 'Bildschirm' },
|
||||||
|
{ key: 'shirt', label: 'Kleidung' },
|
||||||
|
{ key: 'baby', label: 'Kind' },
|
||||||
|
{ key: 'door-open', label: 'Tür' },
|
||||||
|
{ key: 'archive', label: 'Regal' },
|
||||||
|
{ key: 'home', label: 'Haus' },
|
||||||
|
{ key: 'car', label: 'Auto' },
|
||||||
|
{ key: 'tree-pine', label: 'Garten' },
|
||||||
|
{ key: 'hammer', label: 'Werkzeug' },
|
||||||
|
{ key: 'dumbbell', label: 'Fitness' },
|
||||||
|
{ key: 'music', label: 'Musik' },
|
||||||
|
{ key: 'flame', label: 'Feuer' },
|
||||||
|
{ key: 'bike', label: 'Fahrrad' },
|
||||||
|
{ key: 'box', label: 'Kiste' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface RoomColorDef {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
hex: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROOM_COLORS: RoomColorDef[] = [
|
||||||
|
{ key: 'indigo', label: 'Indigo', hex: '#818cf8' },
|
||||||
|
{ key: 'violet', label: 'Violett', hex: '#a78bfa' },
|
||||||
|
{ key: 'purple', label: 'Lila', hex: '#c084fc' },
|
||||||
|
{ key: 'pink', label: 'Pink', hex: '#f472b6' },
|
||||||
|
{ key: 'rose', label: 'Rot', hex: '#f87171' },
|
||||||
|
{ key: 'orange', label: 'Orange', hex: '#fb923c' },
|
||||||
|
{ key: 'amber', label: 'Gelb', hex: '#fbbf24' },
|
||||||
|
{ key: 'green', label: 'Grün', hex: '#4ade80' },
|
||||||
|
{ key: 'teal', label: 'Türkis', hex: '#2dd4bf' },
|
||||||
|
{ key: 'cyan', label: 'Cyan', hex: '#22d3ee' },
|
||||||
|
{ key: 'blue', label: 'Blau', hex: '#60a5fa' },
|
||||||
|
{ key: 'slate', label: 'Grau', hex: '#94a3b8' },
|
||||||
|
{ key: 'stone', label: 'Stein', hex: '#a8a29e' },
|
||||||
|
];
|
||||||
|
|
@ -4,9 +4,11 @@
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import { createRoom, updateRoom, softDeleteRoom } from '$lib/db/repositories';
|
import { createRoom, updateRoom, softDeleteRoom } from '$lib/db/repositories';
|
||||||
import { account } from '$lib/auth/authStore';
|
import { account } from '$lib/auth/authStore';
|
||||||
import { Plus, Package, MoreHorizontal } from 'lucide-svelte';
|
import { Plus, MoreHorizontal } from 'lucide-svelte';
|
||||||
import DarkSelect from '$lib/components/DarkSelect.svelte';
|
import DarkSelect from '$lib/components/DarkSelect.svelte';
|
||||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||||
|
import RoomIcon from '$lib/components/RoomIcon.svelte';
|
||||||
|
import { ROOM_ICONS, ROOM_COLORS } from '$lib/utils/roomAppearance';
|
||||||
import type { Room, RoomGroupType } from '@ka-note/shared';
|
import type { Room, RoomGroupType } from '@ka-note/shared';
|
||||||
|
|
||||||
const rooms$ = liveQuery(() => db.rooms.filter(r => !r.deletedAt).sortBy('sortOrder'));
|
const rooms$ = liveQuery(() => db.rooms.filter(r => !r.deletedAt).sortBy('sortOrder'));
|
||||||
|
|
@ -26,19 +28,27 @@
|
||||||
return ($assets$ ?? []).filter(a => a.roomId === roomId).length;
|
return ($assets$ ?? []).filter(a => a.roomId === roomId).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add room
|
// --- Add room ---
|
||||||
let showAddRoom = $state(false);
|
let showAddRoom = $state(false);
|
||||||
let newRoomName = $state('');
|
let newRoomName = $state('');
|
||||||
let newRoomGroup = $state<RoomGroupType>('living');
|
let newRoomGroup = $state<RoomGroupType>('living');
|
||||||
|
let newRoomIcon = $state('sofa');
|
||||||
|
let newRoomColor = $state('#818cf8');
|
||||||
|
|
||||||
async function addRoom() {
|
async function addRoom() {
|
||||||
if (!newRoomName.trim()) return;
|
if (!newRoomName.trim()) return;
|
||||||
await createRoom({ name: newRoomName.trim(), groupType: newRoomGroup, icon: 'package', userId: $account?.homeAccountId ?? '' });
|
await createRoom({
|
||||||
|
name: newRoomName.trim(),
|
||||||
|
groupType: newRoomGroup,
|
||||||
|
icon: newRoomIcon,
|
||||||
|
color: newRoomColor,
|
||||||
|
userId: $account?.homeAccountId ?? '',
|
||||||
|
});
|
||||||
newRoomName = '';
|
newRoomName = '';
|
||||||
showAddRoom = false;
|
showAddRoom = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename room
|
// --- Rename ---
|
||||||
let renamingId = $state<string | null>(null);
|
let renamingId = $state<string | null>(null);
|
||||||
let renameValue = $state('');
|
let renameValue = $state('');
|
||||||
|
|
||||||
|
|
@ -54,14 +64,32 @@
|
||||||
renamingId = null;
|
renamingId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete room
|
// --- Appearance picker ---
|
||||||
|
let appearanceRoom = $state<Room | null>(null);
|
||||||
|
let pickIcon = $state('sofa');
|
||||||
|
let pickColor = $state('#818cf8');
|
||||||
|
|
||||||
|
function openAppearance(room: Room) {
|
||||||
|
appearanceRoom = room;
|
||||||
|
pickIcon = room.icon;
|
||||||
|
pickColor = room.color ?? '#818cf8';
|
||||||
|
menuRoomId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveAppearance() {
|
||||||
|
if (!appearanceRoom) return;
|
||||||
|
await updateRoom(appearanceRoom.id, { icon: pickIcon, color: pickColor });
|
||||||
|
appearanceRoom = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Delete ---
|
||||||
let deleteTarget = $state<Room | null>(null);
|
let deleteTarget = $state<Room | null>(null);
|
||||||
async function confirmDelete() {
|
async function confirmDelete() {
|
||||||
if (deleteTarget) await softDeleteRoom(deleteTarget.id);
|
if (deleteTarget) await softDeleteRoom(deleteTarget.id);
|
||||||
deleteTarget = null;
|
deleteTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context menu
|
// --- Context menu ---
|
||||||
let menuRoomId = $state<string | null>(null);
|
let menuRoomId = $state<string | null>(null);
|
||||||
function toggleMenu(id: string) { menuRoomId = menuRoomId === id ? null : id; }
|
function toggleMenu(id: string) { menuRoomId = menuRoomId === id ? null : id; }
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -92,7 +120,7 @@
|
||||||
onclick={() => renamingId !== room.id && goto('/inventory/rooms/' + room.id)}
|
onclick={() => renamingId !== room.id && goto('/inventory/rooms/' + room.id)}
|
||||||
onkeydown={(e) => renamingId !== room.id && e.key === 'Enter' && goto('/inventory/rooms/' + room.id)}
|
onkeydown={(e) => renamingId !== room.id && e.key === 'Enter' && goto('/inventory/rooms/' + room.id)}
|
||||||
>
|
>
|
||||||
<Package size={28} class="text-accent" />
|
<RoomIcon icon={room.icon} color={room.color} size={28} />
|
||||||
{#if renamingId === room.id}
|
{#if renamingId === room.id}
|
||||||
<input
|
<input
|
||||||
class="input w-full text-center text-sm"
|
class="input w-full text-center text-sm"
|
||||||
|
|
@ -114,8 +142,9 @@
|
||||||
<MoreHorizontal size={14} />
|
<MoreHorizontal size={14} />
|
||||||
</button>
|
</button>
|
||||||
{#if menuRoomId === room.id}
|
{#if menuRoomId === room.id}
|
||||||
<div class="absolute right-0 top-8 z-20 min-w-[140px] rounded-lg border border-border bg-surface shadow-lg py-1" onclick={(e) => e.stopPropagation()}>
|
<div class="absolute right-0 top-8 z-20 min-w-[160px] rounded-lg border border-border bg-surface shadow-lg py-1" onclick={(e) => e.stopPropagation()}>
|
||||||
<button class="w-full px-4 py-2 text-left text-sm text-white hover:bg-white/10" onclick={() => { startRename(room); menuRoomId = null; }}>Umbenennen</button>
|
<button class="w-full px-4 py-2 text-left text-sm text-white hover:bg-white/10" onclick={() => { startRename(room); menuRoomId = null; }}>Umbenennen</button>
|
||||||
|
<button class="w-full px-4 py-2 text-left text-sm text-white hover:bg-white/10" onclick={() => openAppearance(room)}>Icon & Farbe</button>
|
||||||
<button class="w-full px-4 py-2 text-left text-sm text-danger hover:bg-white/10" onclick={() => { deleteTarget = room; menuRoomId = null; }}>Löschen</button>
|
<button class="w-full px-4 py-2 text-left text-sm text-danger hover:bg-white/10" onclick={() => { deleteTarget = room; menuRoomId = null; }}>Löschen</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -126,6 +155,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add room modal -->
|
||||||
{#if showAddRoom}
|
{#if showAddRoom}
|
||||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60" role="dialog">
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60" role="dialog">
|
||||||
<div class="mx-4 w-full max-w-sm rounded-xl border border-border bg-surface p-5">
|
<div class="mx-4 w-full max-w-sm rounded-xl border border-border bg-surface p-5">
|
||||||
|
|
@ -136,6 +166,40 @@
|
||||||
bind:value={newRoomGroup}
|
bind:value={newRoomGroup}
|
||||||
options={[{ value: 'living', label: 'Wohnbereiche' }, { value: 'functional', label: 'Funktionsräume' }, { value: 'outdoor', label: 'Außen & Nebenbereiche' }]}
|
options={[{ value: 'living', label: 'Wohnbereiche' }, { value: 'functional', label: 'Funktionsräume' }, { value: 'outdoor', label: 'Außen & Nebenbereiche' }]}
|
||||||
/>
|
/>
|
||||||
|
<!-- Icon picker -->
|
||||||
|
<div>
|
||||||
|
<p class="text-xs text-muted mb-2">Icon</p>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{#each ROOM_ICONS as ic}
|
||||||
|
<button
|
||||||
|
class="p-2 rounded-lg border-2 transition-colors {newRoomIcon === ic.key ? 'border-accent bg-accent/10' : 'border-transparent bg-white/5 hover:bg-white/10'}"
|
||||||
|
onclick={() => newRoomIcon = ic.key}
|
||||||
|
title={ic.label}
|
||||||
|
>
|
||||||
|
<RoomIcon icon={ic.key} color={newRoomIcon === ic.key ? newRoomColor : null} size={18} />
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Color picker -->
|
||||||
|
<div>
|
||||||
|
<p class="text-xs text-muted mb-2">Farbe</p>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{#each ROOM_COLORS as col}
|
||||||
|
<button
|
||||||
|
class="h-7 w-7 rounded-full border-2 transition-all {newRoomColor === col.hex ? 'border-white scale-110' : 'border-transparent'}"
|
||||||
|
style="background-color: {col.hex}"
|
||||||
|
onclick={() => newRoomColor = col.hex}
|
||||||
|
title={col.label}
|
||||||
|
></button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Preview -->
|
||||||
|
<div class="flex items-center gap-3 rounded-lg bg-white/5 px-3 py-2">
|
||||||
|
<RoomIcon icon={newRoomIcon} color={newRoomColor} size={22} />
|
||||||
|
<span class="text-sm text-white">{newRoomName || 'Vorschau'}</span>
|
||||||
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button class="btn-primary flex-1" onclick={addRoom}>Anlegen</button>
|
<button class="btn-primary flex-1" onclick={addRoom}>Anlegen</button>
|
||||||
<button class="btn-ghost flex-1" onclick={() => showAddRoom = false}>Abbrechen</button>
|
<button class="btn-ghost flex-1" onclick={() => showAddRoom = false}>Abbrechen</button>
|
||||||
|
|
@ -145,6 +209,55 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Appearance picker modal -->
|
||||||
|
{#if appearanceRoom}
|
||||||
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60" role="dialog">
|
||||||
|
<div class="mx-4 w-full max-w-sm rounded-xl border border-border bg-surface p-5">
|
||||||
|
<h2 class="text-lg font-semibold text-white mb-4">Icon & Farbe — {appearanceRoom.name}</h2>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<!-- Icon picker -->
|
||||||
|
<div>
|
||||||
|
<p class="text-xs text-muted mb-2">Icon</p>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{#each ROOM_ICONS as ic}
|
||||||
|
<button
|
||||||
|
class="p-2 rounded-lg border-2 transition-colors {pickIcon === ic.key ? 'border-accent bg-accent/10' : 'border-transparent bg-white/5 hover:bg-white/10'}"
|
||||||
|
onclick={() => pickIcon = ic.key}
|
||||||
|
title={ic.label}
|
||||||
|
>
|
||||||
|
<RoomIcon icon={ic.key} color={pickIcon === ic.key ? pickColor : null} size={18} />
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Color picker -->
|
||||||
|
<div>
|
||||||
|
<p class="text-xs text-muted mb-2">Farbe</p>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{#each ROOM_COLORS as col}
|
||||||
|
<button
|
||||||
|
class="h-7 w-7 rounded-full border-2 transition-all {pickColor === col.hex ? 'border-white scale-110' : 'border-transparent'}"
|
||||||
|
style="background-color: {col.hex}"
|
||||||
|
onclick={() => pickColor = col.hex}
|
||||||
|
title={col.label}
|
||||||
|
></button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Preview -->
|
||||||
|
<div class="flex items-center gap-3 rounded-lg bg-white/5 px-3 py-2">
|
||||||
|
<RoomIcon icon={pickIcon} color={pickColor} size={22} />
|
||||||
|
<span class="text-sm text-white">{appearanceRoom.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button class="btn-primary flex-1" onclick={saveAppearance}>Speichern</button>
|
||||||
|
<button class="btn-ghost flex-1" onclick={() => appearanceRoom = null}>Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if deleteTarget}
|
{#if deleteTarget}
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
message="Raum "{deleteTarget.name}" wirklich löschen? Gegenstände bleiben erhalten."
|
message="Raum "{deleteTarget.name}" wirklich löschen? Gegenstände bleiben erhalten."
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE `rooms` ADD `color` text;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE `rooms` ADD `color` text;
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -120,6 +120,13 @@
|
||||||
"when": 1772571259713,
|
"when": 1772571259713,
|
||||||
"tag": "0016_inventory",
|
"tag": "0016_inventory",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 17,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1772831133949,
|
||||||
|
"tag": "0017_kind_luke_cage",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -209,6 +209,7 @@ export const rooms = sqliteTable('rooms', {
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
groupType: text('group_type').notNull(),
|
groupType: text('group_type').notNull(),
|
||||||
icon: text('icon').notNull(),
|
icon: text('icon').notNull(),
|
||||||
|
color: text('color'),
|
||||||
sortOrder: integer('sort_order').notNull().default(0),
|
sortOrder: integer('sort_order').notNull().default(0),
|
||||||
updatedAt: text('updated_at').notNull(),
|
updatedAt: text('updated_at').notNull(),
|
||||||
deletedAt: text('deleted_at'),
|
deletedAt: text('deleted_at'),
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,7 @@ function mapRoom(row: typeof rooms.$inferSelect): Room {
|
||||||
name: row.name,
|
name: row.name,
|
||||||
groupType: row.groupType as Room['groupType'],
|
groupType: row.groupType as Room['groupType'],
|
||||||
icon: row.icon,
|
icon: row.icon,
|
||||||
|
color: row.color ?? null,
|
||||||
sortOrder: row.sortOrder,
|
sortOrder: row.sortOrder,
|
||||||
updatedAt: row.updatedAt,
|
updatedAt: row.updatedAt,
|
||||||
deletedAt: row.deletedAt ?? null,
|
deletedAt: row.deletedAt ?? null,
|
||||||
|
|
@ -421,7 +422,7 @@ export async function pushChanges(request: SyncPushRequest, userId: string): Pro
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const rm of rms) {
|
for (const rm of rms) {
|
||||||
const row = { id: rm.id, userId, name: rm.name, groupType: rm.groupType, icon: rm.icon, sortOrder: rm.sortOrder, updatedAt: rm.updatedAt, deletedAt: rm.deletedAt, purgedAt: rm.purgedAt ?? null, version: rm.version };
|
const row = { id: rm.id, userId, name: rm.name, groupType: rm.groupType, icon: rm.icon, color: rm.color ?? null, sortOrder: rm.sortOrder, updatedAt: rm.updatedAt, deletedAt: rm.deletedAt, purgedAt: rm.purgedAt ?? null, version: rm.version };
|
||||||
if (await upsertEntity(rooms, row, conflicts, 'room', userId)) accepted++;
|
if (await upsertEntity(rooms, row, conflicts, 'room', userId)) accepted++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ export interface Room extends SyncEntity {
|
||||||
name: string;
|
name: string;
|
||||||
groupType: RoomGroupType;
|
groupType: RoomGroupType;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
color?: string | null;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue