@svadmin/lite 0.3.4 → 0.3.5
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/package.json +7 -4
- package/src/components/LiteAlert.svelte +0 -19
- package/src/components/LiteArrayField.svelte +0 -113
- package/src/components/LiteAuditLog.svelte +0 -104
- package/src/components/LiteBreadcrumbs.svelte +0 -39
- package/src/components/LiteChatDialog.svelte +0 -101
- package/src/components/LiteConfirmDialog.svelte +0 -60
- package/src/components/LiteEmptyState.svelte +0 -39
- package/src/components/LiteForm.svelte +0 -138
- package/src/components/LiteLayout.svelte +0 -106
- package/src/components/LiteLogin.svelte +0 -41
- package/src/components/LitePagination.svelte +0 -73
- package/src/components/LitePermissionMatrix.svelte +0 -147
- package/src/components/LiteSearch.svelte +0 -41
- package/src/components/LiteShow.svelte +0 -61
- package/src/components/LiteShowField.svelte +0 -51
- package/src/components/LiteStatsCard.svelte +0 -70
- package/src/components/LiteTable.svelte +0 -132
- package/src/components/LiteTabs.svelte +0 -57
- package/src/components/advanced/LiteAutoSaveIndicator.svelte +0 -26
- package/src/components/advanced/LiteDraggableHeader.svelte +0 -33
- package/src/components/advanced/LiteDrawerForm.svelte +0 -42
- package/src/components/advanced/LiteInlineEdit.svelte +0 -32
- package/src/components/advanced/LiteModalForm.svelte +0 -44
- package/src/components/advanced/LiteToast.svelte +0 -34
- package/src/components/advanced/LiteUndoableNotification.svelte +0 -25
- package/src/components/advanced/LiteVirtualTable.svelte +0 -44
- package/src/components/buttons/LiteCloneButton.svelte +0 -33
- package/src/components/buttons/LiteCreateButton.svelte +0 -31
- package/src/components/buttons/LiteDeleteButton.svelte +0 -57
- package/src/components/buttons/LiteEditButton.svelte +0 -33
- package/src/components/buttons/LiteExportButton.svelte +0 -31
- package/src/components/buttons/LiteImportButton.svelte +0 -50
- package/src/components/buttons/LiteListButton.svelte +0 -31
- package/src/components/buttons/LiteRefreshButton.svelte +0 -27
- package/src/components/buttons/LiteSaveButton.svelte +0 -27
- package/src/components/buttons/LiteShowButton.svelte +0 -33
- package/src/components/fields/LiteBooleanField.svelte +0 -41
- package/src/components/fields/LiteDateField.svelte +0 -51
- package/src/components/fields/LiteEmailField.svelte +0 -40
- package/src/components/fields/LiteFileField.svelte +0 -53
- package/src/components/fields/LiteImageField.svelte +0 -57
- package/src/components/fields/LiteJsonField.svelte +0 -43
- package/src/components/fields/LiteMarkdownField.svelte +0 -33
- package/src/components/fields/LiteMultiSelectField.svelte +0 -57
- package/src/components/fields/LiteNumberField.svelte +0 -34
- package/src/components/fields/LiteRelationField.svelte +0 -66
- package/src/components/fields/LiteRichTextField.svelte +0 -45
- package/src/components/fields/LiteSelectField.svelte +0 -47
- package/src/components/fields/LiteTagField.svelte +0 -44
- package/src/components/fields/LiteTextField.svelte +0 -34
- package/src/components/fields/LiteUrlField.svelte +0 -40
- package/src/components/layout/LiteAuthenticated.svelte +0 -23
- package/src/components/layout/LiteCanAccess.svelte +0 -27
- package/src/components/layout/LiteCatchAllNavigate.svelte +0 -20
- package/src/components/layout/LiteErrorBoundary.svelte +0 -20
- package/src/components/layout/LiteErrorComponent.svelte +0 -37
- package/src/components/layout/LiteHeader.svelte +0 -19
- package/src/components/layout/LiteNavigateToResource.svelte +0 -25
- package/src/components/layout/LiteSidebar.svelte +0 -93
- package/src/components/pages/LiteCreatePage.svelte +0 -39
- package/src/components/pages/LiteEditPage.svelte +0 -54
- package/src/components/pages/LiteForgotPasswordPage.svelte +0 -60
- package/src/components/pages/LiteListPage.svelte +0 -81
- package/src/components/pages/LiteProfilePage.svelte +0 -61
- package/src/components/pages/LiteRegisterPage.svelte +0 -64
- package/src/components/pages/LiteShowPage.svelte +0 -61
- package/src/components/pages/LiteUpdatePasswordPage.svelte +0 -51
- package/src/components/widgets/LiteAnomalyBadge.svelte +0 -40
- package/src/components/widgets/LiteBarChart.svelte +0 -45
- package/src/components/widgets/LiteInsightCard.svelte +0 -28
- package/src/components/widgets/LiteLineChart.svelte +0 -48
- package/src/components/widgets/LitePieChart.svelte +0 -44
- package/src/lite.css +0 -708
- package/static/enhance.js +0 -56
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { ResourceDefinition } from '@svadmin/core';
|
|
3
|
-
import { t } from '@svadmin/core/i18n';
|
|
4
|
-
import LiteTable from '../LiteTable.svelte';
|
|
5
|
-
import LitePagination from '../LitePagination.svelte';
|
|
6
|
-
import LiteSearch from '../LiteSearch.svelte';
|
|
7
|
-
import LiteCreateButton from '../buttons/LiteCreateButton.svelte';
|
|
8
|
-
import LiteRefreshButton from '../buttons/LiteRefreshButton.svelte';
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
resource: ResourceDefinition;
|
|
12
|
-
records: Record<string, unknown>[];
|
|
13
|
-
total: number;
|
|
14
|
-
pagination: { page: number; perPage: number };
|
|
15
|
-
currentSort?: string;
|
|
16
|
-
currentOrder?: 'asc' | 'desc';
|
|
17
|
-
currentSearch?: string;
|
|
18
|
-
basePath?: string;
|
|
19
|
-
canCreate?: boolean;
|
|
20
|
-
canEdit?: boolean;
|
|
21
|
-
canDelete?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let {
|
|
25
|
-
resource,
|
|
26
|
-
records,
|
|
27
|
-
total,
|
|
28
|
-
pagination,
|
|
29
|
-
currentSort,
|
|
30
|
-
currentOrder = 'asc',
|
|
31
|
-
currentSearch,
|
|
32
|
-
basePath = '/lite',
|
|
33
|
-
canCreate = true,
|
|
34
|
-
canEdit = true,
|
|
35
|
-
canDelete = true,
|
|
36
|
-
}: Props = $props();
|
|
37
|
-
</script>
|
|
38
|
-
|
|
39
|
-
<div class="lite-page">
|
|
40
|
-
<div class="lite-page-header">
|
|
41
|
-
<h1 class="lite-page-title">{resource.label || resource.name} {t('common.list') || 'List'}</h1>
|
|
42
|
-
<div class="lite-page-actions">
|
|
43
|
-
{#if canCreate}
|
|
44
|
-
<LiteCreateButton resource={resource.name} {basePath} />
|
|
45
|
-
{/if}
|
|
46
|
-
<LiteRefreshButton hideText />
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<div class="lite-card" style="margin-bottom: 20px;">
|
|
51
|
-
<div style="padding: 16px; border-bottom: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center;">
|
|
52
|
-
<LiteSearch value={currentSearch} placeholder={t('common.search') || 'Search...'} />
|
|
53
|
-
<span style="font-size: 13px; color: #64748b;">
|
|
54
|
-
{t('common.total') || 'Total'}: {total}
|
|
55
|
-
</span>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<LiteTable
|
|
59
|
-
{records}
|
|
60
|
-
{resource}
|
|
61
|
-
{currentSort}
|
|
62
|
-
{currentOrder}
|
|
63
|
-
{currentSearch}
|
|
64
|
-
{basePath}
|
|
65
|
-
{canEdit}
|
|
66
|
-
{canDelete}
|
|
67
|
-
/>
|
|
68
|
-
|
|
69
|
-
{#if total > pagination.perPage}
|
|
70
|
-
<LitePagination
|
|
71
|
-
page={pagination.page}
|
|
72
|
-
totalPages={Math.ceil(total / pagination.perPage)}
|
|
73
|
-
preserveParams={{
|
|
74
|
-
...(currentSort ? { sort: currentSort } : {}),
|
|
75
|
-
...(currentOrder ? { order: currentOrder } : {}),
|
|
76
|
-
...(currentSearch ? { q: currentSearch } : {})
|
|
77
|
-
}}
|
|
78
|
-
/>
|
|
79
|
-
{/if}
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { t } from '@svadmin/core/i18n';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
user: { id?: string | number; name?: string; email?: string; [key: string]: unknown };
|
|
6
|
-
action?: string;
|
|
7
|
-
errors?: string[];
|
|
8
|
-
successMessage?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let {
|
|
12
|
-
user,
|
|
13
|
-
action = '?/update_profile',
|
|
14
|
-
errors = [],
|
|
15
|
-
successMessage = '',
|
|
16
|
-
}: Props = $props();
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<div class="lite-page">
|
|
20
|
-
<div class="lite-page-header">
|
|
21
|
-
<h1 class="lite-page-title">{t('common.profile') || 'User Profile'}</h1>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<div class="lite-card" style="max-width: 600px; margin: 0 auto; padding: 24px;">
|
|
25
|
-
<form method="POST" {action} style="display: flex; flex-direction: column; gap: 16px;">
|
|
26
|
-
<div class="lite-form-group">
|
|
27
|
-
<label for="name">Name</label>
|
|
28
|
-
<input type="text" id="name" name="name" class="lite-input" value={user?.name ?? ''} />
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<div class="lite-form-group">
|
|
32
|
-
<label for="email">Email</label>
|
|
33
|
-
<input type="email" id="email" name="email" class="lite-input" value={user?.email ?? ''} required />
|
|
34
|
-
</div>
|
|
35
|
-
|
|
36
|
-
<!-- Password update fields can be added here or handled in a separate form -->
|
|
37
|
-
|
|
38
|
-
{#if errors.length > 0}
|
|
39
|
-
<div style="padding: 12px; background: #fef2f2; border-radius: 6px; color: #ef4444; font-size: 13px;">
|
|
40
|
-
<ul style="margin: 0; padding-left: 20px;">
|
|
41
|
-
{#each errors as err}
|
|
42
|
-
<li>{err}</li>
|
|
43
|
-
{/each}
|
|
44
|
-
</ul>
|
|
45
|
-
</div>
|
|
46
|
-
{/if}
|
|
47
|
-
|
|
48
|
-
{#if successMessage}
|
|
49
|
-
<div style="padding: 12px; background: #ecfdf5; border-radius: 6px; color: #10b981; font-size: 13px;">
|
|
50
|
-
{successMessage}
|
|
51
|
-
</div>
|
|
52
|
-
{/if}
|
|
53
|
-
|
|
54
|
-
<div style="display: flex; justify-content: flex-end; margin-top: 8px;">
|
|
55
|
-
<button type="submit" class="lite-btn lite-btn-primary">
|
|
56
|
-
{t('common.save') || 'Save Profile'}
|
|
57
|
-
</button>
|
|
58
|
-
</div>
|
|
59
|
-
</form>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { t } from '@svadmin/core/i18n';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
title?: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
action?: string;
|
|
8
|
-
loginUrl?: string;
|
|
9
|
-
errors?: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let {
|
|
13
|
-
title = 'Register',
|
|
14
|
-
description = 'Create a new account',
|
|
15
|
-
action = '?/register',
|
|
16
|
-
loginUrl = '/lite/login',
|
|
17
|
-
errors = [],
|
|
18
|
-
}: Props = $props();
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
|
-
<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #f8fafc; padding: 20px;">
|
|
22
|
-
<div class="lite-card" style="width: 100%; max-width: 400px; padding: 32px;">
|
|
23
|
-
<div style="text-align: center; margin-bottom: 24px;">
|
|
24
|
-
<h1 style="font-size: 24px; font-weight: 600; color: #0f172a; margin: 0 0 8px;">{title}</h1>
|
|
25
|
-
<p style="font-size: 14px; color: #64748b; margin: 0;">{description}</p>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<!-- We use native form action -->
|
|
29
|
-
<form method="POST" {action} style="display: flex; flex-direction: column; gap: 16px;">
|
|
30
|
-
<div class="lite-form-group">
|
|
31
|
-
<label for="email">Email</label>
|
|
32
|
-
<input type="email" id="email" name="email" class="lite-input" required />
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<div class="lite-form-group">
|
|
36
|
-
<label for="password">Password</label>
|
|
37
|
-
<input type="password" id="password" name="password" class="lite-input" required minlength="6" />
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<div class="lite-form-group">
|
|
41
|
-
<label for="confirmPassword">Confirm Password</label>
|
|
42
|
-
<input type="password" id="confirmPassword" name="confirmPassword" class="lite-input" required minlength="6" />
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
{#if errors.length > 0}
|
|
46
|
-
<div style="padding: 12px; background: #fef2f2; border-radius: 6px; color: #ef4444; font-size: 13px;">
|
|
47
|
-
<ul style="margin: 0; padding-left: 20px;">
|
|
48
|
-
{#each errors as err}
|
|
49
|
-
<li>{err}</li>
|
|
50
|
-
{/each}
|
|
51
|
-
</ul>
|
|
52
|
-
</div>
|
|
53
|
-
{/if}
|
|
54
|
-
|
|
55
|
-
<button type="submit" class="lite-btn lite-btn-primary" style="width: 100%; justify-content: center; padding: 10px;">
|
|
56
|
-
Register
|
|
57
|
-
</button>
|
|
58
|
-
|
|
59
|
-
<div style="text-align: center; font-size: 13px; color: #64748b; margin-top: 8px;">
|
|
60
|
-
Already have an account? <a href={loginUrl} style="color: #2563eb; text-decoration: none;">Login</a>
|
|
61
|
-
</div>
|
|
62
|
-
</form>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { ResourceDefinition } from '@svadmin/core';
|
|
3
|
-
import { t } from '@svadmin/core/i18n';
|
|
4
|
-
import LiteShowField from '../LiteShowField.svelte';
|
|
5
|
-
import LiteListButton from '../buttons/LiteListButton.svelte';
|
|
6
|
-
import LiteEditButton from '../buttons/LiteEditButton.svelte';
|
|
7
|
-
import LiteDeleteButton from '../buttons/LiteDeleteButton.svelte';
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
resource: ResourceDefinition;
|
|
11
|
-
record: Record<string, unknown>;
|
|
12
|
-
basePath?: string;
|
|
13
|
-
canEdit?: boolean;
|
|
14
|
-
canDelete?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let {
|
|
18
|
-
resource,
|
|
19
|
-
record,
|
|
20
|
-
basePath = '/lite',
|
|
21
|
-
canEdit = true,
|
|
22
|
-
canDelete = true,
|
|
23
|
-
}: Props = $props();
|
|
24
|
-
|
|
25
|
-
let pk = $derived(resource.primaryKey ?? 'id');
|
|
26
|
-
let idStr = $derived(String(record[pk]));
|
|
27
|
-
|
|
28
|
-
const showFields = $derived(
|
|
29
|
-
resource.fields.filter(f => f.showInShow !== false)
|
|
30
|
-
);
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
<div class="lite-page">
|
|
34
|
-
<div class="lite-page-header">
|
|
35
|
-
<h1 class="lite-page-title">{t('common.show') || 'Show'} {resource.label || resource.name} #{idStr}</h1>
|
|
36
|
-
<div class="lite-page-actions">
|
|
37
|
-
{#if canEdit}
|
|
38
|
-
<LiteEditButton resource={resource.name} recordItemId={idStr} {basePath} />
|
|
39
|
-
{/if}
|
|
40
|
-
<LiteListButton resource={resource.name} {basePath} />
|
|
41
|
-
{#if canDelete}
|
|
42
|
-
<LiteDeleteButton resource={resource.name} recordItemId={idStr} redirectUrl={`${basePath}/${resource.name}`} {basePath} />
|
|
43
|
-
{/if}
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div class="lite-card" style="padding: 24px;">
|
|
48
|
-
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 24px;">
|
|
49
|
-
{#each showFields as field}
|
|
50
|
-
<div>
|
|
51
|
-
<div style="display: block; font-size: 13px; font-weight: 500; color: #64748b; margin-bottom: 8px;">
|
|
52
|
-
{field.label}
|
|
53
|
-
</div>
|
|
54
|
-
<div style="color: #0f172a; font-size: 14px;">
|
|
55
|
-
<LiteShowField {field} value={record[field.key]} />
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
{/each}
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
interface Props {
|
|
3
|
-
title?: string;
|
|
4
|
-
description?: string;
|
|
5
|
-
action?: string;
|
|
6
|
-
errors?: string[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
let {
|
|
10
|
-
title = 'Update Password',
|
|
11
|
-
description = 'Enter your new password below.',
|
|
12
|
-
action = '?/update_password',
|
|
13
|
-
errors = [],
|
|
14
|
-
}: Props = $props();
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #f8fafc; padding: 20px;">
|
|
18
|
-
<div class="lite-card" style="width: 100%; max-width: 400px; padding: 32px;">
|
|
19
|
-
<div style="text-align: center; margin-bottom: 24px;">
|
|
20
|
-
<h1 style="font-size: 24px; font-weight: 600; color: #0f172a; margin: 0 0 8px;">{title}</h1>
|
|
21
|
-
<p style="font-size: 14px; color: #64748b; margin: 0;">{description}</p>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<!-- Native form action -->
|
|
25
|
-
<form method="POST" {action} style="display: flex; flex-direction: column; gap: 16px;">
|
|
26
|
-
<div class="lite-form-group">
|
|
27
|
-
<label for="password">New Password</label>
|
|
28
|
-
<input type="password" id="password" name="password" class="lite-input" required minlength="6" />
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<div class="lite-form-group">
|
|
32
|
-
<label for="confirmPassword">Confirm New Password</label>
|
|
33
|
-
<input type="password" id="confirmPassword" name="confirmPassword" class="lite-input" required minlength="6" />
|
|
34
|
-
</div>
|
|
35
|
-
|
|
36
|
-
{#if errors.length > 0}
|
|
37
|
-
<div style="padding: 12px; background: #fef2f2; border-radius: 6px; color: #ef4444; font-size: 13px;">
|
|
38
|
-
<ul style="margin: 0; padding-left: 20px;">
|
|
39
|
-
{#each errors as err}
|
|
40
|
-
<li>{err}</li>
|
|
41
|
-
{/each}
|
|
42
|
-
</ul>
|
|
43
|
-
</div>
|
|
44
|
-
{/if}
|
|
45
|
-
|
|
46
|
-
<button type="submit" class="lite-btn lite-btn-primary" style="width: 100%; justify-content: center; padding: 10px;">
|
|
47
|
-
Update Password
|
|
48
|
-
</button>
|
|
49
|
-
</form>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR AnomalyBadge — Rendered as a static colored badge.
|
|
4
|
-
* The SPA version has tooltips (requires JS). In lite mode,
|
|
5
|
-
* we use a title attribute for the tooltip fallback.
|
|
6
|
-
*/
|
|
7
|
-
interface Props {
|
|
8
|
-
value: number;
|
|
9
|
-
baseline: number;
|
|
10
|
-
threshold?: number;
|
|
11
|
-
formatter?: (val: number) => string;
|
|
12
|
-
lowerIsBetter?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let {
|
|
16
|
-
value,
|
|
17
|
-
baseline,
|
|
18
|
-
threshold = 0.2,
|
|
19
|
-
formatter = (v: number) => v.toString(),
|
|
20
|
-
lowerIsBetter = false,
|
|
21
|
-
}: Props = $props();
|
|
22
|
-
|
|
23
|
-
const diff = $derived(baseline === 0 ? 0 : (value - baseline) / Math.abs(baseline));
|
|
24
|
-
const isAnomaly = $derived(Math.abs(diff) >= threshold);
|
|
25
|
-
const isGood = $derived(!isAnomaly ? null : (lowerIsBetter ? diff < 0 : diff > 0));
|
|
26
|
-
const percentLabel = $derived(`${Math.abs(diff * 100).toFixed(1)}%`);
|
|
27
|
-
const arrow = $derived(diff > 0 ? '↑' : '↓');
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
{#if !isAnomaly}
|
|
31
|
-
<span style="font-size: 14px; color: #64748b;">{formatter(value)}</span>
|
|
32
|
-
{:else}
|
|
33
|
-
<span
|
|
34
|
-
title="Baseline: {formatter(baseline)} ({diff > 0 ? '+' : '-'}{percentLabel})"
|
|
35
|
-
class="lite-badge"
|
|
36
|
-
style="background: {isGood ? '#ecfdf5' : '#fef2f2'}; color: {isGood ? '#065f46' : '#991b1b'}; border: 1px solid {isGood ? '#a7f3d0' : '#fecaca'};"
|
|
37
|
-
>
|
|
38
|
-
{arrow} {formatter(value)}
|
|
39
|
-
</span>
|
|
40
|
-
{/if}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR BarChart — Rendered as a pure CSS horizontal bar chart.
|
|
4
|
-
* No charting library needed. Data is rendered server-side as
|
|
5
|
-
* colored div bars with percentage widths.
|
|
6
|
-
*/
|
|
7
|
-
interface DataPoint {
|
|
8
|
-
label: string;
|
|
9
|
-
value: number;
|
|
10
|
-
color?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
data: DataPoint[];
|
|
15
|
-
title?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let { data, title }: Props = $props();
|
|
19
|
-
|
|
20
|
-
const maxValue = $derived(Math.max(...data.map(d => d.value), 1));
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<div class="lite-card" style="margin-bottom: 16px;">
|
|
24
|
-
{#if title}
|
|
25
|
-
<div style="padding: 12px 16px; border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 14px; color: #0f172a;">
|
|
26
|
-
{title}
|
|
27
|
-
</div>
|
|
28
|
-
{/if}
|
|
29
|
-
<div style="padding: 16px; display: flex; flex-direction: column; gap: 12px;">
|
|
30
|
-
{#each data as point}
|
|
31
|
-
<div>
|
|
32
|
-
<div style="display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 13px;">
|
|
33
|
-
<span style="color: #334155;">{point.label}</span>
|
|
34
|
-
<span style="color: #64748b; font-weight: 500;">{point.value}</span>
|
|
35
|
-
</div>
|
|
36
|
-
<div style="height: 20px; background: #f1f5f9; border-radius: 4px; overflow: hidden;">
|
|
37
|
-
<div style="height: 100%; width: {(point.value / maxValue) * 100}%; background: {point.color ?? '#6366f1'}; border-radius: 4px; transition: width 0.3s;"></div>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
{/each}
|
|
41
|
-
{#if data.length === 0}
|
|
42
|
-
<p style="color: #94a3b8; font-style: italic; text-align: center;">No data</p>
|
|
43
|
-
{/if}
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR InsightCard — Degraded to static pre-rendered content.
|
|
4
|
-
* The SPA version streams AI-generated insights via ChatProvider.
|
|
5
|
-
* In lite mode, insights must be pre-computed on the server and
|
|
6
|
-
* passed as a static string. No AI streaming is available.
|
|
7
|
-
*/
|
|
8
|
-
interface Props {
|
|
9
|
-
title?: string;
|
|
10
|
-
content?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let { title = 'Insights', content = '' }: Props = $props();
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<div class="lite-card" style="margin-bottom: 16px;">
|
|
17
|
-
<div style="padding: 12px 16px; border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 14px; color: #0f172a; display: flex; align-items: center; gap: 8px;">
|
|
18
|
-
<span style="color: #f59e0b;">✦</span>
|
|
19
|
-
{title}
|
|
20
|
-
</div>
|
|
21
|
-
<div style="padding: 16px; font-size: 14px; color: #334155; line-height: 1.6;">
|
|
22
|
-
{#if content}
|
|
23
|
-
{@html content}
|
|
24
|
-
{:else}
|
|
25
|
-
<p style="color: #94a3b8; font-style: italic;">No insights available. AI analysis requires JavaScript.</p>
|
|
26
|
-
{/if}
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR LineChart — Degraded to an HTML data table.
|
|
4
|
-
* Drawing SVG/Canvas line charts inline is possible but complex.
|
|
5
|
-
* For maximum compatibility (IE11), we render data as a simple table.
|
|
6
|
-
*/
|
|
7
|
-
interface DataPoint {
|
|
8
|
-
label: string;
|
|
9
|
-
value: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
data: DataPoint[];
|
|
14
|
-
title?: string;
|
|
15
|
-
valueLabel?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let { data, title, valueLabel = 'Value' }: Props = $props();
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
|
-
<div class="lite-card" style="margin-bottom: 16px;">
|
|
22
|
-
{#if title}
|
|
23
|
-
<div style="padding: 12px 16px; border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 14px; color: #0f172a;">
|
|
24
|
-
{title}
|
|
25
|
-
</div>
|
|
26
|
-
{/if}
|
|
27
|
-
<table style="width: 100%; border-collapse: collapse;">
|
|
28
|
-
<thead>
|
|
29
|
-
<tr>
|
|
30
|
-
<th style="text-align: left; padding: 10px 16px; font-size: 12px; color: #64748b; border-bottom: 1px solid #e2e8f0;">Label</th>
|
|
31
|
-
<th style="text-align: right; padding: 10px 16px; font-size: 12px; color: #64748b; border-bottom: 1px solid #e2e8f0;">{valueLabel}</th>
|
|
32
|
-
</tr>
|
|
33
|
-
</thead>
|
|
34
|
-
<tbody>
|
|
35
|
-
{#each data as point, i}
|
|
36
|
-
<tr style="background: {i % 2 === 0 ? '#ffffff' : '#f8fafc'};">
|
|
37
|
-
<td style="padding: 8px 16px; font-size: 13px; color: #334155;">{point.label}</td>
|
|
38
|
-
<td style="padding: 8px 16px; font-size: 13px; color: #0f172a; text-align: right; font-weight: 500;">{point.value}</td>
|
|
39
|
-
</tr>
|
|
40
|
-
{/each}
|
|
41
|
-
{#if data.length === 0}
|
|
42
|
-
<tr>
|
|
43
|
-
<td colspan="2" style="padding: 16px; text-align: center; color: #94a3b8; font-style: italic;">No data</td>
|
|
44
|
-
</tr>
|
|
45
|
-
{/if}
|
|
46
|
-
</tbody>
|
|
47
|
-
</table>
|
|
48
|
-
</div>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SSR PieChart — Degraded to a list with colored dots.
|
|
4
|
-
* Drawing SVG pie charts inline is technically possible but IE11
|
|
5
|
-
* doesn't support CSS conic-gradient. We fall back to a plain list.
|
|
6
|
-
*/
|
|
7
|
-
interface DataPoint {
|
|
8
|
-
label: string;
|
|
9
|
-
value: number;
|
|
10
|
-
color?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
data: DataPoint[];
|
|
15
|
-
title?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let { data, title }: Props = $props();
|
|
19
|
-
|
|
20
|
-
const total = $derived(data.reduce((sum, d) => sum + d.value, 0));
|
|
21
|
-
const defaultColors = ['#6366f1', '#f59e0b', '#10b981', '#ef4444', '#8b5cf6', '#06b6d4', '#f97316', '#ec4899'];
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<div class="lite-card" style="margin-bottom: 16px;">
|
|
25
|
-
{#if title}
|
|
26
|
-
<div style="padding: 12px 16px; border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 14px; color: #0f172a;">
|
|
27
|
-
{title}
|
|
28
|
-
</div>
|
|
29
|
-
{/if}
|
|
30
|
-
<div style="padding: 16px; display: flex; flex-direction: column; gap: 10px;">
|
|
31
|
-
{#each data as point, i}
|
|
32
|
-
{@const pct = total > 0 ? ((point.value / total) * 100).toFixed(1) : '0.0'}
|
|
33
|
-
<div style="display: flex; align-items: center; gap: 10px; font-size: 13px;">
|
|
34
|
-
<span style="width: 12px; height: 12px; border-radius: 50%; background: {point.color ?? defaultColors[i % defaultColors.length]}; flex-shrink: 0;"></span>
|
|
35
|
-
<span style="flex: 1; color: #334155;">{point.label}</span>
|
|
36
|
-
<span style="color: #64748b; font-weight: 500;">{point.value}</span>
|
|
37
|
-
<span style="color: #94a3b8; font-size: 12px; min-width: 48px; text-align: right;">{pct}%</span>
|
|
38
|
-
</div>
|
|
39
|
-
{/each}
|
|
40
|
-
{#if data.length === 0}
|
|
41
|
-
<p style="color: #94a3b8; font-style: italic; text-align: center;">No data</p>
|
|
42
|
-
{/if}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|