feat: show event location
This commit is contained in:
parent
472347a365
commit
f727ade539
8 changed files with 191 additions and 25 deletions
1
drizzle/0001_magenta_naoko.sql
Normal file
1
drizzle/0001_magenta_naoko.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE "eventsTable" ADD COLUMN "location" text;
|
||||||
131
drizzle/meta/0001_snapshot.json
Normal file
131
drizzle/meta/0001_snapshot.json
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
{
|
||||||
|
"id": "f3f30a9d-d72d-4e62-86b4-031282707007",
|
||||||
|
"prevId": "c0102a7e-5d09-41ca-88d1-c66f2f299e65",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.eventsTable": {
|
||||||
|
"name": "eventsTable",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"startTime": {
|
||||||
|
"name": "startTime",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"endTime": {
|
||||||
|
"name": "endTime",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"name": "location",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lineId": {
|
||||||
|
"name": "lineId",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"eventsTable_lineId_linesTable_id_fk": {
|
||||||
|
"name": "eventsTable_lineId_linesTable_id_fk",
|
||||||
|
"tableFrom": "eventsTable",
|
||||||
|
"tableTo": "linesTable",
|
||||||
|
"columnsFrom": [
|
||||||
|
"lineId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.linesTable": {
|
||||||
|
"name": "linesTable",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,13 @@
|
||||||
"when": 1750786732013,
|
"when": 1750786732013,
|
||||||
"tag": "0000_noisy_omega_red",
|
"tag": "0000_noisy_omega_red",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1751371464551,
|
||||||
|
"tag": "0001_magenta_naoko",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"push-db": "drizzle-kit push"
|
"db-generate": "drizzle-kit generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electric-sql/pglite": "^0.3.3",
|
"@electric-sql/pglite": "^0.3.3",
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,16 @@ export interface Line {
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userAgentHeader = {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||||
|
};
|
||||||
|
|
||||||
export async function getLines() {
|
export async function getLines() {
|
||||||
const annotation = await fetch(
|
const annotation = await fetch(
|
||||||
"https://amber.festivalfantazie.cz/porady.php",
|
"https://amber.festivalfantazie.cz/porady.php",
|
||||||
{
|
{
|
||||||
headers: {
|
headers: userAgentHeader,
|
||||||
"User-Agent":
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -68,16 +70,14 @@ export interface Event {
|
||||||
description: string;
|
description: string;
|
||||||
type: string;
|
type: string;
|
||||||
lineId: number;
|
lineId: number;
|
||||||
|
location?: string; // Optional location field
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getScheduleForLine(lineId: number) {
|
export async function getScheduleForLine(lineId: number) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://amber.festivalfantazie.cz/program_linie.php?linie=${lineId}`,
|
`https://amber.festivalfantazie.cz/program_linie.php?linie=${lineId}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: userAgentHeader,
|
||||||
"User-Agent":
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -162,8 +162,7 @@ export async function getScheduleForLine(lineId: number) {
|
||||||
"https://amber.festivalfantazie.cz/ajax/anotace.php",
|
"https://amber.festivalfantazie.cz/ajax/anotace.php",
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent":
|
...userAgentHeader,
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0",
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
},
|
},
|
||||||
referrer: `https://amber.festivalfantazie.cz/program_linie.php?linie=${lineId}`,
|
referrer: `https://amber.festivalfantazie.cz/program_linie.php?linie=${lineId}`,
|
||||||
|
|
@ -182,6 +181,25 @@ export async function getScheduleForLine(lineId: number) {
|
||||||
const description$ = cheerio.load(descriptionHtml);
|
const description$ = cheerio.load(descriptionHtml);
|
||||||
const description = description$("description").text().trim();
|
const description = description$("description").text().trim();
|
||||||
|
|
||||||
|
const locationRes = await fetch(
|
||||||
|
`https://app.festivalfantazie.cz/program-detail/${eventIdNumber}`,
|
||||||
|
{
|
||||||
|
headers: userAgentHeader,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!locationRes.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch location for event ID ${eventIdNumber}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const locationHtml = await locationRes.text();
|
||||||
|
const location$ = cheerio.load(locationHtml);
|
||||||
|
const location = location$("div.row.mb-5 > div.col-6.mb-2:nth-of-type(2)")
|
||||||
|
.text()
|
||||||
|
.trim();
|
||||||
|
|
||||||
events.push({
|
events.push({
|
||||||
id: eventIdNumber,
|
id: eventIdNumber,
|
||||||
startTime,
|
startTime,
|
||||||
|
|
@ -191,6 +209,7 @@ export async function getScheduleForLine(lineId: number) {
|
||||||
description,
|
description,
|
||||||
type: $(td).find("td.program_typ").text().trim(),
|
type: $(td).find("td.program_typ").text().trim(),
|
||||||
lineId,
|
lineId,
|
||||||
|
location: location || undefined, // Optional location field
|
||||||
});
|
});
|
||||||
|
|
||||||
currentTime = new Date(endTime);
|
currentTime = new Date(endTime);
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@ import type { Event } from "@/common/parser";
|
||||||
import { isEventFavorite } from "@/common/utils";
|
import { isEventFavorite } from "@/common/utils";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { toggleFavoriteEvent } from "@/app/actions";
|
import { toggleFavoriteEvent } from "@/app/actions";
|
||||||
import {
|
import { HeartMinusIcon, HeartPlusIcon } from "lucide-react";
|
||||||
HeartMinusIcon,
|
|
||||||
HeartPlusIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { getLineById } from "@/db";
|
import { getLineById } from "@/db";
|
||||||
import { Badge } from "./ui/badge";
|
import { Badge } from "./ui/badge";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
@ -24,8 +21,15 @@ export async function EventCard({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-sm w-full">
|
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-sm w-full">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div>
|
||||||
<h3 className="text-lg font-semibold">{event.title}</h3>
|
<h3 className="text-lg font-semibold">{event.title}</h3>
|
||||||
|
{line && (
|
||||||
|
<Badge asChild variant="outline" className="my-2">
|
||||||
|
<Link href={`/line/${line.id}`}>{line.name}</Link>
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<form action={toggleFavoriteEvent}>
|
<form action={toggleFavoriteEvent}>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -34,23 +38,21 @@ export async function EventCard({
|
||||||
hidden
|
hidden
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<Button variant={isFavorite ? "default" : "secondary"} className="cursor-pointer">
|
<Button
|
||||||
|
variant={isFavorite ? "default" : "secondary"}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
{isFavorite ? <HeartMinusIcon /> : <HeartPlusIcon />}
|
{isFavorite ? <HeartMinusIcon /> : <HeartPlusIcon />}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{line && (
|
|
||||||
<Badge asChild variant="outline" className='my-2'>
|
|
||||||
<Link href={`/line/${line.id}`}>{line.name}</Link>
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
<h6 className="text-xs text-gray-600 dark:text-gray-400">{event.name}</h6>
|
<h6 className="text-xs text-gray-600 dark:text-gray-400">{event.name}</h6>
|
||||||
<h5 className="text-md text-gray-500 dark:text-gray-300">
|
<h5 className="text-md text-gray-500 dark:text-gray-300">
|
||||||
{showDate
|
{showDate
|
||||||
? `${event.startTime.toLocaleDateString(["cs-CZ"], {
|
? `${event.startTime.toLocaleDateString(["cs-CZ"], {
|
||||||
month: "numeric",
|
month: "numeric",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
weekday: 'short',
|
weekday: "short",
|
||||||
})} `
|
})} `
|
||||||
: ""}
|
: ""}
|
||||||
{event.startTime.toLocaleTimeString(["cs-CZ"], {
|
{event.startTime.toLocaleTimeString(["cs-CZ"], {
|
||||||
|
|
@ -65,6 +67,11 @@ export async function EventCard({
|
||||||
hour12: false,
|
hour12: false,
|
||||||
})}
|
})}
|
||||||
</h5>
|
</h5>
|
||||||
|
{event.location && (
|
||||||
|
<h6 className="text-xs text-gray-500 dark:text-gray-300">
|
||||||
|
{event.location}
|
||||||
|
</h6>
|
||||||
|
)}
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{event.description}
|
{event.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Event } from "@/common/parser";
|
import type { Event } from "@/common/parser";
|
||||||
import { EventCard } from "./EventCard";
|
import { EventCard } from "./EventCard";
|
||||||
|
|
||||||
export function EventList({
|
export function EventList({
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export const eventsTable = pgTable("eventsTable", {
|
||||||
title: text().notNull(),
|
title: text().notNull(),
|
||||||
description: text().notNull(),
|
description: text().notNull(),
|
||||||
type: text().notNull(),
|
type: text().notNull(),
|
||||||
|
location: text(), // Optional location field
|
||||||
lineId: integer()
|
lineId: integer()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => linesTable.id, {
|
.references(() => linesTable.id, {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue