@@ -5,12 +5,13 @@ import {
55 PlusCircleIcon ,
66 DocumentTextIcon ,
77 UserCircleIcon ,
8- XMarkIcon
8+ CalendarIcon
99} from '@heroicons/react/24/outline' ;
1010import { useTranslation } from '../context/LanguageContext' ;
1111import { useAuth } from '../context/AuthContext' ;
12- import type { User , Wizyta } from '../types/api' ;
12+ import type { Dokument , Pacjent , User , Wizyta , WizytaDetail } from '../types/api' ;
1313import { apiService } from '../services/api' ;
14+ import { openDocumentPdf } from '../utils/documentPdf' ;
1415
1516const 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 ) }
0 commit comments