summaryrefslogtreecommitdiff
path: root/app/components/EventCard.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'app/components/EventCard.tsx')
-rw-r--r--app/components/EventCard.tsx89
1 files changed, 89 insertions, 0 deletions
diff --git a/app/components/EventCard.tsx b/app/components/EventCard.tsx
new file mode 100644
index 0000000..6651ff9
--- /dev/null
+++ b/app/components/EventCard.tsx
@@ -0,0 +1,89 @@
+import { Link } from "react-router";
+import type { Event } from "~/lib/db.server";
+
+interface Props {
+ event: Event;
+}
+
+export default function EventCard({ event }: Props) {
+ const formattedDate = formatDate(event.date);
+ const timeLabel = buildTimeLabel(event.open_time, event.start_time);
+
+ return (
+ <Link
+ to={`/events/${event.id}`}
+ className="group flex flex-col rounded-xl bg-gray-800/60 border border-gray-700/40 overflow-hidden hover:border-indigo-500/60 hover:bg-gray-800 transition-all"
+ >
+ {event.image_url ? (
+ <img
+ src={event.image_url}
+ alt={event.title}
+ className="h-36 w-full object-cover"
+ />
+ ) : (
+ <div className="h-36 w-full bg-gradient-to-br from-gray-800 to-gray-900 flex items-center justify-center">
+ <span className="text-4xl opacity-20">🎸</span>
+ </div>
+ )}
+
+ <div className="flex-1 p-4 flex flex-col gap-2">
+ {/* Title */}
+ <h2 className="font-semibold text-sm leading-snug group-hover:text-indigo-300 transition-colors line-clamp-2">
+ {event.title}
+ </h2>
+
+ {/* Artist — required */}
+ <p className="text-xs text-indigo-300 font-medium line-clamp-1">
+ {event.artist ?? "出演者未定"}
+ </p>
+
+ {/* Date + time */}
+ <div className="flex items-center gap-2 text-xs text-gray-300">
+ <span>📅 {formattedDate}</span>
+ {timeLabel && <span className="text-gray-500">| {timeLabel}</span>}
+ </div>
+
+ {/* Venue */}
+ <div className="flex items-center gap-1 text-xs text-gray-400">
+ <span>📍</span>
+ <span className="rounded-full bg-gray-700/60 px-2 py-0.5">
+ {event.venue_name}
+ {event.venue_area ? `(${event.venue_area})` : ""}
+ </span>
+ </div>
+
+ {/* Fee */}
+ {event.price && (
+ <p className="text-xs text-emerald-400">¥ {event.price}</p>
+ )}
+
+ {/* Ticket URL */}
+ {event.ticket_url && (
+ <a
+ href={event.ticket_url}
+ target="_blank"
+ rel="noopener noreferrer"
+ onClick={(e) => e.stopPropagation()}
+ className="mt-auto inline-flex items-center gap-1 text-xs text-indigo-400 hover:underline"
+ >
+ 🎟 チケット
+ </a>
+ )}
+ </div>
+ </Link>
+ );
+}
+
+function formatDate(iso: string): string {
+ const [y, m, d] = iso.split("-");
+ const days = ["日", "月", "火", "水", "木", "金", "土"];
+ const dayIdx = new Date(`${iso}T00:00:00`).getDay();
+ return `${y}/${m}/${d}(${days[dayIdx]})`;
+}
+
+function buildTimeLabel(open: string | null, start: string | null): string {
+ const parts: string[] = [];
+ if (open) parts.push(`OPEN ${open}`);
+ if (start) parts.push(`START ${start}`);
+ return parts.join(" / ");
+}