forked from hazem/yslootahrobotics
feat: enhance pricing and persona synchronization logic for improved data consistency
This commit is contained in:
parent
13050a6541
commit
11ab8908fa
@ -48,26 +48,75 @@ export default function AdminPage() {
|
|||||||
const res = await fetch('/api/admin/pricing/');
|
const res = await fetch('/api/admin/pricing/');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const serverItems: { id: string; label: string; price: number; modelPath: string | null }[] = data.items ?? [];
|
const serverItems: { id: string; label: string; price: number; modelPath: string | null }[] = data.items ?? [];
|
||||||
|
const localItems = pricingStore.getState().items;
|
||||||
|
|
||||||
if (serverItems.length > 0) {
|
if (serverItems.length > 0) {
|
||||||
// Sync server data into local stores
|
// Merge: server wins for label/price, but keep whichever modelPath exists
|
||||||
serverItems.forEach(({ id, label, price, modelPath }) => {
|
const serverMap = new Map(serverItems.map((s) => [s.id, s]));
|
||||||
const existing = pricingStore.getState().items.find((i) => i.id === id);
|
const localMap = new Map(localItems.map((l) => [l.id, l]));
|
||||||
if (existing) {
|
const itemsToUpdate: { id: string; label: string; price: number; modelPath?: string }[] = [];
|
||||||
pricingStore.getState().updateItem(id, { label, price, modelPath: modelPath ?? undefined });
|
|
||||||
} else {
|
// Merge server items with local modelPaths
|
||||||
pricingStore.getState().addItem({ id, label, price, modelPath: modelPath ?? undefined });
|
for (const server of serverItems) {
|
||||||
|
const local = localMap.get(server.id);
|
||||||
|
// If local has a modelPath but server doesn't, push it to server
|
||||||
|
const mergedModelPath = server.modelPath || local?.modelPath;
|
||||||
|
pricingStore.getState().updateItem(server.id, {
|
||||||
|
label: server.label,
|
||||||
|
price: server.price,
|
||||||
|
modelPath: mergedModelPath ?? undefined,
|
||||||
|
});
|
||||||
|
// If we don't already have this item locally, add it
|
||||||
|
if (!local) {
|
||||||
|
pricingStore.getState().addItem({
|
||||||
|
id: server.id,
|
||||||
|
label: server.label,
|
||||||
|
price: server.price,
|
||||||
|
modelPath: mergedModelPath ?? undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If local had a modelPath but server didn't, push to server
|
||||||
|
if (!server.modelPath && local?.modelPath) {
|
||||||
|
itemsToUpdate.push({ id: server.id, label: server.label, price: server.price, modelPath: local.modelPath });
|
||||||
}
|
}
|
||||||
// Sync persona store
|
// Sync persona store
|
||||||
if (modelPath) {
|
if (mergedModelPath && server.id !== 'base') {
|
||||||
const existingPersona = personaStore.getState().personas.find((p) => p.id === id);
|
const existingPersona = personaStore.getState().personas.find((p) => p.id === server.id);
|
||||||
if (existingPersona) {
|
if (existingPersona) {
|
||||||
personaStore.getState().updatePersona(id, { modelPath });
|
personaStore.getState().updatePersona(server.id, { modelPath: mergedModelPath });
|
||||||
} else if (id !== 'base') {
|
} else {
|
||||||
personaStore.getState().addPersona({ id, label, description: label, colors: { torso: '#3b82f6', legs: '#3b82f6' }, modelPath });
|
personaStore.getState().addPersona({ id: server.id, label: server.label, description: server.label, colors: { torso: '#3b82f6', legs: '#3b82f6' }, modelPath: mergedModelPath });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any local items that are missing from server
|
||||||
|
for (const local of localItems) {
|
||||||
|
if (!serverMap.has(local.id)) {
|
||||||
|
itemsToUpdate.push({ id: local.id, label: local.label, price: local.price, modelPath: local.modelPath });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push missing data back to server
|
||||||
|
if (itemsToUpdate.length > 0) {
|
||||||
|
for (const item of itemsToUpdate) {
|
||||||
|
fetch('/api/admin/pricing/', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(item),
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// DB is empty — push local items to server so they persist for all users
|
||||||
|
if (localItems.length > 0) {
|
||||||
|
await fetch('/api/admin/pricing/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ items: localItems.map((item, i) => ({ ...item, sortOrder: i })) }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Fall back to localStorage data (already hydrated)
|
// Fall back to localStorage data (already hydrated)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,7 +135,7 @@ export const personaStore = createStore<PersonaStore>((set, get) => ({
|
|||||||
set({ personas: [...DEFAULT_PERSONAS], isHydrated: true });
|
set({ personas: [...DEFAULT_PERSONAS], isHydrated: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch pricing items from server DB and auto-register personas for items with a modelPath
|
// Fetch pricing items from server DB and auto-register personas for all attire items
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
fetch('/api/admin/pricing/')
|
fetch('/api/admin/pricing/')
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
@ -144,13 +144,15 @@ export const personaStore = createStore<PersonaStore>((set, get) => ({
|
|||||||
const current = get().personas;
|
const current = get().personas;
|
||||||
const currentIds = new Set(current.map((p) => p.id));
|
const currentIds = new Set(current.map((p) => p.id));
|
||||||
const newPersonas: PersonaOption[] = [];
|
const newPersonas: PersonaOption[] = [];
|
||||||
|
// Items that should not appear as selectable personas
|
||||||
|
const excludeIds = new Set(['base', 'custom-color']);
|
||||||
|
|
||||||
serverItems.forEach(({ id, label, modelPath }) => {
|
serverItems.forEach(({ id, label, modelPath }) => {
|
||||||
if (!modelPath || id === 'base') return;
|
if (excludeIds.has(id)) return;
|
||||||
if (currentIds.has(id)) {
|
if (currentIds.has(id)) {
|
||||||
// Update modelPath if it changed
|
// Update modelPath if it changed
|
||||||
const existing = current.find((p) => p.id === id);
|
const existing = current.find((p) => p.id === id);
|
||||||
if (existing && existing.modelPath !== modelPath) {
|
if (existing && modelPath && existing.modelPath !== modelPath) {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
personas: state.personas.map((p) =>
|
personas: state.personas.map((p) =>
|
||||||
p.id === id ? { ...p, modelPath } : p
|
p.id === id ? { ...p, modelPath } : p
|
||||||
@ -158,12 +160,13 @@ export const personaStore = createStore<PersonaStore>((set, get) => ({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Auto-create a persona entry for every pricing item
|
||||||
newPersonas.push({
|
newPersonas.push({
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
description: label,
|
description: label,
|
||||||
colors: { torso: '#3b82f6', legs: '#3b82f6' },
|
colors: { torso: '#3b82f6', legs: '#3b82f6' },
|
||||||
modelPath,
|
...(modelPath ? { modelPath } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -125,8 +125,24 @@ export const pricingStore = createStore<PricingStore>((set, get) => ({
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
const serverItems: PricingItem[] = data.items ?? [];
|
const serverItems: PricingItem[] = data.items ?? [];
|
||||||
if (serverItems.length > 0) {
|
if (serverItems.length > 0) {
|
||||||
saveToStorage(serverItems);
|
// Merge: server wins for label/price, but keep local modelPath if server doesn't have one
|
||||||
set({ items: serverItems });
|
const localItems = get().items;
|
||||||
|
const localMap = new Map(localItems.map((l) => [l.id, l]));
|
||||||
|
const merged = serverItems.map((s) => {
|
||||||
|
const local = localMap.get(s.id);
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
modelPath: s.modelPath || local?.modelPath,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// Also keep any local-only items not in server
|
||||||
|
for (const local of localItems) {
|
||||||
|
if (!serverItems.some((s) => s.id === local.id)) {
|
||||||
|
merged.push(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveToStorage(merged);
|
||||||
|
set({ items: merged });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {}); // silent — use local data as fallback
|
.catch(() => {}); // silent — use local data as fallback
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user