@alliance-droid/status-feedback-system 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/api.d.ts +57 -0
  2. package/dist/api.js +76 -0
  3. package/dist/components/FeedbackButton.svelte +3 -3
  4. package/dist/components/FeedbackButton.svelte.d.ts +1 -1
  5. package/dist/components/FeedbackForm.svelte +3 -3
  6. package/dist/components/FeedbackForm.svelte.d.ts +1 -1
  7. package/dist/components/StatusBanner.svelte +36 -3
  8. package/dist/components/StatusBanner.svelte.d.ts +7 -1
  9. package/dist/components/StatusPage.svelte +150 -83
  10. package/dist/components/StatusPage.svelte.d.ts +10 -3
  11. package/dist/index.d.ts +6 -9
  12. package/dist/index.js +5 -8
  13. package/dist/types.d.ts +0 -6
  14. package/package.json +2 -13
  15. package/dist/components/admin/AdminDashboard.svelte +0 -274
  16. package/dist/components/admin/AdminDashboard.svelte.d.ts +0 -9
  17. package/dist/components/admin/ApiKeyManager.svelte +0 -68
  18. package/dist/components/admin/ApiKeyManager.svelte.d.ts +0 -7
  19. package/dist/components/admin/FeedbackList.svelte +0 -160
  20. package/dist/components/admin/FeedbackList.svelte.d.ts +0 -17
  21. package/dist/components/admin/IncidentForm.svelte +0 -135
  22. package/dist/components/admin/IncidentForm.svelte.d.ts +0 -14
  23. package/dist/components/admin/IncidentManager.svelte +0 -153
  24. package/dist/components/admin/IncidentManager.svelte.d.ts +0 -13
  25. package/dist/components/admin/ProjectForm.svelte +0 -106
  26. package/dist/components/admin/ProjectForm.svelte.d.ts +0 -13
  27. package/dist/components/admin/ProjectList.svelte +0 -57
  28. package/dist/components/admin/ProjectList.svelte.d.ts +0 -10
  29. package/dist/components/admin/StatusControl.svelte +0 -62
  30. package/dist/components/admin/StatusControl.svelte.d.ts +0 -8
  31. package/dist/components/admin/SystemManager.svelte +0 -119
  32. package/dist/components/admin/SystemManager.svelte.d.ts +0 -13
  33. package/dist/components/admin/index.d.ts +0 -9
  34. package/dist/components/admin/index.js +0 -9
  35. package/dist/server/db.d.ts +0 -17
  36. package/dist/server/db.js +0 -68
  37. package/dist/server/feedback.d.ts +0 -27
  38. package/dist/server/feedback.js +0 -124
  39. package/dist/server/handlers.d.ts +0 -41
  40. package/dist/server/handlers.js +0 -251
  41. package/dist/server/incidents.d.ts +0 -27
  42. package/dist/server/incidents.js +0 -180
  43. package/dist/server/index.d.ts +0 -101
  44. package/dist/server/index.js +0 -71
  45. package/dist/server/projects.d.ts +0 -45
  46. package/dist/server/projects.js +0 -156
  47. package/dist/server/schema.d.ts +0 -9
  48. package/dist/server/schema.js +0 -94
  49. package/dist/server/systems.d.ts +0 -20
  50. package/dist/server/systems.js +0 -146
  51. package/dist/server/types.d.ts +0 -4
  52. package/dist/server/types.js +0 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alliance-droid/status-feedback-system",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Drop-in system status pages and user feedback forms for SvelteKit apps",
6
6
  "svelte": "./dist/index.js",
@@ -9,14 +9,6 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "svelte": "./dist/index.js"
12
- },
13
- "./server": {
14
- "types": "./dist/server/index.d.ts",
15
- "default": "./dist/server/index.js"
16
- },
17
- "./admin": {
18
- "types": "./dist/components/admin/index.d.ts",
19
- "svelte": "./dist/components/admin/index.js"
20
12
  }
21
13
  },
22
14
  "files": [
@@ -37,9 +29,7 @@
37
29
  "svelte": "^5.0.0"
38
30
  },
39
31
  "dependencies": {
40
- "@alliance-droid/svelte-component-library": "^0.1.27",
41
- "mssql": "^11.0.0",
42
- "nanoid": "^5.0.0"
32
+ "@alliance-droid/svelte-component-library": "^0.1.27"
43
33
  },
44
34
  "devDependencies": {
45
35
  "@sveltejs/adapter-auto": "^7.0.0",
@@ -47,7 +37,6 @@
47
37
  "@sveltejs/package": "^2.3.0",
48
38
  "@sveltejs/vite-plugin-svelte": "^6.0.0",
49
39
  "@tailwindcss/vite": "^4.1.0",
50
- "@types/mssql": "^9.1.0",
51
40
  "publint": "^0.3.0",
52
41
  "svelte": "^5.48.0",
53
42
  "svelte-check": "^4.3.0",
@@ -1,274 +0,0 @@
1
- <script lang="ts">
2
- import type { Project, System, Incident, Feedback, FeedbackStatus, SystemStatus, IncidentStatus, IncidentSeverity } from '../../types.js';
3
- import ProjectList from './ProjectList.svelte';
4
- import ProjectForm from './ProjectForm.svelte';
5
- import ApiKeyManager from './ApiKeyManager.svelte';
6
- import SystemManager from './SystemManager.svelte';
7
- import StatusControl from './StatusControl.svelte';
8
- import IncidentManager from './IncidentManager.svelte';
9
- import IncidentForm from './IncidentForm.svelte';
10
- import FeedbackList from './FeedbackList.svelte';
11
-
12
- interface Props {
13
- projects: Project[];
14
- apiEndpoint: string;
15
- onRefresh?: () => Promise<void>;
16
- }
17
-
18
- let { projects, apiEndpoint, onRefresh }: Props = $props();
19
-
20
- type Tab = 'overview' | 'systems' | 'incidents' | 'feedback';
21
-
22
- let selectedProject = $state<Project | null>(null);
23
- let activeTab = $state<Tab>('overview');
24
- let showProjectForm = $state(false);
25
- let showIncidentForm = $state(false);
26
-
27
- // Data for selected project
28
- let systems = $state<System[]>([]);
29
- let incidents = $state<Incident[]>([]);
30
- let feedbackItems = $state<Feedback[]>([]);
31
- let feedbackTotal = $state(0);
32
- let feedbackPage = $state(1);
33
- let feedbackFilterStatus = $state<FeedbackStatus | ''>('');
34
- let loading = $state(false);
35
-
36
- const tabs: { id: Tab; label: string; icon: string }[] = [
37
- { id: 'overview', label: 'Overview', icon: 'fa-gauge' },
38
- { id: 'systems', label: 'Systems', icon: 'fa-server' },
39
- { id: 'incidents', label: 'Incidents', icon: 'fa-triangle-exclamation' },
40
- { id: 'feedback', label: 'Feedback', icon: 'fa-comment' },
41
- ];
42
-
43
- // ── API Helpers ──
44
-
45
- async function apiCall(path: string, options: RequestInit = {}) {
46
- if (!selectedProject) throw new Error('No project selected');
47
- const res = await fetch(`${apiEndpoint}/${path}`, {
48
- ...options,
49
- headers: {
50
- 'Content-Type': 'application/json',
51
- 'x-api-key': selectedProject.apiKey,
52
- ...options.headers,
53
- },
54
- });
55
- const data = await res.json();
56
- if (!data.success) throw new Error(data.error || 'API error');
57
- return data.data;
58
- }
59
-
60
- async function loadProjectData() {
61
- if (!selectedProject) return;
62
- loading = true;
63
- try {
64
- systems = await apiCall('systems');
65
- incidents = await apiCall('incidents?limit=50&includeResolved=true');
66
- await loadFeedback();
67
- } catch (err) {
68
- console.error('Failed to load project data:', err);
69
- } finally {
70
- loading = false;
71
- }
72
- }
73
-
74
- async function loadFeedback() {
75
- const params = new URLSearchParams({
76
- limit: '20',
77
- offset: String((feedbackPage - 1) * 20),
78
- });
79
- if (feedbackFilterStatus) params.set('status', feedbackFilterStatus);
80
- const result = await apiCall(`feedback?${params}`);
81
- feedbackItems = result.items;
82
- feedbackTotal = result.total;
83
- }
84
-
85
- function selectProject(project: Project) {
86
- selectedProject = project;
87
- activeTab = 'overview';
88
- loadProjectData();
89
- }
90
-
91
- // ── Handlers ──
92
-
93
- async function handleCreateProject(data: { name: string; description: string; allowedOrigins: string[] }) {
94
- // This needs the consuming app to provide a server action
95
- // For now, emit to onRefresh
96
- showProjectForm = false;
97
- await onRefresh?.();
98
- }
99
-
100
- async function handleAddSystem(data: { name: string; description: string }) {
101
- // Systems are managed via the API route handlers
102
- await apiCall('systems', {
103
- method: 'POST',
104
- body: JSON.stringify(data),
105
- }).catch(() => {
106
- // Fallback: reload from server
107
- });
108
- await loadProjectData();
109
- }
110
-
111
- async function handleUpdateStatus(systemId: string, status: SystemStatus) {
112
- await apiCall(`systems/${systemId}/status`, {
113
- method: 'PATCH',
114
- body: JSON.stringify({ status }),
115
- });
116
- systems = systems.map((s) => (s.id === systemId ? { ...s, status } : s));
117
- }
118
-
119
- async function handleDeleteSystem(systemId: string) {
120
- await apiCall(`systems/${systemId}`, { method: 'DELETE' }).catch(() => {});
121
- await loadProjectData();
122
- }
123
-
124
- async function handleCreateIncident(data: { title: string; severity: IncidentSeverity; affectedSystems: string[]; message: string }) {
125
- await apiCall('incidents', {
126
- method: 'POST',
127
- body: JSON.stringify(data),
128
- }).catch(() => {});
129
- showIncidentForm = false;
130
- await loadProjectData();
131
- }
132
-
133
- async function handleAddIncidentUpdate(incidentId: string, data: { status: IncidentStatus; message: string }) {
134
- await apiCall(`incidents/${incidentId}/updates`, {
135
- method: 'POST',
136
- body: JSON.stringify(data),
137
- }).catch(() => {});
138
- await loadProjectData();
139
- }
140
-
141
- async function handleDeleteIncident(incidentId: string) {
142
- await apiCall(`incidents/${incidentId}`, { method: 'DELETE' }).catch(() => {});
143
- await loadProjectData();
144
- }
145
-
146
- async function handleFeedbackStatusChange(feedbackId: string, status: FeedbackStatus) {
147
- await apiCall(`feedback/${feedbackId}/status`, {
148
- method: 'PATCH',
149
- body: JSON.stringify({ status }),
150
- });
151
- feedbackItems = feedbackItems.map((f) => (f.id === feedbackId ? { ...f, status } : f));
152
- }
153
-
154
- async function handleDeleteFeedback(feedbackId: string) {
155
- if (!confirm('Delete this feedback?')) return;
156
- await apiCall(`feedback/${feedbackId}`, { method: 'DELETE' }).catch(() => {});
157
- await loadFeedback();
158
- }
159
- </script>
160
-
161
- <div class="flex min-h-screen">
162
- <!-- Sidebar -->
163
- <div class="w-64 border-r border-border bg-background p-4">
164
- <ProjectList
165
- {projects}
166
- selectedId={selectedProject?.id}
167
- onSelect={selectProject}
168
- onCreate={() => (showProjectForm = true)}
169
- />
170
- </div>
171
-
172
- <!-- Main Content -->
173
- <div class="flex-1 p-6">
174
- {#if showProjectForm}
175
- <div class="mx-auto max-w-lg">
176
- <ProjectForm
177
- onSave={handleCreateProject}
178
- onCancel={() => (showProjectForm = false)}
179
- />
180
- </div>
181
- {:else if !selectedProject}
182
- <div class="flex h-full items-center justify-center">
183
- <div class="text-center">
184
- <i class="fa-solid fa-arrow-left mb-2 text-2xl text-foreground-tertiary"></i>
185
- <p class="text-foreground-secondary">Select a project to manage</p>
186
- </div>
187
- </div>
188
- {:else}
189
- <!-- Project Header -->
190
- <div class="mb-6">
191
- <h2 class="text-xl font-semibold text-foreground">{selectedProject.name}</h2>
192
- {#if selectedProject.description}
193
- <p class="mt-1 text-sm text-foreground-tertiary">{selectedProject.description}</p>
194
- {/if}
195
- </div>
196
-
197
- <!-- API Key -->
198
- <div class="mb-6">
199
- <ApiKeyManager apiKey={selectedProject.apiKey} />
200
- </div>
201
-
202
- <!-- Tabs -->
203
- <div class="mb-6 flex gap-1 border-b border-border">
204
- {#each tabs as tab}
205
- <button
206
- onclick={() => (activeTab = tab.id)}
207
- class="border-b-2 px-4 py-2 text-sm font-semibold transition-colors {activeTab === tab.id
208
- ? 'border-primary text-primary'
209
- : 'border-transparent text-foreground-tertiary hover:text-foreground-secondary'}"
210
- >
211
- <i class="fa-solid {tab.icon} mr-1.5"></i>
212
- {tab.label}
213
- </button>
214
- {/each}
215
- </div>
216
-
217
- {#if loading}
218
- <div class="flex items-center justify-center py-12">
219
- <i class="fa-solid fa-spinner fa-spin text-xl text-foreground-tertiary"></i>
220
- </div>
221
- {:else if activeTab === 'overview'}
222
- <div class="space-y-6">
223
- <StatusControl {systems} onUpdateStatus={handleUpdateStatus} />
224
- <IncidentManager
225
- incidents={incidents.filter((i) => i.status !== 'resolved').slice(0, 5)}
226
- onAddUpdate={handleAddIncidentUpdate}
227
- onDelete={handleDeleteIncident}
228
- onCreate={() => (showIncidentForm = true)}
229
- />
230
- </div>
231
- {:else if activeTab === 'systems'}
232
- <SystemManager
233
- {systems}
234
- onAdd={handleAddSystem}
235
- onUpdateStatus={handleUpdateStatus}
236
- onDelete={handleDeleteSystem}
237
- />
238
- {:else if activeTab === 'incidents'}
239
- {#if showIncidentForm}
240
- <IncidentForm
241
- {systems}
242
- onSave={handleCreateIncident}
243
- onCancel={() => (showIncidentForm = false)}
244
- />
245
- {:else}
246
- <IncidentManager
247
- {incidents}
248
- onAddUpdate={handleAddIncidentUpdate}
249
- onDelete={handleDeleteIncident}
250
- onCreate={() => (showIncidentForm = true)}
251
- />
252
- {/if}
253
- {:else if activeTab === 'feedback'}
254
- <FeedbackList
255
- items={feedbackItems}
256
- total={feedbackTotal}
257
- page={feedbackPage}
258
- onStatusChange={handleFeedbackStatusChange}
259
- onDelete={handleDeleteFeedback}
260
- filterStatus={feedbackFilterStatus || undefined}
261
- onFilterStatus={(s) => {
262
- feedbackFilterStatus = s;
263
- feedbackPage = 1;
264
- loadFeedback();
265
- }}
266
- onPageChange={(p) => {
267
- feedbackPage = p;
268
- loadFeedback();
269
- }}
270
- />
271
- {/if}
272
- {/if}
273
- </div>
274
- </div>
@@ -1,9 +0,0 @@
1
- import type { Project } from '../../types.js';
2
- interface Props {
3
- projects: Project[];
4
- apiEndpoint: string;
5
- onRefresh?: () => Promise<void>;
6
- }
7
- declare const AdminDashboard: import("svelte").Component<Props, {}, "">;
8
- type AdminDashboard = ReturnType<typeof AdminDashboard>;
9
- export default AdminDashboard;
@@ -1,68 +0,0 @@
1
- <script lang="ts">
2
- interface Props {
3
- apiKey: string;
4
- onRegenerate?: () => Promise<void>;
5
- }
6
-
7
- let { apiKey, onRegenerate }: Props = $props();
8
-
9
- let revealed = $state(false);
10
- let copied = $state(false);
11
- let regenerating = $state(false);
12
-
13
- let maskedKey = $derived(
14
- revealed ? apiKey : apiKey.slice(0, 6) + '•'.repeat(Math.max(0, apiKey.length - 10)) + apiKey.slice(-4)
15
- );
16
-
17
- async function copyKey() {
18
- await navigator.clipboard.writeText(apiKey);
19
- copied = true;
20
- setTimeout(() => (copied = false), 2000);
21
- }
22
-
23
- async function handleRegenerate() {
24
- if (!confirm('Regenerate API key? The old key will stop working immediately.')) return;
25
- regenerating = true;
26
- try {
27
- await onRegenerate?.();
28
- } finally {
29
- regenerating = false;
30
- }
31
- }
32
- </script>
33
-
34
- <div class="rounded-lg border border-border bg-surface p-4">
35
- <div class="mb-2 flex items-center gap-2">
36
- <i class="fa-solid fa-key text-sm text-foreground-tertiary"></i>
37
- <span class="text-sm font-semibold text-foreground">API Key</span>
38
- </div>
39
- <div class="flex items-center gap-2">
40
- <code class="flex-1 rounded bg-background px-3 py-2 text-xs text-foreground-secondary">
41
- {maskedKey}
42
- </code>
43
- <button
44
- onclick={() => (revealed = !revealed)}
45
- class="rounded-md border border-border px-2.5 py-2 text-xs text-foreground-secondary transition-colors hover:bg-surface-hover"
46
- aria-label={revealed ? 'Hide API key' : 'Reveal API key'}
47
- >
48
- <i class="fa-solid {revealed ? 'fa-eye-slash' : 'fa-eye'}"></i>
49
- </button>
50
- <button
51
- onclick={copyKey}
52
- class="rounded-md border border-border px-2.5 py-2 text-xs text-foreground-secondary transition-colors hover:bg-surface-hover"
53
- aria-label="Copy API key"
54
- >
55
- <i class="fa-solid {copied ? 'fa-check' : 'fa-copy'}"></i>
56
- </button>
57
- {#if onRegenerate}
58
- <button
59
- onclick={handleRegenerate}
60
- disabled={regenerating}
61
- class="rounded-md border border-border px-2.5 py-2 text-xs text-red-500 transition-colors hover:bg-red-500/10 disabled:opacity-50"
62
- aria-label="Regenerate API key"
63
- >
64
- <i class="fa-solid {regenerating ? 'fa-spinner fa-spin' : 'fa-rotate'}"></i>
65
- </button>
66
- {/if}
67
- </div>
68
- </div>
@@ -1,7 +0,0 @@
1
- interface Props {
2
- apiKey: string;
3
- onRegenerate?: () => Promise<void>;
4
- }
5
- declare const ApiKeyManager: import("svelte").Component<Props, {}, "">;
6
- type ApiKeyManager = ReturnType<typeof ApiKeyManager>;
7
- export default ApiKeyManager;
@@ -1,160 +0,0 @@
1
- <script lang="ts">
2
- import type { Feedback, FeedbackStatus } from '../../types.js';
3
-
4
- interface Props {
5
- items: Feedback[];
6
- total: number;
7
- page?: number;
8
- pageSize?: number;
9
- onStatusChange?: (feedbackId: string, status: FeedbackStatus) => Promise<void>;
10
- onDelete?: (feedbackId: string) => Promise<void>;
11
- onPageChange?: (page: number) => void;
12
- filterCategory?: string;
13
- filterStatus?: FeedbackStatus;
14
- onFilterCategory?: (category: string) => void;
15
- onFilterStatus?: (status: FeedbackStatus | '') => void;
16
- }
17
-
18
- let {
19
- items,
20
- total,
21
- page = 1,
22
- pageSize = 20,
23
- onStatusChange,
24
- onDelete,
25
- onPageChange,
26
- filterCategory,
27
- filterStatus,
28
- onFilterCategory,
29
- onFilterStatus,
30
- }: Props = $props();
31
-
32
- let totalPages = $derived(Math.ceil(total / pageSize));
33
-
34
- const statusOptions: FeedbackStatus[] = ['new', 'reviewed', 'resolved', 'archived'];
35
- const statusColors: Record<FeedbackStatus, string> = {
36
- new: 'text-blue-500 bg-blue-500/10',
37
- reviewed: 'text-yellow-500 bg-yellow-500/10',
38
- resolved: 'text-green-500 bg-green-500/10',
39
- archived: 'text-foreground-tertiary bg-surface-hover',
40
- };
41
-
42
- function formatDate(timestamp: number): string {
43
- return new Date(timestamp).toLocaleString();
44
- }
45
-
46
- function truncate(text: string, max: number): string {
47
- return text.length > max ? text.slice(0, max) + '…' : text;
48
- }
49
- </script>
50
-
51
- <div class="space-y-4">
52
- <div class="flex items-center justify-between">
53
- <h3 class="text-base font-semibold text-foreground">
54
- <i class="fa-solid fa-comment mr-1.5 text-foreground-tertiary"></i>
55
- Feedback
56
- <span class="ml-1 text-sm text-foreground-tertiary">({total})</span>
57
- </h3>
58
- </div>
59
-
60
- <!-- Filters -->
61
- <div class="flex flex-wrap gap-2">
62
- <button
63
- onclick={() => onFilterStatus?.('')}
64
- class="rounded-md px-2.5 py-1 text-xs transition-colors {!filterStatus
65
- ? 'bg-primary text-primary-foreground'
66
- : 'border border-border text-foreground-secondary hover:bg-surface-hover'}"
67
- >
68
- All
69
- </button>
70
- {#each statusOptions as s}
71
- <button
72
- onclick={() => onFilterStatus?.(s)}
73
- class="rounded-md px-2.5 py-1 text-xs capitalize transition-colors {filterStatus === s
74
- ? 'bg-primary text-primary-foreground'
75
- : 'border border-border text-foreground-secondary hover:bg-surface-hover'}"
76
- >
77
- {s}
78
- </button>
79
- {/each}
80
- </div>
81
-
82
- <!-- Items -->
83
- {#if items.length === 0}
84
- <div class="rounded-lg border border-border bg-surface p-6 text-center">
85
- <p class="text-sm text-foreground-secondary">No feedback found</p>
86
- </div>
87
- {:else}
88
- <div class="space-y-2">
89
- {#each items as fb}
90
- <div class="rounded-lg border border-border bg-surface p-3">
91
- <div class="flex items-start justify-between gap-2">
92
- <div class="flex-1">
93
- <div class="flex items-center gap-2 mb-1">
94
- {#if fb.category}
95
- <span class="rounded bg-surface-hover px-1.5 py-0.5 text-xs text-foreground-secondary">
96
- {fb.category}
97
- </span>
98
- {/if}
99
- <span class="rounded px-1.5 py-0.5 text-xs capitalize {statusColors[fb.status]}">
100
- {fb.status}
101
- </span>
102
- <span class="text-xs text-foreground-tertiary">{formatDate(fb.createdAt)}</span>
103
- </div>
104
- <p class="text-sm text-foreground">{truncate(fb.message, 200)}</p>
105
- {#if fb.email}
106
- <p class="mt-1 text-xs text-foreground-tertiary">
107
- <i class="fa-solid fa-envelope mr-1"></i>
108
- {fb.email}
109
- </p>
110
- {/if}
111
- </div>
112
- <div class="flex items-center gap-1">
113
- <select
114
- value={fb.status}
115
- onchange={(e) => onStatusChange?.(fb.id, (e.target as HTMLSelectElement).value as FeedbackStatus)}
116
- class="rounded border border-border bg-background px-2 py-1 text-xs text-foreground"
117
- >
118
- {#each statusOptions as s}
119
- <option value={s}>{s}</option>
120
- {/each}
121
- </select>
122
- <button
123
- onclick={() => onDelete?.(fb.id)}
124
- class="rounded p-1 text-xs text-foreground-tertiary transition-colors hover:text-red-500"
125
- aria-label="Delete feedback"
126
- >
127
- <i class="fa-solid fa-trash"></i>
128
- </button>
129
- </div>
130
- </div>
131
- </div>
132
- {/each}
133
- </div>
134
- {/if}
135
-
136
- <!-- Pagination -->
137
- {#if totalPages > 1}
138
- <div class="flex items-center justify-center gap-2">
139
- <button
140
- onclick={() => onPageChange?.(page - 1)}
141
- disabled={page <= 1}
142
- class="rounded-md border border-border px-3 py-1.5 text-xs text-foreground-secondary disabled:opacity-50"
143
- aria-label="Previous page"
144
- >
145
- <i class="fa-solid fa-chevron-left"></i>
146
- </button>
147
- <span class="text-xs text-foreground-tertiary">
148
- Page {page} of {totalPages}
149
- </span>
150
- <button
151
- onclick={() => onPageChange?.(page + 1)}
152
- disabled={page >= totalPages}
153
- class="rounded-md border border-border px-3 py-1.5 text-xs text-foreground-secondary disabled:opacity-50"
154
- aria-label="Next page"
155
- >
156
- <i class="fa-solid fa-chevron-right"></i>
157
- </button>
158
- </div>
159
- {/if}
160
- </div>
@@ -1,17 +0,0 @@
1
- import type { Feedback, FeedbackStatus } from '../../types.js';
2
- interface Props {
3
- items: Feedback[];
4
- total: number;
5
- page?: number;
6
- pageSize?: number;
7
- onStatusChange?: (feedbackId: string, status: FeedbackStatus) => Promise<void>;
8
- onDelete?: (feedbackId: string) => Promise<void>;
9
- onPageChange?: (page: number) => void;
10
- filterCategory?: string;
11
- filterStatus?: FeedbackStatus;
12
- onFilterCategory?: (category: string) => void;
13
- onFilterStatus?: (status: FeedbackStatus | '') => void;
14
- }
15
- declare const FeedbackList: import("svelte").Component<Props, {}, "">;
16
- type FeedbackList = ReturnType<typeof FeedbackList>;
17
- export default FeedbackList;