The UTable component was not reliably updating when items were added, edited, or removed due to a reactivity issue. This commit resolves the problem by forcing the table to re-render when its data source
changes.
- A `watch` with `{ deep: true }` is now used to monitor the `items` array for any changes.
- A `tableKey` is incremented on each change and passed as a `:key` to `UTable`, triggering a re-mount.
Additionally, data handling has been made more robust:
- The localStorage serializer now gracefully handles parsing errors and ensures data integrity for dates and tags.
- Added null-safe access for tags in the table template to prevent rendering errors.
105 lines
3.0 KiB
TypeScript
105 lines
3.0 KiB
TypeScript
import { useLocalStorage, createSharedComposable } from "@vueuse/core";
|
|
|
|
export type Item = {
|
|
id: number;
|
|
name: string;
|
|
imageUrl?: string | null;
|
|
description?: string | null;
|
|
tags?: string[] | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
};
|
|
|
|
const _useItems = () => {
|
|
const stored = useLocalStorage<Item[]>("item-database", [], {
|
|
serializer: {
|
|
read: (v) => {
|
|
if (!v) return [];
|
|
try {
|
|
const parsed = JSON.parse(v);
|
|
return (parsed ?? []).map((item: any) => ({
|
|
...item,
|
|
createdAt: item.createdAt ? new Date(item.createdAt) : new Date(),
|
|
updatedAt: item.updatedAt ? new Date(item.updatedAt) : new Date(),
|
|
// ensure tags is an array or null
|
|
tags: item.tags
|
|
? Array.isArray(item.tags)
|
|
? item.tags
|
|
: String(item.tags)
|
|
.split(",")
|
|
.map((t: string) => t.trim())
|
|
: null,
|
|
}));
|
|
} catch (err) {
|
|
console.error("Failed to parse item-database from localStorage", err);
|
|
return [];
|
|
}
|
|
},
|
|
write: (v) => {
|
|
try {
|
|
return JSON.stringify(v ?? []);
|
|
} catch (err) {
|
|
console.error("Failed to stringify item-database", err);
|
|
return "[]";
|
|
}
|
|
},
|
|
},
|
|
});
|
|
|
|
const items = stored; // Ref<Item[]>
|
|
|
|
const stats = {
|
|
totalItems: computed(() => (items.value ?? []).length),
|
|
addedThisMonth: computed(() => {
|
|
const now = new Date();
|
|
const thisMonth = now.getMonth();
|
|
const thisYear = now.getFullYear();
|
|
return (items.value ?? []).filter(
|
|
(item) =>
|
|
item.createdAt.getMonth() === thisMonth &&
|
|
item.createdAt.getFullYear() === thisYear
|
|
).length;
|
|
}),
|
|
latestId: computed(() => {
|
|
const arr = items.value ?? [];
|
|
if (arr.length === 0) return 1;
|
|
return Math.max(...arr.map((i) => i.id)) + 1;
|
|
}),
|
|
};
|
|
|
|
const isNameAvailable = (name: string) =>
|
|
!(items.value ?? []).some((item) => item.name === name);
|
|
|
|
const addItem = (item: Item) => {
|
|
items.value = [...(items.value ?? []), item];
|
|
console.debug("[addItem] new length:", (items.value ?? []).length);
|
|
};
|
|
|
|
const editItem = (item: Item) => {
|
|
const index = (items.value ?? []).findIndex((i) => i.id === item.id);
|
|
if (index === -1) return;
|
|
const updatedItems = [...(items.value ?? [])];
|
|
updatedItems[index] = {
|
|
...updatedItems[index],
|
|
...item,
|
|
updatedAt: new Date(),
|
|
};
|
|
items.value = updatedItems;
|
|
console.debug("[editItem] updated id:", item.id);
|
|
};
|
|
|
|
const removeItem = (id: number) => {
|
|
items.value = (items.value ?? []).filter((i) => i.id !== id);
|
|
console.debug(
|
|
"[removeItem] removed id:",
|
|
id,
|
|
"new length:",
|
|
(items.value ?? []).length
|
|
);
|
|
};
|
|
|
|
return { items, stats, isNameAvailable, addItem, editItem, removeItem };
|
|
};
|
|
|
|
export const useItemsStore = createSharedComposable(_useItems);
|