File size: 10,364 Bytes
cd99321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import ReactDOM from 'react-dom'
import { Ticket, FileText, ExternalLink } from 'lucide-react'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import remarkBreaks from 'remark-breaks'
import { useSettingsStore } from '../../store/settingsStore'
import { useTripStore } from '../../store/tripStore'
import { formatTime, splitReservationDateTime } from '../../utils/formatters'
import { RES_ICONS, TRANSPORT_DETAIL_COLORS } from './DayPlanSidebar.constants'
import type { Reservation } from '../../types'

interface DayPlanSidebarTransportDetailModalProps {
  transportDetail: Reservation | null
  setTransportDetail: (v: Reservation | null) => void
  onNavigateToFiles?: () => void
  t: (key: string, params?: Record<string, any>) => string
  locale: string
  timeFormat: string
}

export function DayPlanSidebarTransportDetailModal({
  transportDetail, setTransportDetail, onNavigateToFiles, t, locale, timeFormat,
}: DayPlanSidebarTransportDetailModalProps) {
  if (!transportDetail) return null
  return ReactDOM.createPortal(
    <div className="bg-[rgba(0,0,0,0.3)]" style={{
      position: 'fixed', inset: 0, zIndex: 1000,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      backdropFilter: 'blur(3px)',
    }} onClick={() => setTransportDetail(null)}>
      <div className="bg-surface-card" style={{
        width: 380, maxHeight: '80vh', overflowY: 'auto',
        borderRadius: 16,
        boxShadow: '0 16px 48px rgba(0,0,0,0.22)', padding: '22px 22px 18px',
        display: 'flex', flexDirection: 'column', gap: 14,
      }} onClick={e => e.stopPropagation()}>
        {(() => {
          const res = transportDetail
          const TransportIcon = RES_ICONS[res.type] || Ticket
          const TRANSPORT_COLORS = TRANSPORT_DETAIL_COLORS
          const color = TRANSPORT_COLORS[res.type] || 'var(--text-muted)'
          const meta = typeof res.metadata === 'string' ? JSON.parse(res.metadata || '{}') : (res.metadata || {})

          const detailFields = []
          if (res.type === 'flight') {
            if (meta.airline) detailFields.push({ label: t('reservations.meta.airline'), value: meta.airline })
            if (meta.flight_number) detailFields.push({ label: t('reservations.meta.flightNumber'), value: meta.flight_number })
            if (meta.departure_airport) detailFields.push({ label: t('reservations.meta.from'), value: meta.departure_airport })
            if (meta.arrival_airport) detailFields.push({ label: t('reservations.meta.to'), value: meta.arrival_airport })
            if (meta.seat) detailFields.push({ label: t('reservations.meta.seat'), value: meta.seat })
          } else if (res.type === 'train') {
            if (meta.train_number) detailFields.push({ label: t('reservations.meta.trainNumber'), value: meta.train_number })
            if (meta.platform) detailFields.push({ label: t('reservations.meta.platform'), value: meta.platform })
            if (meta.seat) detailFields.push({ label: t('reservations.meta.seat'), value: meta.seat })
          }
          if (res.confirmation_number) detailFields.push({ label: t('reservations.confirmationCode'), value: res.confirmation_number, sensitive: true })
          if (res.location) detailFields.push({ label: t('reservations.locationAddress'), value: res.location })

          return (
            <>
              {/* Header */}
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <div style={{
                  width: 36, height: 36, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
                  borderRadius: '50%', background: `${color}18`,
                }}>
                  <TransportIcon size={18} strokeWidth={1.8} color={color} />
                </div>
                <div style={{ flex: 1 }}>
                  <div className="text-content" style={{ fontSize: 15, fontWeight: 600 }}>{res.title}</div>
                  <div className="text-content-faint" style={{ fontSize: 11, marginTop: 2 }}>
                    {(() => {
                      const { date, time } = splitReservationDateTime(res.reservation_time)
                      const { time: endTime } = splitReservationDateTime(res.reservation_end_time)
                      const dateStr = date
                        ? new Date(date + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })
                        : ''
                      const timeStr = time ? formatTime(time, locale, timeFormat) : ''
                      const endStr = endTime ? formatTime(endTime, locale, timeFormat) : ''
                      const parts: string[] = []
                      if (dateStr) parts.push(dateStr)
                      if (timeStr) parts.push(timeStr + (endStr ? ` – ${endStr}` : ''))
                      return parts.join(', ')
                    })()}
                  </div>
                </div>
                <div className={res.status === 'confirmed' ? 'bg-[rgba(22,163,74,0.1)] text-[#16a34a]' : 'bg-[rgba(217,119,6,0.1)] text-[#d97706]'} style={{
                  padding: '3px 8px', borderRadius: 6, fontSize: 10, fontWeight: 600,
                }}>
                  {(res.status === 'confirmed' ? t('planner.resConfirmed') : t('planner.resPending')).replace(/\s*·\s*$/, '')}
                </div>
              </div>

              {/* Detail-Felder */}
              {detailFields.length > 0 && (
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
                  {detailFields.map((f, i) => {
                    const shouldBlur = f.sensitive && useSettingsStore.getState().settings.blur_booking_codes
                    return (
                      <div key={i} className="bg-surface-tertiary" style={{ padding: '8px 10px', borderRadius: 8 }}>
                        <div className="text-content-faint" style={{ fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 3 }}>{f.label}</div>
                        <div
                          onMouseEnter={e => { if (shouldBlur) e.currentTarget.style.filter = 'none' }}
                          onMouseLeave={e => { if (shouldBlur) e.currentTarget.style.filter = 'blur(5px)' }}
                          onClick={e => { if (shouldBlur) { const el = e.currentTarget; el.style.filter = el.style.filter === 'none' ? 'blur(5px)' : 'none' } }}
                          className="text-content"
                          style={{
                            fontSize: 12, fontWeight: 500, wordBreak: 'break-word',
                            filter: shouldBlur ? 'blur(5px)' : 'none', transition: 'filter 0.2s',
                            cursor: shouldBlur ? 'pointer' : 'default',
                            userSelect: shouldBlur ? 'none' : 'auto',
                          }}
                        >{f.value}</div>
                      </div>
                    )
                  })}
                </div>
              )}

              {/* Notizen */}
              {res.notes && (
                <div className="bg-surface-tertiary" style={{ padding: '8px 10px', borderRadius: 8 }}>
                  <div className="text-content-faint" style={{ fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 3 }}>{t('reservations.notes')}</div>
                  <div className="collab-note-md text-content" style={{ fontSize: 12, wordBreak: 'break-word', overflowWrap: 'anywhere' }}><Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{res.notes}</Markdown></div>
                </div>
              )}

              {/* Dateien */}
              {(() => {
                const resFiles = (useTripStore.getState().files || []).filter(f =>
                  !f.deleted_at && (
                    f.reservation_id === res.id ||
                    (f.linked_reservation_ids && f.linked_reservation_ids.includes(res.id))
                  )
                )
                if (resFiles.length === 0) return null
                return (
                  <div>
                    <div className="text-content-faint" style={{ fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 6 }}>{t('files.title')}</div>
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                      {resFiles.map(f => (
                        <div key={f.id}
                          onClick={() => { setTransportDetail(null); onNavigateToFiles?.() }}
                          className="bg-surface-tertiary"
                          style={{
                            display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px',
                            borderRadius: 8, cursor: 'pointer',
                            transition: 'background 0.1s',
                          }}
                          onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'}
                          onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
                        >
                          <FileText size={14} className="text-content-muted" style={{ flexShrink: 0 }} />
                          <span className="text-content" style={{ flex: 1, fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                            {f.original_name}
                          </span>
                          <ExternalLink size={11} className="text-content-faint" style={{ flexShrink: 0 }} />
                        </div>
                      ))}
                    </div>
                  </div>
                )
              })()}

              {/* Schließen */}
              <div style={{ textAlign: 'right' }}>
                <button onClick={() => setTransportDetail(null)} className="bg-accent text-accent-text" style={{
                  fontSize: 12,
                  border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600, fontFamily: 'inherit',
                }}>
                  {t('common.close')}
                </button>
              </div>
            </>
          )
        })()}
      </div>
    </div>,
    document.body
  )
}