@wakastellar/ui 3.3.3 → 3.5.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/badge-BbwO7QeZ.js +1 -0
- package/dist/badge-BfiocODp.mjs +23 -0
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunk-14q5BKub.js +1 -0
- package/dist/{chunk-BH6uBOac.mjs → chunk-Cr9pTUWm.mjs} +5 -5
- package/dist/cn-DEtaFQsA.js +1 -0
- package/dist/cn-DUn6aSIQ.mjs +24 -0
- package/dist/doc.cjs.js +2 -2
- package/dist/doc.es.js +19 -19
- package/dist/editor.cjs.js +48 -0
- package/dist/editor.d.ts +1 -0
- package/dist/editor.es.js +6551 -0
- package/dist/{exceljs.min-DG9M8IZ1.mjs → exceljs.min-DL1XYDll.mjs} +1 -1
- package/dist/{exceljs.min-BuefmDRS.js → exceljs.min-qeIfSCbF.js} +1 -1
- package/dist/export.cjs.js +1 -1
- package/dist/export.es.js +1 -1
- package/dist/index.cjs.js +150 -150
- package/dist/index.es.js +26782 -27591
- package/dist/input-BfaSAGVw.js +1 -0
- package/dist/input-DVr_Qkl8.mjs +14 -0
- package/dist/rich-text.cjs.js +1 -1
- package/dist/rich-text.es.js +1 -1
- package/dist/security-CyBpuklN.mjs +122 -0
- package/dist/security-bFWwDrlg.js +1 -0
- package/dist/separator-NrkltulH.js +1 -0
- package/dist/separator-ibN2mycs.mjs +51 -0
- package/dist/src/components/editor/blocks/index.d.ts +51 -0
- package/dist/src/components/editor/blocks/waka-acceptance-criteria-block.d.ts +60 -0
- package/dist/src/components/editor/blocks/waka-ai-assist-block.d.ts +58 -0
- package/dist/src/components/editor/blocks/waka-api-endpoint-block.d.ts +63 -0
- package/dist/src/components/editor/blocks/waka-code-playground-block.d.ts +61 -0
- package/dist/src/components/editor/blocks/waka-comment-thread-block.d.ts +85 -0
- package/dist/src/components/editor/blocks/waka-diagram-block.d.ts +52 -0
- package/dist/src/components/editor/blocks/waka-embed-block.d.ts +58 -0
- package/dist/src/components/editor/blocks/waka-slash-menu-block.d.ts +67 -0
- package/dist/src/components/editor/blocks/waka-user-story-block.d.ts +79 -0
- package/dist/src/components/editor/blocks/waka-version-diff-block.d.ts +73 -0
- package/dist/src/components/editor/index.d.ts +66 -0
- package/dist/src/components/editor/waka-ai-writer.d.ts +80 -0
- package/dist/src/components/editor/waka-collaborative-editor.d.ts +93 -0
- package/dist/src/components/editor/waka-diff-viewer.d.ts +71 -0
- package/dist/src/components/editor/waka-dnd-editor.d.ts +64 -0
- package/dist/src/components/editor/waka-document-editor.d.ts +92 -0
- package/dist/src/components/editor/waka-editor-elements.d.ts +79 -0
- package/dist/src/components/editor/waka-editor-leaves.d.ts +39 -0
- package/dist/src/components/editor/waka-editor-plugins.d.ts +41 -0
- package/dist/src/components/editor/waka-editor-toolbar.d.ts +20 -0
- package/dist/src/components/editor/waka-editor.d.ts +59 -0
- package/dist/src/components/editor/waka-floating-toolbar.d.ts +47 -0
- package/dist/src/components/editor/waka-markdown-editor.d.ts +60 -0
- package/dist/src/components/editor/waka-mention-editor.d.ts +125 -0
- package/dist/src/components/editor/waka-slash-menu.d.ts +70 -0
- package/dist/src/components/editor/waka-spec-editor.d.ts +88 -0
- package/dist/src/components/index.d.ts +1 -15
- package/dist/src/editor.d.ts +26 -0
- package/dist/textarea-CdQWggYG.js +1 -0
- package/dist/textarea-DJDXJ3nd.mjs +23 -0
- package/dist/types-C2St0wOW.js +1 -0
- package/dist/{types-B6GVaSIP.mjs → types-JnqoLyuv.mjs} +214 -211
- package/dist/{useDataTableImport-BPvfo--2.mjs → useDataTableImport-BWUFesPi.mjs} +3 -3
- package/dist/{useDataTableImport-Cm_pCKnO.js → useDataTableImport-T7ddpN5k.js} +3 -3
- package/dist/waka-doc-renderer-CTxC7Trf.js +3 -0
- package/dist/{waka-doc-renderer-BkIvas3z.mjs → waka-doc-renderer-Cw-Xnyen.mjs} +264 -281
- package/dist/waka-editor-plugins-DR6tpsUC.mjs +135 -0
- package/dist/waka-editor-plugins-sGSh9hn2.js +1 -0
- package/dist/waka-rich-text-editor-BlIdtknG.js +1 -0
- package/dist/waka-rich-text-editor-D1uA3zbB.js +1 -0
- package/dist/waka-rich-text-editor-DgSWiXMW.mjs +342 -0
- package/dist/waka-rich-text-editor-DndVJuDw.mjs +2 -0
- package/package.json +87 -2
- package/src/blocks/footer/index.tsx +1 -6
- package/src/blocks/login/index.tsx +1 -7
- package/src/blocks/profile/index.tsx +3 -5
- package/src/components/editor/blocks/index.ts +182 -0
- package/src/components/editor/blocks/waka-acceptance-criteria-block.tsx +326 -0
- package/src/components/editor/blocks/waka-ai-assist-block.tsx +284 -0
- package/src/components/editor/blocks/waka-api-endpoint-block.tsx +382 -0
- package/src/components/editor/blocks/waka-code-playground-block.tsx +331 -0
- package/src/components/editor/blocks/waka-comment-thread-block.tsx +448 -0
- package/src/components/editor/blocks/waka-diagram-block.tsx +293 -0
- package/src/components/editor/blocks/waka-embed-block.tsx +416 -0
- package/src/components/editor/blocks/waka-slash-menu-block.tsx +432 -0
- package/src/components/editor/blocks/waka-user-story-block.tsx +295 -0
- package/src/components/editor/blocks/waka-version-diff-block.tsx +426 -0
- package/src/components/editor/index.ts +279 -0
- package/src/components/editor/waka-ai-writer.tsx +434 -0
- package/src/components/editor/waka-collaborative-editor.tsx +426 -0
- package/src/components/editor/waka-diff-viewer.tsx +352 -0
- package/src/components/editor/waka-dnd-editor.tsx +284 -0
- package/src/components/editor/waka-document-editor.tsx +502 -0
- package/src/components/editor/waka-editor-elements.tsx +312 -0
- package/src/components/editor/waka-editor-leaves.tsx +101 -0
- package/src/components/editor/waka-editor-plugins.ts +207 -0
- package/src/components/editor/waka-editor-toolbar.tsx +358 -0
- package/src/components/editor/waka-editor.tsx +431 -0
- package/src/components/editor/waka-floating-toolbar.tsx +268 -0
- package/src/components/editor/waka-markdown-editor.tsx +395 -0
- package/src/components/editor/waka-mention-editor.tsx +459 -0
- package/src/components/editor/waka-slash-menu.tsx +392 -0
- package/src/components/editor/waka-spec-editor.tsx +657 -0
- package/src/components/index.ts +1 -18
- package/dist/chunk-BDDJmn7V.js +0 -1
- package/dist/cn-DnPbmOCy.js +0 -1
- package/dist/cn-DpLcAzrf.mjs +0 -22
- package/dist/separator-BDReXBvI.mjs +0 -59
- package/dist/separator-BKjNl9sI.js +0 -1
- package/dist/src/components/waka-actor-badge/index.d.ts +0 -8
- package/dist/src/components/waka-actors-list/index.d.ts +0 -18
- package/dist/src/components/waka-ai-assistant-button/index.d.ts +0 -8
- package/dist/src/components/waka-document-flyover/index.d.ts +0 -10
- package/dist/src/components/waka-document-preview-popup/index.d.ts +0 -26
- package/dist/src/components/waka-hour-balance-badge/index.d.ts +0 -8
- package/dist/src/components/waka-hour-consumption-table/index.d.ts +0 -15
- package/dist/src/components/waka-hour-pack-dialog/index.d.ts +0 -8
- package/dist/src/components/waka-project-stats-header/index.d.ts +0 -15
- package/dist/src/components/waka-step-comment-bubble/index.d.ts +0 -13
- package/dist/src/components/waka-step-comment-panel/index.d.ts +0 -20
- package/dist/src/components/waka-step-permission-matrix/index.d.ts +0 -12
- package/dist/src/components/waka-time-entry-dialog/index.d.ts +0 -16
- package/dist/src/components/waka-time-tracking-flyover/index.d.ts +0 -11
- package/dist/types-BH9cQRqZ.js +0 -1
- package/dist/waka-doc-renderer-BZ2-SqyT.js +0 -3
- package/dist/waka-rich-text-editor-BJGlQgpq.js +0 -1
- package/dist/waka-rich-text-editor-BJzzxeP1.mjs +0 -361
- package/dist/waka-rich-text-editor-wnXLwvUo.js +0 -1
- package/src/components/waka-actor-badge/index.tsx +0 -34
- package/src/components/waka-actors-list/index.tsx +0 -125
- package/src/components/waka-ai-assistant-button/index.tsx +0 -31
- package/src/components/waka-document-flyover/index.tsx +0 -36
- package/src/components/waka-document-preview-popup/index.tsx +0 -103
- package/src/components/waka-hour-balance-badge/index.tsx +0 -43
- package/src/components/waka-hour-consumption-table/index.tsx +0 -72
- package/src/components/waka-hour-pack-dialog/index.tsx +0 -72
- package/src/components/waka-project-stats-header/index.tsx +0 -69
- package/src/components/waka-step-comment-bubble/index.tsx +0 -71
- package/src/components/waka-step-comment-panel/index.tsx +0 -106
- package/src/components/waka-step-permission-matrix/index.tsx +0 -65
- package/src/components/waka-time-entry-dialog/index.tsx +0 -131
- package/src/components/waka-time-tracking-flyover/index.tsx +0 -41
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../dialog"
|
|
5
|
-
|
|
6
|
-
export interface DocumentVersion {
|
|
7
|
-
id: string
|
|
8
|
-
label: string
|
|
9
|
-
date: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface PreviewDocument {
|
|
13
|
-
name: string
|
|
14
|
-
mimeType: string
|
|
15
|
-
content?: string
|
|
16
|
-
previewUrl?: string
|
|
17
|
-
sizeBytes: number
|
|
18
|
-
uploadedBy: string
|
|
19
|
-
uploadedAt: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface DocumentPreviewPopupProps {
|
|
23
|
-
open: boolean
|
|
24
|
-
onOpenChange: (open: boolean) => void
|
|
25
|
-
document: PreviewDocument
|
|
26
|
-
versions?: DocumentVersion[]
|
|
27
|
-
selectedVersion?: string
|
|
28
|
-
onVersionChange?: (versionId: string) => void
|
|
29
|
-
onDownload: (versionId?: string) => void
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const TEXT_TYPES = ["text/", "application/json", "application/xml", "application/javascript"]
|
|
33
|
-
const IMAGE_TYPES = ["image/"]
|
|
34
|
-
|
|
35
|
-
function isTextual(mimeType: string): boolean {
|
|
36
|
-
return TEXT_TYPES.some((t) => mimeType.startsWith(t)) || mimeType.includes("markdown")
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isImage(mimeType: string): boolean {
|
|
40
|
-
return IMAGE_TYPES.some((t) => mimeType.startsWith(t))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function formatSize(bytes: number): string {
|
|
44
|
-
if (bytes < 1024) return `${bytes} o`
|
|
45
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} Ko`
|
|
46
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} Mo`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function DocumentPreviewPopup({
|
|
50
|
-
open, onOpenChange, document: doc, versions, selectedVersion, onVersionChange, onDownload,
|
|
51
|
-
}: DocumentPreviewPopupProps) {
|
|
52
|
-
return (
|
|
53
|
-
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
54
|
-
<DialogContent className="sm:max-w-4xl max-h-[90vh] flex flex-col">
|
|
55
|
-
<DialogHeader>
|
|
56
|
-
<div className="flex items-center justify-between gap-4">
|
|
57
|
-
<DialogTitle className="truncate">{doc.name}</DialogTitle>
|
|
58
|
-
<div className="flex items-center gap-2 shrink-0">
|
|
59
|
-
{versions && versions.length > 1 && (
|
|
60
|
-
<select
|
|
61
|
-
value={selectedVersion ?? versions[0]?.id}
|
|
62
|
-
onChange={(e) => onVersionChange?.(e.target.value)}
|
|
63
|
-
className="rounded-md border bg-background px-2 py-1 text-xs"
|
|
64
|
-
>
|
|
65
|
-
{versions.map((v) => (
|
|
66
|
-
<option key={v.id} value={v.id}>{v.label} — {v.date}</option>
|
|
67
|
-
))}
|
|
68
|
-
</select>
|
|
69
|
-
)}
|
|
70
|
-
<button
|
|
71
|
-
onClick={() => onDownload(selectedVersion)}
|
|
72
|
-
className="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90"
|
|
73
|
-
>
|
|
74
|
-
Telecharger
|
|
75
|
-
</button>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
</DialogHeader>
|
|
79
|
-
|
|
80
|
-
<div className="flex-1 overflow-auto rounded-lg border bg-muted/30 p-4 min-h-[300px]">
|
|
81
|
-
{isTextual(doc.mimeType) && doc.content ? (
|
|
82
|
-
<pre className="whitespace-pre-wrap text-sm font-mono leading-relaxed">{doc.content}</pre>
|
|
83
|
-
) : isImage(doc.mimeType) && doc.previewUrl ? (
|
|
84
|
-
<img src={doc.previewUrl} alt={doc.name} className="max-w-full h-auto mx-auto rounded" />
|
|
85
|
-
) : (
|
|
86
|
-
<div className="flex flex-col items-center justify-center h-full gap-3 text-muted-foreground py-12">
|
|
87
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
88
|
-
<p className="text-sm font-medium">Previsualisation non disponible</p>
|
|
89
|
-
<p className="text-xs">{doc.mimeType} — {formatSize(doc.sizeBytes)}</p>
|
|
90
|
-
</div>
|
|
91
|
-
)}
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
<div className="flex items-center justify-between text-xs text-muted-foreground border-t pt-2">
|
|
95
|
-
<span>{formatSize(doc.sizeBytes)}</span>
|
|
96
|
-
<span>Uploade par {doc.uploadedBy} le {doc.uploadedAt}</span>
|
|
97
|
-
</div>
|
|
98
|
-
</DialogContent>
|
|
99
|
-
</Dialog>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export { DocumentPreviewPopup, formatSize }
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { cn } from "../../utils/cn"
|
|
5
|
-
|
|
6
|
-
export interface HourBalanceBadgeProps {
|
|
7
|
-
remainingMinutes: number
|
|
8
|
-
totalMinutes: number
|
|
9
|
-
className?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function formatMinutes(minutes: number): string {
|
|
13
|
-
const abs = Math.abs(minutes)
|
|
14
|
-
const h = Math.floor(abs / 60)
|
|
15
|
-
const m = abs % 60
|
|
16
|
-
const sign = minutes < 0 ? "-" : ""
|
|
17
|
-
return m > 0 ? `${sign}${h}h${m.toString().padStart(2, "0")}` : `${sign}${h}h`
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function getColorClass(remainingMinutes: number): string {
|
|
21
|
-
if (remainingMinutes > 600) return "text-green-600 dark:text-green-400"
|
|
22
|
-
if (remainingMinutes > 300) return "text-orange-500 dark:text-orange-400"
|
|
23
|
-
return "text-red-600 dark:text-red-400 font-bold"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function HourBalanceBadge({ remainingMinutes, totalMinutes, className }: HourBalanceBadgeProps) {
|
|
27
|
-
return (
|
|
28
|
-
<span
|
|
29
|
-
className={cn(
|
|
30
|
-
"inline-flex items-center gap-0.5 text-sm tabular-nums",
|
|
31
|
-
getColorClass(remainingMinutes),
|
|
32
|
-
className,
|
|
33
|
-
)}
|
|
34
|
-
aria-label={`${formatMinutes(remainingMinutes)} restantes sur ${formatMinutes(totalMinutes)} total`}
|
|
35
|
-
>
|
|
36
|
-
<span>{formatMinutes(remainingMinutes)}</span>
|
|
37
|
-
<span className="text-muted-foreground font-normal">/</span>
|
|
38
|
-
<span className="text-muted-foreground font-normal">{formatMinutes(totalMinutes)}</span>
|
|
39
|
-
</span>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export { HourBalanceBadge, formatMinutes }
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { formatMinutes } from "../waka-hour-balance-badge"
|
|
5
|
-
|
|
6
|
-
export interface HourConsumption {
|
|
7
|
-
date: string
|
|
8
|
-
consultant: string
|
|
9
|
-
reason: string
|
|
10
|
-
stepName?: string
|
|
11
|
-
minutes: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface HourConsumptionTableProps {
|
|
15
|
-
consumptions: HourConsumption[]
|
|
16
|
-
page: number
|
|
17
|
-
totalPages: number
|
|
18
|
-
onPageChange: (page: number) => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function HourConsumptionTable({ consumptions, page, totalPages, onPageChange }: HourConsumptionTableProps) {
|
|
22
|
-
return (
|
|
23
|
-
<div className="space-y-2">
|
|
24
|
-
<table className="w-full text-sm">
|
|
25
|
-
<thead>
|
|
26
|
-
<tr className="border-b text-xs text-muted-foreground">
|
|
27
|
-
<th className="text-left p-2">Date</th>
|
|
28
|
-
<th className="text-left p-2">Consultant</th>
|
|
29
|
-
<th className="text-left p-2">Motif</th>
|
|
30
|
-
<th className="text-left p-2">Etape</th>
|
|
31
|
-
<th className="text-right p-2">Duree</th>
|
|
32
|
-
</tr>
|
|
33
|
-
</thead>
|
|
34
|
-
<tbody>
|
|
35
|
-
{consumptions.map((c, i) => (
|
|
36
|
-
<tr key={i} className="border-b hover:bg-muted/30">
|
|
37
|
-
<td className="p-2 text-xs">{c.date}</td>
|
|
38
|
-
<td className="p-2">{c.consultant}</td>
|
|
39
|
-
<td className="p-2 truncate max-w-[200px]">{c.reason}</td>
|
|
40
|
-
<td className="p-2 text-xs text-muted-foreground">{c.stepName ?? "—"}</td>
|
|
41
|
-
<td className="p-2 text-right tabular-nums font-medium">{formatMinutes(c.minutes)}</td>
|
|
42
|
-
</tr>
|
|
43
|
-
))}
|
|
44
|
-
{consumptions.length === 0 && (
|
|
45
|
-
<tr><td colSpan={5} className="p-4 text-center text-muted-foreground">Aucune consommation</td></tr>
|
|
46
|
-
)}
|
|
47
|
-
</tbody>
|
|
48
|
-
</table>
|
|
49
|
-
{totalPages > 1 && (
|
|
50
|
-
<div className="flex items-center justify-center gap-2">
|
|
51
|
-
<button
|
|
52
|
-
onClick={() => onPageChange(page - 1)}
|
|
53
|
-
disabled={page <= 1}
|
|
54
|
-
className="rounded border px-2 py-1 text-xs disabled:opacity-50 hover:bg-accent"
|
|
55
|
-
>
|
|
56
|
-
Precedent
|
|
57
|
-
</button>
|
|
58
|
-
<span className="text-xs text-muted-foreground">{page}/{totalPages}</span>
|
|
59
|
-
<button
|
|
60
|
-
onClick={() => onPageChange(page + 1)}
|
|
61
|
-
disabled={page >= totalPages}
|
|
62
|
-
className="rounded border px-2 py-1 text-xs disabled:opacity-50 hover:bg-accent"
|
|
63
|
-
>
|
|
64
|
-
Suivant
|
|
65
|
-
</button>
|
|
66
|
-
</div>
|
|
67
|
-
)}
|
|
68
|
-
</div>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export { HourConsumptionTable }
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "../dialog"
|
|
5
|
-
|
|
6
|
-
export interface HourPackDialogProps {
|
|
7
|
-
open: boolean
|
|
8
|
-
onOpenChange: (open: boolean) => void
|
|
9
|
-
onSubmit: (hours: number, comment: string) => void
|
|
10
|
-
isLoading?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function HourPackDialog({ open, onOpenChange, onSubmit, isLoading }: HourPackDialogProps) {
|
|
14
|
-
const [hours, setHours] = React.useState(25)
|
|
15
|
-
const [comment, setComment] = React.useState("")
|
|
16
|
-
|
|
17
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
18
|
-
e.preventDefault()
|
|
19
|
-
if (hours < 1 || comment.trim().length < 10) return
|
|
20
|
-
onSubmit(hours, comment.trim())
|
|
21
|
-
setHours(25)
|
|
22
|
-
setComment("")
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
27
|
-
<DialogContent className="sm:max-w-md">
|
|
28
|
-
<DialogHeader>
|
|
29
|
-
<DialogTitle>Ajouter un pack d'heures</DialogTitle>
|
|
30
|
-
</DialogHeader>
|
|
31
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
32
|
-
<div>
|
|
33
|
-
<label className="text-sm font-medium">Nombre d'heures</label>
|
|
34
|
-
<input
|
|
35
|
-
type="number"
|
|
36
|
-
min={1}
|
|
37
|
-
value={hours}
|
|
38
|
-
onChange={(e) => setHours(Math.max(1, parseInt(e.target.value) || 1))}
|
|
39
|
-
className="mt-1 w-full rounded-md border bg-background px-3 py-2 text-sm"
|
|
40
|
-
/>
|
|
41
|
-
</div>
|
|
42
|
-
<div>
|
|
43
|
-
<label className="text-sm font-medium">Commentaire (obligatoire, min 10 chars)</label>
|
|
44
|
-
<textarea
|
|
45
|
-
value={comment}
|
|
46
|
-
onChange={(e) => setComment(e.target.value)}
|
|
47
|
-
placeholder="Ex: Commande initiale Biped du 30 mars 2026"
|
|
48
|
-
className="mt-1 w-full min-h-[80px] rounded-md border bg-background px-3 py-2 text-sm resize-none"
|
|
49
|
-
/>
|
|
50
|
-
{comment.length > 0 && comment.length < 10 && (
|
|
51
|
-
<p className="text-xs text-red-500 mt-1">{10 - comment.length} caracteres restants</p>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
<DialogFooter>
|
|
55
|
-
<button type="button" onClick={() => onOpenChange(false)} className="rounded-md border px-4 py-2 text-sm hover:bg-accent">
|
|
56
|
-
Annuler
|
|
57
|
-
</button>
|
|
58
|
-
<button
|
|
59
|
-
type="submit"
|
|
60
|
-
disabled={hours < 1 || comment.trim().length < 10 || isLoading}
|
|
61
|
-
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
|
62
|
-
>
|
|
63
|
-
Ajouter
|
|
64
|
-
</button>
|
|
65
|
-
</DialogFooter>
|
|
66
|
-
</form>
|
|
67
|
-
</DialogContent>
|
|
68
|
-
</Dialog>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export { HourPackDialog }
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { cn } from "../../utils/cn"
|
|
5
|
-
import { HourBalanceBadge, formatMinutes } from "../waka-hour-balance-badge"
|
|
6
|
-
|
|
7
|
-
export interface ProjectStatsHeaderProps {
|
|
8
|
-
stats: {
|
|
9
|
-
ppMinutes: number
|
|
10
|
-
wakaMinutes: number
|
|
11
|
-
billableMinutes: number
|
|
12
|
-
billableTotalMinutes: number
|
|
13
|
-
breakdown?: { role: string; minutes: number }[]
|
|
14
|
-
}
|
|
15
|
-
onExpand?: (category: "pp" | "waka" | "billable") => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function StatPill({
|
|
19
|
-
label, value, colorClass, onClick,
|
|
20
|
-
}: {
|
|
21
|
-
label: string; value: React.ReactNode; colorClass: string; onClick?: () => void
|
|
22
|
-
}) {
|
|
23
|
-
return (
|
|
24
|
-
<button
|
|
25
|
-
onClick={onClick}
|
|
26
|
-
className={cn(
|
|
27
|
-
"inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-colors",
|
|
28
|
-
"hover:bg-accent",
|
|
29
|
-
colorClass,
|
|
30
|
-
)}
|
|
31
|
-
>
|
|
32
|
-
<span>{label}</span>
|
|
33
|
-
<span className="font-bold tabular-nums">{value}</span>
|
|
34
|
-
</button>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function ProjectStatsHeader({ stats, onExpand }: ProjectStatsHeaderProps) {
|
|
39
|
-
return (
|
|
40
|
-
<div className="flex items-center gap-2 flex-wrap">
|
|
41
|
-
<StatPill
|
|
42
|
-
label="PP"
|
|
43
|
-
value={formatMinutes(stats.ppMinutes)}
|
|
44
|
-
colorClass="border-emerald-200 text-emerald-700 dark:text-emerald-400"
|
|
45
|
-
onClick={() => onExpand?.("pp")}
|
|
46
|
-
/>
|
|
47
|
-
<StatPill
|
|
48
|
-
label="Waka"
|
|
49
|
-
value={formatMinutes(stats.wakaMinutes)}
|
|
50
|
-
colorClass="border-blue-200 text-blue-700 dark:text-blue-400"
|
|
51
|
-
onClick={() => onExpand?.("waka")}
|
|
52
|
-
/>
|
|
53
|
-
<StatPill
|
|
54
|
-
label="Facturables"
|
|
55
|
-
value={
|
|
56
|
-
<HourBalanceBadge
|
|
57
|
-
remainingMinutes={stats.billableTotalMinutes - stats.billableMinutes}
|
|
58
|
-
totalMinutes={stats.billableTotalMinutes}
|
|
59
|
-
className="text-xs"
|
|
60
|
-
/>
|
|
61
|
-
}
|
|
62
|
-
colorClass="border-amber-200"
|
|
63
|
-
onClick={() => onExpand?.("billable")}
|
|
64
|
-
/>
|
|
65
|
-
</div>
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export { ProjectStatsHeader }
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { cn } from "../../utils/cn"
|
|
5
|
-
import { HoverCard, HoverCardContent, HoverCardTrigger } from "../hover-card"
|
|
6
|
-
|
|
7
|
-
export interface RecentComment {
|
|
8
|
-
author: string
|
|
9
|
-
content: string
|
|
10
|
-
date: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface StepCommentBubbleProps {
|
|
14
|
-
count: number
|
|
15
|
-
recentComments?: RecentComment[]
|
|
16
|
-
onClick: () => void
|
|
17
|
-
className?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function StepCommentBubble({ count, recentComments, onClick, className }: StepCommentBubbleProps) {
|
|
21
|
-
const hasComments = count > 0
|
|
22
|
-
|
|
23
|
-
const button = (
|
|
24
|
-
<button
|
|
25
|
-
onClick={onClick}
|
|
26
|
-
className={cn(
|
|
27
|
-
"relative inline-flex items-center justify-center h-8 w-8 rounded-full transition-colors",
|
|
28
|
-
hasComments
|
|
29
|
-
? "bg-blue-100 text-blue-600 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400"
|
|
30
|
-
: "bg-muted text-muted-foreground hover:bg-accent",
|
|
31
|
-
className,
|
|
32
|
-
)}
|
|
33
|
-
aria-label={`${count} commentaire${count !== 1 ? "s" : ""}`}
|
|
34
|
-
>
|
|
35
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>
|
|
36
|
-
{hasComments && (
|
|
37
|
-
<span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-blue-600 text-[10px] font-bold text-white">
|
|
38
|
-
{count > 9 ? "9+" : count}
|
|
39
|
-
</span>
|
|
40
|
-
)}
|
|
41
|
-
</button>
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
if (!recentComments?.length) return button
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<HoverCard openDelay={200}>
|
|
48
|
-
<HoverCardTrigger asChild>{button}</HoverCardTrigger>
|
|
49
|
-
<HoverCardContent className="w-72 p-3" side="top">
|
|
50
|
-
<div className="space-y-2">
|
|
51
|
-
{recentComments.slice(0, 3).map((c, i) => (
|
|
52
|
-
<div key={i} className="text-xs">
|
|
53
|
-
<div className="flex justify-between text-muted-foreground">
|
|
54
|
-
<span className="font-medium">{c.author}</span>
|
|
55
|
-
<span>{c.date}</span>
|
|
56
|
-
</div>
|
|
57
|
-
<p className="line-clamp-2 mt-0.5">{c.content}</p>
|
|
58
|
-
</div>
|
|
59
|
-
))}
|
|
60
|
-
{count > 3 && (
|
|
61
|
-
<p className="text-xs text-muted-foreground text-center">
|
|
62
|
-
+{count - 3} autre{count - 3 > 1 ? "s" : ""}
|
|
63
|
-
</p>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
</HoverCardContent>
|
|
67
|
-
</HoverCard>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export { StepCommentBubble }
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "../sheet"
|
|
5
|
-
import { ActorBadge } from "../waka-actor-badge"
|
|
6
|
-
|
|
7
|
-
export interface StepComment {
|
|
8
|
-
id: string
|
|
9
|
-
authorName: string
|
|
10
|
-
authorRole: string
|
|
11
|
-
content: string
|
|
12
|
-
createdAt: string
|
|
13
|
-
isEdited: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface StepCommentPanelProps {
|
|
17
|
-
open: boolean
|
|
18
|
-
onOpenChange: (open: boolean) => void
|
|
19
|
-
comments: StepComment[]
|
|
20
|
-
onAdd: (content: string) => void
|
|
21
|
-
onEdit?: (id: string, content: string) => void
|
|
22
|
-
onDelete?: (id: string) => void
|
|
23
|
-
canEditDelete?: boolean
|
|
24
|
-
isLoading?: boolean
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function StepCommentPanel({
|
|
28
|
-
open, onOpenChange, comments, onAdd, onEdit, onDelete, canEditDelete, isLoading,
|
|
29
|
-
}: StepCommentPanelProps) {
|
|
30
|
-
const [draft, setDraft] = React.useState("")
|
|
31
|
-
|
|
32
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
33
|
-
e.preventDefault()
|
|
34
|
-
if (!draft.trim()) return
|
|
35
|
-
onAdd(draft.trim())
|
|
36
|
-
setDraft("")
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
41
|
-
<SheetContent className="flex flex-col w-full sm:max-w-md">
|
|
42
|
-
<SheetHeader>
|
|
43
|
-
<SheetTitle>Commentaires ({comments.length})</SheetTitle>
|
|
44
|
-
</SheetHeader>
|
|
45
|
-
|
|
46
|
-
<div className="flex-1 overflow-y-auto space-y-3 py-4">
|
|
47
|
-
{comments.length === 0 && (
|
|
48
|
-
<p className="text-sm text-muted-foreground text-center py-8">Aucun commentaire</p>
|
|
49
|
-
)}
|
|
50
|
-
{comments.map((c) => (
|
|
51
|
-
<div key={c.id} className="rounded-lg border p-3 space-y-1.5">
|
|
52
|
-
<div className="flex items-center justify-between">
|
|
53
|
-
<ActorBadge role={c.authorRole} name={c.authorName} />
|
|
54
|
-
<span className="text-xs text-muted-foreground">{c.createdAt}</span>
|
|
55
|
-
</div>
|
|
56
|
-
<p className="text-sm whitespace-pre-wrap">{c.content}</p>
|
|
57
|
-
<div className="flex items-center gap-2">
|
|
58
|
-
{c.isEdited && <span className="text-xs text-muted-foreground italic">(modifie)</span>}
|
|
59
|
-
{canEditDelete && onEdit && (
|
|
60
|
-
<button
|
|
61
|
-
className="text-xs text-blue-600 hover:underline"
|
|
62
|
-
onClick={() => {
|
|
63
|
-
const newContent = prompt("Modifier le commentaire:", c.content)
|
|
64
|
-
if (newContent && newContent !== c.content) onEdit(c.id, newContent)
|
|
65
|
-
}}
|
|
66
|
-
>
|
|
67
|
-
Modifier
|
|
68
|
-
</button>
|
|
69
|
-
)}
|
|
70
|
-
{canEditDelete && onDelete && (
|
|
71
|
-
<button
|
|
72
|
-
className="text-xs text-red-600 hover:underline"
|
|
73
|
-
onClick={() => {
|
|
74
|
-
if (confirm("Supprimer ce commentaire ?")) onDelete(c.id)
|
|
75
|
-
}}
|
|
76
|
-
>
|
|
77
|
-
Supprimer
|
|
78
|
-
</button>
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
))}
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<form onSubmit={handleSubmit} className="border-t pt-3 flex gap-2">
|
|
86
|
-
<textarea
|
|
87
|
-
value={draft}
|
|
88
|
-
onChange={(e) => setDraft(e.target.value)}
|
|
89
|
-
placeholder="Ajouter un commentaire..."
|
|
90
|
-
className="flex-1 min-h-[60px] rounded-md border bg-background px-3 py-2 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-ring"
|
|
91
|
-
disabled={isLoading}
|
|
92
|
-
/>
|
|
93
|
-
<button
|
|
94
|
-
type="submit"
|
|
95
|
-
disabled={!draft.trim() || isLoading}
|
|
96
|
-
className="self-end rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
|
97
|
-
>
|
|
98
|
-
Envoyer
|
|
99
|
-
</button>
|
|
100
|
-
</form>
|
|
101
|
-
</SheetContent>
|
|
102
|
-
</Sheet>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export { StepCommentPanel }
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../tooltip"
|
|
5
|
-
|
|
6
|
-
export interface PermissionAction {
|
|
7
|
-
key: string
|
|
8
|
-
label: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface StepPermissionMatrixProps {
|
|
12
|
-
roles: string[]
|
|
13
|
-
actions: PermissionAction[]
|
|
14
|
-
value: Record<string, string[]>
|
|
15
|
-
onChange: (role: string, action: string, enabled: boolean) => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function StepPermissionMatrix({ roles, actions, value, onChange }: StepPermissionMatrixProps) {
|
|
19
|
-
return (
|
|
20
|
-
<TooltipProvider>
|
|
21
|
-
<div className="overflow-x-auto">
|
|
22
|
-
<table className="w-full text-xs">
|
|
23
|
-
<thead>
|
|
24
|
-
<tr className="border-b">
|
|
25
|
-
<th className="text-left p-2 font-medium text-muted-foreground sticky left-0 bg-background">Role</th>
|
|
26
|
-
{actions.map((a) => (
|
|
27
|
-
<th key={a.key} className="p-2 text-center">
|
|
28
|
-
<Tooltip>
|
|
29
|
-
<TooltipTrigger asChild>
|
|
30
|
-
<span className="font-medium cursor-help">{a.key.replace("_", " ").slice(0, 8)}</span>
|
|
31
|
-
</TooltipTrigger>
|
|
32
|
-
<TooltipContent>{a.label}</TooltipContent>
|
|
33
|
-
</Tooltip>
|
|
34
|
-
</th>
|
|
35
|
-
))}
|
|
36
|
-
</tr>
|
|
37
|
-
</thead>
|
|
38
|
-
<tbody>
|
|
39
|
-
{roles.map((role) => (
|
|
40
|
-
<tr key={role} className="border-b hover:bg-muted/30">
|
|
41
|
-
<td className="p-2 font-medium sticky left-0 bg-background">{role}</td>
|
|
42
|
-
{actions.map((a) => {
|
|
43
|
-
const checked = (value[role] ?? []).includes(a.key)
|
|
44
|
-
return (
|
|
45
|
-
<td key={a.key} className="p-2 text-center">
|
|
46
|
-
<input
|
|
47
|
-
type="checkbox"
|
|
48
|
-
checked={checked}
|
|
49
|
-
onChange={(e) => onChange(role, a.key, e.target.checked)}
|
|
50
|
-
className="rounded border-input"
|
|
51
|
-
aria-label={`${role} — ${a.label}`}
|
|
52
|
-
/>
|
|
53
|
-
</td>
|
|
54
|
-
)
|
|
55
|
-
})}
|
|
56
|
-
</tr>
|
|
57
|
-
))}
|
|
58
|
-
</tbody>
|
|
59
|
-
</table>
|
|
60
|
-
</div>
|
|
61
|
-
</TooltipProvider>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export { StepPermissionMatrix }
|