| [700e2f9] | 1 | import { type Component, For, Show } from "solid-js";
|
|---|
| 2 | import TherapyList from "@/components/TherapyList";
|
|---|
| 3 | import ExistingTherapyList from "@/components/ExistingTherapyList";
|
|---|
| 4 | import type { Consultation } from "@/api/consultation";
|
|---|
| 5 | import type { Patient } from "@/api/patient";
|
|---|
| 6 | import type { Therapy } from "@/api/therapy";
|
|---|
| 7 |
|
|---|
| 8 | interface ConsultationFormData {
|
|---|
| 9 | patientId: number;
|
|---|
| 10 | date: string;
|
|---|
| 11 | price: number;
|
|---|
| 12 | advice: string;
|
|---|
| 13 | dateOfPayment: string | null;
|
|---|
| 14 | }
|
|---|
| 15 |
|
|---|
| 16 | interface ConsultationModalProps {
|
|---|
| 17 | editingConsultation: Consultation | null;
|
|---|
| 18 | formData: ConsultationFormData;
|
|---|
| 19 | patients: Patient[];
|
|---|
| 20 | newTherapies: Therapy[];
|
|---|
| 21 | existingTherapies: Therapy[];
|
|---|
| 22 | onClose: () => void;
|
|---|
| 23 | onSubmit: (e: Event) => void;
|
|---|
| 24 | onFormChange: (data: ConsultationFormData) => void;
|
|---|
| 25 | onNewTherapiesChange: (therapies: Therapy[]) => void;
|
|---|
| 26 | onExistingTherapiesChange: (therapies: Therapy[]) => void;
|
|---|
| 27 | readOnly?: boolean;
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | const ConsultationModal: Component<ConsultationModalProps> = (props) => (
|
|---|
| 31 | <div
|
|---|
| 32 | class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
|
|---|
| 33 | onClick={props.onClose}
|
|---|
| 34 | >
|
|---|
| 35 | <div
|
|---|
| 36 | class="bg-white rounded-lg p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto"
|
|---|
| 37 | onClick={(e) => e.stopPropagation()}
|
|---|
| 38 | >
|
|---|
| 39 | <h2 class="text-2xl font-bold mb-4">
|
|---|
| 40 | {props.readOnly
|
|---|
| 41 | ? "Consultation Details"
|
|---|
| 42 | : props.editingConsultation
|
|---|
| 43 | ? "Edit Consultation"
|
|---|
| 44 | : "Log New Consultation"}
|
|---|
| 45 | </h2>
|
|---|
| 46 |
|
|---|
| 47 | <form onSubmit={props.onSubmit}>
|
|---|
| 48 | <div class="space-y-4">
|
|---|
| 49 | <Show when={!props.editingConsultation}>
|
|---|
| 50 | <div>
|
|---|
| 51 | <label class="block text-sm font-medium text-gray-700 mb-1">
|
|---|
| 52 | Patient
|
|---|
| 53 | </label>
|
|---|
| 54 | <select
|
|---|
| 55 | class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|---|
| 56 | value={props.formData.patientId}
|
|---|
| 57 | onChange={(e) =>
|
|---|
| 58 | props.onFormChange({
|
|---|
| 59 | ...props.formData,
|
|---|
| 60 | patientId: Number.parseInt(e.currentTarget.value),
|
|---|
| 61 | })
|
|---|
| 62 | }
|
|---|
| 63 | required
|
|---|
| 64 | disabled={props.readOnly}
|
|---|
| 65 | >
|
|---|
| 66 | <For each={props.patients}>
|
|---|
| 67 | {(patient) => (
|
|---|
| 68 | <option value={patient.userId}>
|
|---|
| 69 | {patient.firstName} {patient.lastName}
|
|---|
| 70 | </option>
|
|---|
| 71 | )}
|
|---|
| 72 | </For>
|
|---|
| 73 | </select>
|
|---|
| 74 | </div>
|
|---|
| 75 | </Show>
|
|---|
| 76 |
|
|---|
| 77 | <div>
|
|---|
| 78 | <label class="block text-sm font-medium text-gray-700 mb-1">
|
|---|
| 79 | Consultation Date
|
|---|
| 80 | </label>
|
|---|
| 81 | <input
|
|---|
| 82 | type="date"
|
|---|
| 83 | class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|---|
| 84 | value={props.formData.date}
|
|---|
| 85 | onChange={(e) =>
|
|---|
| 86 | props.onFormChange({
|
|---|
| 87 | ...props.formData,
|
|---|
| 88 | date: e.currentTarget.value,
|
|---|
| 89 | })
|
|---|
| 90 | }
|
|---|
| 91 | required
|
|---|
| 92 | disabled={props.readOnly}
|
|---|
| 93 | />
|
|---|
| 94 | </div>
|
|---|
| 95 |
|
|---|
| 96 | <div>
|
|---|
| 97 | <label class="block text-sm font-medium text-gray-700 mb-1">
|
|---|
| 98 | Price
|
|---|
| 99 | </label>
|
|---|
| 100 | <input
|
|---|
| 101 | type="number"
|
|---|
| 102 | step="0.01"
|
|---|
| 103 | min="0"
|
|---|
| 104 | class={`w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${props.readOnly ? "[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" : ""}`}
|
|---|
| 105 | value={props.formData.price}
|
|---|
| 106 | onChange={(e) =>
|
|---|
| 107 | props.onFormChange({
|
|---|
| 108 | ...props.formData,
|
|---|
| 109 | price: Number.parseFloat(e.currentTarget.value),
|
|---|
| 110 | })
|
|---|
| 111 | }
|
|---|
| 112 | required
|
|---|
| 113 | disabled={props.readOnly}
|
|---|
| 114 | />
|
|---|
| 115 | </div>
|
|---|
| 116 |
|
|---|
| 117 | <div>
|
|---|
| 118 | <label class="block text-sm font-medium text-gray-700 mb-1">
|
|---|
| 119 | Advice / Notes
|
|---|
| 120 | </label>
|
|---|
| 121 | <textarea
|
|---|
| 122 | class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|---|
| 123 | rows="4"
|
|---|
| 124 | value={props.formData.advice}
|
|---|
| 125 | onChange={(e) =>
|
|---|
| 126 | props.onFormChange({
|
|---|
| 127 | ...props.formData,
|
|---|
| 128 | advice: e.currentTarget.value,
|
|---|
| 129 | })
|
|---|
| 130 | }
|
|---|
| 131 | placeholder="Enter any advice or notes from the consultation..."
|
|---|
| 132 | disabled={props.readOnly}
|
|---|
| 133 | />
|
|---|
| 134 | </div>
|
|---|
| 135 |
|
|---|
| 136 | <Show when={!props.readOnly}>
|
|---|
| 137 | <div>
|
|---|
| 138 | <label class="flex items-center space-x-2 cursor-pointer">
|
|---|
| 139 | <input
|
|---|
| 140 | type="checkbox"
|
|---|
| 141 | class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 cursor-pointer"
|
|---|
| 142 | checked={props.formData.dateOfPayment !== null}
|
|---|
| 143 | onChange={(e) => {
|
|---|
| 144 | if (e.currentTarget.checked) {
|
|---|
| 145 | props.onFormChange({
|
|---|
| 146 | ...props.formData,
|
|---|
| 147 | dateOfPayment: new Date().toISOString().split("T")[0],
|
|---|
| 148 | });
|
|---|
| 149 | } else {
|
|---|
| 150 | props.onFormChange({
|
|---|
| 151 | ...props.formData,
|
|---|
| 152 | dateOfPayment: null,
|
|---|
| 153 | });
|
|---|
| 154 | }
|
|---|
| 155 | }}
|
|---|
| 156 | />
|
|---|
| 157 | <span class="text-sm font-medium text-gray-700">
|
|---|
| 158 | Mark as paid
|
|---|
| 159 | </span>
|
|---|
| 160 | </label>
|
|---|
| 161 | </div>
|
|---|
| 162 | </Show>
|
|---|
| 163 |
|
|---|
| 164 | <Show when={props.formData.dateOfPayment !== null && !props.readOnly}>
|
|---|
| 165 | <div>
|
|---|
| 166 | <label class="block text-sm font-medium text-gray-700 mb-1">
|
|---|
| 167 | Payment Date
|
|---|
| 168 | </label>
|
|---|
| 169 | <input
|
|---|
| 170 | type="date"
|
|---|
| 171 | class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|---|
| 172 | value={props.formData.dateOfPayment || ""}
|
|---|
| 173 | onChange={(e) =>
|
|---|
| 174 | props.onFormChange({
|
|---|
| 175 | ...props.formData,
|
|---|
| 176 | dateOfPayment: e.currentTarget.value,
|
|---|
| 177 | })
|
|---|
| 178 | }
|
|---|
| 179 | disabled={props.readOnly}
|
|---|
| 180 | />
|
|---|
| 181 | </div>
|
|---|
| 182 | </Show>
|
|---|
| 183 | </div>
|
|---|
| 184 |
|
|---|
| 185 | <Show when={props.editingConsultation}>
|
|---|
| 186 | <div class="mt-6">
|
|---|
| 187 | <h3 class="text-lg font-semibold text-gray-900 mb-3">
|
|---|
| 188 | {props.readOnly ? "Prescribed Therapies" : "Existing Therapies"}
|
|---|
| 189 | </h3>
|
|---|
| 190 | <ExistingTherapyList
|
|---|
| 191 | consultationId={props.editingConsultation!.idConsultation}
|
|---|
| 192 | therapies={props.existingTherapies}
|
|---|
| 193 | onTherapiesChange={props.onExistingTherapiesChange}
|
|---|
| 194 | readOnly={props.readOnly}
|
|---|
| 195 | />
|
|---|
| 196 | </div>
|
|---|
| 197 | </Show>
|
|---|
| 198 |
|
|---|
| 199 | <Show when={!props.readOnly}>
|
|---|
| 200 | <div class="mt-6">
|
|---|
| 201 | <h3 class="text-lg font-semibold text-gray-900 mb-3">
|
|---|
| 202 | <Show when={props.editingConsultation} fallback={<>Therapies</>}>
|
|---|
| 203 | New Therapies to Add
|
|---|
| 204 | </Show>
|
|---|
| 205 | </h3>
|
|---|
| 206 | <TherapyList
|
|---|
| 207 | therapies={props.newTherapies}
|
|---|
| 208 | onPendingTherapiesChange={props.onNewTherapiesChange}
|
|---|
| 209 | readOnly={props.readOnly}
|
|---|
| 210 | />
|
|---|
| 211 | </div>
|
|---|
| 212 | </Show>
|
|---|
| 213 |
|
|---|
| 214 | <div class="mt-6 flex justify-end space-x-3">
|
|---|
| 215 | <Show
|
|---|
| 216 | when={!props.readOnly}
|
|---|
| 217 | fallback={
|
|---|
| 218 | <button
|
|---|
| 219 | type="button"
|
|---|
| 220 | onClick={props.onClose}
|
|---|
| 221 | class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition cursor-pointer"
|
|---|
| 222 | >
|
|---|
| 223 | Close
|
|---|
| 224 | </button>
|
|---|
| 225 | }
|
|---|
| 226 | >
|
|---|
| 227 | <button
|
|---|
| 228 | type="button"
|
|---|
| 229 | onClick={props.onClose}
|
|---|
| 230 | class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition cursor-pointer"
|
|---|
| 231 | >
|
|---|
| 232 | Cancel
|
|---|
| 233 | </button>
|
|---|
| 234 | <button
|
|---|
| 235 | type="submit"
|
|---|
| 236 | class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition cursor-pointer"
|
|---|
| 237 | >
|
|---|
| 238 | {props.editingConsultation ? "Update" : "Create"}
|
|---|
| 239 | </button>
|
|---|
| 240 | </Show>
|
|---|
| 241 | </div>
|
|---|
| 242 | </form>
|
|---|
| 243 | </div>
|
|---|
| 244 | </div>
|
|---|
| 245 | );
|
|---|
| 246 |
|
|---|
| 247 | export default ConsultationModal;
|
|---|