@makolabs/ripple 0.0.1-dev.8 → 0.0.1-dev.81
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/README.md +1 -1
- package/dist/adapters/storage/BaseAdapter.d.ts +20 -0
- package/dist/adapters/storage/BaseAdapter.js +171 -0
- package/dist/adapters/storage/S3Adapter.d.ts +21 -0
- package/dist/adapters/storage/S3Adapter.js +194 -0
- package/dist/adapters/storage/index.d.ts +3 -0
- package/dist/adapters/storage/index.js +3 -0
- package/dist/adapters/storage/types.d.ts +102 -0
- package/dist/adapters/storage/types.js +4 -0
- package/dist/charts/Chart.svelte +59 -47
- package/dist/charts/Chart.svelte.d.ts +1 -1
- package/dist/drawer/drawer.js +3 -3
- package/dist/elements/accordion/Accordion.svelte +98 -0
- package/dist/elements/accordion/Accordion.svelte.d.ts +4 -0
- package/dist/elements/accordion/accordion.d.ts +227 -0
- package/dist/elements/accordion/accordion.js +138 -0
- package/dist/elements/alert/Alert.svelte +7 -3
- package/dist/elements/dropdown/Dropdown.svelte +74 -107
- package/dist/elements/dropdown/Select.svelte +81 -62
- package/dist/elements/dropdown/dropdown.js +1 -1
- package/dist/elements/dropdown/select.js +8 -8
- package/dist/elements/file-upload/FileUpload.svelte +17 -95
- package/dist/elements/file-upload/FilesPreview.svelte +93 -0
- package/dist/elements/file-upload/FilesPreview.svelte.d.ts +4 -0
- package/dist/elements/progress/Progress.svelte +83 -25
- package/dist/file-browser/FileBrowser.svelte +837 -0
- package/dist/file-browser/FileBrowser.svelte.d.ts +14 -0
- package/dist/file-browser/index.d.ts +1 -0
- package/dist/file-browser/index.js +1 -0
- package/dist/filters/CompactFilters.svelte +147 -0
- package/dist/filters/CompactFilters.svelte.d.ts +4 -0
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.js +1 -0
- package/dist/forms/Checkbox.svelte +2 -2
- package/dist/forms/DateRange.svelte +21 -21
- package/dist/forms/Input.svelte +3 -3
- package/dist/forms/NumberInput.svelte +1 -1
- package/dist/forms/RadioInputs.svelte +3 -3
- package/dist/forms/Tags.svelte +5 -5
- package/dist/forms/Toggle.svelte +3 -3
- package/dist/forms/slider.js +4 -4
- package/dist/header/PageHeader.svelte +49 -11
- package/dist/index.d.ts +256 -143
- package/dist/index.js +19 -2
- package/dist/layout/card/MetricCard.svelte +64 -0
- package/dist/layout/card/MetricCard.svelte.d.ts +4 -0
- package/dist/layout/card/StatsCard.svelte +4 -3
- package/dist/layout/card/StatsCard.svelte.d.ts +1 -1
- package/dist/layout/card/metric-card.d.ts +49 -0
- package/dist/layout/card/metric-card.js +10 -0
- package/dist/layout/card/stats-card.d.ts +0 -15
- package/dist/layout/card/stats-card.js +1 -1
- package/dist/layout/sidebar/NavGroup.svelte +8 -9
- package/dist/layout/sidebar/NavItem.svelte +2 -2
- package/dist/layout/sidebar/Sidebar.svelte +102 -49
- package/dist/layout/table/Table.svelte +464 -87
- package/dist/layout/table/Table.svelte.d.ts +1 -1
- package/dist/layout/table/table.d.ts +0 -47
- package/dist/layout/table/table.js +0 -8
- package/dist/layout/tabs/Tab.svelte +9 -6
- package/dist/layout/tabs/Tab.svelte.d.ts +1 -1
- package/dist/layout/tabs/TabContent.svelte +1 -2
- package/dist/layout/tabs/TabContent.svelte.d.ts +1 -1
- package/dist/layout/tabs/TabGroup.svelte +10 -5
- package/dist/layout/tabs/TabGroup.svelte.d.ts +2 -2
- package/dist/layout/tabs/tabs.d.ts +61 -76
- package/dist/layout/tabs/tabs.js +170 -28
- package/dist/modal/Modal.svelte +3 -3
- package/dist/modal/modal.js +3 -3
- package/dist/utils/Portal.svelte +108 -0
- package/dist/utils/Portal.svelte.d.ts +8 -0
- package/dist/utils/dateUtils.d.ts +7 -0
- package/dist/utils/dateUtils.js +26 -0
- package/dist/variants.d.ts +11 -1
- package/dist/variants.js +17 -0
- package/package.json +2 -2
- package/dist/header/pageheaders.d.ts +0 -10
- package/dist/header/pageheaders.js +0 -1
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Paste the following CSS import code in `app.css`
|
|
|
41
41
|
@source "../node_modules/@makolabs/ripple";
|
|
42
42
|
|
|
43
43
|
@theme {
|
|
44
|
-
/* Default (
|
|
44
|
+
/* Default (default) */
|
|
45
45
|
--color-default-50: oklch(0.984 0.003 247.858);
|
|
46
46
|
--color-default-100: oklch(0.96 0.006 247.858);
|
|
47
47
|
--color-default-200: oklch(0.91 0.008 247.858);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StorageAdapter, FileItem, FileListResult, ImportOptions, ImportResult, ImportStatus, BatchImportResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Base storage adapter with common functionality
|
|
4
|
+
*/
|
|
5
|
+
export declare abstract class BaseAdapter implements StorageAdapter {
|
|
6
|
+
abstract getName(): string;
|
|
7
|
+
abstract list(path: string, searchQuery?: string): Promise<FileListResult>;
|
|
8
|
+
abstract download(file: FileItem): Promise<string>;
|
|
9
|
+
abstract isConfigured(): Promise<boolean>;
|
|
10
|
+
abstract authenticate?(): Promise<boolean>;
|
|
11
|
+
import(file: FileItem, options: ImportOptions): Promise<ImportResult>;
|
|
12
|
+
getImportStatus(importId: string, fileKey: string): Promise<ImportStatus>;
|
|
13
|
+
batchImport(files: FileItem[], options: ImportOptions): Promise<BatchImportResult>;
|
|
14
|
+
protected abstract getApiPath(): string;
|
|
15
|
+
setReopenFlag(): void;
|
|
16
|
+
shouldReopenBrowser(): boolean;
|
|
17
|
+
clearReopenFlag(): void;
|
|
18
|
+
protected getDetailedStatus(status: string): string;
|
|
19
|
+
protected calculateProgress(status: string, processedRows?: number, totalRows?: number): number;
|
|
20
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base storage adapter with common functionality
|
|
3
|
+
*/
|
|
4
|
+
export class BaseAdapter {
|
|
5
|
+
// Common implementation of import and getImportStatus
|
|
6
|
+
async import(file, options) {
|
|
7
|
+
try {
|
|
8
|
+
const endpoint = `/api/${this.getApiPath()}/import`;
|
|
9
|
+
const response = await fetch(endpoint, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json'
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
key: file.key,
|
|
16
|
+
fileId: file.id, // For Google Drive
|
|
17
|
+
contentType: options.contentType,
|
|
18
|
+
fileFormat: options.fileFormat,
|
|
19
|
+
reference: options.reference || file.name,
|
|
20
|
+
insurer: options.contentType === 'insurer_statement' ? options.insurer : null
|
|
21
|
+
})
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
const errorData = await response.json();
|
|
25
|
+
throw new Error(errorData.error || 'Failed to import file');
|
|
26
|
+
}
|
|
27
|
+
const result = await response.json();
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
message: `File ${file.name} imported successfully`,
|
|
31
|
+
fileId: result.fileId || result.importResponse?.uuid,
|
|
32
|
+
status: 'pending',
|
|
33
|
+
progress: 10
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error(`Error importing ${this.getName()} file:`, err);
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
message: `Failed to import file from ${this.getName()}`,
|
|
41
|
+
error: err instanceof Error ? err.message : 'Unknown error'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async getImportStatus(importId, fileKey) {
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(`/api/v1/uploads/${importId}`);
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
if (response.status === 404) {
|
|
50
|
+
throw new Error(`404: File ${importId} not found on server`);
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Failed to get file status: ${response.status} ${response.statusText}`);
|
|
53
|
+
}
|
|
54
|
+
const fileData = await response.json();
|
|
55
|
+
return {
|
|
56
|
+
fileId: importId,
|
|
57
|
+
status: fileData.processing_status,
|
|
58
|
+
progress: this.calculateProgress(fileData.processing_status, fileData.processed_rows, fileData.total_rows),
|
|
59
|
+
processedRows: fileData.processed_rows,
|
|
60
|
+
totalRows: fileData.total_rows,
|
|
61
|
+
detailedStatus: this.getDetailedStatus(fileData.processing_status),
|
|
62
|
+
error: fileData.error_details?.message,
|
|
63
|
+
created_at: fileData.created_at
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error('Error polling file status:', err);
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Common implementation of batch import
|
|
72
|
+
async batchImport(files, options) {
|
|
73
|
+
// Process files sequentially
|
|
74
|
+
const individualResults = [];
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
try {
|
|
77
|
+
const result = await this.import(file, options);
|
|
78
|
+
individualResults.push(result);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
individualResults.push({
|
|
82
|
+
success: false,
|
|
83
|
+
message: `Failed to import ${file.name}`,
|
|
84
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
85
|
+
fileId: file.id
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Count successes and failures
|
|
90
|
+
const succeeded = individualResults.filter(r => r.success).length;
|
|
91
|
+
const failed = individualResults.length - succeeded;
|
|
92
|
+
// Return batch import result
|
|
93
|
+
return {
|
|
94
|
+
success: failed === 0, // Overall success if all files succeeded
|
|
95
|
+
message: `Imported ${succeeded} of ${individualResults.length} files`,
|
|
96
|
+
summary: {
|
|
97
|
+
total: individualResults.length,
|
|
98
|
+
succeeded,
|
|
99
|
+
failed
|
|
100
|
+
},
|
|
101
|
+
results: individualResults.map(r => ({
|
|
102
|
+
key: r.fileId || '',
|
|
103
|
+
success: r.success,
|
|
104
|
+
fileId: r.fileId,
|
|
105
|
+
error: r.error
|
|
106
|
+
}))
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Set a flag to reopen the browser after authentication
|
|
110
|
+
setReopenFlag() {
|
|
111
|
+
// Default implementation does nothing
|
|
112
|
+
// Subclasses should override this method to implement storage-specific reopening flags
|
|
113
|
+
}
|
|
114
|
+
// Check if the browser should be reopened
|
|
115
|
+
shouldReopenBrowser() {
|
|
116
|
+
// Default implementation returns false
|
|
117
|
+
// Subclasses should override this method to check their specific reopening flags
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
// Clear the browser reopening flag
|
|
121
|
+
clearReopenFlag() {
|
|
122
|
+
// Default implementation does nothing
|
|
123
|
+
// Subclasses should override this method to clear their specific reopening flags
|
|
124
|
+
}
|
|
125
|
+
// Helper method to get detailed status
|
|
126
|
+
getDetailedStatus(status) {
|
|
127
|
+
const statusMap = {
|
|
128
|
+
'pending': 'Waiting to start',
|
|
129
|
+
'pending_import': 'Preparing import',
|
|
130
|
+
'initializing': 'Initializing import',
|
|
131
|
+
'pending_file_retrieval': 'Retrieving file',
|
|
132
|
+
'retrieving_file': 'Downloading file',
|
|
133
|
+
'file_retrieved': 'File downloaded',
|
|
134
|
+
'loading_file_contents_to_db': 'Loading data',
|
|
135
|
+
'file_contents_loaded': 'Data loaded',
|
|
136
|
+
'ready_to_process': 'Ready for processing',
|
|
137
|
+
'validating_source_file_rows': 'Validating file',
|
|
138
|
+
'converting_to_business_objects': 'Converting data',
|
|
139
|
+
'processing': 'Processing data',
|
|
140
|
+
'completed': 'Import completed',
|
|
141
|
+
'failed': 'Import failed',
|
|
142
|
+
'partially_processed': 'Partially processed'
|
|
143
|
+
};
|
|
144
|
+
return statusMap[status] || status;
|
|
145
|
+
}
|
|
146
|
+
// Helper method to calculate progress
|
|
147
|
+
calculateProgress(status, processedRows, totalRows) {
|
|
148
|
+
// If we have row counts, use them for more accurate progress
|
|
149
|
+
if (processedRows !== undefined && totalRows && totalRows > 0) {
|
|
150
|
+
return Math.min(95, Math.round((processedRows / totalRows) * 100));
|
|
151
|
+
}
|
|
152
|
+
// Otherwise, use status-based progress estimates
|
|
153
|
+
const progressMap = {
|
|
154
|
+
'pending': 5,
|
|
155
|
+
'pending_import': 10,
|
|
156
|
+
'initializing': 15,
|
|
157
|
+
'pending_file_retrieval': 20,
|
|
158
|
+
'retrieving_file': 30,
|
|
159
|
+
'file_retrieved': 40,
|
|
160
|
+
'loading_file_contents_to_db': 50,
|
|
161
|
+
'file_contents_loaded': 60,
|
|
162
|
+
'ready_to_process': 70,
|
|
163
|
+
'validating_source_file_rows': 80,
|
|
164
|
+
'converting_to_business_objects': 90,
|
|
165
|
+
'completed': 100,
|
|
166
|
+
'failed': 0,
|
|
167
|
+
'partially_processed': 95
|
|
168
|
+
};
|
|
169
|
+
return progressMap[status] || 50;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FileItem, FileListResult } from './types.js';
|
|
2
|
+
import { BaseAdapter } from './BaseAdapter.js';
|
|
3
|
+
/**
|
|
4
|
+
* S3 storage adapter for the FileBrowser component
|
|
5
|
+
*/
|
|
6
|
+
export declare class S3Adapter extends BaseAdapter {
|
|
7
|
+
private basePath;
|
|
8
|
+
constructor(basePath?: string, publicS3BasePath?: string);
|
|
9
|
+
getName(): string;
|
|
10
|
+
isConfigured(): Promise<boolean>;
|
|
11
|
+
authenticate(): Promise<boolean>;
|
|
12
|
+
setReopenFlag(): void;
|
|
13
|
+
shouldReopenBrowser(): boolean;
|
|
14
|
+
clearReopenFlag(): void;
|
|
15
|
+
list(path: string, searchQuery?: string): Promise<FileListResult>;
|
|
16
|
+
download(file: FileItem): Promise<string>;
|
|
17
|
+
private removeFileExtensions;
|
|
18
|
+
private createBreadcrumbs;
|
|
19
|
+
private extractFolderName;
|
|
20
|
+
protected getApiPath(): string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { BaseAdapter } from './BaseAdapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* S3 storage adapter for the FileBrowser component
|
|
4
|
+
*/
|
|
5
|
+
export class S3Adapter extends BaseAdapter {
|
|
6
|
+
basePath;
|
|
7
|
+
constructor(basePath, publicS3BasePath) {
|
|
8
|
+
super();
|
|
9
|
+
this.basePath = basePath || publicS3BasePath || 'export/clarkfin/';
|
|
10
|
+
}
|
|
11
|
+
getName() {
|
|
12
|
+
return 'S3';
|
|
13
|
+
}
|
|
14
|
+
async isConfigured() {
|
|
15
|
+
// S3 should always be configured if the environment variables are set
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
// S3 does not require authentication, but we implement the method to satisfy the interface
|
|
19
|
+
async authenticate() {
|
|
20
|
+
return true; // S3 is considered authenticated if isConfigured() returns true
|
|
21
|
+
}
|
|
22
|
+
// Override setReopenFlag to store the S3 browser reopening flag
|
|
23
|
+
setReopenFlag() {
|
|
24
|
+
if (typeof localStorage !== 'undefined') {
|
|
25
|
+
localStorage.setItem('reopen_s3_browser', 'true');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Check if the S3 browser should be reopened
|
|
29
|
+
shouldReopenBrowser() {
|
|
30
|
+
if (typeof localStorage !== 'undefined') {
|
|
31
|
+
return localStorage.getItem('reopen_s3_browser') === 'true';
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
// Clear the S3 browser reopening flag
|
|
36
|
+
clearReopenFlag() {
|
|
37
|
+
if (typeof localStorage !== 'undefined') {
|
|
38
|
+
localStorage.removeItem('reopen_s3_browser');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async list(path, searchQuery) {
|
|
42
|
+
try {
|
|
43
|
+
// Ensure path ends with a forward slash
|
|
44
|
+
const normalizedPath = path.endsWith('/') ? path : path + '/';
|
|
45
|
+
// Build API URL including search term if provided
|
|
46
|
+
let apiUrl = `/api/s3/list?prefix=${encodeURIComponent(normalizedPath)}`;
|
|
47
|
+
if (searchQuery) {
|
|
48
|
+
apiUrl += `&search=${encodeURIComponent(searchQuery)}`;
|
|
49
|
+
}
|
|
50
|
+
// Use the API endpoint for listing S3 files
|
|
51
|
+
const response = await fetch(apiUrl);
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
const errorData = await response.json();
|
|
54
|
+
throw new Error(errorData.error || 'Failed to fetch files');
|
|
55
|
+
}
|
|
56
|
+
const result = await response.json();
|
|
57
|
+
// Transform folders - use the folderName property from the API
|
|
58
|
+
const folders = (result.folders || [])
|
|
59
|
+
.map((folder) => {
|
|
60
|
+
return {
|
|
61
|
+
key: folder.prefix,
|
|
62
|
+
// Use the folderName from the API if available, otherwise extract it from the prefix
|
|
63
|
+
name: folder.folderName || this.extractFolderName(folder.prefix, normalizedPath),
|
|
64
|
+
lastModified: new Date(),
|
|
65
|
+
size: 0,
|
|
66
|
+
isFolder: true
|
|
67
|
+
};
|
|
68
|
+
})
|
|
69
|
+
.filter((folder) => folder.name); // Filter out empty folder names
|
|
70
|
+
// Transform files - only include files that are directly in this directory
|
|
71
|
+
const fileItems = (result.files || [])
|
|
72
|
+
.filter((file) => {
|
|
73
|
+
// Skip the directory marker itself if it exists as a file
|
|
74
|
+
if (file.key === normalizedPath)
|
|
75
|
+
return false;
|
|
76
|
+
// Get the relative path within the current directory
|
|
77
|
+
const relativePath = file.key.slice(normalizedPath.length);
|
|
78
|
+
// Only include files that don't have additional path separators
|
|
79
|
+
// (indicating they're directly in this directory)
|
|
80
|
+
return !relativePath.includes('/');
|
|
81
|
+
})
|
|
82
|
+
.map((file) => ({
|
|
83
|
+
key: file.key,
|
|
84
|
+
name: this.removeFileExtensions(file.key.slice(normalizedPath.length)),
|
|
85
|
+
lastModified: new Date(file.lastModified),
|
|
86
|
+
size: file.size,
|
|
87
|
+
isFolder: false
|
|
88
|
+
}));
|
|
89
|
+
// Combine folders and files
|
|
90
|
+
const files = [...folders, ...fileItems];
|
|
91
|
+
// Sort by lastModified in reverse order (newest first), but keep folders first
|
|
92
|
+
files.sort((a, b) => {
|
|
93
|
+
// Keep folders first
|
|
94
|
+
if (a.isFolder !== b.isFolder) {
|
|
95
|
+
return a.isFolder ? -1 : 1;
|
|
96
|
+
}
|
|
97
|
+
// Sort by lastModified in reverse order (newest first)
|
|
98
|
+
return b.lastModified.getTime() - a.lastModified.getTime();
|
|
99
|
+
});
|
|
100
|
+
// Create breadcrumbs
|
|
101
|
+
const breadcrumbs = this.createBreadcrumbs(normalizedPath);
|
|
102
|
+
// Determine parent path
|
|
103
|
+
const pathParts = normalizedPath.split('/').filter(Boolean);
|
|
104
|
+
let parentPath = '';
|
|
105
|
+
if (pathParts.length > 0) {
|
|
106
|
+
// Remove the last part and join the rest
|
|
107
|
+
pathParts.pop();
|
|
108
|
+
parentPath = pathParts.length > 0 ? pathParts.join('/') + '/' : this.basePath;
|
|
109
|
+
// Ensure we never navigate outside the basePath
|
|
110
|
+
if (!parentPath.startsWith(this.basePath)) {
|
|
111
|
+
parentPath = this.basePath;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Debug log
|
|
115
|
+
console.log(`S3 listing path: ${normalizedPath}, files: ${fileItems.length}, folders: ${folders.length}`);
|
|
116
|
+
if (folders.length > 0) {
|
|
117
|
+
console.log('Sample folder names:', folders.slice(0, 3).map((f) => f.name));
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
files,
|
|
121
|
+
currentPath: normalizedPath,
|
|
122
|
+
parentPath: normalizedPath !== this.basePath ? parentPath : undefined,
|
|
123
|
+
breadcrumbs
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error('Error fetching S3 files:', err);
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async download(file) {
|
|
132
|
+
try {
|
|
133
|
+
const response = await fetch(`/api/s3/download?key=${encodeURIComponent(file.key)}`);
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const errorData = await response.json();
|
|
136
|
+
throw new Error(errorData.error || 'Failed to download file');
|
|
137
|
+
}
|
|
138
|
+
const url = await response.text();
|
|
139
|
+
return url;
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
console.error('Error downloading S3 file:', err);
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Helper method to remove file extensions
|
|
147
|
+
removeFileExtensions(filename) {
|
|
148
|
+
// First remove .csv.gz if it exists
|
|
149
|
+
let name = filename.replace(/\.csv\.gz$/, '');
|
|
150
|
+
// Then remove any remaining extension
|
|
151
|
+
return name.replace(/\.[^/.]+$/, '');
|
|
152
|
+
}
|
|
153
|
+
// Helper method to create breadcrumbs
|
|
154
|
+
createBreadcrumbs(path) {
|
|
155
|
+
// If we're at base path, reset breadcrumbs
|
|
156
|
+
if (path === this.basePath) {
|
|
157
|
+
return [{ name: 'S3', path: this.basePath, current: true, clickable: true }];
|
|
158
|
+
}
|
|
159
|
+
// Extract parts for display
|
|
160
|
+
const pathWithoutBase = path.startsWith(this.basePath) ? path.slice(this.basePath.length) : '';
|
|
161
|
+
// Add base path as the first item
|
|
162
|
+
let breadcrumbs = [{ name: 'S3', path: this.basePath, current: false, clickable: true }];
|
|
163
|
+
// Add current directory parts
|
|
164
|
+
if (pathWithoutBase) {
|
|
165
|
+
const parts = pathWithoutBase.split('/').filter(Boolean);
|
|
166
|
+
let currentPath = this.basePath;
|
|
167
|
+
parts.forEach((part, index) => {
|
|
168
|
+
currentPath += part + '/';
|
|
169
|
+
breadcrumbs.push({
|
|
170
|
+
name: part,
|
|
171
|
+
path: currentPath,
|
|
172
|
+
current: index === parts.length - 1,
|
|
173
|
+
clickable: true
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return breadcrumbs;
|
|
178
|
+
}
|
|
179
|
+
// Helper method to extract folder name from a prefix
|
|
180
|
+
extractFolderName(prefix, currentPath) {
|
|
181
|
+
// If the prefix starts with the current path, extract the next segment
|
|
182
|
+
if (prefix.startsWith(currentPath)) {
|
|
183
|
+
const relativePath = prefix.slice(currentPath.length);
|
|
184
|
+
return relativePath.split('/')[0];
|
|
185
|
+
}
|
|
186
|
+
// Fallback: use the last non-empty segment before the trailing slash
|
|
187
|
+
const segments = prefix.split('/').filter(Boolean);
|
|
188
|
+
return segments.length > 0 ? segments[segments.length - 1] : '';
|
|
189
|
+
}
|
|
190
|
+
// Implement the abstract method from BaseAdapter
|
|
191
|
+
getApiPath() {
|
|
192
|
+
return 's3';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage adapter interfaces for the unified FileBrowser component
|
|
3
|
+
*/
|
|
4
|
+
export interface FileItem {
|
|
5
|
+
key: string;
|
|
6
|
+
name: string;
|
|
7
|
+
lastModified: Date;
|
|
8
|
+
createdAt?: Date;
|
|
9
|
+
size: number;
|
|
10
|
+
isFolder: boolean;
|
|
11
|
+
id?: string;
|
|
12
|
+
mimeType?: string;
|
|
13
|
+
parentId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FileActionSingle {
|
|
16
|
+
label: (file: FileItem) => string;
|
|
17
|
+
action: (file: FileItem) => void | Promise<void>;
|
|
18
|
+
isAllowed: (file: FileItem) => boolean;
|
|
19
|
+
batchAction?: never;
|
|
20
|
+
}
|
|
21
|
+
export interface FileActionBatch {
|
|
22
|
+
label: (files: FileItem[]) => string;
|
|
23
|
+
batchAction: (files: FileItem[]) => void | Promise<void>;
|
|
24
|
+
isAllowed: (files: FileItem[]) => boolean;
|
|
25
|
+
action?: never;
|
|
26
|
+
}
|
|
27
|
+
export type FileAction = FileActionSingle | FileActionBatch;
|
|
28
|
+
export interface Breadcrumb {
|
|
29
|
+
name: string;
|
|
30
|
+
path: string;
|
|
31
|
+
id?: string;
|
|
32
|
+
current: boolean;
|
|
33
|
+
clickable: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface FileListResult {
|
|
36
|
+
files: FileItem[];
|
|
37
|
+
currentPath: string;
|
|
38
|
+
parentPath?: string;
|
|
39
|
+
breadcrumbs?: Breadcrumb[];
|
|
40
|
+
}
|
|
41
|
+
export interface ImportOptions {
|
|
42
|
+
contentType: string;
|
|
43
|
+
fileFormat: string;
|
|
44
|
+
reference?: string;
|
|
45
|
+
insurer?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ImportResult {
|
|
48
|
+
success: boolean;
|
|
49
|
+
message: string;
|
|
50
|
+
fileId?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
status?: string;
|
|
53
|
+
progress?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface BatchImportResult {
|
|
56
|
+
success: boolean;
|
|
57
|
+
message: string;
|
|
58
|
+
summary: {
|
|
59
|
+
total: number;
|
|
60
|
+
succeeded: number;
|
|
61
|
+
failed: number;
|
|
62
|
+
};
|
|
63
|
+
results: {
|
|
64
|
+
key: string;
|
|
65
|
+
success: boolean;
|
|
66
|
+
fileId?: string;
|
|
67
|
+
error?: string;
|
|
68
|
+
}[];
|
|
69
|
+
}
|
|
70
|
+
export interface ImportStatus {
|
|
71
|
+
fileId: string;
|
|
72
|
+
status: string;
|
|
73
|
+
progress: number;
|
|
74
|
+
processedRows?: number;
|
|
75
|
+
totalRows?: number;
|
|
76
|
+
detailedStatus?: string;
|
|
77
|
+
error?: string;
|
|
78
|
+
created_at?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface ErrorReportOptions {
|
|
81
|
+
errorType: string;
|
|
82
|
+
errorMessage: string;
|
|
83
|
+
errorDetails?: string;
|
|
84
|
+
clientInfo?: Record<string, string>;
|
|
85
|
+
userAction?: string;
|
|
86
|
+
}
|
|
87
|
+
type URLString = string;
|
|
88
|
+
export interface StorageAdapter {
|
|
89
|
+
getName(): string;
|
|
90
|
+
list(path: string, searchQuery?: string): Promise<FileListResult>;
|
|
91
|
+
download(file: FileItem): Promise<URLString>;
|
|
92
|
+
import(file: FileItem, options: ImportOptions): Promise<ImportResult>;
|
|
93
|
+
batchImport(files: FileItem[], options: ImportOptions): Promise<BatchImportResult>;
|
|
94
|
+
getImportStatus(importId: string, fileKey: string): Promise<ImportStatus>;
|
|
95
|
+
reportError?(importId: string, options: ErrorReportOptions): Promise<boolean>;
|
|
96
|
+
isConfigured(): Promise<boolean>;
|
|
97
|
+
authenticate?(): Promise<boolean>;
|
|
98
|
+
setReopenFlag(): void;
|
|
99
|
+
shouldReopenBrowser(): boolean;
|
|
100
|
+
clearReopenFlag(): void;
|
|
101
|
+
}
|
|
102
|
+
export {};
|
package/dist/charts/Chart.svelte
CHANGED
|
@@ -26,7 +26,13 @@
|
|
|
26
26
|
} from 'echarts/components';
|
|
27
27
|
// @ts-expect-error - ECharts types are not available
|
|
28
28
|
import { SVGRenderer } from 'echarts/renderers';
|
|
29
|
-
import
|
|
29
|
+
import {
|
|
30
|
+
type ChartProps,
|
|
31
|
+
type ChartColorString,
|
|
32
|
+
type ChartColors,
|
|
33
|
+
type SeriesConfig
|
|
34
|
+
} from '../index.js';
|
|
35
|
+
import { defaultChartColors } from '../variants.js';
|
|
30
36
|
|
|
31
37
|
// @ts-expect-error - ECharts types are not available
|
|
32
38
|
echarts.use([
|
|
@@ -104,31 +110,54 @@
|
|
|
104
110
|
let chart: echarts.ECharts;
|
|
105
111
|
let resizeObserver: ResizeObserver;
|
|
106
112
|
|
|
107
|
-
const defaultChartColors: ChartColors = {
|
|
108
|
-
health: '#1F69FF',
|
|
109
|
-
property: '#2D9D78',
|
|
110
|
-
auto: '#E8A317',
|
|
111
|
-
life: '#E34974',
|
|
112
|
-
other: '#7B3FE4',
|
|
113
|
-
default: '#6B7280'
|
|
114
|
-
};
|
|
115
|
-
|
|
116
113
|
const chartColors: ChartColors = { ...defaultChartColors, ...colors };
|
|
114
|
+
const baseColors = [
|
|
115
|
+
chartColors.health,
|
|
116
|
+
chartColors.other,
|
|
117
|
+
chartColors.property,
|
|
118
|
+
chartColors.auto,
|
|
119
|
+
chartColors.life,
|
|
120
|
+
chartColors.default
|
|
121
|
+
];
|
|
117
122
|
|
|
118
123
|
function getColor(color?: ChartColorString): string | undefined {
|
|
119
124
|
return color ? chartColors[color as keyof ChartColors] || color : undefined;
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
function getAreaStyle(seriesConfig: SeriesConfig<any>) {
|
|
128
|
+
if (!seriesConfig.color) return undefined;
|
|
129
|
+
if (!seriesConfig.showArea) return undefined;
|
|
130
|
+
|
|
131
|
+
const baseLinearAreaStyle = {
|
|
132
|
+
type: 'linear',
|
|
133
|
+
x: 0,
|
|
134
|
+
y: 0,
|
|
135
|
+
x2: 0,
|
|
136
|
+
y2: 1
|
|
137
|
+
};
|
|
138
|
+
const color = getColor(seriesConfig.color);
|
|
139
|
+
const opacity = seriesConfig.areaOpacity || 0.2;
|
|
140
|
+
const opacityHex = Math.floor(opacity * 255)
|
|
141
|
+
.toString(16)
|
|
142
|
+
.padStart(2, '0');
|
|
143
|
+
return {
|
|
144
|
+
color: {
|
|
145
|
+
...baseLinearAreaStyle,
|
|
146
|
+
colorStops: [
|
|
147
|
+
{
|
|
148
|
+
offset: 0,
|
|
149
|
+
color: color + opacityHex
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
offset: 1,
|
|
153
|
+
color: color + '00'
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
131
159
|
|
|
160
|
+
function getSeriesColors(count: number): string[] {
|
|
132
161
|
let colors = [...baseColors];
|
|
133
162
|
while (colors.length < count) {
|
|
134
163
|
const index = colors.length % baseColors.length;
|
|
@@ -228,29 +257,7 @@
|
|
|
228
257
|
color: getColor(seriesConfig.color),
|
|
229
258
|
type: seriesConfig.lineStyle?.type || 'solid'
|
|
230
259
|
},
|
|
231
|
-
areaStyle: seriesConfig
|
|
232
|
-
? {
|
|
233
|
-
color: {
|
|
234
|
-
type: 'linear',
|
|
235
|
-
x: 0,
|
|
236
|
-
y: 0,
|
|
237
|
-
x2: 0,
|
|
238
|
-
y2: 1,
|
|
239
|
-
colorStops: [
|
|
240
|
-
{
|
|
241
|
-
offset: 0,
|
|
242
|
-
color:
|
|
243
|
-
getColor(seriesConfig.color) +
|
|
244
|
-
Math.floor((seriesConfig.areaOpacity || 0.2) * 255).toString(16)
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
offset: 1,
|
|
248
|
-
color: getColor(seriesConfig.color) + '00'
|
|
249
|
-
}
|
|
250
|
-
]
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
: undefined
|
|
260
|
+
areaStyle: getAreaStyle(seriesConfig)
|
|
254
261
|
}),
|
|
255
262
|
|
|
256
263
|
...(actualType === 'bar' && {
|
|
@@ -422,11 +429,19 @@
|
|
|
422
429
|
}),
|
|
423
430
|
formatter: xAxis.format || '{value}'
|
|
424
431
|
},
|
|
432
|
+
boundaryGap: series.every((s) => s.type === 'line')
|
|
433
|
+
? false
|
|
434
|
+
: series.some((s) => s.type === 'bar' || s.type === 'horizontal-bar')
|
|
435
|
+
? true
|
|
436
|
+
: undefined,
|
|
425
437
|
name: xAxis.label,
|
|
426
438
|
nameLocation: 'middle',
|
|
427
439
|
nameGap: 30,
|
|
428
440
|
splitLine: {
|
|
429
441
|
show: grid.vertical || false
|
|
442
|
+
},
|
|
443
|
+
axisLine: xAxis.axisLine || {
|
|
444
|
+
show: false
|
|
430
445
|
}
|
|
431
446
|
},
|
|
432
447
|
|
|
@@ -439,11 +454,8 @@
|
|
|
439
454
|
position: axis.position || (index === 0 ? 'left' : 'right'),
|
|
440
455
|
min: axis.min,
|
|
441
456
|
max: axis.max,
|
|
442
|
-
axisLine: {
|
|
443
|
-
show:
|
|
444
|
-
lineStyle: {
|
|
445
|
-
color: getColor(axis.color)
|
|
446
|
-
}
|
|
457
|
+
axisLine: axis.axisLine || {
|
|
458
|
+
show: false
|
|
447
459
|
},
|
|
448
460
|
axisLabel: {
|
|
449
461
|
// @ts-expect-error - ECharts types are not available
|