Etter Steder er optimalisert
This commit is contained in:
parent
356a8642b9
commit
a0af7a1151
4 changed files with 126 additions and 25 deletions
107
backend/main.py
107
backend/main.py
|
|
@ -2283,10 +2283,113 @@ async def upsert_facility_rating(request: Request, payload: FacilityRatingUpsert
|
|||
# --- DATA ENDPOINTS ---
|
||||
|
||||
@app.get("/api/facilities")
|
||||
async def get_facilities():
|
||||
async def get_facilities(summary: bool = False):
|
||||
"""Henter alle golfanlegg med aggregert banestatus for forsiden."""
|
||||
async with app.state.pool.acquire() as conn:
|
||||
rows = await conn.fetch("""
|
||||
if summary:
|
||||
rows = await conn.fetch("""
|
||||
SELECT
|
||||
f.id,
|
||||
f.slug,
|
||||
f.name,
|
||||
f.architect,
|
||||
f.description,
|
||||
f.city,
|
||||
f.county,
|
||||
f.banetype,
|
||||
f.image_url,
|
||||
f.phone,
|
||||
f.website_url,
|
||||
f.golfbox_booking_url,
|
||||
f.golfbox_tournament_url,
|
||||
f.weather_url,
|
||||
f.lat,
|
||||
f.lng,
|
||||
f.golfamore,
|
||||
f.golfamore_url,
|
||||
f.nsg_url,
|
||||
f.greenfee,
|
||||
f.standard_medlemskap,
|
||||
f.vtg_pris,
|
||||
f.vtg_lenke,
|
||||
f.vtg_beskrivelse,
|
||||
f.amenities,
|
||||
f.golfamore_data,
|
||||
f.nsg_data,
|
||||
f.vtg_datoer,
|
||||
f.footnote,
|
||||
f.footnote_updated_at,
|
||||
f.status_updated_at,
|
||||
(
|
||||
SELECT jsonb_agg(cs) FROM (
|
||||
SELECT id, name, status FROM courses
|
||||
WHERE facility_id = f.id AND status != 'finnes_ingen_bane_to'
|
||||
ORDER BY is_main_course DESC, id ASC
|
||||
) cs
|
||||
) as course_statuses,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM holes h
|
||||
JOIN courses c ON c.id = h.course_id
|
||||
WHERE c.facility_id = f.id
|
||||
) as total_hole_count,
|
||||
(
|
||||
SELECT jsonb_build_object(
|
||||
'3', COUNT(*) FILTER (WHERE h.par = 3),
|
||||
'4', COUNT(*) FILTER (WHERE h.par = 4),
|
||||
'5', COUNT(*) FILTER (WHERE h.par = 5),
|
||||
'6', COUNT(*) FILTER (WHERE h.par = 6)
|
||||
)
|
||||
FROM holes h
|
||||
JOIN courses c ON c.id = h.course_id
|
||||
WHERE c.facility_id = f.id
|
||||
) as hole_par_counts,
|
||||
(
|
||||
SELECT MIN((length_value.value)::int)
|
||||
FROM holes h
|
||||
JOIN courses c ON c.id = h.course_id
|
||||
CROSS JOIN LATERAL jsonb_each_text(COALESCE(h.lengths, '{}'::jsonb)) AS length_value(key, value)
|
||||
WHERE c.facility_id = f.id
|
||||
AND length_value.key IN ('kortest', 'kort', 'mellomkort', 'mellomlang', 'lang', 'lengst')
|
||||
AND length_value.value ~ '^[0-9]+$'
|
||||
AND (length_value.value)::int BETWEEN 30 AND 900
|
||||
) as shortest_hole_meters,
|
||||
(
|
||||
SELECT MAX((length_value.value)::int)
|
||||
FROM holes h
|
||||
JOIN courses c ON c.id = h.course_id
|
||||
CROSS JOIN LATERAL jsonb_each_text(COALESCE(h.lengths, '{}'::jsonb)) AS length_value(key, value)
|
||||
WHERE c.facility_id = f.id
|
||||
AND length_value.key IN ('kortest', 'kort', 'mellomkort', 'mellomlang', 'lang', 'lengst')
|
||||
AND length_value.value ~ '^[0-9]+$'
|
||||
AND (length_value.value)::int BETWEEN 30 AND 900
|
||||
) as longest_hole_meters,
|
||||
(
|
||||
SELECT jsonb_agg(w_data ORDER BY w_data.day_offset ASC) FROM (
|
||||
SELECT
|
||||
forecast_date,
|
||||
day_offset,
|
||||
dry_all_day,
|
||||
dry_daylight,
|
||||
precip_mm,
|
||||
precip_probability_max,
|
||||
daylight_precip_mm,
|
||||
daylight_precip_probability_max,
|
||||
confidence,
|
||||
source_updated_at,
|
||||
source_expires_at,
|
||||
calculated_at
|
||||
FROM facility_weather_forecast
|
||||
WHERE facility_id = f.id
|
||||
ORDER BY day_offset ASC
|
||||
) w_data
|
||||
) as weather_forecast
|
||||
FROM facilities f
|
||||
WHERE COALESCE(f.is_published, TRUE) = TRUE
|
||||
ORDER BY f.name ASC
|
||||
""")
|
||||
else:
|
||||
rows = await conn.fetch("""
|
||||
SELECT f.*, (
|
||||
SELECT jsonb_agg(cs) FROM (
|
||||
SELECT id, name, status FROM courses
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export type FacilityRecord = {
|
|||
id: number;
|
||||
slug: string;
|
||||
name: string;
|
||||
architect?: string | null;
|
||||
description?: string | null;
|
||||
city?: string | null;
|
||||
county?: string | null;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,25 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect, useState } from "react";
|
||||
import FacilitySearch from "@/app/FacilitySearch";
|
||||
import PlaceMap from "@/components/PlaceMap";
|
||||
import {
|
||||
type EnrichedFacility,
|
||||
type FacilityRecord,
|
||||
enrichFacilities,
|
||||
filterFacilitiesByArea,
|
||||
getPlacePreposition,
|
||||
} from "@/app/facilityData";
|
||||
|
||||
const PlaceMap = dynamic(() => import("@/components/PlaceMap"), {
|
||||
loading: () => (
|
||||
<div className="mt-8 overflow-hidden rounded-[2rem] border border-[#D7DED0] bg-white shadow-sm">
|
||||
<div className="flex h-[26rem] w-full items-center justify-center bg-[#F3F6EE] text-sm font-bold text-[#617063] sm:h-[34rem]">
|
||||
Laster kart…
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
type PlaceExplorerProps = {
|
||||
facilities: FacilityRecord[];
|
||||
facilities: EnrichedFacility[];
|
||||
placeLabel: string;
|
||||
placeAreaFilter: string;
|
||||
placeTitle: string;
|
||||
|
|
@ -24,15 +31,11 @@ export default function PlaceExplorer({
|
|||
placeAreaFilter,
|
||||
placeTitle,
|
||||
}: PlaceExplorerProps) {
|
||||
const facilitiesInPlace = useMemo(
|
||||
() => filterFacilitiesByArea(enrichFacilities(facilities), placeAreaFilter),
|
||||
[facilities, placeAreaFilter]
|
||||
);
|
||||
const [filteredFacilities, setFilteredFacilities] = useState<EnrichedFacility[]>(facilitiesInPlace);
|
||||
const [filteredFacilities, setFilteredFacilities] = useState<EnrichedFacility[]>(facilities);
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredFacilities(facilitiesInPlace);
|
||||
}, [facilitiesInPlace]);
|
||||
setFilteredFacilities(facilities);
|
||||
}, [facilities]);
|
||||
|
||||
const preposition = getPlacePreposition(placeLabel);
|
||||
const filterHeading = `Filtrer golfbaner ${preposition} ${placeLabel}`;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
type FacilityRecord,
|
||||
enrichFacilities,
|
||||
filterFacilitiesByArea,
|
||||
getAvailablePlaceConfigs,
|
||||
getPlaceConfigFromSlug,
|
||||
getPlacePreposition,
|
||||
} from "@/app/facilityData";
|
||||
|
|
@ -23,11 +22,7 @@ import {
|
|||
} from "@/app/seo";
|
||||
|
||||
export const dynamicParams = true;
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return getAvailablePlaceConfigs().map((slug) => ({ slug }));
|
||||
}
|
||||
export const revalidate = 3600;
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
|
|
@ -63,9 +58,8 @@ export default async function PlacePage({ params }: { params: Promise<{ slug: st
|
|||
let facilities: FacilityRecord[] = [];
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/facilities`, {
|
||||
next: { revalidate: 0 },
|
||||
cache: "no-store",
|
||||
const res = await fetch(`${API_URL}/facilities?summary=1`, {
|
||||
next: { revalidate },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
|
@ -211,7 +205,7 @@ export default async function PlacePage({ params }: { params: Promise<{ slug: st
|
|||
</section>
|
||||
|
||||
<PlaceExplorer
|
||||
facilities={safeData}
|
||||
facilities={facilitiesInPlace}
|
||||
placeLabel={place.label}
|
||||
placeAreaFilter={place.areaFilter}
|
||||
placeTitle={place.title}
|
||||
|
|
|
|||
Loading…
Reference in a new issue