253 lines
5.8 KiB
TypeScript
253 lines
5.8 KiB
TypeScript
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
|
|
export interface MapLayer {
|
|
id: string;
|
|
department_id: string;
|
|
name: string;
|
|
layer_type: 'osm' | 'image';
|
|
image_url: string | null;
|
|
image_width: number | null;
|
|
image_height: number | null;
|
|
center_lat: number;
|
|
center_lng: number;
|
|
zoom_level: number;
|
|
sort_order: number;
|
|
created_by: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface MapPin {
|
|
id: string;
|
|
layer_id: string;
|
|
label: string;
|
|
description: string;
|
|
color: string;
|
|
lat: number;
|
|
lng: number;
|
|
bounds_north: number | null;
|
|
bounds_south: number | null;
|
|
bounds_east: number | null;
|
|
bounds_west: number | null;
|
|
sort_order: number;
|
|
created_by: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface MapShape {
|
|
id: string;
|
|
layer_id: string;
|
|
shape_type: 'polygon' | 'rectangle';
|
|
label: string;
|
|
color: string;
|
|
fill_opacity: number;
|
|
stroke_width: number;
|
|
vertices: [number, number][];
|
|
rotation: number;
|
|
sort_order: number;
|
|
created_by: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface MapLayerWithPins extends MapLayer {
|
|
pins: MapPin[];
|
|
shapes: MapShape[];
|
|
}
|
|
|
|
// ── Layers ──
|
|
|
|
export async function fetchMapLayers(supabase: SupabaseClient, departmentId: string): Promise<MapLayerWithPins[]> {
|
|
const { data: layers, error: layerErr } = await (supabase as any)
|
|
.from('map_layers')
|
|
.select('*')
|
|
.eq('department_id', departmentId)
|
|
.order('sort_order');
|
|
|
|
if (layerErr) throw layerErr;
|
|
if (!layers || layers.length === 0) return [];
|
|
|
|
const layerIds = layers.map((l: any) => l.id);
|
|
|
|
const [pinResult, shapeResult] = await Promise.all([
|
|
(supabase as any).from('map_pins').select('*').in('layer_id', layerIds).order('sort_order'),
|
|
(supabase as any).from('map_shapes').select('*').in('layer_id', layerIds).order('sort_order'),
|
|
]);
|
|
|
|
if (pinResult.error) throw pinResult.error;
|
|
if (shapeResult.error) throw shapeResult.error;
|
|
|
|
const pins = pinResult.data ?? [];
|
|
const shapes = shapeResult.data ?? [];
|
|
|
|
return layers.map((layer: any) => ({
|
|
...layer,
|
|
pins: pins.filter((p: any) => p.layer_id === layer.id),
|
|
shapes: shapes.filter((s: any) => s.layer_id === layer.id),
|
|
}));
|
|
}
|
|
|
|
export async function createMapLayer(
|
|
supabase: SupabaseClient,
|
|
departmentId: string,
|
|
data: {
|
|
name: string;
|
|
layer_type: 'osm' | 'image';
|
|
image_url?: string;
|
|
image_width?: number;
|
|
image_height?: number;
|
|
center_lat?: number;
|
|
center_lng?: number;
|
|
zoom_level?: number;
|
|
sort_order?: number;
|
|
},
|
|
): Promise<MapLayer> {
|
|
const { data: layer, error } = await (supabase as any)
|
|
.from('map_layers')
|
|
.insert({
|
|
department_id: departmentId,
|
|
...data,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return layer;
|
|
}
|
|
|
|
export async function updateMapLayer(
|
|
supabase: SupabaseClient,
|
|
layerId: string,
|
|
data: Partial<Pick<MapLayer, 'name' | 'layer_type' | 'image_url' | 'image_width' | 'image_height' | 'center_lat' | 'center_lng' | 'zoom_level' | 'sort_order'>>,
|
|
): Promise<MapLayer> {
|
|
const { data: layer, error } = await (supabase as any)
|
|
.from('map_layers')
|
|
.update({ ...data, updated_at: new Date().toISOString() })
|
|
.eq('id', layerId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return layer;
|
|
}
|
|
|
|
export async function deleteMapLayer(supabase: SupabaseClient, layerId: string): Promise<void> {
|
|
const { error } = await (supabase as any)
|
|
.from('map_layers')
|
|
.delete()
|
|
.eq('id', layerId);
|
|
|
|
if (error) throw error;
|
|
}
|
|
|
|
// ── Pins ──
|
|
|
|
export async function createMapPin(
|
|
supabase: SupabaseClient,
|
|
layerId: string,
|
|
data: {
|
|
label: string;
|
|
description?: string;
|
|
color?: string;
|
|
lat: number;
|
|
lng: number;
|
|
bounds_north?: number;
|
|
bounds_south?: number;
|
|
bounds_east?: number;
|
|
bounds_west?: number;
|
|
sort_order?: number;
|
|
},
|
|
): Promise<MapPin> {
|
|
const { data: pin, error } = await (supabase as any)
|
|
.from('map_pins')
|
|
.insert({
|
|
layer_id: layerId,
|
|
...data,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return pin;
|
|
}
|
|
|
|
export async function updateMapPin(
|
|
supabase: SupabaseClient,
|
|
pinId: string,
|
|
data: Partial<Pick<MapPin, 'label' | 'description' | 'color' | 'lat' | 'lng' | 'bounds_north' | 'bounds_south' | 'bounds_east' | 'bounds_west' | 'sort_order'>>,
|
|
): Promise<MapPin> {
|
|
const { data: pin, error } = await (supabase as any)
|
|
.from('map_pins')
|
|
.update({ ...data, updated_at: new Date().toISOString() })
|
|
.eq('id', pinId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return pin;
|
|
}
|
|
|
|
export async function deleteMapPin(supabase: SupabaseClient, pinId: string): Promise<void> {
|
|
const { error } = await (supabase as any)
|
|
.from('map_pins')
|
|
.delete()
|
|
.eq('id', pinId);
|
|
|
|
if (error) throw error;
|
|
}
|
|
|
|
// ── Shapes ──
|
|
|
|
export async function createMapShape(
|
|
supabase: SupabaseClient,
|
|
layerId: string,
|
|
data: {
|
|
shape_type: 'polygon' | 'rectangle';
|
|
label?: string;
|
|
color?: string;
|
|
fill_opacity?: number;
|
|
stroke_width?: number;
|
|
vertices: [number, number][];
|
|
rotation?: number;
|
|
sort_order?: number;
|
|
},
|
|
): Promise<MapShape> {
|
|
const { data: shape, error } = await (supabase as any)
|
|
.from('map_shapes')
|
|
.insert({
|
|
layer_id: layerId,
|
|
...data,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return shape;
|
|
}
|
|
|
|
export async function updateMapShape(
|
|
supabase: SupabaseClient,
|
|
shapeId: string,
|
|
data: Partial<Pick<MapShape, 'label' | 'color' | 'fill_opacity' | 'stroke_width' | 'vertices' | 'rotation' | 'sort_order'>>,
|
|
): Promise<MapShape> {
|
|
const { data: shape, error } = await (supabase as any)
|
|
.from('map_shapes')
|
|
.update({ ...data, updated_at: new Date().toISOString() })
|
|
.eq('id', shapeId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return shape;
|
|
}
|
|
|
|
export async function deleteMapShape(supabase: SupabaseClient, shapeId: string): Promise<void> {
|
|
const { error } = await (supabase as any)
|
|
.from('map_shapes')
|
|
.delete()
|
|
.eq('id', shapeId);
|
|
|
|
if (error) throw error;
|
|
}
|