@mytec Phase 1 - Core UI & Manual Input, Phase 2 - RF Calculation Engine, Phase 3 - Heatmap Visualization

This commit is contained in:
2026-01-30 07:12:00 +02:00
parent 343c8e078d
commit 18a7d6de81
41 changed files with 6014 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
import { create } from 'zustand';
import type { CoverageResult, CoverageSettings } from '@/types/index.ts';
interface CoverageState {
result: CoverageResult | null;
isCalculating: boolean;
settings: CoverageSettings;
heatmapVisible: boolean;
setResult: (result: CoverageResult | null) => void;
setIsCalculating: (val: boolean) => void;
updateSettings: (settings: Partial<CoverageSettings>) => void;
toggleHeatmap: () => void;
setHeatmapVisible: (val: boolean) => void;
}
export const useCoverageStore = create<CoverageState>((set) => ({
result: null,
isCalculating: false,
settings: {
radius: 5,
resolution: 200,
rsrpThreshold: -120,
},
heatmapVisible: true,
setResult: (result) => set({ result }),
setIsCalculating: (val) => set({ isCalculating: val }),
updateSettings: (newSettings) =>
set((state) => ({
settings: { ...state.settings, ...newSettings },
})),
toggleHeatmap: () => set((s) => ({ heatmapVisible: !s.heatmapVisible })),
setHeatmapVisible: (val) => set({ heatmapVisible: val }),
}));

View File

@@ -0,0 +1,99 @@
import { create } from 'zustand';
import { v4 as uuidv4 } from 'uuid';
import type { Site, SiteFormData } from '@/types/index.ts';
import { db } from '@/db/schema.ts';
const SITE_COLORS = [
'#ef4444', '#3b82f6', '#22c55e', '#f59e0b', '#8b5cf6',
'#ec4899', '#14b8a6', '#f97316', '#6366f1', '#84cc16',
];
interface SitesState {
sites: Site[];
selectedSiteId: string | null;
editingSiteId: string | null;
isPlacingMode: boolean;
loadSites: () => Promise<void>;
addSite: (data: SiteFormData) => Promise<Site>;
updateSite: (id: string, data: Partial<Site>) => Promise<void>;
deleteSite: (id: string) => Promise<void>;
selectSite: (id: string | null) => void;
setEditingSite: (id: string | null) => void;
togglePlacingMode: () => void;
setPlacingMode: (val: boolean) => void;
}
export const useSitesStore = create<SitesState>((set, get) => ({
sites: [],
selectedSiteId: null,
editingSiteId: null,
isPlacingMode: false,
loadSites: async () => {
const dbSites = await db.sites.toArray();
const sites: Site[] = dbSites.map((s) => ({
...JSON.parse(s.data),
createdAt: new Date(s.createdAt),
updatedAt: new Date(s.updatedAt),
}));
set({ sites });
},
addSite: async (data: SiteFormData) => {
const id = uuidv4();
const now = new Date();
const colorIndex = get().sites.length % SITE_COLORS.length;
const site: Site = {
...data,
id,
color: data.color || SITE_COLORS[colorIndex],
visible: true,
createdAt: now,
updatedAt: now,
};
await db.sites.put({
id,
data: JSON.stringify(site),
createdAt: now.getTime(),
updatedAt: now.getTime(),
});
set((state) => ({ sites: [...state.sites, site] }));
return site;
},
updateSite: async (id: string, data: Partial<Site>) => {
const sites = get().sites;
const existing = sites.find((s) => s.id === id);
if (!existing) return;
const updated: Site = {
...existing,
...data,
updatedAt: new Date(),
};
await db.sites.put({
id,
data: JSON.stringify(updated),
createdAt: existing.createdAt.getTime(),
updatedAt: updated.updatedAt.getTime(),
});
set((state) => ({
sites: state.sites.map((s) => (s.id === id ? updated : s)),
}));
},
deleteSite: async (id: string) => {
await db.sites.delete(id);
set((state) => ({
sites: state.sites.filter((s) => s.id !== id),
selectedSiteId: state.selectedSiteId === id ? null : state.selectedSiteId,
editingSiteId: state.editingSiteId === id ? null : state.editingSiteId,
}));
},
selectSite: (id: string | null) => set({ selectedSiteId: id }),
setEditingSite: (id: string | null) => set({ editingSiteId: id }),
togglePlacingMode: () => set((s) => ({ isPlacingMode: !s.isPlacingMode })),
setPlacingMode: (val: boolean) => set({ isPlacingMode: val }),
}));