source: frontend/src/pages/ConsultationSlots.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.7 KB
RevLine 
[700e2f9]1import {
2 type Component,
3 createEffect,
4 createResource,
5 createSignal,
6 For,
7 Show,
8} from "solid-js";
9import { useNavigate } from "@solidjs/router";
10import { useAuth } from "@/context/AuthContext";
11import { consultationSlotApi } from "@/api/consultationSlot";
12import { formatDateWithWeekday, getTodayString } from "@/utils";
13import { UserType } from "@/enums/UserType";
14
15const ConsultationSlots: Component = () => {
16 const { user, isAuthenticated } = useAuth();
17 const navigate = useNavigate();
18 const [selectedDate, setSelectedDate] = createSignal("");
19 const [error, setError] = createSignal("");
20
21 const [slots, { refetch }] = createResource(
22 () => ({
23 authenticated: isAuthenticated(),
24 userId: user()?.userId,
25 }),
26
27 async (params) => {
28 if (!params.authenticated || !params.userId) return null;
29 return await consultationSlotApi.getSlots(params.userId);
30 },
31 );
32
33 createEffect(() => {
34 if (!isAuthenticated()) {
35 navigate("/login", { replace: true });
36 return;
37 }
38
39 const currentUser = user();
40 if (currentUser?.userType !== UserType.THERAPIST) {
41 navigate("/", { replace: true });
42 }
43 });
44
45 const handleAddSlot = async (e: Event) => {
46 e.preventDefault();
47 const dateStr = selectedDate();
48
49 if (!dateStr) {
50 setError("Please select a date");
51 return;
52 }
53
54 if (dateStr < getTodayString()) {
55 setError("Cannot add slots for past dates");
56 return;
57 }
58
59 const currentUser = user();
60 if (!currentUser?.userId) return;
61
62 try {
63 await consultationSlotApi.addSlot(currentUser.userId, dateStr);
64 setSelectedDate("");
65 setError("");
66 refetch();
67 } catch (err: any) {
68 setError(err.message || "Failed to add slot");
69 }
70 };
71
72 const handleRemoveSlot = async (date: string) => {
73 const currentUser = user();
74 if (!currentUser?.userId) return;
75
76 if (
77 !confirm(`Remove consultation slot for ${formatDateWithWeekday(date)}?`)
78 ) {
79 return;
80 }
81
82 try {
83 await consultationSlotApi.removeSlot(currentUser.userId, date);
84 setError("");
85 refetch();
86 } catch (err: any) {
87 setError(err.message || "Failed to remove slot");
88 }
89 };
90
91 const sortedSlots = () => {
92 const slotData = slots();
93 if (!slotData?.consultationSlots) return [];
94 return [...slotData.consultationSlots].sort();
95 };
96
97 const futureSlots = () => {
98 const today = getTodayString();
99 return sortedSlots().filter((slot) => slot >= today);
100 };
101
102 const pastSlots = () => {
103 const today = getTodayString();
104 return sortedSlots().filter((slot) => slot < today);
105 };
106
107 return (
108 <div class="container mx-auto px-4 py-8">
109 <div class="mb-8">
110 <h1 class="text-3xl font-bold text-gray-900 mb-2">
111 Manage Consultation Slots
112 </h1>
113 <p class="text-gray-600">
114 Add or remove dates when you're available for patient consultations
115 </p>
116 </div>
117
118 <Show
119 when={!slots.loading}
120 fallback={
121 <div class="flex justify-center items-center py-12">
122 <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
123 </div>
124 }
125 >
126 <div class="grid gap-6 lg:grid-cols-2">
127 <div class="bg-white rounded-lg shadow-md p-6">
128 <h2 class="text-xl font-bold text-gray-900 mb-4">
129 Add New Consultation Slot
130 </h2>
131 <form onSubmit={handleAddSlot} class="space-y-4">
132 <div>
133 <label class="block text-sm font-semibold text-gray-700 mb-2">
134 Select Date
135 </label>
136 <input
137 type="date"
138 value={selectedDate()}
139 onInput={(e) => setSelectedDate(e.currentTarget.value)}
140 min={getTodayString()}
141 class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
142 required
143 />
144 </div>
145
146 <Show when={error()}>
147 <div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm">
148 {error()}
149 </div>
150 </Show>
151
152 <button
153 type="submit"
154 class="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold cursor-pointer"
155 >
156 Add Slot
157 </button>
158 </form>
159 </div>
160
161 <div class="bg-white rounded-lg shadow-md p-6">
162 <h2 class="text-xl font-bold text-gray-900 mb-4">
163 Upcoming Consultation Slots ({futureSlots().length})
164 </h2>
165 <Show
166 when={futureSlots().length > 0}
167 fallback={
168 <p class="text-gray-500 text-sm italic">
169 No upcoming consultation slots scheduled
170 </p>
171 }
172 >
173 <div class="space-y-2 max-h-96 overflow-y-auto">
174 <For each={futureSlots()}>
175 {(slot) => (
176 <div class="flex items-center justify-between p-3 bg-green-50 border border-green-200 rounded-lg">
177 <div class="flex items-center gap-3">
178 <svg
179 class="w-5 h-5 text-green-600"
180 fill="none"
181 stroke="currentColor"
182 viewBox="0 0 24 24"
183 >
184 <path
185 stroke-linecap="round"
186 stroke-linejoin="round"
187 stroke-width="2"
188 d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
189 />
190 </svg>
191 <span class="text-sm font-medium text-gray-900">
192 {formatDateWithWeekday(slot)}
193 </span>
194 </div>
195 <button
196 onClick={() => handleRemoveSlot(slot)}
197 class="px-3 py-1 text-red-700 bg-red-50 border border-red-200 rounded hover:bg-red-100 transition-colors text-sm font-medium cursor-pointer"
198 >
199 Remove
200 </button>
201 </div>
202 )}
203 </For>
204 </div>
205 </Show>
206 </div>
207 </div>
208
209 <Show when={pastSlots().length > 0}>
210 <div class="mt-6 bg-gray-50 rounded-lg shadow-md p-6">
211 <h2 class="text-xl font-bold text-gray-700 mb-4">
212 Past Consultation Slots ({pastSlots().length})
213 </h2>
214 <div class="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
215 <For each={pastSlots()}>
216 {(slot) => (
217 <div class="flex items-center gap-2 p-2 bg-gray-100 border border-gray-300 rounded text-gray-600">
218 <svg
219 class="w-4 h-4"
220 fill="none"
221 stroke="currentColor"
222 viewBox="0 0 24 24"
223 >
224 <path
225 stroke-linecap="round"
226 stroke-linejoin="round"
227 stroke-width="2"
228 d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
229 />
230 </svg>
231 <span class="text-xs">{formatDateWithWeekday(slot)}</span>
232 </div>
233 )}
234 </For>
235 </div>
236 </div>
237 </Show>
238 </Show>
239 </div>
240 );
241};
242
243export default ConsultationSlots;
Note: See TracBrowser for help on using the repository browser.