@myrjfa/ui 1.0.8 → 1.0.10

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 (161) hide show
  1. package/dist/chat/ChatMain.d.ts +9 -0
  2. package/dist/chat/ChatMain.d.ts.map +1 -0
  3. package/dist/chat/ChatMain.js +272 -0
  4. package/dist/chat/ChatSidebar.d.ts +13 -0
  5. package/dist/chat/ChatSidebar.d.ts.map +1 -0
  6. package/dist/chat/ChatSidebar.js +23 -0
  7. package/dist/chat/ConversationList.d.ts +12 -0
  8. package/dist/chat/ConversationList.d.ts.map +1 -0
  9. package/dist/chat/ConversationList.js +70 -0
  10. package/dist/chat/ExpensePanel.d.ts +11 -0
  11. package/dist/chat/ExpensePanel.d.ts.map +1 -0
  12. package/dist/chat/ExpensePanel.js +235 -0
  13. package/dist/chat/MessageBubble.d.ts +18 -0
  14. package/dist/chat/MessageBubble.d.ts.map +1 -0
  15. package/dist/chat/MessageBubble.js +284 -0
  16. package/dist/chat/MessageInput.d.ts +21 -0
  17. package/dist/chat/MessageInput.d.ts.map +1 -0
  18. package/dist/chat/MessageInput.js +252 -0
  19. package/dist/chat/MilestonePanel.d.ts +11 -0
  20. package/dist/chat/MilestonePanel.d.ts.map +1 -0
  21. package/dist/chat/MilestonePanel.js +173 -0
  22. package/dist/chat/components/ParticipantSelector.d.ts +11 -0
  23. package/dist/chat/components/ParticipantSelector.d.ts.map +1 -0
  24. package/dist/chat/components/ParticipantSelector.js +17 -0
  25. package/dist/chat/components/SplitInputs.d.ts +11 -0
  26. package/dist/chat/components/SplitInputs.d.ts.map +1 -0
  27. package/dist/chat/components/SplitInputs.js +83 -0
  28. package/dist/chat/components/SplitTypeSelector.d.ts +8 -0
  29. package/dist/chat/components/SplitTypeSelector.d.ts.map +1 -0
  30. package/dist/chat/components/SplitTypeSelector.js +13 -0
  31. package/dist/chat/dialogs/AddMemberDialog.d.ts +10 -0
  32. package/dist/chat/dialogs/AddMemberDialog.d.ts.map +1 -0
  33. package/dist/chat/dialogs/AddMemberDialog.js +50 -0
  34. package/dist/chat/dialogs/CameraDialog.d.ts +8 -0
  35. package/dist/chat/dialogs/CameraDialog.d.ts.map +1 -0
  36. package/dist/chat/dialogs/CameraDialog.js +80 -0
  37. package/dist/chat/dialogs/CreatePollDialog.d.ts +8 -0
  38. package/dist/chat/dialogs/CreatePollDialog.d.ts.map +1 -0
  39. package/dist/chat/dialogs/CreatePollDialog.js +38 -0
  40. package/dist/chat/dialogs/ExpenseAnalyticsDialog.d.ts +19 -0
  41. package/dist/chat/dialogs/ExpenseAnalyticsDialog.d.ts.map +1 -0
  42. package/dist/chat/dialogs/ExpenseAnalyticsDialog.js +52 -0
  43. package/dist/chat/dialogs/GroupInfoDialog.d.ts +10 -0
  44. package/dist/chat/dialogs/GroupInfoDialog.d.ts.map +1 -0
  45. package/dist/chat/dialogs/GroupInfoDialog.js +71 -0
  46. package/dist/chat/dialogs/GroupInfoViewDialog.d.ts +9 -0
  47. package/dist/chat/dialogs/GroupInfoViewDialog.d.ts.map +1 -0
  48. package/dist/chat/dialogs/GroupInfoViewDialog.js +15 -0
  49. package/dist/chat/dialogs/NewChatDialog.d.ts +2 -0
  50. package/dist/chat/dialogs/NewChatDialog.d.ts.map +1 -0
  51. package/dist/chat/dialogs/NewChatDialog.js +63 -0
  52. package/dist/chat/dialogs/NewGroupDialog.d.ts +2 -0
  53. package/dist/chat/dialogs/NewGroupDialog.d.ts.map +1 -0
  54. package/dist/chat/dialogs/NewGroupDialog.js +69 -0
  55. package/dist/chat/dialogs/ParticipantsDialog.d.ts +11 -0
  56. package/dist/chat/dialogs/ParticipantsDialog.d.ts.map +1 -0
  57. package/dist/chat/dialogs/ParticipantsDialog.js +93 -0
  58. package/dist/chat/dialogs/UserProfileDialog.d.ts +10 -0
  59. package/dist/chat/dialogs/UserProfileDialog.d.ts.map +1 -0
  60. package/dist/chat/dialogs/UserProfileDialog.js +100 -0
  61. package/dist/chat/index.d.ts +8 -0
  62. package/dist/chat/index.d.ts.map +1 -0
  63. package/dist/chat/index.js +7 -0
  64. package/dist/dashboard/action-buttons.js +4 -5
  65. package/dist/dashboard/app-sidebar.js +46 -128
  66. package/dist/dashboard/card-header.js +1 -2
  67. package/dist/dashboard/dashboard-components.js +3 -5
  68. package/dist/dashboard/data-table.d.ts +18 -18
  69. package/dist/dashboard/data-table.js +54 -66
  70. package/dist/dashboard/nav-main.js +4 -5
  71. package/dist/dashboard/nav-secondary.js +2 -25
  72. package/dist/dashboard/stats-card.js +1 -2
  73. package/dist/dashboard/table.js +8 -54
  74. package/dist/header/mobile-nav-item.js +1 -2
  75. package/dist/header/mobile-navbar.js +8 -9
  76. package/dist/header/nav-item.d.ts +2 -1
  77. package/dist/header/nav-item.d.ts.map +1 -1
  78. package/dist/header/nav-item.js +2 -3
  79. package/dist/index.d.ts +57 -56
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +57 -57
  82. package/dist/main/footer.js +5 -9
  83. package/dist/main/navbar.d.ts.map +1 -1
  84. package/dist/main/navbar.js +53 -136
  85. package/dist/main/paginationBar.js +12 -14
  86. package/dist/main/portfolio/portfolioItem.js +1 -2
  87. package/dist/main/portfolio/portfolioPopup.js +16 -17
  88. package/dist/main/review-page.d.ts.map +1 -1
  89. package/dist/main/review-page.js +131 -198
  90. package/dist/misc/accordion.js +8 -34
  91. package/dist/misc/alert-dialog.js +22 -55
  92. package/dist/misc/alert.js +7 -32
  93. package/dist/misc/alertdialogclaude.js +50 -125
  94. package/dist/misc/avatar.js +6 -31
  95. package/dist/misc/badge.d.ts +8 -8
  96. package/dist/misc/badge.js +4 -27
  97. package/dist/misc/button.d.ts +9 -9
  98. package/dist/misc/button.js +4 -27
  99. package/dist/misc/card.js +12 -40
  100. package/dist/misc/carousel.js +37 -61
  101. package/dist/misc/checkbox.js +2 -25
  102. package/dist/misc/command.d.ts +3 -1
  103. package/dist/misc/command.d.ts.map +1 -1
  104. package/dist/misc/command.js +18 -49
  105. package/dist/misc/datepicker.js +66 -76
  106. package/dist/misc/dialog.js +20 -52
  107. package/dist/misc/dropdown-menu.js +30 -67
  108. package/dist/misc/faq.js +2 -3
  109. package/dist/misc/file-preview.js +43 -97
  110. package/dist/misc/filter.d.ts.map +1 -1
  111. package/dist/misc/filter.js +18 -21
  112. package/dist/misc/filterBar.js +136 -160
  113. package/dist/misc/form.d.ts +1 -1
  114. package/dist/misc/form.d.ts.map +1 -1
  115. package/dist/misc/form.js +39 -60
  116. package/dist/misc/image-cropper.d.ts +10 -0
  117. package/dist/misc/image-cropper.d.ts.map +1 -0
  118. package/dist/misc/image-cropper.js +44 -0
  119. package/dist/misc/input.js +2 -25
  120. package/dist/misc/label.js +2 -25
  121. package/dist/misc/line-carousel.js +3 -13
  122. package/dist/misc/multiImageUploader.d.ts +8 -8
  123. package/dist/misc/multiImageUploader.js +10 -11
  124. package/dist/misc/newsLetter.js +18 -78
  125. package/dist/misc/notification.d.ts +1 -2
  126. package/dist/misc/notification.d.ts.map +1 -1
  127. package/dist/misc/notification.js +89 -125
  128. package/dist/misc/pagination.js +16 -45
  129. package/dist/misc/password-eye.js +2 -3
  130. package/dist/misc/popover.d.ts.map +1 -1
  131. package/dist/misc/popover.js +8 -34
  132. package/dist/misc/progress.js +2 -25
  133. package/dist/misc/scroll-area.js +5 -29
  134. package/dist/misc/searchBar.js +27 -31
  135. package/dist/misc/searchBox.js +18 -22
  136. package/dist/misc/select.d.ts.map +1 -1
  137. package/dist/misc/select.js +22 -54
  138. package/dist/misc/separator.js +2 -25
  139. package/dist/misc/share.js +48 -104
  140. package/dist/misc/sheet.js +21 -53
  141. package/dist/misc/sidebar.d.ts +69 -69
  142. package/dist/misc/sidebar.js +112 -153
  143. package/dist/misc/skeleton.d.ts +2 -2
  144. package/dist/misc/skeleton.js +6 -29
  145. package/dist/misc/slider.js +3 -26
  146. package/dist/misc/starRating.js +5 -15
  147. package/dist/misc/stateCard.js +7 -8
  148. package/dist/misc/switch.d.ts +5 -0
  149. package/dist/misc/switch.d.ts.map +1 -0
  150. package/dist/misc/switch.js +8 -0
  151. package/dist/misc/tabs.js +8 -34
  152. package/dist/misc/textarea.js +2 -25
  153. package/dist/misc/tile.js +31 -92
  154. package/dist/misc/tooltip.js +8 -34
  155. package/dist/profile/socialLink.d.ts +1 -1
  156. package/dist/profile/socialLink.d.ts.map +1 -1
  157. package/dist/profile/socialLink.js +53 -69
  158. package/dist/utils/permissions.d.ts +7 -0
  159. package/dist/utils/permissions.d.ts.map +1 -0
  160. package/dist/utils/permissions.js +38 -0
  161. package/package.json +8 -5
@@ -0,0 +1,252 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useRef, useEffect } from "react";
4
+ import { Send, Smile, Image as ImageIcon, FileText, Camera, MapPin, Mic, X, CircleStopIcon, VideoIcon, PlusCircleIcon, ListTodo, ReceiptText, ChartBar } from "lucide-react";
5
+ import { Button } from "../misc/button";
6
+ import { Textarea } from "../misc/textarea";
7
+ import { Popover, PopoverContent, PopoverTrigger } from "../misc/popover";
8
+ import { Progress } from "../misc/progress";
9
+ import { CameraDialog } from "./dialogs/CameraDialog";
10
+ import { CreatePollDialog } from "./dialogs/CreatePollDialog";
11
+ import { showAlert } from "../misc/alertdialogclaude";
12
+ import { isCompressibleImage, compressImage } from "@myrjfa/state/dist/lib/utils/fileCompression";
13
+ import EmojiPicker from 'emoji-picker-react';
14
+ export function MessageInput({ onSend, onTypingStart, onTypingStop, disabled, replyTo, editMessage, onEdit, onCancelEdit, onCancelReply, onAddMilestone, onAddExpense, onCreatePoll, participants = [], // Added
15
+ activePanel = "chat" }) {
16
+ var _a, _b, _c;
17
+ const [text, setText] = useState(editMessage ? (_b = (_a = editMessage === null || editMessage === void 0 ? void 0 : editMessage.content) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : "" : "");
18
+ const [mentionSearch, setMentionSearch] = useState(null);
19
+ const [mentionIndex, setMentionIndex] = useState(-1);
20
+ const [selectedFiles, setSelectedFiles] = useState([]);
21
+ const [uploadProgress, setUploadProgress] = useState(null);
22
+ const [isSending, setIsSending] = useState(false);
23
+ const textareaRef = useRef(null);
24
+ const fileInputRef = useRef(null);
25
+ const [fileAcceptType, setFileAcceptType] = useState("");
26
+ const typingTimeoutRef = useRef(null);
27
+ const isTypingRef = useRef(false);
28
+ const [showEmojiPicker, setShowEmojiPicker] = useState(false);
29
+ const [isCameraOpen, setIsCameraOpen] = useState(false);
30
+ const [isPollOpen, setIsPollOpen] = useState(false);
31
+ const [isRecording, setIsRecording] = useState(false);
32
+ const mediaRecorderRef = useRef(null);
33
+ const audioChunksRef = useRef([]);
34
+ const [mentionCoords, setMentionCoords] = useState({ top: 0, left: 0 }); // Added
35
+ // Auto-resize textarea
36
+ useEffect(() => {
37
+ const el = textareaRef.current;
38
+ if (el) {
39
+ el.style.height = "auto";
40
+ el.style.height = Math.min(el.scrollHeight, 120) + "px";
41
+ }
42
+ }, [text]);
43
+ // Focus input when replyTo message is set
44
+ useEffect(() => {
45
+ var _a;
46
+ if (replyTo) {
47
+ (_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.focus();
48
+ }
49
+ }, [replyTo]);
50
+ const handleTextChange = (e) => {
51
+ const value = e.target.value;
52
+ const cursorPosition = e.target.selectionStart;
53
+ setText(value);
54
+ // Mention detection
55
+ const textBeforeCursor = value.substring(0, cursorPosition);
56
+ const lastAtChar = textBeforeCursor.lastIndexOf("@");
57
+ if (lastAtChar !== -1 && (lastAtChar === 0 || textBeforeCursor[lastAtChar - 1] === " ")) {
58
+ const search = textBeforeCursor.substring(lastAtChar + 1);
59
+ if (!search.includes(" ")) {
60
+ setMentionSearch(search);
61
+ setMentionIndex(lastAtChar);
62
+ }
63
+ else {
64
+ setMentionSearch(null);
65
+ }
66
+ }
67
+ else {
68
+ setMentionSearch(null);
69
+ }
70
+ // Typing indicator
71
+ if (!isTypingRef.current && e.target.value.length > 0) {
72
+ isTypingRef.current = true;
73
+ onTypingStart === null || onTypingStart === void 0 ? void 0 : onTypingStart();
74
+ }
75
+ // Reset typing timeout
76
+ if (typingTimeoutRef.current)
77
+ clearTimeout(typingTimeoutRef.current);
78
+ typingTimeoutRef.current = setTimeout(() => {
79
+ isTypingRef.current = false;
80
+ onTypingStop === null || onTypingStop === void 0 ? void 0 : onTypingStop();
81
+ }, 2000);
82
+ };
83
+ const insertMention = (username) => {
84
+ var _a;
85
+ if (mentionIndex === -1)
86
+ return;
87
+ const before = text.substring(0, mentionIndex);
88
+ const after = text.substring(((_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.selectionStart) || text.length);
89
+ const newText = `${before}@${username} ${after}`;
90
+ setText(newText);
91
+ setMentionSearch(null);
92
+ // Set cursor after the inserted mention
93
+ setTimeout(() => {
94
+ if (textareaRef.current) {
95
+ const newCursorPos = before.length + `@${username} `.length;
96
+ textareaRef.current.selectionStart = newCursorPos;
97
+ textareaRef.current.selectionEnd = newCursorPos;
98
+ textareaRef.current.focus();
99
+ }
100
+ }, 0);
101
+ };
102
+ async function handleSend() {
103
+ var _a;
104
+ const trimmed = text.trim();
105
+ if ((!trimmed && selectedFiles.length === 0) || disabled)
106
+ return;
107
+ setIsSending(true);
108
+ setUploadProgress(0);
109
+ try {
110
+ let filesToSend = [...selectedFiles];
111
+ // Compress images
112
+ setUploadProgress(10); // Start progress for compression
113
+ const compressed = await Promise.all(filesToSend.map(async (file) => {
114
+ if (isCompressibleImage(file)) {
115
+ try {
116
+ return await compressImage(file);
117
+ }
118
+ catch (e) {
119
+ console.error("Compression failed for", file.name, e);
120
+ return file;
121
+ }
122
+ }
123
+ return file;
124
+ }));
125
+ setUploadProgress(30); // Compression done, starting upload
126
+ // Note: sendMessage doesn't currently support progress callback.
127
+ // We'll simulate progress or update it if chatApi supports it.
128
+ // For now, call sendMessage.
129
+ if (editMessage) {
130
+ await (onEdit === null || onEdit === void 0 ? void 0 : onEdit(editMessage._id, trimmed));
131
+ }
132
+ else {
133
+ await onSend(trimmed, compressed);
134
+ }
135
+ setUploadProgress(100);
136
+ setText("");
137
+ setSelectedFiles([]);
138
+ // Clear typing
139
+ if (typingTimeoutRef.current)
140
+ clearTimeout(typingTimeoutRef.current);
141
+ isTypingRef.current = false;
142
+ onTypingStop === null || onTypingStop === void 0 ? void 0 : onTypingStop();
143
+ // Refocus
144
+ (_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.focus();
145
+ }
146
+ catch (error) {
147
+ console.error(error);
148
+ showAlert("Failed to send message", "Error");
149
+ }
150
+ finally {
151
+ setIsSending(false);
152
+ setUploadProgress(null);
153
+ }
154
+ }
155
+ function handleKeyDown(e) {
156
+ if (e.key === "Enter" && !e.shiftKey) {
157
+ e.preventDefault();
158
+ handleSend();
159
+ }
160
+ }
161
+ const handleFileChange = (e) => {
162
+ if (e.target.files) {
163
+ setSelectedFiles(prev => [...prev, ...Array.from(e.target.files)]);
164
+ }
165
+ };
166
+ const removeFile = (index) => {
167
+ setSelectedFiles(prev => prev.filter((_, i) => i !== index));
168
+ };
169
+ const handleLocation = () => {
170
+ if ("geolocation" in navigator) {
171
+ navigator.geolocation.getCurrentPosition((position) => {
172
+ const { latitude, longitude } = position.coords;
173
+ const mapLink = `https://www.google.com/maps?q=${latitude},${longitude}`;
174
+ onSend(mapLink);
175
+ }, (error) => {
176
+ console.error("Error getting location:", error);
177
+ showAlert("Could not get location.");
178
+ });
179
+ }
180
+ else {
181
+ showAlert("Geolocation is not supported by this browser.");
182
+ }
183
+ };
184
+ const handleCameraCapture = (file) => {
185
+ setSelectedFiles(prev => [...prev, file]);
186
+ };
187
+ const startRecording = async () => {
188
+ try {
189
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
190
+ const mediaRecorder = new MediaRecorder(stream);
191
+ mediaRecorderRef.current = mediaRecorder;
192
+ audioChunksRef.current = [];
193
+ mediaRecorder.ondataavailable = (event) => {
194
+ if (event.data.size > 0) {
195
+ audioChunksRef.current.push(event.data);
196
+ }
197
+ };
198
+ mediaRecorder.onstop = () => {
199
+ const audioBlob = new Blob(audioChunksRef.current, { type: "audio/webm" });
200
+ const audioFile = new File([audioBlob], `voice_${Date.now()}.webm`, { type: "audio/webm" });
201
+ setSelectedFiles(prev => [...prev, audioFile]);
202
+ stream.getTracks().forEach(track => track.stop());
203
+ };
204
+ mediaRecorder.start();
205
+ setIsRecording(true);
206
+ }
207
+ catch (err) {
208
+ console.error("Error accessing microphone:", err);
209
+ alert("Could not access microphone");
210
+ }
211
+ };
212
+ const stopRecording = () => {
213
+ if (mediaRecorderRef.current && isRecording) {
214
+ mediaRecorderRef.current.stop();
215
+ setIsRecording(false);
216
+ }
217
+ };
218
+ const handleEmojiSelect = (emojiObject) => {
219
+ if (textareaRef.current) {
220
+ const cursorPos = textareaRef.current.selectionStart;
221
+ const newText = text.slice(0, cursorPos) + emojiObject.emoji + text.slice(cursorPos);
222
+ setText(newText);
223
+ setShowEmojiPicker(false);
224
+ textareaRef.current.focus();
225
+ }
226
+ };
227
+ // Only show message input for chat panel
228
+ const showMessageInput = activePanel === "chat";
229
+ return (_jsxs("div", { className: "border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-3 relative", children: [_jsx(CameraDialog, { open: isCameraOpen, onOpenChange: setIsCameraOpen, onCapture: handleCameraCapture }), _jsx(CreatePollDialog, { open: isPollOpen, onOpenChange: setIsPollOpen, onCreate: (q, opts, multi) => {
230
+ onCreatePoll === null || onCreatePoll === void 0 ? void 0 : onCreatePoll(q, opts, multi);
231
+ } }), mentionSearch !== null && participants.length > 0 && (_jsxs("div", { className: "absolute bottom-full left-4 mb-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xl w-48 py-1 z-50 overflow-y-auto max-h-48", children: [participants
232
+ .filter(p => !mentionSearch || p.userId.toLowerCase().includes(mentionSearch.toLowerCase()))
233
+ .map(p => (_jsx("button", { onClick: () => insertMention(p.userId), className: "w-full text-left px-3 py-2 text-sm hover:bg-indigo-50 dark:hover:bg-indigo-900/40 flex items-center gap-2", children: _jsxs("span", { className: "truncate", children: ["@", p.userId] }) }, p.userId))), participants.filter(p => !mentionSearch || p.userId.toLowerCase().includes(mentionSearch.toLowerCase())).length === 0 && (_jsx("div", { className: "px-3 py-2 text-xs text-gray-500 italic", children: "No members found" }))] })), selectedFiles.length > 0 && !editMessage && (_jsx("div", { className: "max-w-4xl mx-auto mb-2 flex flex-wrap gap-2", children: selectedFiles.map((file, i) => (_jsxs("div", { className: "flex items-center bg-gray-100 dark:bg-gray-800 rounded-lg px-2 py-1 text-xs text-gray-600 dark:text-gray-300", children: [_jsx("span", { className: "truncate max-w-[150px]", children: file.name }), _jsx("button", { onClick: () => removeFile(i), className: "ml-2 text-red-500 hover:text-red-700 font-bold", children: "\u00D7" })] }, i))) })), replyTo && (_jsxs("div", { className: "max-w-4xl mx-auto mb-2 flex items-center gap-2 bg-gray-100 dark:bg-gray-800 px-3 py-2 rounded-lg border-l-2 border-teal-500", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-xs font-semibold text-teal-600 dark:text-teal-400", children: editMessage ? "Editing" : ("Replying to" + replyTo.senderId) }), _jsx("div", { className: "text-xs text-gray-600 dark:text-gray-400 truncate", children: (_c = replyTo.content.text) === null || _c === void 0 ? void 0 : _c.substring(0, 60) })] }), _jsx("button", { onClick: onCancelReply, className: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 flex-shrink-0", children: _jsx(X, { className: "w-4 h-4" }) })] })), _jsxs("div", { className: `${showMessageInput ? "flex" : "hidden md:flex"} items-end gap-2 max-w-4xl mx-auto`, children: [_jsx("input", { type: "file", ref: fileInputRef, onChange: handleFileChange, accept: fileAcceptType, multiple: true, className: "hidden" }), !editMessage && _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full h-10 w-10 shrink-0", children: _jsx(PlusCircleIcon, { className: "w-5 h-5" }) }) }), _jsxs(PopoverContent, { side: "top", align: "start", className: "w-48 p-2 flex flex-col gap-1", children: [_jsxs(Button, { variant: "ghost", className: "justify-start gap-2 w-full", onClick: async () => {
234
+ var _a;
235
+ await setFileAcceptType(".pdf,.doc,.docx,.txt,.xlsx,.pptx");
236
+ (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
237
+ }, children: [_jsx(FileText, { className: "w-4 h-4 text-purple-500" }), "Document"] }), _jsxs(Button, { variant: "ghost", className: "justify-start gap-2 w-full", onClick: async () => {
238
+ var _a;
239
+ await setFileAcceptType("image/*");
240
+ (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
241
+ }, children: [_jsx(ImageIcon, { className: "w-4 h-4 text-blue-500" }), "Photos"] }), _jsxs(Button, { variant: "ghost", className: "justify-start gap-2 w-full", onClick: async () => {
242
+ var _a;
243
+ await setFileAcceptType("video/*");
244
+ (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
245
+ }, children: [_jsx(VideoIcon, { className: "w-4 h-4 text-rose-500" }), "Videos"] }), _jsxs(Button, { variant: "ghost", className: "justify-start gap-2 w-full", onClick: handleLocation, children: [_jsx(MapPin, { className: "w-4 h-4 text-green-500" }), "Location"] }), _jsxs(Button, { variant: "ghost", className: "justify-start gap-2 w-full", onClick: () => setIsPollOpen(true), children: [_jsx(ChartBar, { className: "w-4 h-4 text-orange-500" }), "Poll"] }), _jsx("div", { className: "border-t border-gray-200 dark:border-gray-700 my-1" }), _jsxs(Button, { variant: "ghost", className: "justify-start gap-2 w-full text-teal-600 dark:text-teal-400", onClick: () => {
246
+ onAddMilestone === null || onAddMilestone === void 0 ? void 0 : onAddMilestone();
247
+ }, children: [_jsx(ListTodo, { className: "w-4 h-4" }), "Add Milestone"] }), _jsxs(Button, { variant: "ghost", className: "justify-start gap-2 w-full text-teal-600 dark:text-teal-400", onClick: () => {
248
+ onAddExpense === null || onAddExpense === void 0 ? void 0 : onAddExpense();
249
+ }, children: [_jsx(ReceiptText, { className: "w-4 h-4" }), "Add Expense"] })] })] }), _jsxs("div", { className: "flex-1 bg-gray-100 dark:bg-gray-800 rounded-2xl flex items-end relative", children: [_jsx(Textarea, { ref: textareaRef, value: text, onChange: handleTextChange, onKeyDown: handleKeyDown, placeholder: "Type a message...", disabled: disabled || isSending, rows: 1, className: "flex-1 bg-transparent border-none focus-visible:ring-0 px-4 py-3 text-[15px] resize-none text-gray-900 dark:text-white placeholder-gray-400 max-h-[120px] min-h-[48px]" }), uploadProgress !== null && (_jsx("div", { className: "absolute left-0 bottom-full w-full px-4 mb-2", children: _jsxs("div", { className: "bg-white dark:bg-gray-800 p-2 rounded-lg shadow-lg border border-gray-100 dark:border-gray-700", children: [_jsxs("div", { className: "flex justify-between items-center mb-1", children: [_jsx("span", { className: "text-[10px] text-gray-500 font-medium", children: "Uploading..." }), _jsxs("span", { className: "text-[10px] text-indigo-500 font-bold", children: [uploadProgress, "%"] })] }), _jsx(Progress, { value: uploadProgress, className: "h-1" })] }) })), _jsx(Button, { variant: "ghost", size: "icon", className: "mr-1 mb-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hidden sm:inline-flex", onClick: () => setShowEmojiPicker(!showEmojiPicker), children: _jsx(Smile, { className: "w-5 h-5" }) }), !editMessage && _jsx(Button, { variant: "ghost", size: "icon", className: "mr-1 mb-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full", onClick: () => setIsCameraOpen(true), children: _jsx(Camera, { className: "w-5 h-5" }) })] }), text.trim() || selectedFiles.length > 0 || editMessage ? (_jsx(Button, { onClick: handleSend, disabled: disabled, className: "rounded-full h-11 w-11 p-0 bg-gradient-to-r from-teal-500 to-cyan-600 hover:from-teal-600 hover:to-cyan-700 shadow-lg shadow-teal-500/25 shrink-0", children: _jsx(Send, { className: "w-5 h-5" }) })) : (_jsx(Button, { variant: "ghost", size: "icon", className: `rounded-full h-11 w-11 shrink-0 transition-all ${isRecording
250
+ ? "bg-red-500 hover:bg-red-600 animate-pulse text-white"
251
+ : "text-teal-400 hover:bg-teal-100 dark:hover:bg-teal-800"}`, onClick: () => isRecording ? stopRecording() : startRecording(), children: isRecording ? _jsx(CircleStopIcon, { className: "w-6 h-6" }) : _jsx(Mic, { className: "w-6 h-6" }) })), editMessage && _jsx(Button, { variant: "ghost", size: "icon", className: "rounded-full h-11 w-11 shrink-0", onClick: onCancelEdit, children: _jsx(X, { className: "w-5 h-5" }) }), showEmojiPicker && _jsx(EmojiPicker, { onEmojiClick: handleEmojiSelect })] })] }));
252
+ }
@@ -0,0 +1,11 @@
1
+ interface MilestonePanelProps {
2
+ conversationId: string;
3
+ currentUserId: string;
4
+ isFullScreen?: boolean;
5
+ isFeatureEnabled?: boolean;
6
+ isAdmin?: boolean;
7
+ onEnableFeature?: () => void;
8
+ }
9
+ export declare function MilestonePanel({ conversationId, currentUserId, isFullScreen, isFeatureEnabled, isAdmin, onEnableFeature }: MilestonePanelProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=MilestonePanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MilestonePanel.d.ts","sourceRoot":"","sources":["../../src/chat/MilestonePanel.tsx"],"names":[],"mappings":"AAYA,UAAU,mBAAmB;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,wBAAgB,cAAc,CAAC,EAC3B,cAAc,EACd,aAAa,EACb,YAAoB,EACpB,gBAAuB,EACvB,OAAe,EACf,eAAe,EAClB,EAAE,mBAAmB,2CA6brB"}
@@ -0,0 +1,173 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect } from "react";
4
+ import { chatApi } from "@myrjfa/state/dist/lib/actions/chat";
5
+ import { CheckCircle2, Circle, Plus, Trash2, Pencil, MoreVertical, Lock, Unlock, ListTodo, ChevronDown, AlertCircle, ChevronDownIcon, ChevronRightIcon } from "lucide-react";
6
+ import { Button } from "../misc/button";
7
+ import { Input } from "../misc/input";
8
+ import { Textarea } from "../misc/textarea";
9
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../misc/dropdown-menu";
10
+ import { deleteActions, showAlert } from "../misc/alertdialogclaude";
11
+ export function MilestonePanel({ conversationId, currentUserId, isFullScreen = false, isFeatureEnabled = true, isAdmin = false, onEnableFeature }) {
12
+ const [milestones, setMilestones] = useState([]);
13
+ const [loading, setLoading] = useState(true);
14
+ const [isAdding, setIsAdding] = useState(false);
15
+ const [milestoneId, setMilestoneId] = useState(null);
16
+ const [newTitle, setNewTitle] = useState("");
17
+ const [newSection, setNewSection] = useState("");
18
+ const [newDescription, setNewDescription] = useState("");
19
+ const [newDueDate, setNewDueDate] = useState("");
20
+ const [showMoreOptions, setShowMoreOptions] = useState(false);
21
+ const [sections, setSections] = useState([]);
22
+ const [expandedSections, setExpandedSections] = useState(new Set());
23
+ const [expandedMilestones, setExpandedMilestones] = useState(new Set());
24
+ const [allParticipants, setAllParticipants] = useState([]);
25
+ const fetchMilestones = async () => {
26
+ var _a;
27
+ setLoading(true);
28
+ const data = await chatApi.getMilestones(conversationId);
29
+ const convData = await chatApi.getConversation(conversationId);
30
+ setMilestones(data);
31
+ const participants = ((_a = convData === null || convData === void 0 ? void 0 : convData.participants) === null || _a === void 0 ? void 0 : _a.map((p) => p.userId)) || [];
32
+ setAllParticipants(participants);
33
+ setLoading(false);
34
+ };
35
+ useEffect(() => {
36
+ fetchMilestones();
37
+ }, [conversationId]);
38
+ useEffect(() => {
39
+ // Extract unique sections from existing milestones
40
+ const uniqueSections = Array.from(new Set(milestones.map(m => m.section).filter(Boolean)));
41
+ setSections(uniqueSections);
42
+ }, [milestones]);
43
+ const resetForm = () => {
44
+ setNewTitle("");
45
+ setNewSection("");
46
+ setNewDescription("");
47
+ setNewDueDate("");
48
+ setShowMoreOptions(false);
49
+ setIsAdding(false);
50
+ setShowMoreOptions(false);
51
+ setIsAdding(false);
52
+ setMilestoneId(null);
53
+ };
54
+ const toggleSection = (section) => {
55
+ setExpandedSections(prev => {
56
+ const newSet = new Set(prev);
57
+ if (newSet.has(section)) {
58
+ newSet.delete(section);
59
+ }
60
+ else {
61
+ newSet.add(section);
62
+ }
63
+ return newSet;
64
+ });
65
+ };
66
+ const toggleMilestoneExpand = (id) => {
67
+ setExpandedMilestones(prev => {
68
+ const newSet = new Set(prev);
69
+ if (newSet.has(id)) {
70
+ newSet.delete(id);
71
+ }
72
+ else {
73
+ newSet.add(id);
74
+ }
75
+ return newSet;
76
+ });
77
+ };
78
+ const handleCreate = async () => {
79
+ if (!newTitle.trim())
80
+ return;
81
+ try {
82
+ const milestoneData = {
83
+ title: newTitle.trim(),
84
+ section: newSection.trim() || "General",
85
+ description: newDescription.trim() || undefined,
86
+ dueDate: newDueDate || undefined
87
+ };
88
+ const milestoneResponse = milestoneId
89
+ ? await chatApi.updateMilestone(milestoneId, milestoneData)
90
+ : await chatApi.createMilestone(conversationId, milestoneData);
91
+ if (milestoneResponse.error) {
92
+ console.error("Failed to create milestone:", milestoneResponse.error);
93
+ showAlert(milestoneResponse.error, "Failed to create milestone");
94
+ }
95
+ else if (milestoneResponse.data) {
96
+ await fetchMilestones();
97
+ resetForm();
98
+ }
99
+ }
100
+ catch (err) {
101
+ console.error("Failed to create milestone:", err);
102
+ showAlert(err, "Failed to create milestone");
103
+ }
104
+ };
105
+ const handleToggle = async (id) => {
106
+ const response = await chatApi.toggleMilestone(id);
107
+ if (response.error) {
108
+ console.log("error toggling milestone " + id + "Error: " + response.error);
109
+ await showAlert("Error checking/unchecking milestone");
110
+ return;
111
+ }
112
+ else if (response.data) {
113
+ setMilestones(prev => prev.map(m => m._id === id ? response.data : m));
114
+ }
115
+ };
116
+ const handleDelete = async (id) => {
117
+ let confirmDelete = true;
118
+ await showAlert("Are you sure you want to delete this milestone?", undefined, deleteActions(() => confirmDelete = false));
119
+ if (confirmDelete) {
120
+ const response = await chatApi.deleteMilestone(id);
121
+ if (response.error) {
122
+ console.log("Unable to delete milestone:" + id + "Error: " + response.error);
123
+ await showAlert("Unable to delete milestone");
124
+ return;
125
+ }
126
+ setMilestones(prev => prev.filter(m => m._id !== id));
127
+ }
128
+ };
129
+ const handleEdit = async (milestone) => {
130
+ var _a, _b, _c;
131
+ setNewTitle(milestone.title);
132
+ setNewSection((_a = milestone.section) !== null && _a !== void 0 ? _a : "");
133
+ setNewDescription((_b = milestone.description) !== null && _b !== void 0 ? _b : "");
134
+ setNewDueDate((_c = milestone.dueDate) !== null && _c !== void 0 ? _c : "");
135
+ setMilestoneId(milestone._id);
136
+ setIsAdding(true);
137
+ };
138
+ // Check if current user has checked this milestone
139
+ const hasUserChecked = (milestone) => {
140
+ return milestone.checks.some(check => check.userId === currentUserId);
141
+ };
142
+ // Get users who haven't checked the milestone
143
+ const getUncheckedUsers = (milestone) => {
144
+ const checkedUserIds = new Set(milestone.checks.map(check => check.userId));
145
+ return allParticipants.filter(userId => !checkedUserIds.has(userId));
146
+ };
147
+ const groupedMilestones = milestones.reduce((acc, m) => {
148
+ const section = m.section || "General";
149
+ if (!acc[section])
150
+ acc[section] = [];
151
+ acc[section].push(m);
152
+ return acc;
153
+ }, {});
154
+ const panelClasses = isFullScreen
155
+ ? "w-full h-full flex flex-col"
156
+ : "flex flex-col h-full w-full md:w-80 border-l border-gray-200 dark:border-gray-700";
157
+ return (_jsxs("div", { className: `bg-white dark:bg-gray-900 ${panelClasses}`, children: [_jsx("div", { className: "p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0", children: _jsx("h3", { className: "font-semibold text-gray-900 dark:text-white", children: "Milestones" }) }), _jsx("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: !isFeatureEnabled ? (_jsxs("div", { className: "flex flex-col items-center justify-center h-full py-12 px-6 text-center", children: [_jsx("div", { className: "mb-4 p-4 bg-teal-50 dark:bg-teal-900/20 rounded-full", children: _jsx(ListTodo, { className: "w-12 h-12 text-teal-500" }) }), _jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white mb-2", children: "Milestones Feature" }), _jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-4 max-w-sm", children: "Track group tasks, organize by sections, and monitor progress together. Perfect for trip planning and group coordination." }), isAdmin ? (_jsxs(Button, { onClick: onEnableFeature, className: "bg-gradient-to-r from-teal-500 to-cyan-600 hover:from-teal-600 hover:to-cyan-700 text-white", children: [_jsx(Unlock, { className: "w-4 h-4 mr-2" }), "Enable Feature"] })) : (_jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [_jsx(Lock, { className: "w-4 h-4" }), "Ask admin to enable this feature"] }))] })) : loading ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx("div", { className: "animate-spin w-5 h-5 border-2 border-teal-500 border-t-transparent rounded-full" }) })) : milestones.length === 0 && !isAdding ? (_jsx("div", { className: "text-center py-8 text-gray-400 text-sm", children: "No milestones yet" })) : (_jsxs(_Fragment, { children: [isAdding && (_jsxs("div", { className: "p-4 bg-gray-50 dark:bg-gray-800 rounded-lg space-y-3", children: [_jsx(Input, { placeholder: "Section (e.g., Day 1, Transport)", value: newSection, onChange: (e) => setNewSection(e.target.value), list: "section-suggestions", className: "text-sm" }), _jsx("datalist", { id: "section-suggestions", children: sections.map(section => (_jsx("option", { value: section }, section))) }), _jsx(Input, { placeholder: "Milestone title *", value: newTitle, onChange: (e) => setNewTitle(e.target.value), onKeyDown: (e) => e.key === "Enter" && !showMoreOptions && handleCreate(), className: "text-sm font-medium", autoFocus: true }), _jsxs("button", { type: "button", onClick: () => setShowMoreOptions(!showMoreOptions), className: "text-xs text-gray-600 dark:text-gray-400 hover:text-teal-600 dark:hover:text-teal-400 flex items-center gap-1 cursor-pointer", children: [showMoreOptions ? _jsx(ChevronDownIcon, { className: "w-3 h-3" }) : _jsx(ChevronRightIcon, { className: "w-3 h-3" }), " More Options"] }), showMoreOptions && (_jsxs("div", { className: "space-y-2 pl-4 border-l-2 border-teal-200 dark:border-teal-800", children: [_jsx(Textarea, { placeholder: "Description (optional)", value: newDescription, onChange: (e) => setNewDescription(e.target.value), className: "text-sm resize-none", rows: 2 }), _jsxs("div", { children: [_jsx("label", { className: "text-xs text-gray-600 dark:text-gray-400 block mb-1", children: "Due Date (optional)" }), _jsx(Input, { type: "date", value: newDueDate, onChange: (e) => setNewDueDate(e.target.value), className: "text-sm" })] })] })), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleCreate, size: "sm", className: "flex-1 bg-gradient-to-r from-teal-500 to-cyan-600 hover:from-teal-600 hover:to-cyan-700", children: "Add" }), _jsx(Button, { onClick: resetForm, size: "sm", variant: "outline", children: "Cancel" })] })] })), milestones.length > 0 && (Object.entries(groupedMilestones).map(([section, items]) => {
158
+ const isExpanded = expandedSections.has(section);
159
+ const completedCount = items.filter(m => m.isCompleted).length;
160
+ const totalCount = items.length;
161
+ return (_jsxs("div", { className: "space-y-2", children: [_jsxs("button", { onClick: () => toggleSection(section), className: "w-full flex items-center justify-between p-3 bg-gradient-to-r from-teal-50 to-cyan-50 dark:from-teal-900/20 dark:to-cyan-900/20 hover:from-teal-100 hover:to-cyan-100 dark:hover:from-teal-900/30 dark:hover:to-cyan-900/30 rounded-lg border border-teal-100 dark:border-teal-800 transition-colors", children: [_jsxs("div", { className: "flex items-center gap-3 flex-1 text-left", children: [_jsx(ChevronDown, { className: `w-4 h-4 text-teal-600 dark:text-teal-400 transition-transform flex-shrink-0 ${isExpanded ? "" : "-rotate-90"}` }), _jsx("h4", { className: "text-sm font-semibold text-teal-900 dark:text-teal-100 uppercase tracking-widest", children: section })] }), _jsxs("span", { className: "text-xs font-semibold text-teal-700 dark:text-teal-300 bg-white dark:bg-gray-800 px-2.5 py-1 rounded-full flex-shrink-0", children: [completedCount, "/", totalCount] })] }), isExpanded && (_jsx("div", { className: "space-y-2 pl-4", children: items.map((milestone) => {
162
+ const userChecked = hasUserChecked(milestone);
163
+ const uncheckedUsers = getUncheckedUsers(milestone);
164
+ const isExpanded = expandedMilestones.has(milestone._id);
165
+ return (_jsxs("div", { className: "space-y-0", children: [_jsxs("button", { onClick: () => toggleMilestoneExpand(milestone._id), className: "w-full flex items-start gap-3 p-3 bg-gray-50 dark:bg-gray-800/30 hover:bg-gray-100 dark:hover:bg-gray-800/50 rounded-lg border border-gray-100 dark:border-gray-800 transition-colors group text-left", children: [_jsx("button", { onClick: (e) => {
166
+ e.stopPropagation();
167
+ handleToggle(milestone._id);
168
+ }, className: `mt-0.5 transition-colors flex-shrink-0 ${userChecked ? "text-green-500" : "text-gray-300 hover:text-teal-400"}`, children: userChecked ? _jsx(CheckCircle2, { className: "w-5 h-5" }) : _jsx(Circle, { className: "w-5 h-5" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: `text-sm font-medium transition-all ${userChecked ? "text-gray-400 line-through" : "text-gray-700 dark:text-gray-200"}`, children: milestone.title }), milestone.dueDate && (_jsxs("p", { className: "text-[10px] text-gray-500 dark:text-gray-400 mt-1", children: ["Due: ", new Date(milestone.dueDate).toLocaleDateString()] }))] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsxs("span", { className: `text-xs font-semibold px-2 py-1 rounded-full ${milestone.isCompleted
169
+ ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
170
+ : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300"}`, children: [milestone.checks.length, "/", allParticipants.length] }), _jsx(ChevronDown, { className: `w-4 h-4 text-gray-400 transition-transform flex-shrink-0 ${isExpanded ? "" : "-rotate-90"}` })] }), (milestone.createdBy === currentUserId || isAdmin) && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { onClick: (e) => e.stopPropagation(), className: "p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-all flex-shrink-0", children: _jsx(MoreVertical, { className: "w-4 h-4 text-gray-400" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => handleEdit(milestone), children: [_jsx(Pencil, { className: "w-4 h-4 mr-2" }), "Edit"] }), _jsxs(DropdownMenuItem, { className: "text-red-500", onClick: () => handleDelete(milestone._id), children: [_jsx(Trash2, { className: "w-4 h-4 mr-2" }), "Delete"] })] })] }))] }), isExpanded && (_jsxs("div", { className: "ml-8 bg-white dark:bg-gray-700/30 p-3 rounded-lg border border-gray-200 dark:border-gray-700 space-y-2", children: [milestone.description && (_jsxs("div", { children: [_jsx("p", { className: "text-[10px] font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-1", children: "Description" }), _jsx("p", { className: "text-xs text-gray-700 dark:text-gray-300", children: milestone.description })] })), uncheckedUsers.length > 0 && (_jsxs("div", { children: [_jsxs("p", { className: "text-[10px] font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-1", children: ["Pending (", uncheckedUsers.length, ")"] }), _jsx("div", { className: "space-y-1", children: uncheckedUsers.map((userId) => (_jsxs("div", { className: "flex items-center gap-2 p-1.5 bg-yellow-50 dark:bg-yellow-900/20 rounded border border-yellow-100 dark:border-yellow-800/30", children: [_jsx(AlertCircle, { className: "w-3 h-3 text-yellow-600 dark:text-yellow-500 flex-shrink-0" }), _jsx("span", { className: "text-xs text-yellow-700 dark:text-yellow-300", children: userId })] }, userId))) })] })), milestone.isCompleted && (_jsxs("div", { className: "flex items-center gap-2 p-2 bg-green-50 dark:bg-green-900/20 rounded border border-green-200 dark:border-green-800/30", children: [_jsx(CheckCircle2, { className: "w-4 h-4 text-green-600 dark:text-green-400 flex-shrink-0" }), _jsx("p", { className: "text-xs text-green-700 dark:text-green-400 font-medium", children: "All participants have checked this milestone" })] }))] }))] }, milestone._id));
171
+ }) }))] }, section));
172
+ }))] })) }), isFeatureEnabled && !isAdding && (_jsx("div", { className: "p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0", children: _jsxs(Button, { onClick: () => setIsAdding(true), className: "w-full bg-gradient-to-r from-teal-500 to-cyan-600 hover:from-teal-600 hover:to-cyan-700 text-white rounded-lg flex items-center justify-center gap-2", children: [_jsx(Plus, { className: "w-4 h-4" }), "Add Milestone"] }) }))] }));
173
+ }
@@ -0,0 +1,11 @@
1
+ import { ExpenseSplit } from "@myrjfa/state";
2
+ import { Dispatch, SetStateAction } from "react";
3
+ interface ParticipantSelectorProps {
4
+ participants: string[];
5
+ onChange: (participants: string[]) => void;
6
+ onSplitsChange: Dispatch<SetStateAction<ExpenseSplit[]>>;
7
+ availableUsers: string[];
8
+ }
9
+ export declare function ParticipantSelector({ participants, onChange, onSplitsChange, availableUsers }: ParticipantSelectorProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=ParticipantSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ParticipantSelector.d.ts","sourceRoot":"","sources":["../../../src/chat/components/ParticipantSelector.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAEjD,UAAU,wBAAwB;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IACzD,cAAc,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,CAAC,EAChC,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,cAAc,EACjB,EAAE,wBAAwB,2CA2D1B"}
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { X } from "lucide-react";
4
+ export function ParticipantSelector({ participants, onChange, onSplitsChange, availableUsers }) {
5
+ const addParticipant = (userId) => {
6
+ if (!participants.includes(userId)) {
7
+ onChange([...participants, userId]);
8
+ onSplitsChange(prev => [...prev, { userId }]);
9
+ }
10
+ };
11
+ const removeParticipant = (userId) => {
12
+ onChange(participants.filter(p => p !== userId));
13
+ onSplitsChange(prev => prev.filter(s => s.userId !== userId));
14
+ };
15
+ const unselectedUsers = availableUsers.filter(u => !participants.includes(u));
16
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider", children: "Split Among" }), _jsx("div", { className: "flex flex-wrap gap-2", children: participants.length === 0 ? (_jsx("p", { className: "text-xs text-gray-500", children: "Select participants..." })) : (participants.map(userId => (_jsxs("div", { className: "flex items-center gap-1 bg-teal-100 dark:bg-teal-900/30 text-teal-700 dark:text-teal-300 rounded-full px-3 py-1 text-xs", children: [userId, _jsx("button", { onClick: () => removeParticipant(userId), className: "hover:text-teal-900 dark:hover:text-teal-100 transition-colors", children: _jsx(X, { className: "w-3 h-3" }) })] }, userId)))) }), unselectedUsers.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1", children: unselectedUsers.map(userId => (_jsxs("button", { onClick: () => addParticipant(userId), className: "text-xs px-2 py-1 rounded-full border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors", children: ["+ ", userId] }, userId))) }))] }));
17
+ }
@@ -0,0 +1,11 @@
1
+ import type { SplitType, ExpenseSplit } from "@myrjfa/state/dist/lib/models/chat";
2
+ interface SplitInputsProps {
3
+ splitType: SplitType;
4
+ totalAmount: number;
5
+ participants: string[];
6
+ splits: ExpenseSplit[];
7
+ onChange: (splits: ExpenseSplit[]) => void;
8
+ }
9
+ export declare function SplitInputs({ splitType, totalAmount, participants, splits, onChange }: SplitInputsProps): import("react/jsx-runtime").JSX.Element | null;
10
+ export {};
11
+ //# sourceMappingURL=SplitInputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SplitInputs.d.ts","sourceRoot":"","sources":["../../../src/chat/components/SplitInputs.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAGlF,UAAU,gBAAgB;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;CAC9C;AAED,wBAAgB,WAAW,CAAC,EACxB,SAAS,EACT,WAAW,EACX,YAAY,EACZ,MAAM,EACN,QAAQ,EACX,EAAE,gBAAgB,kDA8KlB"}
@@ -0,0 +1,83 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect } from "react";
4
+ import { ChevronDown } from "lucide-react";
5
+ export function SplitInputs({ splitType, totalAmount, participants, splits, onChange }) {
6
+ const [isExpanded, setIsExpanded] = useState(splitType !== "equal");
7
+ useEffect(() => {
8
+ setIsExpanded(splitType !== "equal");
9
+ }, [splitType]);
10
+ if (splitType === "equal") {
11
+ return null;
12
+ }
13
+ const handleInputChange = (userId, value, field) => {
14
+ const numValue = parseFloat(value) || 0;
15
+ const updatedSplits = splits.map(s => {
16
+ if (s.userId === userId) {
17
+ return { ...s, [field]: numValue, amount: field === "amount" ? numValue : s.amount };
18
+ }
19
+ return s;
20
+ });
21
+ // Auto-calculate amounts for percentage and shares
22
+ if (field === "percentage") {
23
+ updatedSplits.forEach(s => {
24
+ if (s.userId === userId) {
25
+ s.amount = (numValue / 100) * totalAmount;
26
+ }
27
+ });
28
+ }
29
+ else if (field === "shares") {
30
+ const totalShares = updatedSplits.reduce((sum, s) => sum + (s.shares || 0), 0);
31
+ updatedSplits.forEach(s => {
32
+ s.amount = (s.shares && s.shares > 0) ? (s.shares / totalShares) * totalAmount : 0;
33
+ });
34
+ }
35
+ onChange(updatedSplits);
36
+ };
37
+ const getInputLabel = () => {
38
+ switch (splitType) {
39
+ case "exact":
40
+ return "Amount";
41
+ case "percentage":
42
+ return "Percentage";
43
+ case "shares":
44
+ return "Shares";
45
+ default:
46
+ return "Amount";
47
+ }
48
+ };
49
+ const validateSplits = () => {
50
+ if (splitType === "percentage") {
51
+ const total = splits.reduce((sum, s) => sum + (s.percentage || 0), 0);
52
+ return total === 100;
53
+ }
54
+ else if (splitType === "exact") {
55
+ const total = splits.reduce((sum, s) => sum + (s.amount || 0), 0);
56
+ return Math.abs(total - totalAmount) < 0.01;
57
+ }
58
+ return true;
59
+ };
60
+ const isValid = getInputLabel();
61
+ return (_jsxs("div", { className: "space-y-2", children: [_jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "w-full flex items-center justify-between p-2 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors", children: [_jsx("span", { className: "text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider", children: "Split Details" }), _jsx(ChevronDown, { className: `w-4 h-4 text-gray-400 transition-transform ${isExpanded ? "rotate-180" : ""}` })] }), isExpanded && (_jsx("div", { className: "bg-gray-50 dark:bg-gray-800/30 p-3 rounded-lg border border-gray-200 dark:border-gray-700 space-y-2", children: participants.length === 0 ? (_jsx("p", { className: "text-xs text-gray-500 text-center py-2", children: "Add participants to split expense" })) : (_jsxs(_Fragment, { children: [splits.map((split, idx) => {
62
+ var _a;
63
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "text-xs text-gray-600 dark:text-gray-400 flex-1 truncate font-medium", children: split.userId }), _jsxs("div", { className: "flex items-center gap-1 flex-1", children: [_jsx("input", { type: "number", step: "1", value: splitType === "exact"
64
+ ? split.amount || ""
65
+ : splitType === "percentage"
66
+ ? split.percentage || ""
67
+ : split.shares || "", onChange: (e) => handleInputChange(split.userId, e.target.value, splitType === "exact"
68
+ ? "amount"
69
+ : splitType === "percentage"
70
+ ? "percentage"
71
+ : "shares"), placeholder: "0", className: "w-20 bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded p-1 text-xs text-gray-900 dark:text-white outline-none focus:border-teal-500" }), _jsx("span", { className: "text-[10px] text-gray-500 min-w-fit", children: splitType === "percentage"
72
+ ? "%"
73
+ : splitType === "shares"
74
+ ? "shares"
75
+ : `₹${((_a = split.amount) === null || _a === void 0 ? void 0 : _a.toFixed(2)) || "0.00"}` })] })] }, split.userId));
76
+ }), splitType === "percentage" && (_jsx("div", { className: "text-[10px] text-gray-600 dark:text-gray-400 pt-2 border-t border-gray-200 dark:border-gray-600", children: _jsxs("p", { children: ["Total:", " ", _jsxs("span", { className: Math.abs(splits.reduce((sum, s) => sum + (s.percentage || 0), 0) -
77
+ 100) < 0.01
78
+ ? "text-green-600"
79
+ : "text-red-500", children: [splits.reduce((sum, s) => sum + (s.percentage || 0), 0).toFixed(1), "%"] })] }) })), splitType === "exact" && (_jsx("div", { className: "text-[10px] text-gray-600 dark:text-gray-400 pt-2 border-t border-gray-200 dark:border-gray-600", children: _jsxs("p", { children: ["Total:", " ", _jsxs("span", { className: Math.abs(splits.reduce((sum, s) => sum + (s.amount || 0), 0) -
80
+ totalAmount) < 0.01
81
+ ? "text-green-600"
82
+ : "text-red-500", children: ["\u20B9", splits.reduce((sum, s) => sum + (s.amount || 0), 0).toFixed(2)] })] }) }))] })) }))] }));
83
+ }
@@ -0,0 +1,8 @@
1
+ import type { SplitType } from "@myrjfa/state/dist/lib/models/chat";
2
+ interface SplitTypeSelectorProps {
3
+ value: SplitType;
4
+ onChange: (type: SplitType) => void;
5
+ }
6
+ export declare function SplitTypeSelector({ value, onChange }: SplitTypeSelectorProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=SplitTypeSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SplitTypeSelector.d.ts","sourceRoot":"","sources":["../../../src/chat/components/SplitTypeSelector.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAGpE,UAAU,sBAAsB;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC;AASD,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,sBAAsB,2CA4B5E"}
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ const SPLIT_TYPES = [
4
+ { value: "equal", label: "Equal", description: "Split evenly among all" },
5
+ { value: "exact", label: "Exact", description: "Specify exact amounts" },
6
+ { value: "percentage", label: "Percentage", description: "Split by percentages" },
7
+ { value: "shares", label: "Shares", description: "Split by number of shares" }
8
+ ];
9
+ export function SplitTypeSelector({ value, onChange }) {
10
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider", children: "Split Type" }), _jsx("div", { className: "grid grid-cols-2 gap-2", children: SPLIT_TYPES.map((type) => (_jsxs("button", { onClick: () => onChange(type.value), className: `p-2 rounded-lg border-2 transition-all text-left ${value === type.value
11
+ ? "border-teal-500 bg-teal-50 dark:bg-teal-900/20"
12
+ : "border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600"}`, children: [_jsx("p", { className: "text-sm font-semibold text-gray-900 dark:text-white", children: type.label }), _jsx("p", { className: "text-[10px] text-gray-500 dark:text-gray-400", children: type.description })] }, type.value))) })] }));
13
+ }