mytec: after methods
This commit is contained in:
@@ -472,7 +472,7 @@ export default function App() {
|
||||
RF Coverage Planner
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-3 mr-4">
|
||||
<ThemeToggle />
|
||||
{/* Undo / Redo buttons */}
|
||||
<div className="hidden sm:flex items-center gap-1">
|
||||
|
||||
@@ -68,7 +68,7 @@ function FlyToSelected({ site, isSelected }: { site: Site; isSelected: boolean }
|
||||
export default memo(function SiteMarker({ site, onEdit }: SiteMarkerProps) {
|
||||
const selectedSiteId = useSitesStore((s) => s.selectedSiteId);
|
||||
const selectSite = useSitesStore((s) => s.selectSite);
|
||||
const updateSite = useSitesStore((s) => s.updateSite);
|
||||
const moveSiteWithColocated = useSitesStore((s) => s.moveSiteWithColocated);
|
||||
|
||||
const isSelected = selectedSiteId === site.id;
|
||||
|
||||
@@ -84,7 +84,7 @@ export default memo(function SiteMarker({ site, onEdit }: SiteMarkerProps) {
|
||||
dragend: (e) => {
|
||||
const marker = e.target as L.Marker;
|
||||
const pos = marker.getLatLng();
|
||||
updateSite(site.id, { lat: pos.lat, lon: pos.lng });
|
||||
moveSiteWithColocated(site.id, pos.lat, pos.lng);
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -89,18 +89,29 @@ class WebSocketService {
|
||||
|
||||
switch (msg.type) {
|
||||
case 'progress':
|
||||
pending?.onProgress?.({
|
||||
phase: msg.phase,
|
||||
progress: msg.progress,
|
||||
eta_seconds: msg.eta_seconds,
|
||||
});
|
||||
if (pending?.onProgress) {
|
||||
pending.onProgress({
|
||||
phase: msg.phase,
|
||||
progress: msg.progress,
|
||||
eta_seconds: msg.eta_seconds,
|
||||
});
|
||||
} else {
|
||||
console.warn('[WS] progress msg but no pending calc:', calcId, msg.phase, msg.progress);
|
||||
}
|
||||
break;
|
||||
case 'result':
|
||||
pending?.onResult(msg.data);
|
||||
if (pending) {
|
||||
pending.onResult(msg.data);
|
||||
} else {
|
||||
console.warn('[WS] result msg but no pending calc:', calcId);
|
||||
}
|
||||
if (calcId) this._pendingCalcs.delete(calcId);
|
||||
break;
|
||||
case 'error':
|
||||
pending?.onError(msg.message);
|
||||
console.error('[WS] error:', msg.message);
|
||||
if (pending) {
|
||||
pending.onError(msg.message);
|
||||
}
|
||||
if (calcId) this._pendingCalcs.delete(calcId);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ const SITE_COLORS = [
|
||||
'#ec4899', '#14b8a6', '#f97316', '#6366f1', '#84cc16',
|
||||
];
|
||||
|
||||
/** Proximity threshold for co-located sector grouping (~11 meters). */
|
||||
const COLOCATION_THRESHOLD = 0.0001;
|
||||
|
||||
interface SitesState {
|
||||
sites: Site[];
|
||||
selectedSiteId: string | null;
|
||||
@@ -34,6 +37,7 @@ interface SitesState {
|
||||
loadSites: () => Promise<void>;
|
||||
addSite: (data: SiteFormData) => Promise<Site>;
|
||||
updateSite: (id: string, data: Partial<Site>) => Promise<void>;
|
||||
moveSiteWithColocated: (id: string, newLat: number, newLon: number) => Promise<void>;
|
||||
deleteSite: (id: string) => Promise<void>;
|
||||
selectSite: (id: string | null) => void;
|
||||
setEditingSite: (id: string | null) => void;
|
||||
@@ -124,13 +128,78 @@ export const useSitesStore = create<SitesState>((set, get) => ({
|
||||
}));
|
||||
},
|
||||
|
||||
moveSiteWithColocated: async (id: string, newLat: number, newLon: number) => {
|
||||
const sites = get().sites;
|
||||
const site = sites.find((s) => s.id === id);
|
||||
if (!site) return;
|
||||
|
||||
const deltaLat = newLat - site.lat;
|
||||
const deltaLon = newLon - site.lon;
|
||||
|
||||
// Find co-located sector siblings
|
||||
const colocated = sites.filter(
|
||||
(s) =>
|
||||
s.id !== id &&
|
||||
Math.abs(s.lat - site.lat) < COLOCATION_THRESHOLD &&
|
||||
Math.abs(s.lon - site.lon) < COLOCATION_THRESHOLD,
|
||||
);
|
||||
|
||||
if (colocated.length === 0) {
|
||||
// No siblings — plain update
|
||||
get().updateSite(id, { lat: newLat, lon: newLon });
|
||||
return;
|
||||
}
|
||||
|
||||
pushSnapshot('move site group', sites);
|
||||
const now = new Date();
|
||||
const idsToMove = new Set([id, ...colocated.map((s) => s.id)]);
|
||||
|
||||
const updatedSites = sites.map((s) => {
|
||||
if (!idsToMove.has(s.id)) return s;
|
||||
return {
|
||||
...s,
|
||||
lat: s.id === id ? newLat : s.lat + deltaLat,
|
||||
lon: s.id === id ? newLon : s.lon + deltaLon,
|
||||
updatedAt: now,
|
||||
};
|
||||
});
|
||||
|
||||
for (const s of updatedSites) {
|
||||
if (!idsToMove.has(s.id)) continue;
|
||||
await db.sites.put({
|
||||
id: s.id,
|
||||
data: JSON.stringify(s),
|
||||
createdAt: s.createdAt.getTime(),
|
||||
updatedAt: now.getTime(),
|
||||
});
|
||||
}
|
||||
|
||||
set({ sites: updatedSites });
|
||||
},
|
||||
|
||||
deleteSite: async (id: string) => {
|
||||
pushSnapshot('delete site', get().sites);
|
||||
await db.sites.delete(id);
|
||||
const sites = get().sites;
|
||||
const site = sites.find((s) => s.id === id);
|
||||
if (!site) return;
|
||||
|
||||
// Find co-located sector siblings to delete together
|
||||
const colocated = sites.filter(
|
||||
(s) =>
|
||||
Math.abs(s.lat - site.lat) < COLOCATION_THRESHOLD &&
|
||||
Math.abs(s.lon - site.lon) < COLOCATION_THRESHOLD,
|
||||
);
|
||||
const idsToDelete = new Set(colocated.map((s) => s.id));
|
||||
|
||||
pushSnapshot('delete site', sites);
|
||||
|
||||
for (const delId of idsToDelete) {
|
||||
await db.sites.delete(delId);
|
||||
}
|
||||
|
||||
set((state) => ({
|
||||
sites: state.sites.filter((s) => s.id !== id),
|
||||
selectedSiteId: state.selectedSiteId === id ? null : state.selectedSiteId,
|
||||
editingSiteId: state.editingSiteId === id ? null : state.editingSiteId,
|
||||
sites: state.sites.filter((s) => !idsToDelete.has(s.id)),
|
||||
selectedSiteId: idsToDelete.has(state.selectedSiteId ?? '') ? null : state.selectedSiteId,
|
||||
editingSiteId: idsToDelete.has(state.editingSiteId ?? '') ? null : state.editingSiteId,
|
||||
}));
|
||||
// Clear stale coverage data
|
||||
useCoverageStore.getState().clearCoverage();
|
||||
|
||||
Reference in New Issue
Block a user