@zveltio/components 1.0.0
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/LICENSE +21 -0
- package/README.md +39 -0
- package/admin/ColumnList/ColumnList.svelte +68 -0
- package/admin/ColumnList/component.json +13 -0
- package/admin/MetadataSettings/MetadataSettings.svelte +62 -0
- package/admin/MetadataSettings/component.json +9 -0
- package/admin/RelationshipManager/RelationshipManager.svelte +86 -0
- package/admin/RelationshipManager/component.json +10 -0
- package/admin/RoleManager/RoleManager.svelte +167 -0
- package/admin/RoleManager/component.json +17 -0
- package/admin/TableDesigner/TableDesigner.svelte +37 -0
- package/admin/TableDesigner/component.json +14 -0
- package/ai/AIFeedback/AIFeedback.svelte +43 -0
- package/ai/AIInsightWidget/AIInsightWidget.svelte +56 -0
- package/ai/AIQueryBuilder/AIQueryBuilder.svelte +91 -0
- package/ai/Omnisearch/Omnisearch.svelte +112 -0
- package/ai/VoiceSearch/VoiceSearch.svelte +73 -0
- package/attachments/AttachmentManager/AttachmentManager.svelte +175 -0
- package/attachments/AttachmentManager/component.json +21 -0
- package/charts/SimpleBarChart/SimpleBarChart.svelte +28 -0
- package/charts/SimpleBarChart/component.json +14 -0
- package/common/AddressInput/AddressInput.svelte +88 -0
- package/common/AddressInput/component.json +12 -0
- package/common/Alert/Alert.svelte +20 -0
- package/common/Alert/component.json +15 -0
- package/common/Button/Button.svelte +28 -0
- package/common/Button/component.json +16 -0
- package/common/Card/Card.svelte +25 -0
- package/common/Card/component.json +13 -0
- package/common/DynamicDataTable/DynamicDataTable.svelte +84 -0
- package/common/DynamicDataTable/component.json +14 -0
- package/common/Input/Input.svelte +21 -0
- package/common/Input/component.json +16 -0
- package/common/Loading/Loading.svelte +12 -0
- package/common/Loading/component.json +12 -0
- package/common/Modal/Modal.svelte +31 -0
- package/common/Modal/component.json +14 -0
- package/common/Pagination/Pagination.svelte +40 -0
- package/common/Pagination/component.json +1 -0
- package/common/PermissionGuard/PermissionGuard.svelte +53 -0
- package/common/PermissionGuard/component.json +21 -0
- package/common/SearchableSelect/SearchableSelect.svelte +136 -0
- package/common/SearchableSelect/component.json +17 -0
- package/common/StatusBadge/StatusBadge.svelte +22 -0
- package/common/StatusBadge/component.json +1 -0
- package/dashboard/RecentActivity/RecentActivity.svelte +70 -0
- package/dashboard/RecentActivity/component.json +1 -0
- package/forms/FormField/FormField.svelte +39 -0
- package/forms/FormField/component.json +17 -0
- package/navigation/NavGroup/NavGroup.svelte +40 -0
- package/navigation/NavGroup/component.json +1 -0
- package/navigation/NavLink/NavLink.svelte +18 -0
- package/navigation/NavLink/component.json +15 -0
- package/navigation/SmartNavbar/SmartNavbar.svelte +184 -0
- package/navigation/SmartNavbar/component.json +20 -0
- package/package.json +53 -0
- package/pages/Settings/Settings.svelte +154 -0
- package/pages/Settings/component.json +11 -0
- package/src/lib/index.ts +53 -0
- package/views/ListView/ListView.svelte +19 -0
- package/views/ListView/component.json +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 liviumarianiordache
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Svelto Components - Final Clean
|
|
2
|
+
|
|
3
|
+
**84 componente** | Un singur navbar: SmartNavbar
|
|
4
|
+
|
|
5
|
+
## Navigation
|
|
6
|
+
|
|
7
|
+
**SmartNavbar** - navbar universal cu toate funcțiile:
|
|
8
|
+
- Employee UI (ex-IntranetNavbar logic)
|
|
9
|
+
- Partners UI (ex-PartnersNavbar logic)
|
|
10
|
+
- Public/Guest UI (ex-PublicNavbar logic)
|
|
11
|
+
- User menu integrat (ex-UserDropdown)
|
|
12
|
+
- Poziție configurabilă: top/bottom/left/right
|
|
13
|
+
- Theme toggle integrat
|
|
14
|
+
- Mobile responsive
|
|
15
|
+
- Nested navigation
|
|
16
|
+
- Role filtering
|
|
17
|
+
|
|
18
|
+
**Navigation (3):**
|
|
19
|
+
- SmartNavbar ← folosește DOAR pe acesta
|
|
20
|
+
- NavLink
|
|
21
|
+
- NavGroup
|
|
22
|
+
|
|
23
|
+
## Layout (5)
|
|
24
|
+
Header, Sidebar, Navbar, Breadcrumbs, PageLayout
|
|
25
|
+
|
|
26
|
+
## Common (34)
|
|
27
|
+
Button, Input, Select, Modal, Card, Loading, Alert, Avatar, Badge, Checkbox, Divider, Drawer, DynamicDataTable, Pagination, PermissionGuard, SearchableSelect, SecureField, StatusBadge, Toaster, Tooltip, TrustBadge, ConfirmationDialog, EmptyState, LoadingSkeleton, PendingIndicator, TeamChat, Stat, Indicator, MapView, AddressInput, ExportActions, ThemeToggle, ViewSwitcher, WorkflowActions
|
|
28
|
+
|
|
29
|
+
## Alte Categorii
|
|
30
|
+
Admin (9) | AI (5) | Views (16) | Charts (2) | Dashboard (2) | Forms (1) | Reports (3) | Attachments (1) | Audit (2) | Pages (1)
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
- Svelte 5 Runes
|
|
34
|
+
- Props-based
|
|
35
|
+
- DaisyUI/Tailwind
|
|
36
|
+
- Zero duplicate navbars
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
MIT
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ColumnList - Table columns editor
|
|
4
|
+
*/
|
|
5
|
+
interface Column {
|
|
6
|
+
name: string;
|
|
7
|
+
type: string;
|
|
8
|
+
nullable: boolean;
|
|
9
|
+
default_value?: string;
|
|
10
|
+
is_primary?: boolean;
|
|
11
|
+
is_unique?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
columns = $bindable<Column[]>([]),
|
|
16
|
+
dataTypes = ['text', 'varchar', 'integer', 'bigint', 'decimal', 'boolean', 'date', 'timestamp', 'uuid', 'jsonb']
|
|
17
|
+
}: {
|
|
18
|
+
columns?: Column[];
|
|
19
|
+
dataTypes?: string[];
|
|
20
|
+
} = $props();
|
|
21
|
+
|
|
22
|
+
function addColumn() {
|
|
23
|
+
columns = [...columns, { name: '', type: 'text', nullable: true }];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function removeColumn(index: number) {
|
|
27
|
+
columns = columns.filter((_, i) => i !== index);
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div>
|
|
32
|
+
<div class="flex items-center justify-between mb-3">
|
|
33
|
+
<h3 class="font-semibold">Columns</h3>
|
|
34
|
+
<button class="btn btn-sm btn-primary" onclick={addColumn}>+ Add Column</button>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="space-y-2">
|
|
38
|
+
{#each columns as col, i}
|
|
39
|
+
<div class="grid grid-cols-12 gap-2 items-center p-3 bg-base-200 rounded-lg">
|
|
40
|
+
<input
|
|
41
|
+
type="text"
|
|
42
|
+
bind:value={col.name}
|
|
43
|
+
placeholder="name"
|
|
44
|
+
class="col-span-4 input input-sm input-bordered font-mono"
|
|
45
|
+
/>
|
|
46
|
+
<select bind:value={col.type} class="col-span-3 select select-sm select-bordered">
|
|
47
|
+
{#each dataTypes as t}
|
|
48
|
+
<option value={t}>{t}</option>
|
|
49
|
+
{/each}
|
|
50
|
+
</select>
|
|
51
|
+
<div class="col-span-4 flex gap-2 text-xs">
|
|
52
|
+
<label class="flex items-center gap-1 cursor-pointer">
|
|
53
|
+
<input type="checkbox" bind:checked={col.nullable} class="checkbox checkbox-xs" />
|
|
54
|
+
Null
|
|
55
|
+
</label>
|
|
56
|
+
<label class="flex items-center gap-1 cursor-pointer">
|
|
57
|
+
<input type="checkbox" bind:checked={col.is_unique} class="checkbox checkbox-xs" />
|
|
58
|
+
Unique
|
|
59
|
+
</label>
|
|
60
|
+
</div>
|
|
61
|
+
<button class="col-span-1 btn btn-xs btn-ghost text-error" onclick={() => removeColumn(i)}>✕</button>
|
|
62
|
+
</div>
|
|
63
|
+
{/each}
|
|
64
|
+
{#if columns.length === 0}
|
|
65
|
+
<div class="text-center py-4 text-sm opacity-50">No columns</div>
|
|
66
|
+
{/if}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ColumnList",
|
|
3
|
+
"category": "admin",
|
|
4
|
+
"description": "Editable list of table columns",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"packages": [],
|
|
7
|
+
"components": []
|
|
8
|
+
},
|
|
9
|
+
"props": {
|
|
10
|
+
"columns": "$bindable<Column[]>",
|
|
11
|
+
"dataTypes": "string[] - Available data types"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MetadataSettings - Table metadata editor (display name, menu group, audit)
|
|
4
|
+
*/
|
|
5
|
+
interface Metadata {
|
|
6
|
+
displayName?: string;
|
|
7
|
+
menuGroup?: string;
|
|
8
|
+
isLoggable?: boolean;
|
|
9
|
+
icon?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
metadata = $bindable<Metadata>({})
|
|
14
|
+
}: {
|
|
15
|
+
metadata?: Metadata;
|
|
16
|
+
} = $props();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<div class="space-y-4 p-4 bg-base-200 rounded-lg">
|
|
20
|
+
<h4 class="font-semibold">Table Metadata</h4>
|
|
21
|
+
|
|
22
|
+
<div class="form-control">
|
|
23
|
+
<label class="label"><span class="label-text-alt">Display Name</span></label>
|
|
24
|
+
<input
|
|
25
|
+
type="text"
|
|
26
|
+
bind:value={metadata.displayName}
|
|
27
|
+
placeholder="User Profiles"
|
|
28
|
+
class="input input-sm input-bordered"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="form-control">
|
|
33
|
+
<label class="label"><span class="label-text-alt">Menu Group</span></label>
|
|
34
|
+
<input
|
|
35
|
+
type="text"
|
|
36
|
+
bind:value={metadata.menuGroup}
|
|
37
|
+
placeholder="Administration"
|
|
38
|
+
class="input input-sm input-bordered"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="form-control">
|
|
43
|
+
<label class="label"><span class="label-text-alt">Icon</span></label>
|
|
44
|
+
<input
|
|
45
|
+
type="text"
|
|
46
|
+
bind:value={metadata.icon}
|
|
47
|
+
placeholder="Users"
|
|
48
|
+
class="input input-sm input-bordered"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="form-control">
|
|
53
|
+
<label class="label cursor-pointer justify-start gap-2">
|
|
54
|
+
<input
|
|
55
|
+
type="checkbox"
|
|
56
|
+
bind:checked={metadata.isLoggable}
|
|
57
|
+
class="toggle toggle-sm toggle-primary"
|
|
58
|
+
/>
|
|
59
|
+
<span class="label-text">Enable Audit Log</span>
|
|
60
|
+
</label>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "MetadataSettings",
|
|
3
|
+
"category": "admin",
|
|
4
|
+
"description": "Table metadata editor (display name, menu group, audit logging)",
|
|
5
|
+
"dependencies": {"packages": [], "components": []},
|
|
6
|
+
"props": {
|
|
7
|
+
"metadata": "$bindable<Metadata> - {displayName?, menuGroup?, isLoggable?, icon?}"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* RelationshipManager - Foreign key relationship viewer
|
|
4
|
+
*/
|
|
5
|
+
interface Constraint {
|
|
6
|
+
constraint_type: string;
|
|
7
|
+
constraint_name: string;
|
|
8
|
+
column_name?: string;
|
|
9
|
+
foreign_table?: string;
|
|
10
|
+
foreign_column?: string;
|
|
11
|
+
definition?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
tableName,
|
|
16
|
+
constraints = []
|
|
17
|
+
}: {
|
|
18
|
+
tableName: string;
|
|
19
|
+
constraints: Constraint[];
|
|
20
|
+
} = $props();
|
|
21
|
+
|
|
22
|
+
const foreignKeys = $derived(constraints.filter(c => c.constraint_type === 'FOREIGN KEY'));
|
|
23
|
+
const uniqueConstraints = $derived(constraints.filter(c => c.constraint_type === 'UNIQUE'));
|
|
24
|
+
const checkConstraints = $derived(constraints.filter(c => c.constraint_type === 'CHECK'));
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div class="space-y-4">
|
|
28
|
+
<div class="flex items-center gap-2">
|
|
29
|
+
<h3 class="font-semibold">Relationships & Constraints</h3>
|
|
30
|
+
<span class="badge badge-sm">{constraints.length}</span>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="alert alert-info">
|
|
34
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
35
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
36
|
+
</svg>
|
|
37
|
+
<span class="text-sm">Viewing existing constraints. Full editor coming soon.</span>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
{#if foreignKeys.length > 0}
|
|
41
|
+
<div>
|
|
42
|
+
<h4 class="text-xs uppercase font-bold opacity-50 mb-2">Foreign Keys ({foreignKeys.length})</h4>
|
|
43
|
+
<div class="space-y-2">
|
|
44
|
+
{#each foreignKeys as fk}
|
|
45
|
+
<div class="p-3 bg-base-200 rounded-lg">
|
|
46
|
+
<div class="font-mono text-sm font-bold">{fk.constraint_name}</div>
|
|
47
|
+
{#if fk.definition}
|
|
48
|
+
<div class="text-xs opacity-60 mt-1">{fk.definition}</div>
|
|
49
|
+
{/if}
|
|
50
|
+
</div>
|
|
51
|
+
{/each}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
55
|
+
|
|
56
|
+
{#if uniqueConstraints.length > 0}
|
|
57
|
+
<div>
|
|
58
|
+
<h4 class="text-xs uppercase font-bold opacity-50 mb-2">Unique Constraints ({uniqueConstraints.length})</h4>
|
|
59
|
+
<div class="space-y-2">
|
|
60
|
+
{#each uniqueConstraints as uc}
|
|
61
|
+
<div class="p-3 bg-base-200 rounded-lg">
|
|
62
|
+
<div class="font-mono text-sm">{uc.constraint_name}</div>
|
|
63
|
+
</div>
|
|
64
|
+
{/each}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
68
|
+
|
|
69
|
+
{#if checkConstraints.length > 0}
|
|
70
|
+
<div>
|
|
71
|
+
<h4 class="text-xs uppercase font-bold opacity-50 mb-2">Check Constraints ({checkConstraints.length})</h4>
|
|
72
|
+
<div class="space-y-2">
|
|
73
|
+
{#each checkConstraints as cc}
|
|
74
|
+
<div class="p-3 bg-base-200 rounded-lg">
|
|
75
|
+
<div class="font-mono text-sm">{cc.constraint_name}</div>
|
|
76
|
+
{#if cc.definition}<div class="text-xs opacity-60 mt-1">{cc.definition}</div>{/if}
|
|
77
|
+
</div>
|
|
78
|
+
{/each}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
{/if}
|
|
82
|
+
|
|
83
|
+
{#if constraints.length === 0}
|
|
84
|
+
<div class="text-center py-8 text-sm opacity-50">No constraints defined</div>
|
|
85
|
+
{/if}
|
|
86
|
+
</div>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* RoleManager - RBAC role management UI
|
|
4
|
+
* Standalone, callback-based
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface Role {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
allowed_for: 'INTERNAL' | 'PARTNER';
|
|
12
|
+
parent_role_id?: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
roles = [],
|
|
17
|
+
onSave,
|
|
18
|
+
onDelete
|
|
19
|
+
}: {
|
|
20
|
+
roles: Role[];
|
|
21
|
+
onSave: (role: Omit<Role, 'id'> | Role) => Promise<void>;
|
|
22
|
+
onDelete: (roleId: string) => Promise<void>;
|
|
23
|
+
} = $props();
|
|
24
|
+
|
|
25
|
+
let isModalOpen = $state(false);
|
|
26
|
+
let editingRole = $state<Partial<Role> | null>(null);
|
|
27
|
+
let saving = $state(false);
|
|
28
|
+
|
|
29
|
+
function openNew() {
|
|
30
|
+
editingRole = {
|
|
31
|
+
name: '',
|
|
32
|
+
description: '',
|
|
33
|
+
allowed_for: 'INTERNAL',
|
|
34
|
+
parent_role_id: null
|
|
35
|
+
};
|
|
36
|
+
isModalOpen = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function openEdit(role: Role) {
|
|
40
|
+
editingRole = { ...role };
|
|
41
|
+
isModalOpen = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function handleSave() {
|
|
45
|
+
if (!editingRole) return;
|
|
46
|
+
saving = true;
|
|
47
|
+
try {
|
|
48
|
+
await onSave(editingRole as Role);
|
|
49
|
+
isModalOpen = false;
|
|
50
|
+
editingRole = null;
|
|
51
|
+
} finally {
|
|
52
|
+
saving = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function handleDelete(roleId: string) {
|
|
57
|
+
if (!confirm('Delete this role?')) return;
|
|
58
|
+
await onDelete(roleId);
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<div class="p-4">
|
|
63
|
+
<div class="flex items-center justify-between mb-4">
|
|
64
|
+
<h2 class="text-2xl font-bold">Roles</h2>
|
|
65
|
+
<button class="btn btn-primary" onclick={openNew}>
|
|
66
|
+
+ New Role
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
71
|
+
{#each roles as role}
|
|
72
|
+
<div class="card bg-base-100 border border-base-200 shadow-sm hover:shadow-md transition">
|
|
73
|
+
<div class="card-body p-4">
|
|
74
|
+
<div class="flex items-start justify-between">
|
|
75
|
+
<div class="flex items-center gap-2">
|
|
76
|
+
<div class="avatar placeholder">
|
|
77
|
+
<div class="w-10 rounded-full bg-primary text-primary-content">
|
|
78
|
+
<span class="text-sm">{role.name.substring(0, 2).toUpperCase()}</span>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div>
|
|
82
|
+
<h3 class="font-bold">{role.name}</h3>
|
|
83
|
+
<span class="badge badge-xs {role.allowed_for === 'INTERNAL' ? 'badge-primary' : 'badge-secondary'}">
|
|
84
|
+
{role.allowed_for}
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="dropdown dropdown-end">
|
|
89
|
+
<button tabindex="0" class="btn btn-ghost btn-xs btn-square">
|
|
90
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
91
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
|
|
92
|
+
</svg>
|
|
93
|
+
</button>
|
|
94
|
+
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-32">
|
|
95
|
+
<li><button onclick={() => openEdit(role)}>Edit</button></li>
|
|
96
|
+
<li><button class="text-error" onclick={() => handleDelete(role.id)}>Delete</button></li>
|
|
97
|
+
</ul>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<p class="text-sm opacity-70 mt-2">{role.description || '—'}</p>
|
|
102
|
+
|
|
103
|
+
{#if role.parent_role_id}
|
|
104
|
+
<div class="text-xs text-info mt-2">
|
|
105
|
+
Parent: {roles.find(r => r.id === role.parent_role_id)?.name || 'Unknown'}
|
|
106
|
+
</div>
|
|
107
|
+
{/if}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
{/each}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Modal -->
|
|
115
|
+
{#if isModalOpen && editingRole}
|
|
116
|
+
<dialog class="modal modal-open">
|
|
117
|
+
<div class="modal-box">
|
|
118
|
+
<h3 class="font-bold text-lg mb-4">{editingRole.id ? 'Edit' : 'Create'} Role</h3>
|
|
119
|
+
|
|
120
|
+
<div class="space-y-3">
|
|
121
|
+
<div class="form-control">
|
|
122
|
+
<label class="label"><span class="label-text">Name</span></label>
|
|
123
|
+
<input
|
|
124
|
+
type="text"
|
|
125
|
+
bind:value={editingRole.name}
|
|
126
|
+
class="input input-bordered"
|
|
127
|
+
placeholder="Administrator"
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div class="form-control">
|
|
132
|
+
<label class="label"><span class="label-text">Type</span></label>
|
|
133
|
+
<select bind:value={editingRole.allowed_for} class="select select-bordered">
|
|
134
|
+
<option value="INTERNAL">Internal (Employee)</option>
|
|
135
|
+
<option value="PARTNER">Partner (External)</option>
|
|
136
|
+
</select>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="form-control">
|
|
140
|
+
<label class="label"><span class="label-text">Parent Role</span></label>
|
|
141
|
+
<select bind:value={editingRole.parent_role_id} class="select select-bordered">
|
|
142
|
+
<option value={null}>— None (Top Level) —</option>
|
|
143
|
+
{#each roles.filter(r => r.id !== editingRole?.id) as r}
|
|
144
|
+
<option value={r.id}>{r.name}</option>
|
|
145
|
+
{/each}
|
|
146
|
+
</select>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="form-control">
|
|
150
|
+
<label class="label"><span class="label-text">Description</span></label>
|
|
151
|
+
<textarea
|
|
152
|
+
bind:value={editingRole.description}
|
|
153
|
+
class="textarea textarea-bordered"
|
|
154
|
+
placeholder="Role description..."
|
|
155
|
+
></textarea>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="modal-action">
|
|
160
|
+
<button class="btn" onclick={() => isModalOpen = false} disabled={saving}>Cancel</button>
|
|
161
|
+
<button class="btn btn-primary" onclick={handleSave} disabled={saving}>
|
|
162
|
+
{#if saving}Saving...{:else}Save{/if}
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</dialog>
|
|
167
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "RoleManager",
|
|
3
|
+
"category": "admin",
|
|
4
|
+
"description": "RBAC role management with hierarchy support",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"packages": [],
|
|
7
|
+
"components": []
|
|
8
|
+
},
|
|
9
|
+
"props": {
|
|
10
|
+
"roles": "Role[] - List of roles",
|
|
11
|
+
"onSave": "(role: Role) => Promise<void> - Save callback",
|
|
12
|
+
"onDelete": "(roleId: string) => Promise<void> - Delete callback"
|
|
13
|
+
},
|
|
14
|
+
"types": {
|
|
15
|
+
"Role": "{id: string; name: string; description: string; allowed_for: 'INTERNAL' | 'PARTNER'; parent_role_id?: string | null}"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
tableName = $bindable(''),
|
|
4
|
+
columns = $bindable([]),
|
|
5
|
+
onSave
|
|
6
|
+
}: {
|
|
7
|
+
tableName?: string;
|
|
8
|
+
columns?: any[];
|
|
9
|
+
onSave: (data: any) => Promise<void>;
|
|
10
|
+
} = $props();
|
|
11
|
+
|
|
12
|
+
function addColumn() {
|
|
13
|
+
columns = [...columns, { name: '', type: 'text', nullable: true }];
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div class="space-y-4">
|
|
18
|
+
<input bind:value={tableName} placeholder="Table Name" class="input input-bordered w-full" />
|
|
19
|
+
|
|
20
|
+
<div class="space-y-2">
|
|
21
|
+
{#each columns as col, i}
|
|
22
|
+
<div class="flex gap-2">
|
|
23
|
+
<input bind:value={col.name} placeholder="Column Name" class="input input-bordered flex-1" />
|
|
24
|
+
<select bind:value={col.type} class="select select-bordered">
|
|
25
|
+
<option>text</option>
|
|
26
|
+
<option>number</option>
|
|
27
|
+
<option>boolean</option>
|
|
28
|
+
<option>date</option>
|
|
29
|
+
</select>
|
|
30
|
+
<button class="btn btn-error btn-square" onclick={() => columns = columns.filter((_, idx) => idx !== i)}>✕</button>
|
|
31
|
+
</div>
|
|
32
|
+
{/each}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<button class="btn btn-primary" onclick={addColumn}>+ Add Column</button>
|
|
36
|
+
<button class="btn btn-success" onclick={() => onSave({ tableName, columns })}>Save</button>
|
|
37
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "TableDesigner",
|
|
3
|
+
"category": "admin",
|
|
4
|
+
"description": "TableDesigner - Svelte 5 component",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"packages": [],
|
|
7
|
+
"components": []
|
|
8
|
+
},
|
|
9
|
+
"props": {
|
|
10
|
+
"tableName": "$bindable<string>",
|
|
11
|
+
"columns": "$bindable<Array>",
|
|
12
|
+
"onSave": "function"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
onFeedback
|
|
4
|
+
}: {
|
|
5
|
+
onFeedback: (type: 'positive' | 'negative', comment?: string) => Promise<void>;
|
|
6
|
+
} = $props();
|
|
7
|
+
|
|
8
|
+
let showComment = $state(false);
|
|
9
|
+
let comment = $state('');
|
|
10
|
+
let submitted = $state(false);
|
|
11
|
+
|
|
12
|
+
async function handleFeedback(type: 'positive' | 'negative') {
|
|
13
|
+
await onFeedback(type, comment || undefined);
|
|
14
|
+
submitted = true;
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
submitted = false;
|
|
17
|
+
showComment = false;
|
|
18
|
+
comment = '';
|
|
19
|
+
}, 2000);
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<div class="flex items-center gap-2">
|
|
24
|
+
{#if !submitted}
|
|
25
|
+
<span class="text-xs opacity-60">Was this helpful?</span>
|
|
26
|
+
<button class="btn btn-xs btn-ghost" onclick={() => handleFeedback('positive')} title="Helpful">👍</button>
|
|
27
|
+
<button class="btn btn-xs btn-ghost" onclick={() => { showComment = true; }} title="Not helpful">👎</button>
|
|
28
|
+
|
|
29
|
+
{#if showComment}
|
|
30
|
+
<div class="flex gap-1">
|
|
31
|
+
<input
|
|
32
|
+
type="text"
|
|
33
|
+
bind:value={comment}
|
|
34
|
+
placeholder="Optional feedback..."
|
|
35
|
+
class="input input-xs input-bordered w-40"
|
|
36
|
+
/>
|
|
37
|
+
<button class="btn btn-xs btn-primary" onclick={() => handleFeedback('negative')}>Send</button>
|
|
38
|
+
</div>
|
|
39
|
+
{/if}
|
|
40
|
+
{:else}
|
|
41
|
+
<span class="text-xs text-success">✓ Thank you!</span>
|
|
42
|
+
{/if}
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Insight {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
confidence?: number;
|
|
6
|
+
category?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
insights = [],
|
|
11
|
+
loading = false,
|
|
12
|
+
onRefresh = null
|
|
13
|
+
}: {
|
|
14
|
+
insights: Insight[];
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
onRefresh?: (() => Promise<void>) | null;
|
|
17
|
+
} = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div class="card bg-base-100 border border-base-200">
|
|
21
|
+
<div class="card-body">
|
|
22
|
+
<div class="flex items-center justify-between mb-4">
|
|
23
|
+
<h3 class="card-title text-sm">AI Insights</h3>
|
|
24
|
+
{#if onRefresh}
|
|
25
|
+
<button class="btn btn-xs btn-ghost" onclick={onRefresh} disabled={loading}>
|
|
26
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 {loading ? 'animate-spin' : ''}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
27
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
28
|
+
</svg>
|
|
29
|
+
</button>
|
|
30
|
+
{/if}
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{#if loading}
|
|
34
|
+
<div class="flex justify-center py-4"><span class="loading loading-spinner"></span></div>
|
|
35
|
+
{:else if insights.length === 0}
|
|
36
|
+
<p class="text-sm opacity-60 text-center py-4">No insights available</p>
|
|
37
|
+
{:else}
|
|
38
|
+
<div class="space-y-3">
|
|
39
|
+
{#each insights as insight}
|
|
40
|
+
<div class="p-3 bg-base-200 rounded-lg">
|
|
41
|
+
<div class="flex items-start justify-between mb-1">
|
|
42
|
+
<h4 class="font-semibold text-sm">{insight.title}</h4>
|
|
43
|
+
{#if insight.confidence}
|
|
44
|
+
<span class="badge badge-xs">{Math.round(insight.confidence * 100)}%</span>
|
|
45
|
+
{/if}
|
|
46
|
+
</div>
|
|
47
|
+
<p class="text-xs opacity-70">{insight.description}</p>
|
|
48
|
+
{#if insight.category}
|
|
49
|
+
<span class="badge badge-xs badge-primary mt-2">{insight.category}</span>
|
|
50
|
+
{/if}
|
|
51
|
+
</div>
|
|
52
|
+
{/each}
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|