@svadmin/ui 0.64.0 → 0.65.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/dist/components/ActivityFeed.svelte +135 -0
- package/dist/components/ActivityFeed.svelte.d.ts +21 -0
- package/dist/components/ApprovalActionCard.svelte +248 -0
- package/dist/components/ApprovalActionCard.svelte.d.ts +22 -0
- package/dist/components/CanvasAnnotation.svelte +389 -0
- package/dist/components/CanvasAnnotation.svelte.d.ts +17 -0
- package/dist/components/ColumnHeaderFilter.svelte +178 -0
- package/dist/components/ColumnHeaderFilter.svelte.d.ts +12 -0
- package/dist/components/ColumnSettings.svelte +198 -0
- package/dist/components/ColumnSettings.svelte.d.ts +19 -0
- package/dist/components/DecisionTable.svelte +240 -0
- package/dist/components/DecisionTable.svelte.d.ts +22 -0
- package/dist/components/DraggableRowTable.svelte +117 -0
- package/dist/components/DraggableRowTable.svelte.d.ts +15 -0
- package/dist/components/DrawerForm.svelte +110 -26
- package/dist/components/DrawerForm.svelte.d.ts +20 -8
- package/dist/components/EditableTable.svelte +195 -0
- package/dist/components/EditableTable.svelte.d.ts +25 -0
- package/dist/components/GanttChart.svelte +144 -0
- package/dist/components/GanttChart.svelte.d.ts +20 -0
- package/dist/components/ImageCropper.svelte +122 -0
- package/dist/components/ImageCropper.svelte.d.ts +12 -0
- package/dist/components/ImportWizard.svelte +336 -0
- package/dist/components/ImportWizard.svelte.d.ts +12 -0
- package/dist/components/JsonSchemaForm.svelte +149 -0
- package/dist/components/JsonSchemaForm.svelte.d.ts +10 -0
- package/dist/components/KanbanBoard.svelte +212 -0
- package/dist/components/KanbanBoard.svelte.d.ts +30 -0
- package/dist/components/MasterDetailView.svelte +134 -0
- package/dist/components/MasterDetailView.svelte.d.ts +16 -0
- package/dist/components/MediaLibraryModal.svelte +195 -0
- package/dist/components/MediaLibraryModal.svelte.d.ts +22 -0
- package/dist/components/MentionsInput.svelte +174 -0
- package/dist/components/MentionsInput.svelte.d.ts +21 -0
- package/dist/components/ModalForm.svelte +113 -28
- package/dist/components/ModalForm.svelte.d.ts +20 -8
- package/dist/components/MultiTabKeepAlive.svelte +143 -0
- package/dist/components/MultiTabKeepAlive.svelte.d.ts +21 -0
- package/dist/components/OfflineSyncBanner.svelte +172 -0
- package/dist/components/OfflineSyncBanner.svelte.d.ts +19 -0
- package/dist/components/PdfDocumentViewer.svelte +255 -0
- package/dist/components/PdfDocumentViewer.svelte.d.ts +22 -0
- package/dist/components/PivotTable.svelte +140 -0
- package/dist/components/PivotTable.svelte.d.ts +16 -0
- package/dist/components/PresenceAvatarGroup.svelte +89 -0
- package/dist/components/PresenceAvatarGroup.svelte.d.ts +15 -0
- package/dist/components/PrintableBill.svelte +149 -0
- package/dist/components/PrintableBill.svelte.d.ts +24 -0
- package/dist/components/SensitiveDataMask.svelte +125 -0
- package/dist/components/SensitiveDataMask.svelte.d.ts +13 -0
- package/dist/components/SignaturePad.svelte +264 -0
- package/dist/components/SignaturePad.svelte.d.ts +17 -0
- package/dist/components/SplitPaneLayout.svelte +113 -0
- package/dist/components/SplitPaneLayout.svelte.d.ts +13 -0
- package/dist/components/SpreadsheetView.svelte +363 -0
- package/dist/components/SpreadsheetView.svelte.d.ts +18 -0
- package/dist/components/StepForm.svelte +180 -0
- package/dist/components/StepForm.svelte.d.ts +23 -0
- package/dist/components/TableSummary.svelte +110 -0
- package/dist/components/TableSummary.svelte.d.ts +20 -0
- package/dist/components/TreeTable.svelte +237 -0
- package/dist/components/TreeTable.svelte.d.ts +28 -0
- package/dist/components/UnsavedChangesPrompt.svelte +44 -0
- package/dist/components/UnsavedChangesPrompt.svelte.d.ts +7 -0
- package/dist/components/VersionDiffViewer.svelte +196 -0
- package/dist/components/VersionDiffViewer.svelte.d.ts +12 -0
- package/dist/components/VirtualTable.svelte +86 -54
- package/dist/components/VirtualTable.svelte.d.ts +14 -21
- package/dist/components/Watermark.svelte +166 -0
- package/dist/components/Watermark.svelte.d.ts +26 -0
- package/dist/index.d.ts +58 -3
- package/dist/index.js +35 -3
- package/package.json +2 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from './ui/button/index.js';
|
|
3
|
+
import { Badge } from './ui/badge/index.js';
|
|
4
|
+
import { Send, Clock, MessageSquare, Loader2 } from '@lucide/svelte';
|
|
5
|
+
import { cn } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
export interface ActivityItem {
|
|
8
|
+
id: string;
|
|
9
|
+
user?: {
|
|
10
|
+
name: string;
|
|
11
|
+
avatar?: string;
|
|
12
|
+
};
|
|
13
|
+
action: string;
|
|
14
|
+
target?: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
comment?: string;
|
|
17
|
+
status?: 'info' | 'success' | 'warning' | 'destructive';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
activities?: ActivityItem[];
|
|
22
|
+
onaddcomment?: (comment: string) => void | Promise<void>;
|
|
23
|
+
allowComment?: boolean;
|
|
24
|
+
class?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
activities = [],
|
|
29
|
+
onaddcomment,
|
|
30
|
+
allowComment = true,
|
|
31
|
+
class: className = '',
|
|
32
|
+
}: Props = $props();
|
|
33
|
+
|
|
34
|
+
let newComment = $state('');
|
|
35
|
+
let isSending = $state(false);
|
|
36
|
+
|
|
37
|
+
async function handleSend() {
|
|
38
|
+
if (!newComment.trim()) return;
|
|
39
|
+
isSending = true;
|
|
40
|
+
try {
|
|
41
|
+
await onaddcomment?.(newComment.trim());
|
|
42
|
+
newComment = '';
|
|
43
|
+
} finally {
|
|
44
|
+
isSending = false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getStatusBadge(status?: ActivityItem['status']) {
|
|
49
|
+
switch (status) {
|
|
50
|
+
case 'success':
|
|
51
|
+
return 'bg-success/15 text-success border-success/20';
|
|
52
|
+
case 'warning':
|
|
53
|
+
return 'bg-warning/15 text-warning-foreground border-warning/20';
|
|
54
|
+
case 'destructive':
|
|
55
|
+
return 'bg-destructive/15 text-destructive border-destructive/20';
|
|
56
|
+
default:
|
|
57
|
+
return 'bg-primary/10 text-primary border-primary/20';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<div class={cn('space-y-4 rounded-xl border border-border bg-card p-4 shadow-xs text-xs', className)}>
|
|
63
|
+
<div class="flex items-center justify-between pb-2 border-b border-border/60">
|
|
64
|
+
<h4 class="font-semibold text-foreground flex items-center gap-1.5">
|
|
65
|
+
<MessageSquare class="h-4 w-4 text-primary" />
|
|
66
|
+
Activity & Timeline Stream
|
|
67
|
+
</h4>
|
|
68
|
+
<span class="text-muted-foreground text-[11px]">{activities.length} event{activities.length === 1 ? '' : 's'}</span>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- Timeline Stream -->
|
|
72
|
+
<div class="relative space-y-4 pl-4 before:absolute before:left-1.5 before:top-2 before:bottom-2 before:w-0.5 before:bg-border/60">
|
|
73
|
+
{#each activities as act (act.id)}
|
|
74
|
+
<div class="relative flex items-start gap-3 group">
|
|
75
|
+
<!-- Bullet dot -->
|
|
76
|
+
<div class="absolute -left-4 top-1 h-3 w-3 rounded-full border-2 border-card bg-primary ring-2 ring-primary/20"></div>
|
|
77
|
+
|
|
78
|
+
<div class="flex-1 space-y-1.5">
|
|
79
|
+
<div class="flex flex-wrap items-center justify-between gap-1">
|
|
80
|
+
<div class="flex items-center gap-1.5">
|
|
81
|
+
<span class="font-semibold text-foreground">{act.user?.name ?? 'System'}</span>
|
|
82
|
+
<span class="text-muted-foreground">{act.action}</span>
|
|
83
|
+
{#if act.target}
|
|
84
|
+
<strong class="text-foreground">{act.target}</strong>
|
|
85
|
+
{/if}
|
|
86
|
+
{#if act.status}
|
|
87
|
+
<Badge variant="outline" class={cn('text-[10px] px-1 py-0', getStatusBadge(act.status))}>
|
|
88
|
+
{act.status}
|
|
89
|
+
</Badge>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="flex items-center gap-1 text-[11px] text-muted-foreground">
|
|
94
|
+
<Clock class="h-3 w-3" />
|
|
95
|
+
<span>{act.timestamp}</span>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{#if act.comment}
|
|
100
|
+
<div class="rounded-lg bg-muted/40 p-2.5 text-foreground border border-border/40 font-normal">
|
|
101
|
+
{act.comment}
|
|
102
|
+
</div>
|
|
103
|
+
{/if}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
{/each}
|
|
107
|
+
|
|
108
|
+
{#if activities.length === 0}
|
|
109
|
+
<div class="py-6 text-center text-muted-foreground">
|
|
110
|
+
No recent activities
|
|
111
|
+
</div>
|
|
112
|
+
{/if}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Add Comment Box -->
|
|
116
|
+
{#if allowComment && onaddcomment}
|
|
117
|
+
<div class="pt-3 border-t border-border/60 space-y-2">
|
|
118
|
+
<textarea
|
|
119
|
+
bind:value={newComment}
|
|
120
|
+
placeholder="Add an internal comment or update note..."
|
|
121
|
+
class="w-full h-16 rounded-md border border-input bg-background p-2 text-xs focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
122
|
+
></textarea>
|
|
123
|
+
<div class="flex justify-end">
|
|
124
|
+
<Button size="sm" class="h-7 text-xs gap-1" disabled={isSending || !newComment.trim()} onclick={handleSend}>
|
|
125
|
+
{#if isSending}
|
|
126
|
+
<Loader2 class="h-3.5 w-3.5 animate-spin" />
|
|
127
|
+
{:else}
|
|
128
|
+
<Send class="h-3.5 w-3.5" />
|
|
129
|
+
{/if}
|
|
130
|
+
Send Comment
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
{/if}
|
|
135
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ActivityItem {
|
|
2
|
+
id: string;
|
|
3
|
+
user?: {
|
|
4
|
+
name: string;
|
|
5
|
+
avatar?: string;
|
|
6
|
+
};
|
|
7
|
+
action: string;
|
|
8
|
+
target?: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
comment?: string;
|
|
11
|
+
status?: 'info' | 'success' | 'warning' | 'destructive';
|
|
12
|
+
}
|
|
13
|
+
interface Props {
|
|
14
|
+
activities?: ActivityItem[];
|
|
15
|
+
onaddcomment?: (comment: string) => void | Promise<void>;
|
|
16
|
+
allowComment?: boolean;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const ActivityFeed: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type ActivityFeed = ReturnType<typeof ActivityFeed>;
|
|
21
|
+
export default ActivityFeed;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { Check, X, ArrowRightLeft, Clock, CheckCircle2, XCircle, Undo2 } from '@lucide/svelte';
|
|
4
|
+
import { Button } from './ui/button/index.js';
|
|
5
|
+
import { Badge } from './ui/badge/index.js';
|
|
6
|
+
import * as Dialog from './ui/dialog/index.js';
|
|
7
|
+
import { cn } from '../utils.js';
|
|
8
|
+
|
|
9
|
+
export type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'recalled';
|
|
10
|
+
|
|
11
|
+
interface ApplicantInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
avatar?: string;
|
|
14
|
+
department?: string;
|
|
15
|
+
time?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
title?: string;
|
|
20
|
+
status?: ApprovalStatus;
|
|
21
|
+
applicant?: ApplicantInfo;
|
|
22
|
+
onapprove?: (comment?: string) => void | Promise<void>;
|
|
23
|
+
onreject?: (reason: string) => void | Promise<void>;
|
|
24
|
+
ontransfer?: (targetUser: string, note?: string) => void | Promise<void>;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
class?: string;
|
|
27
|
+
children?: Snippet;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
title = 'Approval Request',
|
|
32
|
+
status = 'pending',
|
|
33
|
+
applicant,
|
|
34
|
+
onapprove,
|
|
35
|
+
onreject,
|
|
36
|
+
ontransfer,
|
|
37
|
+
disabled = false,
|
|
38
|
+
class: className = '',
|
|
39
|
+
children,
|
|
40
|
+
}: Props = $props();
|
|
41
|
+
|
|
42
|
+
let approveDialogOpen = $state(false);
|
|
43
|
+
let rejectDialogOpen = $state(false);
|
|
44
|
+
let transferDialogOpen = $state(false);
|
|
45
|
+
|
|
46
|
+
let approveComment = $state('');
|
|
47
|
+
let rejectReason = $state('');
|
|
48
|
+
let transferTarget = $state('');
|
|
49
|
+
let isSubmitting = $state(false);
|
|
50
|
+
|
|
51
|
+
const statusConfig = $derived.by(() => {
|
|
52
|
+
switch (status) {
|
|
53
|
+
case 'approved':
|
|
54
|
+
return { label: 'Approved', badge: 'bg-success/15 text-success border-success/20', icon: CheckCircle2 };
|
|
55
|
+
case 'rejected':
|
|
56
|
+
return { label: 'Rejected', badge: 'bg-destructive/15 text-destructive border-destructive/20', icon: XCircle };
|
|
57
|
+
case 'recalled':
|
|
58
|
+
return { label: 'Recalled', badge: 'bg-muted text-muted-foreground border-border', icon: Undo2 };
|
|
59
|
+
default:
|
|
60
|
+
return { label: 'Pending Approval', badge: 'bg-warning/15 text-warning-foreground border-warning/20', icon: Clock };
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
async function handleApprove() {
|
|
65
|
+
isSubmitting = true;
|
|
66
|
+
try {
|
|
67
|
+
await onapprove?.(approveComment || undefined);
|
|
68
|
+
approveDialogOpen = false;
|
|
69
|
+
approveComment = '';
|
|
70
|
+
} finally {
|
|
71
|
+
isSubmitting = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function handleReject() {
|
|
76
|
+
if (!rejectReason.trim()) return;
|
|
77
|
+
isSubmitting = true;
|
|
78
|
+
try {
|
|
79
|
+
await onreject?.(rejectReason);
|
|
80
|
+
rejectDialogOpen = false;
|
|
81
|
+
rejectReason = '';
|
|
82
|
+
} finally {
|
|
83
|
+
isSubmitting = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function handleTransfer() {
|
|
88
|
+
if (!transferTarget.trim()) return;
|
|
89
|
+
isSubmitting = true;
|
|
90
|
+
try {
|
|
91
|
+
await ontransfer?.(transferTarget);
|
|
92
|
+
transferDialogOpen = false;
|
|
93
|
+
transferTarget = '';
|
|
94
|
+
} finally {
|
|
95
|
+
isSubmitting = false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<div class={cn('rounded-xl border border-border bg-card p-4 shadow-xs space-y-3', className)}>
|
|
101
|
+
<div class="flex flex-wrap items-center justify-between gap-2 pb-3 border-b border-border/50">
|
|
102
|
+
<div class="space-y-0.5">
|
|
103
|
+
<div class="flex items-center gap-2">
|
|
104
|
+
<h4 class="text-sm font-semibold text-foreground">{title}</h4>
|
|
105
|
+
<Badge variant="outline" class={cn('gap-1 text-[11px] font-medium', statusConfig.badge)}>
|
|
106
|
+
<statusConfig.icon class="h-3 w-3" />
|
|
107
|
+
{statusConfig.label}
|
|
108
|
+
</Badge>
|
|
109
|
+
</div>
|
|
110
|
+
{#if applicant}
|
|
111
|
+
<p class="text-xs text-muted-foreground">
|
|
112
|
+
Submitted by <strong class="text-foreground">{applicant.name}</strong>
|
|
113
|
+
{#if applicant.department} ({applicant.department}){/if}
|
|
114
|
+
{#if applicant.time} · {applicant.time}{/if}
|
|
115
|
+
</p>
|
|
116
|
+
{/if}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{#if status === 'pending'}
|
|
120
|
+
<div class="flex items-center gap-1.5">
|
|
121
|
+
{#if ontransfer}
|
|
122
|
+
<Button
|
|
123
|
+
variant="outline"
|
|
124
|
+
size="sm"
|
|
125
|
+
class="h-8 text-xs gap-1 text-muted-foreground"
|
|
126
|
+
{disabled}
|
|
127
|
+
onclick={() => { transferDialogOpen = true; }}
|
|
128
|
+
>
|
|
129
|
+
<ArrowRightLeft class="h-3.5 w-3.5" />
|
|
130
|
+
Transfer
|
|
131
|
+
</Button>
|
|
132
|
+
{/if}
|
|
133
|
+
|
|
134
|
+
{#if onreject}
|
|
135
|
+
<Button
|
|
136
|
+
variant="outline"
|
|
137
|
+
size="sm"
|
|
138
|
+
class="h-8 text-xs gap-1 border-destructive/30 text-destructive hover:bg-destructive/10"
|
|
139
|
+
{disabled}
|
|
140
|
+
onclick={() => { rejectDialogOpen = true; }}
|
|
141
|
+
>
|
|
142
|
+
<X class="h-3.5 w-3.5" />
|
|
143
|
+
Reject
|
|
144
|
+
</Button>
|
|
145
|
+
{/if}
|
|
146
|
+
|
|
147
|
+
{#if onapprove}
|
|
148
|
+
<Button
|
|
149
|
+
size="sm"
|
|
150
|
+
class="h-8 text-xs gap-1 bg-success hover:bg-success/90 text-success-foreground"
|
|
151
|
+
{disabled}
|
|
152
|
+
onclick={() => { approveDialogOpen = true; }}
|
|
153
|
+
>
|
|
154
|
+
<Check class="h-3.5 w-3.5" />
|
|
155
|
+
Approve
|
|
156
|
+
</Button>
|
|
157
|
+
{/if}
|
|
158
|
+
</div>
|
|
159
|
+
{/if}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{#if children}
|
|
163
|
+
<div class="text-xs text-foreground space-y-2">
|
|
164
|
+
{@render children()}
|
|
165
|
+
</div>
|
|
166
|
+
{/if}
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<!-- Approve Dialog -->
|
|
170
|
+
{#if approveDialogOpen}
|
|
171
|
+
<Dialog.Dialog bind:open={approveDialogOpen}>
|
|
172
|
+
<Dialog.DialogContent class="sm:max-w-md">
|
|
173
|
+
<Dialog.DialogHeader>
|
|
174
|
+
<Dialog.DialogTitle>Confirm Approval</Dialog.DialogTitle>
|
|
175
|
+
</Dialog.DialogHeader>
|
|
176
|
+
<div class="space-y-3 py-2 text-xs">
|
|
177
|
+
<label class="block font-medium text-foreground" for="approve_comment_input">Optional Approval Comment</label>
|
|
178
|
+
<textarea
|
|
179
|
+
id="approve_comment_input"
|
|
180
|
+
bind:value={approveComment}
|
|
181
|
+
placeholder="e.g. Verified and approved"
|
|
182
|
+
class="w-full h-20 rounded-md border border-input bg-background p-2 text-xs focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
183
|
+
></textarea>
|
|
184
|
+
</div>
|
|
185
|
+
<Dialog.DialogFooter>
|
|
186
|
+
<Button variant="outline" size="sm" onclick={() => { approveDialogOpen = false; }}>Cancel</Button>
|
|
187
|
+
<Button size="sm" class="bg-success hover:bg-success/90 text-success-foreground" disabled={isSubmitting} onclick={handleApprove}>
|
|
188
|
+
Confirm Approval
|
|
189
|
+
</Button>
|
|
190
|
+
</Dialog.DialogFooter>
|
|
191
|
+
</Dialog.DialogContent>
|
|
192
|
+
</Dialog.Dialog>
|
|
193
|
+
{/if}
|
|
194
|
+
|
|
195
|
+
<!-- Reject Dialog -->
|
|
196
|
+
{#if rejectDialogOpen}
|
|
197
|
+
<Dialog.Dialog bind:open={rejectDialogOpen}>
|
|
198
|
+
<Dialog.DialogContent class="sm:max-w-md">
|
|
199
|
+
<Dialog.DialogHeader>
|
|
200
|
+
<Dialog.DialogTitle class="text-destructive">Reject Request</Dialog.DialogTitle>
|
|
201
|
+
</Dialog.DialogHeader>
|
|
202
|
+
<div class="space-y-3 py-2 text-xs">
|
|
203
|
+
<label class="block font-medium text-foreground" for="reject_reason_input">Rejection Reason (Required)</label>
|
|
204
|
+
<textarea
|
|
205
|
+
id="reject_reason_input"
|
|
206
|
+
bind:value={rejectReason}
|
|
207
|
+
placeholder="Please explain why this request is being rejected..."
|
|
208
|
+
class="w-full h-20 rounded-md border border-destructive/40 bg-background p-2 text-xs focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-destructive"
|
|
209
|
+
required
|
|
210
|
+
></textarea>
|
|
211
|
+
</div>
|
|
212
|
+
<Dialog.DialogFooter>
|
|
213
|
+
<Button variant="outline" size="sm" onclick={() => { rejectDialogOpen = false; }}>Cancel</Button>
|
|
214
|
+
<Button variant="destructive" size="sm" disabled={isSubmitting || !rejectReason.trim()} onclick={handleReject}>
|
|
215
|
+
Confirm Rejection
|
|
216
|
+
</Button>
|
|
217
|
+
</Dialog.DialogFooter>
|
|
218
|
+
</Dialog.DialogContent>
|
|
219
|
+
</Dialog.Dialog>
|
|
220
|
+
{/if}
|
|
221
|
+
|
|
222
|
+
<!-- Transfer Dialog -->
|
|
223
|
+
{#if transferDialogOpen}
|
|
224
|
+
<Dialog.Dialog bind:open={transferDialogOpen}>
|
|
225
|
+
<Dialog.DialogContent class="sm:max-w-md">
|
|
226
|
+
<Dialog.DialogHeader>
|
|
227
|
+
<Dialog.DialogTitle>Transfer Approval</Dialog.DialogTitle>
|
|
228
|
+
</Dialog.DialogHeader>
|
|
229
|
+
<div class="space-y-3 py-2 text-xs">
|
|
230
|
+
<label class="block font-medium text-foreground" for="transfer_target_input">Target Approver Email / Username</label>
|
|
231
|
+
<input
|
|
232
|
+
id="transfer_target_input"
|
|
233
|
+
type="text"
|
|
234
|
+
bind:value={transferTarget}
|
|
235
|
+
placeholder="e.g. manager@example.com"
|
|
236
|
+
class="w-full h-8 rounded-md border border-input bg-background px-2 text-xs focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
237
|
+
required
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
<Dialog.DialogFooter>
|
|
241
|
+
<Button variant="outline" size="sm" onclick={() => { transferDialogOpen = false; }}>Cancel</Button>
|
|
242
|
+
<Button size="sm" disabled={isSubmitting || !transferTarget.trim()} onclick={handleTransfer}>
|
|
243
|
+
Transfer
|
|
244
|
+
</Button>
|
|
245
|
+
</Dialog.DialogFooter>
|
|
246
|
+
</Dialog.DialogContent>
|
|
247
|
+
</Dialog.Dialog>
|
|
248
|
+
{/if}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'recalled';
|
|
3
|
+
interface ApplicantInfo {
|
|
4
|
+
name: string;
|
|
5
|
+
avatar?: string;
|
|
6
|
+
department?: string;
|
|
7
|
+
time?: string;
|
|
8
|
+
}
|
|
9
|
+
interface Props {
|
|
10
|
+
title?: string;
|
|
11
|
+
status?: ApprovalStatus;
|
|
12
|
+
applicant?: ApplicantInfo;
|
|
13
|
+
onapprove?: (comment?: string) => void | Promise<void>;
|
|
14
|
+
onreject?: (reason: string) => void | Promise<void>;
|
|
15
|
+
ontransfer?: (targetUser: string, note?: string) => void | Promise<void>;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
class?: string;
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
}
|
|
20
|
+
declare const ApprovalActionCard: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type ApprovalActionCard = ReturnType<typeof ApprovalActionCard>;
|
|
22
|
+
export default ApprovalActionCard;
|