@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.
- package/dist/apps/antenna-tools/components/AntennaControls.svelte +14 -6
- package/dist/apps/antenna-tools/components/AntennaTools.svelte +6 -5
- package/dist/core/Auth/auth.svelte.js +72 -39
- package/dist/core/Auth/config.d.ts +1 -0
- package/dist/core/Auth/config.js +3 -4
- package/dist/map-v3/demo/DemoMap.svelte +3 -1
- package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte +105 -10
- package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte.d.ts +3 -0
- package/dist/map-v3/features/custom/components/ServerSetBrowser.svelte +398 -0
- package/dist/map-v3/features/custom/components/ServerSetBrowser.svelte.d.ts +22 -0
- package/dist/map-v3/features/custom/components/index.d.ts +1 -0
- package/dist/map-v3/features/custom/components/index.js +1 -0
- package/dist/map-v3/features/custom/db/custom-sets-api.d.ts +65 -0
- package/dist/map-v3/features/custom/db/custom-sets-api.js +220 -0
- package/dist/map-v3/features/custom/db/custom-sets-repository.d.ts +77 -0
- package/dist/map-v3/features/custom/db/custom-sets-repository.js +195 -0
- package/dist/map-v3/features/custom/db/index.d.ts +10 -0
- package/dist/map-v3/features/custom/db/index.js +9 -0
- package/dist/map-v3/features/custom/db/schema.sql +102 -0
- package/dist/map-v3/features/custom/db/types.d.ts +95 -0
- package/dist/map-v3/features/custom/db/types.js +95 -0
- package/dist/map-v3/features/custom/index.d.ts +2 -0
- package/dist/map-v3/features/custom/index.js +2 -0
- package/dist/map-v3/features/custom/logic/csv-parser.js +8 -3
- 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;
|