Edit app-9xzmfic2e4g1/src/components/trip/Timeline.tsx via Editor
This commit is contained in:
parent
044b506f7a
commit
3cb97d4eee
@ -38,13 +38,11 @@ function formatTime(mins: number): string {
|
|||||||
return `${String(Math.floor(mins / 60) % 24).padStart(2, '0')}:${String(mins % 60).padStart(2, '0')}`;
|
return `${String(Math.floor(mins / 60) % 24).padStart(2, '0')}:${String(mins % 60).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compute end_time from start_time + estimated_duration_minutes */
|
|
||||||
function calcEndTime(item: Place): string {
|
function calcEndTime(item: Place): string {
|
||||||
const start = parseMinutes(item.start_time || '09:00');
|
const start = parseMinutes(item.start_time || '09:00');
|
||||||
return formatTime(start + (item.estimated_duration_minutes || 60));
|
return formatTime(start + (item.estimated_duration_minutes || 60));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Time-of-day segment label */
|
|
||||||
function getSegment(time: string): 'morning' | 'afternoon' | 'evening' {
|
function getSegment(time: string): 'morning' | 'afternoon' | 'evening' {
|
||||||
const mins = parseMinutes(time);
|
const mins = parseMinutes(time);
|
||||||
if (mins < 12 * 60) return 'morning';
|
if (mins < 12 * 60) return 'morning';
|
||||||
@ -53,9 +51,9 @@ function getSegment(time: string): 'morning' | 'afternoon' | 'evening' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SEGMENT_META = {
|
const SEGMENT_META = {
|
||||||
morning: { label: 'Sabah', icon: Sun, color: 'text-amber-500' },
|
morning: { label: 'Sabah', icon: Sun, color: 'text-amber-500' },
|
||||||
afternoon: { label: 'Öğleden Sonra', icon: Coffee, color: 'text-orange-500' },
|
afternoon: { label: 'Öğleden Sonra', icon: Coffee, color: 'text-orange-500' },
|
||||||
evening: { label: 'Akşam', icon: Sunset, color: 'text-rose-500' },
|
evening: { label: 'Akşam', icon: Sunset, color: 'text-rose-500' },
|
||||||
};
|
};
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
@ -112,7 +110,6 @@ function DaySection({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Group items by time-of-day
|
|
||||||
const grouped = useMemo(() => {
|
const grouped = useMemo(() => {
|
||||||
const segments: Record<string, Place[]> = {};
|
const segments: Record<string, Place[]> = {};
|
||||||
for (const item of day.items) {
|
for (const item of day.items) {
|
||||||
@ -189,7 +186,6 @@ function DaySection({
|
|||||||
if (!items?.length) return null;
|
if (!items?.length) return null;
|
||||||
const meta = SEGMENT_META[seg];
|
const meta = SEGMENT_META[seg];
|
||||||
const SegIcon = meta.icon;
|
const SegIcon = meta.icon;
|
||||||
const globalOffset = day.items.indexOf(items[0]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={seg} className="space-y-3">
|
<div key={seg} className="space-y-3">
|
||||||
@ -272,7 +268,7 @@ function DaySection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
// SortableItem — redesigned with larger photo
|
// SortableItem — Wanderlog stili küçük thumbnail
|
||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
function SortableItem({
|
function SortableItem({
|
||||||
item, index, isActive, onClick, onDelete, onUpdateNote
|
item, index, isActive, onClick, onDelete, onUpdateNote
|
||||||
@ -319,66 +315,24 @@ function SortableItem({
|
|||||||
: "border-gray-100 hover:border-gray-200 hover:shadow-md"
|
: "border-gray-100 hover:border-gray-200 hover:shadow-md"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Photo (16:9) */}
|
<CardContent className="p-3">
|
||||||
{photoUrl && !imgError && (
|
{/* Ana satır */}
|
||||||
<div className="relative w-full aspect-video overflow-hidden bg-gray-100">
|
<div className="flex items-start gap-2">
|
||||||
{!imgLoaded && (
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-gray-200 via-gray-100 to-gray-200 animate-pulse" />
|
{/* Index numarası */}
|
||||||
)}
|
<div className="w-6 h-6 rounded-full bg-orange-100 flex items-center justify-center text-orange-700 font-black text-[10px] shrink-0 mt-0.5">
|
||||||
<img
|
|
||||||
src={photoUrl}
|
|
||||||
alt={item.name}
|
|
||||||
onLoad={() => setImgLoaded(true)}
|
|
||||||
onError={() => setImgError(true)}
|
|
||||||
className={cn(
|
|
||||||
"w-full h-full object-cover transition-all duration-500",
|
|
||||||
imgLoaded ? "opacity-100 scale-100" : "opacity-0 scale-105"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* Time badge on photo */}
|
|
||||||
<div className="absolute top-3 left-3 bg-black/60 backdrop-blur-sm text-white text-[10px] font-black px-2 py-1 rounded-lg">
|
|
||||||
{item.start_time || '09:00'} – {endTime}
|
|
||||||
</div>
|
|
||||||
{/* Rating badge */}
|
|
||||||
{item.rating && (
|
|
||||||
<div className="absolute top-3 right-3 flex items-center gap-1 bg-white/90 backdrop-blur-sm text-gray-900 text-[10px] font-black px-2 py-1 rounded-lg">
|
|
||||||
<Star className="h-2.5 w-2.5 fill-amber-400 text-amber-400" />
|
|
||||||
{item.rating}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* Index bubble */}
|
|
||||||
<div className="absolute bottom-3 left-3 w-7 h-7 rounded-full bg-white/90 backdrop-blur-sm flex items-center justify-center text-gray-900 font-black text-xs shadow-sm">
|
|
||||||
{index + 1}
|
{index + 1}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CardContent className="p-3">
|
{/* Bilgi alanı */}
|
||||||
{/* No photo: compact row layout */}
|
|
||||||
{(!photoUrl || imgError) && (
|
|
||||||
<div className="flex items-center gap-3 mb-2">
|
|
||||||
<div className="w-8 h-8 rounded-full bg-orange-100 flex items-center justify-center text-orange-700 font-black text-xs shrink-0">
|
|
||||||
{index + 1}
|
|
||||||
</div>
|
|
||||||
{item.start_time && (
|
|
||||||
<span className="text-[10px] font-bold text-gray-500 bg-gray-100 px-2 py-0.5 rounded-md">
|
|
||||||
{item.start_time} – {endTime}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{item.rating && (
|
|
||||||
<span className="flex items-center gap-1 text-[10px] font-bold text-gray-500">
|
|
||||||
<Star className="h-3 w-3 fill-amber-400 text-amber-400" />
|
|
||||||
{item.rating}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Name + actions */}
|
|
||||||
<div className="flex items-start justify-between gap-2">
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="text-sm font-black text-gray-900 truncate">{item.name}</h4>
|
<h4 className="text-sm font-black text-gray-900 truncate">{item.name}</h4>
|
||||||
<div className="flex items-center gap-2 mt-1">
|
<div className="flex items-center gap-1.5 mt-0.5 flex-wrap">
|
||||||
|
{item.start_time && (
|
||||||
|
<span className="text-[10px] font-bold text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded-md">
|
||||||
|
{item.start_time} – {endTime}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Badge variant="secondary" className="bg-gray-100 text-gray-500 text-[9px] font-bold px-1.5 py-0 h-4 border-0">
|
<Badge variant="secondary" className="bg-gray-100 text-gray-500 text-[9px] font-bold px-1.5 py-0 h-4 border-0">
|
||||||
{item.category}
|
{item.category}
|
||||||
</Badge>
|
</Badge>
|
||||||
@ -388,9 +342,35 @@ function SortableItem({
|
|||||||
{item.estimated_duration_minutes}dk
|
{item.estimated_duration_minutes}dk
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{item.rating && (
|
||||||
|
<span className="flex items-center gap-0.5 text-[9px] font-bold text-gray-400">
|
||||||
|
<Star className="h-2.5 w-2.5 fill-amber-400 text-amber-400" />
|
||||||
|
{item.rating}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Küçük thumbnail */}
|
||||||
|
{photoUrl && !imgError && (
|
||||||
|
<div className="relative w-16 h-16 rounded-xl overflow-hidden bg-gray-100 shrink-0">
|
||||||
|
{!imgLoaded && (
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-gray-200 via-gray-100 to-gray-200 animate-pulse" />
|
||||||
|
)}
|
||||||
|
<img
|
||||||
|
src={photoUrl}
|
||||||
|
alt={item.name}
|
||||||
|
onLoad={() => setImgLoaded(true)}
|
||||||
|
onError={() => setImgError(true)}
|
||||||
|
className={cn(
|
||||||
|
"w-full h-full object-cover transition-opacity duration-300",
|
||||||
|
imgLoaded ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Aksiyonlar (hover'da görünür) */}
|
||||||
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-all shrink-0">
|
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-all shrink-0">
|
||||||
<div
|
<div
|
||||||
{...attributes} {...listeners}
|
{...attributes} {...listeners}
|
||||||
@ -406,7 +386,10 @@ function SortableItem({
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-36 rounded-xl p-1">
|
<DropdownMenuContent align="end" className="w-36 rounded-xl p-1">
|
||||||
<DropdownMenuItem onClick={e => { e.stopPropagation(); setIsEditingNote(true); }} className="text-xs font-bold gap-2 rounded-lg">
|
<DropdownMenuItem
|
||||||
|
onClick={e => { e.stopPropagation(); setIsEditingNote(true); }}
|
||||||
|
className="text-xs font-bold gap-2 rounded-lg"
|
||||||
|
>
|
||||||
<Edit3 className="h-3.5 w-3.5 text-orange-500" />Not Düzenle
|
<Edit3 className="h-3.5 w-3.5 text-orange-500" />Not Düzenle
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@ -420,7 +403,7 @@ function SortableItem({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Note */}
|
{/* Not düzenleme alanı */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isEditingNote && (
|
{isEditingNote && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -448,6 +431,7 @@ function SortableItem({
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* Mevcut not gösterimi */}
|
||||||
{!isEditingNote && item.notes && (
|
{!isEditingNote && item.notes && (
|
||||||
<div className="mt-2.5 p-2 bg-orange-50 rounded-xl border-l-2 border-orange-500">
|
<div className="mt-2.5 p-2 bg-orange-50 rounded-xl border-l-2 border-orange-500">
|
||||||
<p className="text-[11px] font-medium italic text-orange-800">{item.notes}</p>
|
<p className="text-[11px] font-medium italic text-orange-800">{item.notes}</p>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user