Initial commit: Japan PWA guide
This commit is contained in:
103
src/components/PlaceCard.tsx
Normal file
103
src/components/PlaceCard.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Place } from '../types';
|
||||
|
||||
interface PlaceCardProps {
|
||||
place: Place;
|
||||
onClick: () => void;
|
||||
showDay?: boolean;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const getCategoryIcon = (category: string): string => {
|
||||
const icons: Record<string, string> = {
|
||||
sight: '🏯',
|
||||
restaurant: '🍜',
|
||||
coffee: '☕',
|
||||
snack: '🍡',
|
||||
hotel: '🏨',
|
||||
};
|
||||
return icons[category] || '📍';
|
||||
};
|
||||
|
||||
const getCategoryLabel = (category: string): string => {
|
||||
const labels: Record<string, string> = {
|
||||
sight: 'Достопримечательность',
|
||||
restaurant: 'Ресторан',
|
||||
coffee: 'Кофейня',
|
||||
snack: 'Перекус',
|
||||
hotel: 'Отель',
|
||||
};
|
||||
return labels[category] || 'Место';
|
||||
};
|
||||
|
||||
export function PlaceCard({ place, onClick, showDay = false, compact = false }: PlaceCardProps) {
|
||||
if (compact) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="flex items-center gap-3 w-full p-3 bg-white rounded-xl shadow-sm border border-gray-100 card-hover text-left"
|
||||
>
|
||||
<span className="text-2xl">{getCategoryIcon(place.category)}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-gray-900 truncate">{place.name}</h3>
|
||||
<p className="text-xs text-gray-500">{place.nameJp}</p>
|
||||
</div>
|
||||
{place.rating === 5 && <span className="text-yellow-500">★</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="w-full text-left bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden card-hover"
|
||||
>
|
||||
{/* Image placeholder */}
|
||||
<div className="h-32 bg-gradient-to-br from-gray-100 to-gray-200 relative">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-5xl opacity-50">{getCategoryIcon(place.category)}</span>
|
||||
</div>
|
||||
{place.rating === 5 && (
|
||||
<div className="absolute top-2 right-2 bg-white/90 backdrop-blur-sm px-2 py-1 rounded-full">
|
||||
<span className="text-yellow-500 text-sm">★ Must See</span>
|
||||
</div>
|
||||
)}
|
||||
{showDay && place.day && (
|
||||
<div className="absolute top-2 left-2 bg-[#FF6B6B] text-white px-2 py-1 rounded-full text-xs font-medium">
|
||||
День {place.day}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900 text-lg leading-tight">{place.name}</h3>
|
||||
<p className="text-sm text-gray-400">{place.nameJp}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600 line-clamp-2 mb-3">{place.description}</p>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs">
|
||||
<span className="px-2 py-1 bg-gray-100 rounded-full text-gray-600">
|
||||
{getCategoryLabel(place.category)}
|
||||
</span>
|
||||
{place.duration && (
|
||||
<span className="px-2 py-1 bg-gray-100 rounded-full text-gray-600">
|
||||
⏱️ {place.duration}
|
||||
</span>
|
||||
)}
|
||||
{place.price && (
|
||||
<span className={"px-2 py-1 rounded-full " +
|
||||
(place.price === 'Бесплатно'
|
||||
? "bg-green-50 text-green-600"
|
||||
: "bg-[#FFE8E8] text-[#FF6B6B]")
|
||||
}>
|
||||
{place.price === 'Бесплатно' ? '✓ Бесплатно' : place.price}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user