@mytec: iter8.1 ready for test
This commit is contained in:
@@ -407,6 +407,11 @@ export default function App() {
|
|||||||
<option value={600}>600m — Smooth</option>
|
<option value={600}>600m — Smooth</option>
|
||||||
<option value={800}>800m — Wide</option>
|
<option value={800}>800m — Wide</option>
|
||||||
</select>
|
</select>
|
||||||
|
{settings.heatmapRadius >= 600 && settings.resolution > 200 && (
|
||||||
|
<p className="mt-1 text-xs text-amber-600 dark:text-amber-400">
|
||||||
|
⚠ Wide radius works best with ≤200m resolution. Current: {settings.resolution}m
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-gray-500 dark:text-dark-muted">
|
<label className="text-xs text-gray-500 dark:text-dark-muted">
|
||||||
|
|||||||
@@ -154,12 +154,12 @@ export default function SiteList({ onEditSite, onAddSite }: SiteListProps) {
|
|||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
await cloneSector(site.id);
|
await cloneSector(site.id);
|
||||||
addToast(`Cloned "${site.name}" (+30° azimuth)`, 'success');
|
addToast(`Added sector to "${site.name}" (+120°)`, 'success');
|
||||||
}}
|
}}
|
||||||
className="px-2 py-1 text-xs text-purple-600 dark:text-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 rounded min-h-[32px] flex items-center justify-center"
|
className="px-2 py-1 text-xs text-purple-600 dark:text-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 rounded min-h-[32px] flex items-center justify-center"
|
||||||
title="Clone sector (+30° azimuth offset)"
|
title="Add sector at same location (+120° azimuth)"
|
||||||
>
|
>
|
||||||
Clone
|
+ Sector
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ interface CoverageState {
|
|||||||
heatmapVisible: boolean;
|
heatmapVisible: boolean;
|
||||||
|
|
||||||
setResult: (result: CoverageResult | null) => void;
|
setResult: (result: CoverageResult | null) => void;
|
||||||
|
clearCoverage: () => void;
|
||||||
setIsCalculating: (val: boolean) => void;
|
setIsCalculating: (val: boolean) => void;
|
||||||
updateSettings: (settings: Partial<CoverageSettings>) => void;
|
updateSettings: (settings: Partial<CoverageSettings>) => void;
|
||||||
toggleHeatmap: () => void;
|
toggleHeatmap: () => void;
|
||||||
@@ -27,6 +28,7 @@ export const useCoverageStore = create<CoverageState>((set) => ({
|
|||||||
heatmapVisible: true,
|
heatmapVisible: true,
|
||||||
|
|
||||||
setResult: (result) => set({ result }),
|
setResult: (result) => set({ result }),
|
||||||
|
clearCoverage: () => set({ result: null }),
|
||||||
setIsCalculating: (val) => set({ isCalculating: val }),
|
setIsCalculating: (val) => set({ isCalculating: val }),
|
||||||
updateSettings: (newSettings) =>
|
updateSettings: (newSettings) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { create } from 'zustand';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import type { Site, SiteFormData } from '@/types/index.ts';
|
import type { Site, SiteFormData } from '@/types/index.ts';
|
||||||
import { db } from '@/db/schema.ts';
|
import { db } from '@/db/schema.ts';
|
||||||
|
import { useCoverageStore } from '@/store/coverage.ts';
|
||||||
|
|
||||||
const SITE_COLORS = [
|
const SITE_COLORS = [
|
||||||
'#ef4444', '#3b82f6', '#22c55e', '#f59e0b', '#8b5cf6',
|
'#ef4444', '#3b82f6', '#22c55e', '#f59e0b', '#8b5cf6',
|
||||||
@@ -106,6 +107,8 @@ export const useSitesStore = create<SitesState>((set, get) => ({
|
|||||||
selectedSiteId: state.selectedSiteId === id ? null : state.selectedSiteId,
|
selectedSiteId: state.selectedSiteId === id ? null : state.selectedSiteId,
|
||||||
editingSiteId: state.editingSiteId === id ? null : state.editingSiteId,
|
editingSiteId: state.editingSiteId === id ? null : state.editingSiteId,
|
||||||
}));
|
}));
|
||||||
|
// Clear stale coverage data
|
||||||
|
useCoverageStore.getState().clearCoverage();
|
||||||
},
|
},
|
||||||
|
|
||||||
selectSite: (id: string | null) => set({ selectedSiteId: id }),
|
selectSite: (id: string | null) => set({ selectedSiteId: id }),
|
||||||
@@ -145,29 +148,43 @@ export const useSitesStore = create<SitesState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Clone a single sector: duplicate site with 30° azimuth offset
|
// Clone a single sector: duplicate site at same location with 120° azimuth offset (tri-sector spacing)
|
||||||
cloneSector: async (siteId: string) => {
|
cloneSector: async (siteId: string) => {
|
||||||
const source = get().sites.find((s) => s.id === siteId);
|
const source = get().sites.find((s) => s.id === siteId);
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
|
|
||||||
|
// Count existing sectors at this location to determine naming
|
||||||
|
const colocated = get().sites.filter(
|
||||||
|
(s) => s.lat === source.lat && s.lon === source.lon
|
||||||
|
);
|
||||||
|
const sectorLabels = ['Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta'];
|
||||||
|
const sectorIndex = colocated.length; // 0-indexed, next sector
|
||||||
|
const label = sectorLabels[sectorIndex] ?? `S${sectorIndex + 1}`;
|
||||||
|
|
||||||
|
// Base name without any existing sector suffix
|
||||||
|
const baseName = source.name.replace(/-(Alpha|Beta|Gamma|Delta|Epsilon|Zeta|S\d+|clone)$/i, '');
|
||||||
|
|
||||||
const addSite = get().addSite;
|
const addSite = get().addSite;
|
||||||
const newAzimuth = ((source.azimuth ?? 0) + 30) % 360;
|
const newAzimuth = ((source.azimuth ?? 0) + 120) % 360; // 120° for tri-sector
|
||||||
|
|
||||||
await addSite({
|
await addSite({
|
||||||
name: `${source.name}-clone`,
|
name: `${baseName}-${label}`,
|
||||||
lat: source.lat,
|
lat: source.lat,
|
||||||
lon: source.lon,
|
lon: source.lon,
|
||||||
height: source.height,
|
height: source.height,
|
||||||
power: source.power,
|
power: source.power,
|
||||||
gain: source.gain,
|
gain: source.gain >= 15 ? source.gain : 18, // sector gain default
|
||||||
frequency: source.frequency,
|
frequency: source.frequency,
|
||||||
antennaType: source.antennaType,
|
antennaType: 'sector',
|
||||||
azimuth: newAzimuth,
|
azimuth: newAzimuth,
|
||||||
beamwidth: source.beamwidth,
|
beamwidth: source.beamwidth ?? 65,
|
||||||
color: '',
|
color: '',
|
||||||
visible: true,
|
visible: true,
|
||||||
notes: source.notes ? `Clone of: ${source.notes}` : `Clone of ${source.name}`,
|
notes: `Sector ${label} at ${baseName}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear coverage to force recalculation
|
||||||
|
useCoverageStore.getState().clearCoverage();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Import sites from parsed data
|
// Import sites from parsed data
|
||||||
|
|||||||
Reference in New Issue
Block a user