source: frontend/src/components/DiaryModal.tsx@ 700e2f9

main
Last change on this file since 700e2f9 was 700e2f9, checked in by 186079 <matej.milevski@…>, 5 days ago

Init

  • Property mode set to 100644
File size: 7.1 KB
RevLine 
[700e2f9]1import { type Component, Show, createSignal, createEffect } from "solid-js";
2import { formatDateWithLongWeekday } from "@/utils";
3import type { DiaryEntry } from "@/api/diary";
4
5interface DiaryModalProps {
6 isOpen: boolean;
7 onClose: () => void;
8 entry?: DiaryEntry;
9 date: Date;
10 onSave: (rating: number, content: string) => Promise<void>;
11 onDelete?: () => Promise<void>;
12 isToday: boolean;
13}
14
15const DiaryModal: Component<DiaryModalProps> = (props) => {
16 const [rating, setRating] = createSignal(props.entry?.dailyRating || 5);
17 const [content, setContent] = createSignal(props.entry?.content || "");
18 const [isSaving, setIsSaving] = createSignal(false);
19 const [isDeleting, setIsDeleting] = createSignal(false);
20 const [error, setError] = createSignal("");
21
22 createEffect(() => {
23 if (props.isOpen) {
24 setRating(props.entry?.dailyRating || 5);
25 setContent(props.entry?.content || "");
26 setError("");
27 }
28 });
29
30 const isEditMode = () => !!props.entry;
31
32 const handleSave = async () => {
33 if (!content().trim()) {
34 setError("Please enter some content");
35 return;
36 }
37
38 setIsSaving(true);
39 setError("");
40
41 try {
42 await props.onSave(rating(), content());
43 props.onClose();
44 } catch (err: any) {
45 setError(err.message || "Failed to save diary entry");
46 } finally {
47 setIsSaving(false);
48 }
49 };
50
51 const handleDelete = async () => {
52 if (!confirm("Are you sure you want to delete this diary entry?")) {
53 return;
54 }
55
56 setIsDeleting(true);
57 setError("");
58
59 try {
60 if (props.onDelete) {
61 await props.onDelete();
62 props.onClose();
63 }
64 } catch (err: any) {
65 setError(err.message || "Failed to delete diary entry");
66 } finally {
67 setIsDeleting(false);
68 }
69 };
70
71 return (
72 <Show when={props.isOpen}>
73 <div
74 class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
75 onClick={props.onClose}
76 >
77 <div
78 class="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
79 onClick={(e) => e.stopPropagation()}
80 >
81 <div class="p-6 border-b border-gray-200">
82 <div class="flex justify-between items-start">
83 <div>
84 <h2 class="text-2xl font-bold text-gray-900">
85 {isEditMode() ? "Edit" : "Create"} Diary Entry
86 </h2>
87 <p class="text-sm text-gray-600 mt-1">
88 {formatDateWithLongWeekday(props.date)}
89 </p>
90 </div>
91 <button
92 onClick={props.onClose}
93 class="text-gray-400 hover:text-gray-600 transition-colors"
94 >
95 <svg
96 class="w-6 h-6"
97 fill="none"
98 stroke="currentColor"
99 viewBox="0 0 24 24"
100 >
101 <path
102 stroke-linecap="round"
103 stroke-linejoin="round"
104 stroke-width="2"
105 d="M6 18L18 6M6 6l12 12"
106 />
107 </svg>
108 </button>
109 </div>
110 </div>
111
112 <div class="p-6 space-y-6">
113 <div>
114 <label class="block text-sm font-semibold text-gray-700 mb-2">
115 How was your day? (1-10)
116 </label>
117 <Show
118 when={!isEditMode() || props.isToday}
119 fallback={
120 <div class="flex items-center gap-3">
121 <div class="text-4xl font-bold text-blue-600">
122 {rating()}
123 </div>
124 <span class="text-sm text-gray-500">
125 (Rating cannot be changed for past entries)
126 </span>
127 </div>
128 }
129 >
130 <div class="space-y-2">
131 <input
132 type="range"
133 min="1"
134 max="10"
135 value={rating()}
136 onInput={(e) =>
137 setRating(Number.parseInt(e.currentTarget.value))
138 }
139 class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
140 />
141 <div class="flex justify-between text-xs text-gray-600">
142 <span>1 (Very Bad)</span>
143 <span class="text-xl font-bold text-blue-600">
144 {rating()}
145 </span>
146 <span>10 (Excellent)</span>
147 </div>
148 </div>
149 </Show>
150 </div>
151
152 <div>
153 <label class="block text-sm font-semibold text-gray-700 mb-2">
154 Your thoughts and feelings
155 </label>
156 <textarea
157 value={content()}
158 onInput={(e) => setContent(e.currentTarget.value)}
159 rows={8}
160 class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
161 placeholder="What happened today? How did it make you feel?"
162 />
163 </div>
164
165 <Show when={error()}>
166 <div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
167 {error()}
168 </div>
169 </Show>
170 </div>
171
172 <div class="p-6 border-t border-gray-200 flex justify-between items-center">
173 <Show when={isEditMode() && props.isToday && props.onDelete}>
174 <button
175 onClick={handleDelete}
176 disabled={isSaving() || isDeleting()}
177 class="px-4 py-2 text-red-700 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 transition-colors disabled:opacity-50 flex items-center gap-2 cursor-pointer"
178 >
179 <Show when={isDeleting()}>
180 <div class="animate-spin rounded-full h-4 w-4 border-b-2 border-red-700"></div>
181 </Show>
182 {isDeleting() ? "Deleting..." : "Delete Entry"}
183 </button>
184 </Show>
185 <div class="flex gap-3 ml-auto">
186 <button
187 onClick={props.onClose}
188 disabled={isSaving() || isDeleting()}
189 class="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors disabled:opacity-50 cursor-pointer"
190 >
191 Cancel
192 </button>
193 <button
194 onClick={handleSave}
195 disabled={isSaving() || isDeleting()}
196 class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2 cursor-pointer"
197 >
198 <Show when={isSaving()}>
199 <div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
200 </Show>
201 {isSaving() ? "Saving..." : "Save Entry"}
202 </button>
203 </div>
204 </div>
205 </div>
206 </div>
207 </Show>
208 );
209};
210
211export default DiaryModal;
Note: See TracBrowser for help on using the repository browser.