File size: 6,624 Bytes
766d85d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { MapPin, Camera, Smile, Laugh, Meh, Frown, Sun, CloudSun, Cloud, CloudRain, CloudLightning, Snowflake } from 'lucide-react'
import { formatLocationName } from '../../utils/formatters'
import type { JourneyEntry, JourneyPhoto } from '../../store/journeyStore'

const MOOD_ICONS: Record<string, typeof Smile> = {
  amazing: Laugh,
  good: Smile,
  neutral: Meh,
  rough: Frown,
}

const MOOD_COLORS: Record<string, string> = {
  amazing: 'text-pink-500',
  good: 'text-amber-500',
  neutral: 'text-zinc-400',
  rough: 'text-violet-500',
}

const WEATHER_ICONS: Record<string, typeof Sun> = {
  sunny: Sun,
  partly: CloudSun,
  cloudy: Cloud,
  rainy: CloudRain,
  stormy: CloudLightning,
  cold: Snowflake,
}

function photoUrl(p: JourneyPhoto): string {
  return `/api/photos/${p.photo_id}/thumbnail`
}

function stripMarkdown(text: string): string {
  return text
    .replace(/[#*_~`>\[\]()!|-]/g, '')
    .replace(/\n+/g, ' ')
    .trim()
}

interface Props {
  entry: JourneyEntry | { id: number; type: string; title?: string | null; location_name?: string | null; location_lat?: number | null; location_lng?: number | null; entry_date: string; entry_time?: string | null; mood?: string | null; weather?: string | null; photos?: { photo_id: number }[]; story?: string | null }
  dayLabel: number
  dayColor: string
  isActive: boolean
  onClick: () => void
  publicPhotoUrl?: (photoId: number) => string
}

export default function MobileEntryCard({ entry, dayLabel, dayColor, isActive, onClick, publicPhotoUrl }: Props) {
  const hasLocation = !!(entry.location_lat && entry.location_lng)
  const hasPhotos = entry.photos && entry.photos.length > 0
  const firstPhoto = hasPhotos ? entry.photos![0] : null
  const MoodIcon = entry.mood ? MOOD_ICONS[entry.mood] : null
  const moodColor = entry.mood ? MOOD_COLORS[entry.mood] : ''
  const WeatherIcon = entry.weather ? WEATHER_ICONS[entry.weather] : null

  const thumbSrc = firstPhoto
    ? publicPhotoUrl
      ? publicPhotoUrl((firstPhoto as any).photo_id ?? (firstPhoto as any).id)
      : photoUrl(firstPhoto as JourneyPhoto)
    : null

  const date = new Date(entry.entry_date + 'T00:00:00')
  const dateStr = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })

  const storyPreview = entry.story ? stripMarkdown(entry.story) : ''

  return (
    <button
      onClick={onClick}
      className={`flex-shrink-0 rounded-xl overflow-hidden text-left transition-all duration-100 ${
        isActive
          ? 'w-[320px] sm:w-[340px] bg-white dark:bg-zinc-800 shadow-lg ring-2 ring-zinc-900/70 dark:ring-white/60'
          : 'w-[240px] sm:w-[260px] bg-white/90 dark:bg-zinc-800/90 shadow-md'
      } backdrop-blur-lg`}
    >
      <div className={`flex ${isActive ? 'h-[140px]' : 'h-[110px]'} transition-all duration-100`}>
        {/* Photo thumbnail */}
        {thumbSrc ? (
          <div className={`${isActive ? 'w-[110px]' : 'w-[90px]'} flex-shrink-0 relative overflow-hidden transition-all duration-100`}>
            <img
              src={thumbSrc}
              alt=""
              className="w-full h-full object-cover"
              loading="lazy"
            />
            {hasPhotos && entry.photos!.length > 1 && (
              <div className="absolute bottom-1 right-1 flex items-center gap-0.5 bg-black/60 text-white rounded px-1 py-0.5 text-[10px] font-medium">
                <Camera size={10} />
                {entry.photos!.length}
              </div>
            )}
          </div>
        ) : (
          <div className={`${isActive ? 'w-[110px]' : 'w-[90px]'} flex-shrink-0 bg-zinc-100 dark:bg-zinc-700 flex items-center justify-center transition-all duration-100`}>
            <MapPin size={20} className="text-zinc-300 dark:text-zinc-500" />
          </div>
        )}

        {/* Content */}
        <div className="flex-1 p-3 flex flex-col min-w-0">
          {/* Day number + date + mood/weather */}
          <div className="flex items-center gap-1.5 mb-1">
            <span className="w-5 h-5 rounded text-white text-[10px] font-bold flex items-center justify-center flex-shrink-0" style={{ background: dayColor }}>
              {dayLabel}
            </span>
            <span className="text-[11px] text-zinc-400 font-medium">{dateStr}</span>
            {entry.entry_time && (
              <span className="text-[11px] text-zinc-400">· {entry.entry_time.slice(0, 5)}</span>
            )}
            <div className="flex items-center gap-1.5 ml-auto flex-shrink-0">
              {MoodIcon && (
                <span className={`inline-flex items-center justify-center w-5 h-5 rounded-full ${
                  entry.mood === 'amazing' ? 'bg-pink-100 dark:bg-pink-900/30' :
                  entry.mood === 'good' ? 'bg-amber-100 dark:bg-amber-900/30' :
                  entry.mood === 'rough' ? 'bg-violet-100 dark:bg-violet-900/30' :
                  'bg-zinc-100 dark:bg-zinc-700'
                }`}>
                  <MoodIcon size={11} className={moodColor} />
                </span>
              )}
              {WeatherIcon && (
                <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-zinc-100 dark:bg-zinc-700">
                  <WeatherIcon size={11} className="text-zinc-500 dark:text-zinc-400" />
                </span>
              )}
            </div>
          </div>

          {/* Title */}
          <h4 className="text-[13px] font-semibold text-zinc-900 dark:text-white leading-tight truncate">
            {entry.title || (entry.type === 'checkin' ? 'Check-in' : entry.type === 'skeleton' ? 'Add your story…' : 'Untitled')}
          </h4>

          {/* Story preview (1-2 lines, only on active card) */}
          {isActive && storyPreview && (
            <p className="text-[11px] text-zinc-400 dark:text-zinc-500 leading-snug mt-0.5 line-clamp-2">
              {storyPreview}
            </p>
          )}

          {/* Location badge */}
          <div className="flex items-center gap-1 mt-auto">
            {hasLocation ? (
              <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-zinc-100 dark:bg-zinc-700 text-[10px] font-medium text-zinc-600 dark:text-zinc-300 max-w-full overflow-hidden">
                <MapPin size={10} className="flex-shrink-0" />
                <span className="truncate">{formatLocationName(entry.location_name) || 'On the map'}</span>
              </span>
            ) : (
              <span className="text-[10px] text-zinc-400 italic">No location</span>
            )}
          </div>
        </div>
      </div>
    </button>
  )
}