@smartnet360/svelte-components 0.0.142 → 0.0.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/apps/antenna-tools/components/AntennaControls.svelte +14 -6
  2. package/dist/apps/antenna-tools/components/AntennaTools.svelte +6 -5
  3. package/dist/core/Auth/auth.svelte.js +72 -39
  4. package/dist/core/Auth/config.d.ts +1 -0
  5. package/dist/core/Auth/config.js +3 -4
  6. package/dist/map-v3/demo/DemoMap.svelte +3 -1
  7. package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte +105 -10
  8. package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte.d.ts +3 -0
  9. package/dist/map-v3/features/custom/components/ServerSetBrowser.svelte +398 -0
  10. package/dist/map-v3/features/custom/components/ServerSetBrowser.svelte.d.ts +22 -0
  11. package/dist/map-v3/features/custom/components/index.d.ts +1 -0
  12. package/dist/map-v3/features/custom/components/index.js +1 -0
  13. package/dist/map-v3/features/custom/db/custom-sets-api.d.ts +65 -0
  14. package/dist/map-v3/features/custom/db/custom-sets-api.js +220 -0
  15. package/dist/map-v3/features/custom/db/custom-sets-repository.d.ts +77 -0
  16. package/dist/map-v3/features/custom/db/custom-sets-repository.js +195 -0
  17. package/dist/map-v3/features/custom/db/index.d.ts +10 -0
  18. package/dist/map-v3/features/custom/db/index.js +9 -0
  19. package/dist/map-v3/features/custom/db/schema.sql +102 -0
  20. package/dist/map-v3/features/custom/db/types.d.ts +95 -0
  21. package/dist/map-v3/features/custom/db/types.js +95 -0
  22. package/dist/map-v3/features/custom/index.d.ts +2 -0
  23. package/dist/map-v3/features/custom/index.js +2 -0
  24. package/dist/map-v3/features/custom/logic/csv-parser.js +8 -3
  25. package/package.json +1 -1
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Custom Feature - API Client
3
+ *
4
+ * Client-side service for interacting with custom cell sets API.
5
+ * Designed to work with any backend that implements the expected endpoints.
6
+ *
7
+ * Usage in consuming app:
8
+ * ```typescript
9
+ * const api = new CustomSetsApiClient('/api/custom-sets');
10
+ * const sets = await api.listSets();
11
+ * ```
12
+ */
13
+ import { serializeSet, deserializeSet, extractListItem } from './types';
14
+ /**
15
+ * API client for custom cell sets
16
+ * Provides CRUD operations against a REST API endpoint
17
+ */
18
+ export class CustomSetsApiClient {
19
+ baseUrl;
20
+ headers;
21
+ /**
22
+ * Create a new API client
23
+ * @param baseUrl Base URL for the API (e.g., '/api/custom-sets')
24
+ * @param headers Optional additional headers (e.g., auth tokens)
25
+ */
26
+ constructor(baseUrl, headers = {}) {
27
+ // Remove trailing slash
28
+ this.baseUrl = baseUrl.replace(/\/$/, '');
29
+ this.headers = {
30
+ 'Content-Type': 'application/json',
31
+ ...headers
32
+ };
33
+ }
34
+ /**
35
+ * Update headers (e.g., after auth token refresh)
36
+ */
37
+ setHeaders(headers) {
38
+ this.headers = {
39
+ 'Content-Type': 'application/json',
40
+ ...headers
41
+ };
42
+ }
43
+ /**
44
+ * List all available sets (lightweight, no cell data)
45
+ */
46
+ async listSets() {
47
+ try {
48
+ const response = await fetch(`${this.baseUrl}`, {
49
+ method: 'GET',
50
+ headers: this.headers
51
+ });
52
+ if (!response.ok) {
53
+ return {
54
+ success: false,
55
+ error: `Failed to fetch sets: ${response.status} ${response.statusText}`
56
+ };
57
+ }
58
+ const data = await response.json();
59
+ return {
60
+ success: true,
61
+ data: data.sets || data
62
+ };
63
+ }
64
+ catch (error) {
65
+ return {
66
+ success: false,
67
+ error: error instanceof Error ? error.message : 'Unknown error'
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * Get a single set by ID (full data including cells)
73
+ */
74
+ async getSet(id) {
75
+ try {
76
+ const response = await fetch(`${this.baseUrl}/${id}`, {
77
+ method: 'GET',
78
+ headers: this.headers
79
+ });
80
+ if (!response.ok) {
81
+ if (response.status === 404) {
82
+ return {
83
+ success: false,
84
+ error: 'Set not found'
85
+ };
86
+ }
87
+ return {
88
+ success: false,
89
+ error: `Failed to fetch set: ${response.status} ${response.statusText}`
90
+ };
91
+ }
92
+ const dbRecord = await response.json();
93
+ const set = deserializeSet(dbRecord);
94
+ return {
95
+ success: true,
96
+ data: set
97
+ };
98
+ }
99
+ catch (error) {
100
+ return {
101
+ success: false,
102
+ error: error instanceof Error ? error.message : 'Unknown error'
103
+ };
104
+ }
105
+ }
106
+ /**
107
+ * Save a set to the server (create or update)
108
+ * @param set The CustomCellSet to save
109
+ * @param id Optional ID for update (omit for create)
110
+ */
111
+ async saveSet(set, id) {
112
+ try {
113
+ const serialized = serializeSet(set);
114
+ const isUpdate = !!id;
115
+ const response = await fetch(isUpdate ? `${this.baseUrl}/${id}` : this.baseUrl, {
116
+ method: isUpdate ? 'PUT' : 'POST',
117
+ headers: this.headers,
118
+ body: JSON.stringify({
119
+ name: set.name,
120
+ data_json: JSON.stringify(serialized)
121
+ })
122
+ });
123
+ if (!response.ok) {
124
+ return {
125
+ success: false,
126
+ error: `Failed to save set: ${response.status} ${response.statusText}`
127
+ };
128
+ }
129
+ const result = await response.json();
130
+ return {
131
+ success: true,
132
+ data: { id: result.id || id }
133
+ };
134
+ }
135
+ catch (error) {
136
+ return {
137
+ success: false,
138
+ error: error instanceof Error ? error.message : 'Unknown error'
139
+ };
140
+ }
141
+ }
142
+ /**
143
+ * Delete a set from the server
144
+ */
145
+ async deleteSet(id) {
146
+ try {
147
+ const response = await fetch(`${this.baseUrl}/${id}`, {
148
+ method: 'DELETE',
149
+ headers: this.headers
150
+ });
151
+ if (!response.ok) {
152
+ if (response.status === 404) {
153
+ return {
154
+ success: false,
155
+ error: 'Set not found'
156
+ };
157
+ }
158
+ return {
159
+ success: false,
160
+ error: `Failed to delete set: ${response.status} ${response.statusText}`
161
+ };
162
+ }
163
+ return { success: true };
164
+ }
165
+ catch (error) {
166
+ return {
167
+ success: false,
168
+ error: error instanceof Error ? error.message : 'Unknown error'
169
+ };
170
+ }
171
+ }
172
+ /**
173
+ * Check if a set with the given name exists
174
+ */
175
+ async checkNameExists(name) {
176
+ try {
177
+ const response = await fetch(`${this.baseUrl}/check-name?name=${encodeURIComponent(name)}`, {
178
+ method: 'GET',
179
+ headers: this.headers
180
+ });
181
+ if (!response.ok) {
182
+ // If endpoint doesn't exist, fall back to listing
183
+ const listResult = await this.listSets();
184
+ if (listResult.success && listResult.data) {
185
+ const existing = listResult.data.find(s => s.name === name);
186
+ return {
187
+ success: true,
188
+ data: {
189
+ exists: !!existing,
190
+ id: existing?.id
191
+ }
192
+ };
193
+ }
194
+ return {
195
+ success: false,
196
+ error: `Failed to check name: ${response.status}`
197
+ };
198
+ }
199
+ const result = await response.json();
200
+ return {
201
+ success: true,
202
+ data: result
203
+ };
204
+ }
205
+ catch (error) {
206
+ return {
207
+ success: false,
208
+ error: error instanceof Error ? error.message : 'Unknown error'
209
+ };
210
+ }
211
+ }
212
+ }
213
+ /**
214
+ * Factory function to create API client
215
+ * @param baseUrl API endpoint base URL
216
+ * @param headers Optional headers (e.g., auth tokens)
217
+ */
218
+ export function createCustomSetsApi(baseUrl, headers) {
219
+ return new CustomSetsApiClient(baseUrl, headers);
220
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Custom Feature - Oracle Repository
3
+ *
4
+ * Server-side repository for Oracle database operations.
5
+ * Uses a factory pattern - accepts a query executor function
6
+ * so the consuming app provides its own database connection.
7
+ *
8
+ * Usage in SvelteKit +server.ts:
9
+ * ```typescript
10
+ * import { createCustomSetsRepository } from 'rf-tool-components/map-v3/features/custom';
11
+ * import { executeQuery } from '../../../../db'; // Your Oracle connection
12
+ *
13
+ * const repo = createCustomSetsRepository(executeQuery);
14
+ * const sets = await repo.list();
15
+ * ```
16
+ */
17
+ import type { CustomSetDbRecord, CustomSetListItem } from './types';
18
+ /**
19
+ * Query executor function signature
20
+ * The consuming app provides this, wrapping their Oracle connection
21
+ *
22
+ * @param sql The SQL query to execute
23
+ * @param params Bind parameters (positional or named)
24
+ * @returns Promise resolving to array of result rows
25
+ */
26
+ export type QueryExecutor = (sql: string, params?: Record<string, unknown> | unknown[]) => Promise<Record<string, unknown>[]>;
27
+ /**
28
+ * Repository interface for custom cell sets
29
+ */
30
+ export interface CustomSetsRepository {
31
+ /**
32
+ * List all sets (lightweight, extracts summary from JSON)
33
+ */
34
+ list(): Promise<CustomSetListItem[]>;
35
+ /**
36
+ * Get a single set by ID (full data)
37
+ */
38
+ get(id: string): Promise<CustomSetDbRecord | null>;
39
+ /**
40
+ * Find set by name
41
+ */
42
+ findByName(name: string): Promise<CustomSetDbRecord | null>;
43
+ /**
44
+ * Insert a new set
45
+ */
46
+ insert(name: string, dataJson: string, createdBy?: string): Promise<string>;
47
+ /**
48
+ * Update an existing set
49
+ */
50
+ update(id: string, name: string, dataJson: string): Promise<boolean>;
51
+ /**
52
+ * Insert or update based on existence
53
+ */
54
+ upsert(id: string | null, name: string, dataJson: string, createdBy?: string): Promise<string>;
55
+ /**
56
+ * Delete a set by ID
57
+ */
58
+ delete(id: string): Promise<boolean>;
59
+ }
60
+ /**
61
+ * Create a repository instance with the provided query executor
62
+ *
63
+ * @param executeQuery Function that executes SQL against Oracle
64
+ * @param tableName Optional custom table name (default: 'custom_cell_sets')
65
+ */
66
+ export declare function createCustomSetsRepository(executeQuery: QueryExecutor, tableName?: string): CustomSetsRepository;
67
+ /**
68
+ * SQL statements for reference (can be used directly if preferred)
69
+ */
70
+ export declare const SQL: {
71
+ readonly LIST: "\n\t\tSELECT id, name, data_json, created_by, created_at, updated_at\n\t\tFROM custom_cell_sets\n\t\tORDER BY updated_at DESC\n\t";
72
+ readonly GET_BY_ID: "\n\t\tSELECT id, name, data_json, created_by, created_at, updated_at\n\t\tFROM custom_cell_sets\n\t\tWHERE id = :id\n\t";
73
+ readonly FIND_BY_NAME: "\n\t\tSELECT id, name, data_json, created_by, created_at, updated_at\n\t\tFROM custom_cell_sets\n\t\tWHERE LOWER(name) = LOWER(:name)\n\t";
74
+ readonly INSERT: "\n\t\tINSERT INTO custom_cell_sets \n\t\t(id, name, data_json, created_by, created_at, updated_at)\n\t\tVALUES \n\t\t(:id, :name, :data_json, :created_by, SYSTIMESTAMP, SYSTIMESTAMP)\n\t";
75
+ readonly UPDATE: "\n\t\tUPDATE custom_cell_sets\n\t\tSET name = :name,\n\t\t data_json = :data_json,\n\t\t updated_at = SYSTIMESTAMP\n\t\tWHERE id = :id\n\t";
76
+ readonly DELETE: "\n\t\tDELETE FROM custom_cell_sets\n\t\tWHERE id = :id\n\t";
77
+ };
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Custom Feature - Oracle Repository
3
+ *
4
+ * Server-side repository for Oracle database operations.
5
+ * Uses a factory pattern - accepts a query executor function
6
+ * so the consuming app provides its own database connection.
7
+ *
8
+ * Usage in SvelteKit +server.ts:
9
+ * ```typescript
10
+ * import { createCustomSetsRepository } from 'rf-tool-components/map-v3/features/custom';
11
+ * import { executeQuery } from '../../../../db'; // Your Oracle connection
12
+ *
13
+ * const repo = createCustomSetsRepository(executeQuery);
14
+ * const sets = await repo.list();
15
+ * ```
16
+ */
17
+ import { extractListItem } from './types';
18
+ /**
19
+ * Generate a UUID v4
20
+ * Using crypto.randomUUID if available, fallback to manual generation
21
+ */
22
+ function generateId() {
23
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
24
+ return crypto.randomUUID();
25
+ }
26
+ // Fallback for older Node.js
27
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
28
+ const r = Math.random() * 16 | 0;
29
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
30
+ return v.toString(16);
31
+ });
32
+ }
33
+ /**
34
+ * Map Oracle row to CustomSetDbRecord
35
+ * Handles Oracle's uppercase column names
36
+ */
37
+ function mapRowToRecord(row) {
38
+ return {
39
+ id: String(row.ID || row.id || ''),
40
+ name: String(row.NAME || row.name || ''),
41
+ data_json: String(row.DATA_JSON || row.data_json || '{}'),
42
+ created_by: String(row.CREATED_BY || row.created_by || 'system'),
43
+ created_at: formatTimestamp(row.CREATED_AT || row.created_at),
44
+ updated_at: formatTimestamp(row.UPDATED_AT || row.updated_at)
45
+ };
46
+ }
47
+ /**
48
+ * Format Oracle timestamp to ISO string
49
+ */
50
+ function formatTimestamp(value) {
51
+ if (!value)
52
+ return new Date().toISOString();
53
+ if (value instanceof Date)
54
+ return value.toISOString();
55
+ if (typeof value === 'string')
56
+ return value;
57
+ return new Date().toISOString();
58
+ }
59
+ /**
60
+ * Create a repository instance with the provided query executor
61
+ *
62
+ * @param executeQuery Function that executes SQL against Oracle
63
+ * @param tableName Optional custom table name (default: 'custom_cell_sets')
64
+ */
65
+ export function createCustomSetsRepository(executeQuery, tableName = 'custom_cell_sets') {
66
+ return {
67
+ async list() {
68
+ const sql = `
69
+ SELECT id, name, data_json, created_by, created_at, updated_at
70
+ FROM ${tableName}
71
+ ORDER BY updated_at DESC
72
+ `;
73
+ const rows = await executeQuery(sql);
74
+ return rows.map(row => extractListItem(mapRowToRecord(row)));
75
+ },
76
+ async get(id) {
77
+ const sql = `
78
+ SELECT id, name, data_json, created_by, created_at, updated_at
79
+ FROM ${tableName}
80
+ WHERE id = :id
81
+ `;
82
+ const rows = await executeQuery(sql, { id });
83
+ if (rows.length === 0)
84
+ return null;
85
+ return mapRowToRecord(rows[0]);
86
+ },
87
+ async findByName(name) {
88
+ const sql = `
89
+ SELECT id, name, data_json, created_by, created_at, updated_at
90
+ FROM ${tableName}
91
+ WHERE LOWER(name) = LOWER(:name)
92
+ `;
93
+ const rows = await executeQuery(sql, { name });
94
+ if (rows.length === 0)
95
+ return null;
96
+ return mapRowToRecord(rows[0]);
97
+ },
98
+ async insert(name, dataJson, createdBy = 'system') {
99
+ const id = generateId();
100
+ const sql = `
101
+ INSERT INTO ${tableName}
102
+ (id, name, data_json, created_by, created_at, updated_at)
103
+ VALUES
104
+ (:id, :name, :data_json, :created_by, SYSTIMESTAMP, SYSTIMESTAMP)
105
+ `;
106
+ await executeQuery(sql, {
107
+ id,
108
+ name,
109
+ data_json: dataJson,
110
+ created_by: createdBy
111
+ });
112
+ return id;
113
+ },
114
+ async update(id, name, dataJson) {
115
+ const sql = `
116
+ UPDATE ${tableName}
117
+ SET name = :name,
118
+ data_json = :data_json,
119
+ updated_at = SYSTIMESTAMP
120
+ WHERE id = :id
121
+ `;
122
+ await executeQuery(sql, {
123
+ id,
124
+ name,
125
+ data_json: dataJson
126
+ });
127
+ // Note: executeQuery doesn't return affected rows count
128
+ // The consuming app can wrap this if needed
129
+ return true;
130
+ },
131
+ async upsert(id, name, dataJson, createdBy = 'system') {
132
+ if (id) {
133
+ // Check if exists
134
+ const existing = await this.get(id);
135
+ if (existing) {
136
+ await this.update(id, name, dataJson);
137
+ return id;
138
+ }
139
+ }
140
+ // Check by name for overwrite scenario
141
+ const byName = await this.findByName(name);
142
+ if (byName) {
143
+ await this.update(byName.id, name, dataJson);
144
+ return byName.id;
145
+ }
146
+ // Insert new
147
+ return this.insert(name, dataJson, createdBy);
148
+ },
149
+ async delete(id) {
150
+ const sql = `
151
+ DELETE FROM ${tableName}
152
+ WHERE id = :id
153
+ `;
154
+ await executeQuery(sql, { id });
155
+ return true;
156
+ }
157
+ };
158
+ }
159
+ /**
160
+ * SQL statements for reference (can be used directly if preferred)
161
+ */
162
+ export const SQL = {
163
+ LIST: `
164
+ SELECT id, name, data_json, created_by, created_at, updated_at
165
+ FROM custom_cell_sets
166
+ ORDER BY updated_at DESC
167
+ `,
168
+ GET_BY_ID: `
169
+ SELECT id, name, data_json, created_by, created_at, updated_at
170
+ FROM custom_cell_sets
171
+ WHERE id = :id
172
+ `,
173
+ FIND_BY_NAME: `
174
+ SELECT id, name, data_json, created_by, created_at, updated_at
175
+ FROM custom_cell_sets
176
+ WHERE LOWER(name) = LOWER(:name)
177
+ `,
178
+ INSERT: `
179
+ INSERT INTO custom_cell_sets
180
+ (id, name, data_json, created_by, created_at, updated_at)
181
+ VALUES
182
+ (:id, :name, :data_json, :created_by, SYSTIMESTAMP, SYSTIMESTAMP)
183
+ `,
184
+ UPDATE: `
185
+ UPDATE custom_cell_sets
186
+ SET name = :name,
187
+ data_json = :data_json,
188
+ updated_at = SYSTIMESTAMP
189
+ WHERE id = :id
190
+ `,
191
+ DELETE: `
192
+ DELETE FROM custom_cell_sets
193
+ WHERE id = :id
194
+ `
195
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Custom Feature - Database Module
3
+ *
4
+ * Exports for database persistence of custom cell sets.
5
+ */
6
+ export type { CustomSetDbRecord, CustomCellSerializable, CustomSetSerializable, CustomSetListItem, ApiResponse } from './types';
7
+ export { serializeSet, deserializeSet, extractListItem } from './types';
8
+ export { CustomSetsApiClient, createCustomSetsApi } from './custom-sets-api';
9
+ export type { QueryExecutor, CustomSetsRepository } from './custom-sets-repository';
10
+ export { createCustomSetsRepository, SQL } from './custom-sets-repository';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Custom Feature - Database Module
3
+ *
4
+ * Exports for database persistence of custom cell sets.
5
+ */
6
+ export { serializeSet, deserializeSet, extractListItem } from './types';
7
+ // API Client (client-side)
8
+ export { CustomSetsApiClient, createCustomSetsApi } from './custom-sets-api';
9
+ export { createCustomSetsRepository, SQL } from './custom-sets-repository';
@@ -0,0 +1,102 @@
1
+ -- ============================================================================
2
+ -- Custom Cell Sets - Oracle Schema
3
+ -- ============================================================================
4
+ --
5
+ -- Table for storing shared custom cell/point sets with JSON CLOB data.
6
+ -- Designed for global sharing between users.
7
+ --
8
+ -- Usage:
9
+ -- 1. Run this script to create the table and indexes
10
+ -- 2. The data_json column stores serialized CustomSetSerializable as JSON
11
+ -- 3. Use the repository functions from custom-sets-repository.ts
12
+ --
13
+ -- ============================================================================
14
+
15
+ -- Drop existing table (uncomment for clean install)
16
+ -- DROP TABLE custom_cell_sets;
17
+
18
+ -- Main table
19
+ CREATE TABLE custom_cell_sets (
20
+ id VARCHAR2(36) PRIMARY KEY, -- UUID primary key
21
+ name VARCHAR2(255) NOT NULL, -- Display name
22
+ data_json CLOB NOT NULL, -- Serialized set data (JSON)
23
+ created_by VARCHAR2(100) DEFAULT 'system', -- Creator username
24
+ created_at TIMESTAMP DEFAULT SYSTIMESTAMP, -- Creation timestamp
25
+ updated_at TIMESTAMP DEFAULT SYSTIMESTAMP, -- Last update timestamp
26
+
27
+ -- Constraints
28
+ CONSTRAINT chk_data_json_valid CHECK (data_json IS JSON)
29
+ );
30
+
31
+ -- Indexes for common queries
32
+ CREATE INDEX idx_custom_sets_name ON custom_cell_sets(name);
33
+ CREATE INDEX idx_custom_sets_created_by ON custom_cell_sets(created_by);
34
+ CREATE INDEX idx_custom_sets_created_at ON custom_cell_sets(created_at DESC);
35
+ CREATE INDEX idx_custom_sets_updated_at ON custom_cell_sets(updated_at DESC);
36
+
37
+ -- Comments
38
+ COMMENT ON TABLE custom_cell_sets IS 'Shared custom cell/point sets imported from CSV';
39
+ COMMENT ON COLUMN custom_cell_sets.id IS 'UUID primary key';
40
+ COMMENT ON COLUMN custom_cell_sets.name IS 'Display name for the set';
41
+ COMMENT ON COLUMN custom_cell_sets.data_json IS 'JSON CLOB containing cells, groups, styling, etc. See CustomSetSerializable type';
42
+ COMMENT ON COLUMN custom_cell_sets.created_by IS 'Username of creator';
43
+ COMMENT ON COLUMN custom_cell_sets.created_at IS 'Creation timestamp';
44
+ COMMENT ON COLUMN custom_cell_sets.updated_at IS 'Last modification timestamp';
45
+
46
+ -- ============================================================================
47
+ -- Sample data_json structure (for reference):
48
+ -- ============================================================================
49
+ -- {
50
+ -- "name": "Q4 Expansion Sites",
51
+ -- "groups": ["high-priority", "phase-2"],
52
+ -- "extraColumns": ["traffic", "notes"],
53
+ -- "unmatchedTxIds": ["CELL_999"],
54
+ -- "cells": [
55
+ -- {
56
+ -- "id": "SITE_001_A",
57
+ -- "customGroup": "high-priority",
58
+ -- "sizeFactor": 1.5,
59
+ -- "extraFields": {"traffic": 1234, "notes": "New site"},
60
+ -- "geometry": "cell"
61
+ -- },
62
+ -- {
63
+ -- "id": "POINT_001",
64
+ -- "customGroup": "phase-2",
65
+ -- "sizeFactor": 1,
66
+ -- "extraFields": {},
67
+ -- "geometry": "point",
68
+ -- "lat": 51.5074,
69
+ -- "lon": -0.1278
70
+ -- }
71
+ -- ],
72
+ -- "baseSize": 50,
73
+ -- "pointSize": 8,
74
+ -- "opacity": 0.7,
75
+ -- "defaultColor": "#3b82f6",
76
+ -- "groupColors": {"high-priority": "#ef4444", "phase-2": "#22c55e"},
77
+ -- "visibleGroups": ["high-priority", "phase-2"]
78
+ -- }
79
+ -- ============================================================================
80
+
81
+ -- ============================================================================
82
+ -- Useful queries for maintenance
83
+ -- ============================================================================
84
+
85
+ -- List all sets with cell counts (using JSON_VALUE)
86
+ -- SELECT
87
+ -- id,
88
+ -- name,
89
+ -- created_by,
90
+ -- created_at,
91
+ -- JSON_VALUE(data_json, '$.cells.size()' RETURNING NUMBER) as cell_count
92
+ -- FROM custom_cell_sets
93
+ -- ORDER BY updated_at DESC;
94
+
95
+ -- Find sets containing a specific cell ID
96
+ -- SELECT id, name
97
+ -- FROM custom_cell_sets
98
+ -- WHERE JSON_EXISTS(data_json, '$.cells[*]?(@.id == "SITE_001_A")');
99
+
100
+ -- Delete sets older than 90 days
101
+ -- DELETE FROM custom_cell_sets
102
+ -- WHERE created_at < SYSTIMESTAMP - INTERVAL '90' DAY;