@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.
Files changed (140) hide show
  1. package/dist/badge-BbwO7QeZ.js +1 -0
  2. package/dist/badge-BfiocODp.mjs +23 -0
  3. package/dist/charts.cjs.js +1 -1
  4. package/dist/charts.es.js +1 -1
  5. package/dist/chunk-14q5BKub.js +1 -0
  6. package/dist/{chunk-BH6uBOac.mjs → chunk-Cr9pTUWm.mjs} +5 -5
  7. package/dist/cn-DEtaFQsA.js +1 -0
  8. package/dist/cn-DUn6aSIQ.mjs +24 -0
  9. package/dist/doc.cjs.js +2 -2
  10. package/dist/doc.es.js +19 -19
  11. package/dist/editor.cjs.js +48 -0
  12. package/dist/editor.d.ts +1 -0
  13. package/dist/editor.es.js +6551 -0
  14. package/dist/{exceljs.min-DG9M8IZ1.mjs → exceljs.min-DL1XYDll.mjs} +1 -1
  15. package/dist/{exceljs.min-BuefmDRS.js → exceljs.min-qeIfSCbF.js} +1 -1
  16. package/dist/export.cjs.js +1 -1
  17. package/dist/export.es.js +1 -1
  18. package/dist/index.cjs.js +150 -150
  19. package/dist/index.es.js +26782 -27591
  20. package/dist/input-BfaSAGVw.js +1 -0
  21. package/dist/input-DVr_Qkl8.mjs +14 -0
  22. package/dist/rich-text.cjs.js +1 -1
  23. package/dist/rich-text.es.js +1 -1
  24. package/dist/security-CyBpuklN.mjs +122 -0
  25. package/dist/security-bFWwDrlg.js +1 -0
  26. package/dist/separator-NrkltulH.js +1 -0
  27. package/dist/separator-ibN2mycs.mjs +51 -0
  28. package/dist/src/components/editor/blocks/index.d.ts +51 -0
  29. package/dist/src/components/editor/blocks/waka-acceptance-criteria-block.d.ts +60 -0
  30. package/dist/src/components/editor/blocks/waka-ai-assist-block.d.ts +58 -0
  31. package/dist/src/components/editor/blocks/waka-api-endpoint-block.d.ts +63 -0
  32. package/dist/src/components/editor/blocks/waka-code-playground-block.d.ts +61 -0
  33. package/dist/src/components/editor/blocks/waka-comment-thread-block.d.ts +85 -0
  34. package/dist/src/components/editor/blocks/waka-diagram-block.d.ts +52 -0
  35. package/dist/src/components/editor/blocks/waka-embed-block.d.ts +58 -0
  36. package/dist/src/components/editor/blocks/waka-slash-menu-block.d.ts +67 -0
  37. package/dist/src/components/editor/blocks/waka-user-story-block.d.ts +79 -0
  38. package/dist/src/components/editor/blocks/waka-version-diff-block.d.ts +73 -0
  39. package/dist/src/components/editor/index.d.ts +66 -0
  40. package/dist/src/components/editor/waka-ai-writer.d.ts +80 -0
  41. package/dist/src/components/editor/waka-collaborative-editor.d.ts +93 -0
  42. package/dist/src/components/editor/waka-diff-viewer.d.ts +71 -0
  43. package/dist/src/components/editor/waka-dnd-editor.d.ts +64 -0
  44. package/dist/src/components/editor/waka-document-editor.d.ts +92 -0
  45. package/dist/src/components/editor/waka-editor-elements.d.ts +79 -0
  46. package/dist/src/components/editor/waka-editor-leaves.d.ts +39 -0
  47. package/dist/src/components/editor/waka-editor-plugins.d.ts +41 -0
  48. package/dist/src/components/editor/waka-editor-toolbar.d.ts +20 -0
  49. package/dist/src/components/editor/waka-editor.d.ts +59 -0
  50. package/dist/src/components/editor/waka-floating-toolbar.d.ts +47 -0
  51. package/dist/src/components/editor/waka-markdown-editor.d.ts +60 -0
  52. package/dist/src/components/editor/waka-mention-editor.d.ts +125 -0
  53. package/dist/src/components/editor/waka-slash-menu.d.ts +70 -0
  54. package/dist/src/components/editor/waka-spec-editor.d.ts +88 -0
  55. package/dist/src/components/index.d.ts +1 -15
  56. package/dist/src/editor.d.ts +26 -0
  57. package/dist/textarea-CdQWggYG.js +1 -0
  58. package/dist/textarea-DJDXJ3nd.mjs +23 -0
  59. package/dist/types-C2St0wOW.js +1 -0
  60. package/dist/{types-B6GVaSIP.mjs → types-JnqoLyuv.mjs} +214 -211
  61. package/dist/{useDataTableImport-BPvfo--2.mjs → useDataTableImport-BWUFesPi.mjs} +3 -3
  62. package/dist/{useDataTableImport-Cm_pCKnO.js → useDataTableImport-T7ddpN5k.js} +3 -3
  63. package/dist/waka-doc-renderer-CTxC7Trf.js +3 -0
  64. package/dist/{waka-doc-renderer-BkIvas3z.mjs → waka-doc-renderer-Cw-Xnyen.mjs} +264 -281
  65. package/dist/waka-editor-plugins-DR6tpsUC.mjs +135 -0
  66. package/dist/waka-editor-plugins-sGSh9hn2.js +1 -0
  67. package/dist/waka-rich-text-editor-BlIdtknG.js +1 -0
  68. package/dist/waka-rich-text-editor-D1uA3zbB.js +1 -0
  69. package/dist/waka-rich-text-editor-DgSWiXMW.mjs +342 -0
  70. package/dist/waka-rich-text-editor-DndVJuDw.mjs +2 -0
  71. package/package.json +87 -2
  72. package/src/blocks/footer/index.tsx +1 -6
  73. package/src/blocks/login/index.tsx +1 -7
  74. package/src/blocks/profile/index.tsx +3 -5
  75. package/src/components/editor/blocks/index.ts +182 -0
  76. package/src/components/editor/blocks/waka-acceptance-criteria-block.tsx +326 -0
  77. package/src/components/editor/blocks/waka-ai-assist-block.tsx +284 -0
  78. package/src/components/editor/blocks/waka-api-endpoint-block.tsx +382 -0
  79. package/src/components/editor/blocks/waka-code-playground-block.tsx +331 -0
  80. package/src/components/editor/blocks/waka-comment-thread-block.tsx +448 -0
  81. package/src/components/editor/blocks/waka-diagram-block.tsx +293 -0
  82. package/src/components/editor/blocks/waka-embed-block.tsx +416 -0
  83. package/src/components/editor/blocks/waka-slash-menu-block.tsx +432 -0
  84. package/src/components/editor/blocks/waka-user-story-block.tsx +295 -0
  85. package/src/components/editor/blocks/waka-version-diff-block.tsx +426 -0
  86. package/src/components/editor/index.ts +279 -0
  87. package/src/components/editor/waka-ai-writer.tsx +434 -0
  88. package/src/components/editor/waka-collaborative-editor.tsx +426 -0
  89. package/src/components/editor/waka-diff-viewer.tsx +352 -0
  90. package/src/components/editor/waka-dnd-editor.tsx +284 -0
  91. package/src/components/editor/waka-document-editor.tsx +502 -0
  92. package/src/components/editor/waka-editor-elements.tsx +312 -0
  93. package/src/components/editor/waka-editor-leaves.tsx +101 -0
  94. package/src/components/editor/waka-editor-plugins.ts +207 -0
  95. package/src/components/editor/waka-editor-toolbar.tsx +358 -0
  96. package/src/components/editor/waka-editor.tsx +431 -0
  97. package/src/components/editor/waka-floating-toolbar.tsx +268 -0
  98. package/src/components/editor/waka-markdown-editor.tsx +395 -0
  99. package/src/components/editor/waka-mention-editor.tsx +459 -0
  100. package/src/components/editor/waka-slash-menu.tsx +392 -0
  101. package/src/components/editor/waka-spec-editor.tsx +657 -0
  102. package/src/components/index.ts +1 -18
  103. package/dist/chunk-BDDJmn7V.js +0 -1
  104. package/dist/cn-DnPbmOCy.js +0 -1
  105. package/dist/cn-DpLcAzrf.mjs +0 -22
  106. package/dist/separator-BDReXBvI.mjs +0 -59
  107. package/dist/separator-BKjNl9sI.js +0 -1
  108. package/dist/src/components/waka-actor-badge/index.d.ts +0 -8
  109. package/dist/src/components/waka-actors-list/index.d.ts +0 -18
  110. package/dist/src/components/waka-ai-assistant-button/index.d.ts +0 -8
  111. package/dist/src/components/waka-document-flyover/index.d.ts +0 -10
  112. package/dist/src/components/waka-document-preview-popup/index.d.ts +0 -26
  113. package/dist/src/components/waka-hour-balance-badge/index.d.ts +0 -8
  114. package/dist/src/components/waka-hour-consumption-table/index.d.ts +0 -15
  115. package/dist/src/components/waka-hour-pack-dialog/index.d.ts +0 -8
  116. package/dist/src/components/waka-project-stats-header/index.d.ts +0 -15
  117. package/dist/src/components/waka-step-comment-bubble/index.d.ts +0 -13
  118. package/dist/src/components/waka-step-comment-panel/index.d.ts +0 -20
  119. package/dist/src/components/waka-step-permission-matrix/index.d.ts +0 -12
  120. package/dist/src/components/waka-time-entry-dialog/index.d.ts +0 -16
  121. package/dist/src/components/waka-time-tracking-flyover/index.d.ts +0 -11
  122. package/dist/types-BH9cQRqZ.js +0 -1
  123. package/dist/waka-doc-renderer-BZ2-SqyT.js +0 -3
  124. package/dist/waka-rich-text-editor-BJGlQgpq.js +0 -1
  125. package/dist/waka-rich-text-editor-BJzzxeP1.mjs +0 -361
  126. package/dist/waka-rich-text-editor-wnXLwvUo.js +0 -1
  127. package/src/components/waka-actor-badge/index.tsx +0 -34
  128. package/src/components/waka-actors-list/index.tsx +0 -125
  129. package/src/components/waka-ai-assistant-button/index.tsx +0 -31
  130. package/src/components/waka-document-flyover/index.tsx +0 -36
  131. package/src/components/waka-document-preview-popup/index.tsx +0 -103
  132. package/src/components/waka-hour-balance-badge/index.tsx +0 -43
  133. package/src/components/waka-hour-consumption-table/index.tsx +0 -72
  134. package/src/components/waka-hour-pack-dialog/index.tsx +0 -72
  135. package/src/components/waka-project-stats-header/index.tsx +0 -69
  136. package/src/components/waka-step-comment-bubble/index.tsx +0 -71
  137. package/src/components/waka-step-comment-panel/index.tsx +0 -106
  138. package/src/components/waka-step-permission-matrix/index.tsx +0 -65
  139. package/src/components/waka-time-entry-dialog/index.tsx +0 -131
  140. 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 }