Skip to content

Commit a95091a

Browse files
committed
Feat: Update document handling in DashboardPage and add wizytaId management in DokumentacjaLekarzPage
1 parent 09831aa commit a95091a

2 files changed

Lines changed: 163 additions & 57 deletions

File tree

‎src/frontend/AliMed.Web/src/pages/DashboardPage.tsx‎

Lines changed: 153 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import {
55
PlusCircleIcon,
66
DocumentTextIcon,
77
UserCircleIcon,
8-
XMarkIcon
8+
CalendarIcon
99
} from '@heroicons/react/24/outline';
1010
import { useTranslation } from '../context/LanguageContext';
1111
import { useAuth } from '../context/AuthContext';
12-
import type { User, Wizyta } from '../types/api';
12+
import type { Dokument, Pacjent, User, Wizyta, WizytaDetail } from '../types/api';
1313
import { apiService } from '../services/api';
14+
import { openDocumentPdf } from '../utils/documentPdf';
1415

1516
const DashboardPage: React.FC = () => {
1617
const { t, language } = useTranslation();
@@ -42,7 +43,10 @@ const DashboardPage: React.FC = () => {
4243
}
4344
return null;
4445
});
45-
const [selectedWizyta, setSelectedWizyta] = useState<Wizyta | null>(null);
46+
const [selectedWizyta, setSelectedWizyta] = useState<WizytaDetail | null>(null);
47+
const [loadingDetails, setLoadingDetails] = useState(false);
48+
const [detailsError, setDetailsError] = useState<string | null>(null);
49+
const [pacjentCache, setPacjentCache] = useState<Pacjent | null>(null);
4650
const navigate = useNavigate();
4751

4852
useEffect(() => {
@@ -94,6 +98,45 @@ const DashboardPage: React.FC = () => {
9498
});
9599
};
96100

101+
const formatDocumentName = (name?: string) => {
102+
if (!name) return '';
103+
return name.endsWith('.txt') ? name.slice(0, -4) : name;
104+
};
105+
106+
const handlePreview = async (dokument: Dokument) => {
107+
const popup = window.open('', '_blank');
108+
if (!popup) {
109+
alert(t('visitDetails.popupBlocked'));
110+
return;
111+
}
112+
try {
113+
const pacjentPromise = pacjentCache
114+
? Promise.resolve(pacjentCache)
115+
: apiService.getMojProfil().catch(() => undefined);
116+
const trescPromise = apiService
117+
.downloadDokument(dokument.dokumentId)
118+
.then((blob) => blob.text())
119+
.catch(() => undefined);
120+
121+
const [pacjent, tresc] = await Promise.all([pacjentPromise, trescPromise]);
122+
123+
if (pacjent && !pacjentCache) {
124+
setPacjentCache(pacjent);
125+
}
126+
127+
openDocumentPdf({
128+
dokument,
129+
pacjent: pacjent ?? undefined,
130+
wizyta: selectedWizyta ?? undefined,
131+
tresc,
132+
targetWindow: popup,
133+
});
134+
} catch {
135+
popup.close();
136+
alert(t('documents.errorDownloading'));
137+
}
138+
};
139+
97140

98141
// Header and outer wrapper removed to rely on Layout
99142

@@ -195,17 +238,29 @@ const DashboardPage: React.FC = () => {
195238
{formatDate(wizyta.dataWizyty)} {formatTime(wizyta.dataWizyty)}
196239
</td>
197240
<td className="py-4 text-gray-900">
198-
{wizyta.lekarz ? `${wizyta.lekarz.imie} ${wizyta.lekarz.nazwisko}` : '-'}
241+
{wizyta.lekarzName
242+
|| (wizyta.lekarz ? `${wizyta.lekarz.imie} ${wizyta.lekarz.nazwisko}` : '-')}
199243
</td>
200244
<td className="py-4 text-gray-600">
201-
{wizyta.lekarz?.specjalizacja || '-'}
245+
{wizyta.specjalizacja || wizyta.lekarz?.specjalizacja || '-'}
202246
</td>
203247
<td className="py-4 text-gray-600">
204248
{typeof wizyta.placowka === 'string' ? wizyta.placowka : wizyta.placowka?.nazwa || '-'}
205249
</td>
206250
<td className="py-4">
207251
<button
208-
onClick={() => setSelectedWizyta(wizyta)}
252+
onClick={async () => {
253+
setDetailsError(null);
254+
setLoadingDetails(true);
255+
try {
256+
const details = await apiService.getWizytaById(wizyta.wizytaId);
257+
setSelectedWizyta(details);
258+
} catch (err) {
259+
setDetailsError(err instanceof Error ? err.message : t('common.error'));
260+
} finally {
261+
setLoadingDetails(false);
262+
}
263+
}}
209264
className="text-alimed-blue hover:underline text-sm"
210265
>
211266
{t('common.details')}
@@ -219,63 +274,105 @@ const DashboardPage: React.FC = () => {
219274
)}
220275
</div>
221276

222-
{/* Visit Details Modal */}
223277
{selectedWizyta && (
224-
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
225-
<div className="bg-white rounded-xl shadow-xl max-w-md w-full p-6 relative">
226-
<button
227-
onClick={() => setSelectedWizyta(null)}
228-
className="absolute top-4 right-4 text-gray-400 hover:text-gray-600"
229-
>
230-
<XMarkIcon className="w-6 h-6" />
231-
</button>
232-
233-
<h3 className="text-xl font-bold text-gray-900 mb-4">{t('visitDetails.title')}</h3>
234-
235-
<div className="space-y-4">
236-
<div>
237-
<label className="text-sm font-medium text-gray-500">{t('visitDetails.visitDate')}</label>
238-
<p className="text-gray-900">
239-
{formatDate(selectedWizyta.dataWizyty)} {formatTime(selectedWizyta.dataWizyty)}
240-
</p>
241-
</div>
242-
<div>
243-
<label className="text-sm font-medium text-gray-500">{t('visitDetails.doctor')}</label>
244-
<p className="text-gray-900">
245-
{selectedWizyta.lekarz ? `${selectedWizyta.lekarz.imie} ${selectedWizyta.lekarz.nazwisko}` : '-'}
246-
</p>
247-
</div>
248-
<div>
249-
<label className="text-sm font-medium text-gray-500">{t('dashboard.specialization')}</label>
250-
<p className="text-gray-900">{selectedWizyta.lekarz?.specjalizacja || '-'}</p>
278+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
279+
<div className="bg-white rounded-2xl shadow-xl max-w-3xl w-full mx-4 overflow-hidden">
280+
<div className="px-6 py-5 border-b border-gray-100 flex flex-col md:flex-row md:items-center md:justify-between gap-3">
281+
<div className="flex items-center gap-3">
282+
<div className="w-11 h-11 rounded-full bg-alimed-blue/10 text-alimed-blue flex items-center justify-center">
283+
<CalendarIcon className="w-6 h-6" />
284+
</div>
285+
<div>
286+
<h3 className="text-lg font-semibold text-gray-900">{t('visitDetails.title')}</h3>
287+
<p className="text-sm text-gray-500">
288+
{t('visitDetails.checkInfo')}
289+
</p>
290+
</div>
251291
</div>
252-
<div>
253-
<label className="text-sm font-medium text-gray-500">{t('visitDetails.facility')}</label>
254-
<p className="text-gray-900">
255-
{typeof selectedWizyta.placowka === 'string'
256-
? selectedWizyta.placowka
257-
: selectedWizyta.placowka?.nazwa || '-'}
258-
</p>
259-
</div>
260-
</div>
261-
262-
<div className="mt-6 flex gap-3">
263292
<button
264293
onClick={() => setSelectedWizyta(null)}
265-
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition"
294+
className="px-3 py-1.5 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 transition"
266295
>
267-
{t('common.close')}
268-
</button>
269-
<button
270-
onClick={() => {
271-
setSelectedWizyta(null);
272-
navigate('/moje-wizyty');
273-
}}
274-
className="flex-1 px-4 py-2 bg-alimed-blue text-white rounded-lg hover:bg-blue-700 transition"
275-
>
276-
{t('dashboard.myVisits')}
296+
{t('visitDetails.close')}
277297
</button>
278298
</div>
299+
<div className="p-6 space-y-6">
300+
{detailsError && (
301+
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-2 rounded">
302+
{detailsError}
303+
</div>
304+
)}
305+
{loadingDetails ? (
306+
<div className="text-gray-500">{t('common.loading')}</div>
307+
) : (
308+
<>
309+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
310+
<div className="rounded-xl border border-gray-100 bg-gray-50 p-4 text-sm text-gray-700 space-y-2">
311+
<div className="text-xs uppercase tracking-wide text-gray-400">{t('visitDetails.visitDate')}</div>
312+
<div className="text-base font-semibold text-gray-900">
313+
{formatDate(selectedWizyta.dataWizyty)} · {formatTime(selectedWizyta.dataWizyty)}
314+
</div>
315+
<div><span className="text-gray-500">{t('visitDetails.status')}:</span> {selectedWizyta.status}</div>
316+
<div><span className="text-gray-500">{t('visitDetails.facility')}:</span> {selectedWizyta.placowka}</div>
317+
</div>
318+
<div className="rounded-xl border border-gray-100 bg-white p-4 text-sm text-gray-700 space-y-2">
319+
<div className="text-xs uppercase tracking-wide text-gray-400">{t('visitDetails.doctor')}</div>
320+
<div className="text-base font-semibold text-gray-900">
321+
{selectedWizyta.lekarz} ({selectedWizyta.specjalizacja})
322+
</div>
323+
{selectedWizyta.diagnoza ? (
324+
<div>
325+
<span className="text-gray-500">{t('visitDetails.diagnosis')}:</span> {selectedWizyta.diagnoza}
326+
</div>
327+
) : (
328+
<div className="text-gray-400">{t('visitDetails.noDiagnosis')}</div>
329+
)}
330+
</div>
331+
</div>
332+
333+
<div className="rounded-xl border border-gray-100 p-4">
334+
<div className="flex items-center justify-between mb-3">
335+
<div>
336+
<h4 className="font-semibold text-gray-900">{t('visitDetails.documents')}</h4>
337+
<p className="text-sm text-gray-500">{t('visitDetails.clickToDownload')}</p>
338+
</div>
339+
<span className="text-xs text-gray-400">
340+
{selectedWizyta.dokumenty.length} {t('visitDetails.files')}
341+
</span>
342+
</div>
343+
{selectedWizyta.dokumenty.length > 0 ? (
344+
<div className="space-y-2">
345+
{selectedWizyta.dokumenty.map((d: Dokument) => (
346+
<div
347+
key={d.dokumentId}
348+
className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 rounded-lg border border-gray-100 px-3 py-2"
349+
>
350+
<div className="text-sm text-gray-700">
351+
<div className="font-medium text-gray-900">
352+
{formatDocumentName(d.nazwaPliku) || `Dokument #${d.dokumentId}`}
353+
</div>
354+
<div className="text-xs text-gray-500">
355+
{t('visitDetails.type')}: {d.typDokumentu || 'inne'}
356+
</div>
357+
</div>
358+
<button
359+
onClick={() => handlePreview(d)}
360+
className="inline-flex items-center justify-center px-3 py-2 text-sm font-medium text-alimed-blue bg-alimed-blue/10 rounded-lg hover:bg-alimed-blue/20 transition"
361+
>
362+
{t('visitDetails.downloadPdf')}
363+
</button>
364+
</div>
365+
))}
366+
</div>
367+
) : (
368+
<div className="text-sm text-gray-500">
369+
{t('visitDetails.noDocuments')}
370+
</div>
371+
)}
372+
</div>
373+
</>
374+
)}
375+
</div>
279376
</div>
280377
</div>
281378
)}

‎src/frontend/AliMed.Web/src/pages/DokumentacjaLekarzPage.tsx‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,18 @@ const DokumentacjaLekarzPage: React.FC = () => {
160160
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>
161161
) => {
162162
const { name, value } = e.target;
163+
if (name === 'wizytaId') {
164+
const parsed = value ? Number(value) : '';
165+
setSelectedWizytaId(parsed);
166+
setNowyDokument(prev => ({
167+
...prev,
168+
wizytaId: parsed,
169+
}));
170+
return;
171+
}
163172
setNowyDokument(prev => ({
164173
...prev,
165-
[name]: name === 'wizytaId' ? (value ? Number(value) : '') : value,
174+
[name]: value,
166175
}));
167176
};
168177

0 commit comments

Comments
 (0)