| [700e2f9] | 1 | import {
|
|---|
| 2 | type Component,
|
|---|
| 3 | createEffect,
|
|---|
| 4 | createResource,
|
|---|
| 5 | createSignal,
|
|---|
| 6 | Show,
|
|---|
| 7 | } from "solid-js";
|
|---|
| 8 | import { useNavigate } from "@solidjs/router";
|
|---|
| 9 | import { useAuth } from "@/context/AuthContext";
|
|---|
| 10 | import { diaryApi, type DiaryEntry } from "@/api/diary";
|
|---|
| 11 | import { therapistApi } from "@/api/therapist";
|
|---|
| 12 | import CalendarHeader from "@/components/CalendarHeader";
|
|---|
| 13 | import DayHeaders from "@/components/DayHeaders";
|
|---|
| 14 | import CalendarGrid from "@/components/CalendarGrid";
|
|---|
| 15 | import RatingLegend from "@/components/RatingLegend";
|
|---|
| 16 | import DiaryModal from "@/components/DiaryModal";
|
|---|
| 17 | import PatientSelector from "@/components/PatientSelector";
|
|---|
| 18 | import { dateToString, getMonthName, getTodayString } from "@/utils";
|
|---|
| 19 | import { UserType } from "@/enums/UserType";
|
|---|
| 20 |
|
|---|
| 21 | const Diary: Component = () => {
|
|---|
| 22 | const { user, isAuthenticated } = useAuth();
|
|---|
| 23 | const navigate = useNavigate();
|
|---|
| 24 |
|
|---|
| 25 | const now = new Date();
|
|---|
| 26 | const [currentYear, setCurrentYear] = createSignal(now.getFullYear());
|
|---|
| 27 | const [currentMonth, setCurrentMonth] = createSignal(now.getMonth() + 1);
|
|---|
| 28 | const [isModalOpen, setIsModalOpen] = createSignal(false);
|
|---|
| 29 | const [selectedDateStr, setSelectedDateStr] = createSignal<string | null>(
|
|---|
| 30 | null,
|
|---|
| 31 | );
|
|---|
| 32 | const [selectedEntry, setSelectedEntry] = createSignal<
|
|---|
| 33 | DiaryEntry | undefined
|
|---|
| 34 | >(undefined);
|
|---|
| 35 |
|
|---|
| 36 | const [selectedPatientId, setSelectedPatientId] = createSignal<number | null>(
|
|---|
| 37 | null,
|
|---|
| 38 | );
|
|---|
| 39 | const isTherapist = () => user()?.userType === UserType.THERAPIST;
|
|---|
| 40 |
|
|---|
| 41 | const [patients] = createResource(
|
|---|
| 42 | () => ({
|
|---|
| 43 | authenticated: isAuthenticated(),
|
|---|
| 44 | isTherapist: isTherapist(),
|
|---|
| 45 | }),
|
|---|
| 46 | async (params) => {
|
|---|
| 47 | if (!params.authenticated || !params.isTherapist) return [];
|
|---|
| 48 | return await therapistApi.getTherapistPatients();
|
|---|
| 49 | },
|
|---|
| 50 | );
|
|---|
| 51 |
|
|---|
| 52 | createEffect(() => {
|
|---|
| 53 | const patientList = patients();
|
|---|
| 54 | if (
|
|---|
| 55 | isTherapist() &&
|
|---|
| 56 | patientList &&
|
|---|
| 57 | patientList.length > 0 &&
|
|---|
| 58 | selectedPatientId() === null
|
|---|
| 59 | ) {
|
|---|
| 60 | setSelectedPatientId(patientList[0].userId);
|
|---|
| 61 | }
|
|---|
| 62 | });
|
|---|
| 63 |
|
|---|
| 64 | const [diaryEntries, { refetch }] = createResource(
|
|---|
| 65 | () => ({
|
|---|
| 66 | authenticated: isAuthenticated(),
|
|---|
| 67 | userId: isTherapist() ? selectedPatientId() : user()?.userId,
|
|---|
| 68 | year: currentYear(),
|
|---|
| 69 | month: currentMonth(),
|
|---|
| 70 | }),
|
|---|
| 71 |
|
|---|
| 72 | async (params) => {
|
|---|
| 73 | if (!params.authenticated || !params.userId) return [];
|
|---|
| 74 | return await diaryApi.getDiaryEntries(
|
|---|
| 75 | params.userId,
|
|---|
| 76 | params.year,
|
|---|
| 77 | params.month,
|
|---|
| 78 | );
|
|---|
| 79 | },
|
|---|
| 80 | );
|
|---|
| 81 |
|
|---|
| 82 | createEffect(() => {
|
|---|
| 83 | if (!isAuthenticated()) {
|
|---|
| 84 | navigate("/login", { replace: true });
|
|---|
| 85 | return;
|
|---|
| 86 | }
|
|---|
| 87 | });
|
|---|
| 88 |
|
|---|
| 89 | const goToPreviousMonth = () => {
|
|---|
| 90 | if (currentMonth() === 1) {
|
|---|
| 91 | setCurrentMonth(12);
|
|---|
| 92 | setCurrentYear(currentYear() - 1);
|
|---|
| 93 | } else {
|
|---|
| 94 | setCurrentMonth(currentMonth() - 1);
|
|---|
| 95 | }
|
|---|
| 96 | };
|
|---|
| 97 |
|
|---|
| 98 | const goToNextMonth = () => {
|
|---|
| 99 | if (currentMonth() === 12) {
|
|---|
| 100 | setCurrentMonth(1);
|
|---|
| 101 | setCurrentYear(currentYear() + 1);
|
|---|
| 102 | } else {
|
|---|
| 103 | setCurrentMonth(currentMonth() + 1);
|
|---|
| 104 | }
|
|---|
| 105 | };
|
|---|
| 106 |
|
|---|
| 107 | const generateCalendarDays = () => {
|
|---|
| 108 | const year = currentYear();
|
|---|
| 109 | const month = currentMonth();
|
|---|
| 110 | const firstDay = new Date(year, month - 1, 1);
|
|---|
| 111 | const lastDay = new Date(year, month, 0);
|
|---|
| 112 | const daysInMonth = lastDay.getDate();
|
|---|
| 113 | const startDayOfWeek = firstDay.getDay();
|
|---|
| 114 |
|
|---|
| 115 | const mondayBasedStart = startDayOfWeek === 0 ? 6 : startDayOfWeek - 1;
|
|---|
| 116 |
|
|---|
| 117 | const days: Array<{
|
|---|
| 118 | day: number | null;
|
|---|
| 119 | isCurrentMonth: boolean;
|
|---|
| 120 | date: Date | null;
|
|---|
| 121 | }> = [];
|
|---|
| 122 |
|
|---|
| 123 | for (let i = 0; i < mondayBasedStart; i++) {
|
|---|
| 124 | days.push({ day: null, isCurrentMonth: false, date: null });
|
|---|
| 125 | }
|
|---|
| 126 |
|
|---|
| 127 | for (let day = 1; day <= daysInMonth; day++) {
|
|---|
| 128 | days.push({
|
|---|
| 129 | day,
|
|---|
| 130 | isCurrentMonth: true,
|
|---|
| 131 | date: new Date(year, month - 1, day),
|
|---|
| 132 | });
|
|---|
| 133 | }
|
|---|
| 134 |
|
|---|
| 135 | return days;
|
|---|
| 136 | };
|
|---|
| 137 |
|
|---|
| 138 | const getEntryForDate = (date: Date | null): DiaryEntry | undefined => {
|
|---|
| 139 | if (!date || !diaryEntries()) return undefined;
|
|---|
| 140 | const dateStr = dateToString(date);
|
|---|
| 141 | return diaryEntries()!.find((entry) => entry.date === dateStr);
|
|---|
| 142 | };
|
|---|
| 143 |
|
|---|
| 144 | const isToday = (dateStr: string): boolean => dateStr === getTodayString();
|
|---|
| 145 |
|
|---|
| 146 | const handleDayClick = (date: Date | null) => {
|
|---|
| 147 | if (isTherapist() || !date) return;
|
|---|
| 148 |
|
|---|
| 149 | const dateStr = dateToString(date);
|
|---|
| 150 | const entry = getEntryForDate(date);
|
|---|
| 151 |
|
|---|
| 152 | if (!entry && !isToday(dateStr)) {
|
|---|
| 153 | return;
|
|---|
| 154 | }
|
|---|
| 155 |
|
|---|
| 156 | setSelectedDateStr(dateStr);
|
|---|
| 157 | setSelectedEntry(entry);
|
|---|
| 158 | setIsModalOpen(true);
|
|---|
| 159 | };
|
|---|
| 160 |
|
|---|
| 161 | const handleSaveEntry = async (rating: number, content: string) => {
|
|---|
| 162 | const dateStr = selectedDateStr();
|
|---|
| 163 | if (!dateStr) return;
|
|---|
| 164 |
|
|---|
| 165 | const entry = selectedEntry();
|
|---|
| 166 |
|
|---|
| 167 | if (entry) {
|
|---|
| 168 | const updateData: { content: string; dailyRating?: number } = { content };
|
|---|
| 169 |
|
|---|
| 170 | if (isToday(dateStr)) {
|
|---|
| 171 | updateData.dailyRating = rating;
|
|---|
| 172 | }
|
|---|
| 173 |
|
|---|
| 174 | await diaryApi.updateDiaryEntry(entry.idDiary, updateData);
|
|---|
| 175 | } else {
|
|---|
| 176 | await diaryApi.createDiaryEntry({
|
|---|
| 177 | dailyRating: rating,
|
|---|
| 178 | content,
|
|---|
| 179 | });
|
|---|
| 180 | }
|
|---|
| 181 |
|
|---|
| 182 | refetch();
|
|---|
| 183 | setIsModalOpen(false);
|
|---|
| 184 | setSelectedDateStr(null);
|
|---|
| 185 | setSelectedEntry(undefined);
|
|---|
| 186 | };
|
|---|
| 187 |
|
|---|
| 188 | const handleDeleteEntry = async () => {
|
|---|
| 189 | const entry = selectedEntry();
|
|---|
| 190 | if (!entry) return;
|
|---|
| 191 |
|
|---|
| 192 | await diaryApi.deleteDiaryEntry(entry.idDiary);
|
|---|
| 193 |
|
|---|
| 194 | refetch();
|
|---|
| 195 | setIsModalOpen(false);
|
|---|
| 196 | setSelectedDateStr(null);
|
|---|
| 197 | setSelectedEntry(undefined);
|
|---|
| 198 | };
|
|---|
| 199 |
|
|---|
| 200 | const handleCloseModal = () => {
|
|---|
| 201 | setIsModalOpen(false);
|
|---|
| 202 | setSelectedDateStr(null);
|
|---|
| 203 | setSelectedEntry(undefined);
|
|---|
| 204 | };
|
|---|
| 205 |
|
|---|
| 206 | const getSelectedDate = (): Date | null => {
|
|---|
| 207 | const dateStr = selectedDateStr();
|
|---|
| 208 | if (!dateStr) return null;
|
|---|
| 209 |
|
|---|
| 210 | const [year, month, day] = dateStr.split("-").map(Number);
|
|---|
| 211 |
|
|---|
| 212 | return new Date(year, month - 1, day);
|
|---|
| 213 | };
|
|---|
| 214 |
|
|---|
| 215 | return (
|
|---|
| 216 | <div class="container mx-auto px-4 py-8">
|
|---|
| 217 | <div class="mb-8">
|
|---|
| 218 | <h1 class="text-3xl font-bold text-gray-900 mb-2">
|
|---|
| 219 | {isTherapist() ? "Patient Diaries" : "My Diary"}
|
|---|
| 220 | </h1>
|
|---|
| 221 | <p class="text-gray-600">
|
|---|
| 222 | {isTherapist()
|
|---|
| 223 | ? "View your patients' daily mood ratings"
|
|---|
| 224 | : "Track your daily thoughts and feelings"}
|
|---|
| 225 | </p>
|
|---|
| 226 | </div>
|
|---|
| 227 |
|
|---|
| 228 | <Show when={isTherapist()}>
|
|---|
| 229 | <PatientSelector
|
|---|
| 230 | patients={patients()}
|
|---|
| 231 | loading={patients.loading}
|
|---|
| 232 | selectedPatientId={selectedPatientId()}
|
|---|
| 233 | onPatientChange={setSelectedPatientId}
|
|---|
| 234 | />
|
|---|
| 235 | </Show>
|
|---|
| 236 |
|
|---|
| 237 | <div class="bg-white rounded-lg shadow-lg p-6">
|
|---|
| 238 | <CalendarHeader
|
|---|
| 239 | monthName={getMonthName(currentYear(), currentMonth())}
|
|---|
| 240 | onPreviousMonth={goToPreviousMonth}
|
|---|
| 241 | onNextMonth={goToNextMonth}
|
|---|
| 242 | />
|
|---|
| 243 |
|
|---|
| 244 | <DayHeaders />
|
|---|
| 245 | <Show
|
|---|
| 246 | when={!diaryEntries.loading}
|
|---|
| 247 | fallback={
|
|---|
| 248 | <div class="flex justify-center items-center py-12">
|
|---|
| 249 | <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600" />
|
|---|
| 250 | </div>
|
|---|
| 251 | }
|
|---|
| 252 | >
|
|---|
| 253 | <CalendarGrid
|
|---|
| 254 | days={generateCalendarDays()}
|
|---|
| 255 | getEntryForDate={getEntryForDate}
|
|---|
| 256 | onDayClick={handleDayClick}
|
|---|
| 257 | isTherapistView={isTherapist()}
|
|---|
| 258 | />
|
|---|
| 259 | </Show>
|
|---|
| 260 | <RatingLegend />
|
|---|
| 261 | </div>
|
|---|
| 262 |
|
|---|
| 263 | <Show when={selectedDateStr() && getSelectedDate()}>
|
|---|
| 264 | <DiaryModal
|
|---|
| 265 | isOpen={isModalOpen()}
|
|---|
| 266 | onClose={handleCloseModal}
|
|---|
| 267 | entry={selectedEntry()}
|
|---|
| 268 | date={getSelectedDate()!}
|
|---|
| 269 | onSave={handleSaveEntry}
|
|---|
| 270 | onDelete={handleDeleteEntry}
|
|---|
| 271 | isToday={isToday(selectedDateStr()!)}
|
|---|
| 272 | />
|
|---|
| 273 | </Show>
|
|---|
| 274 | </div>
|
|---|
| 275 | );
|
|---|
| 276 | };
|
|---|
| 277 |
|
|---|
| 278 | export default Diary;
|
|---|