Dorabiamy funkcjonalnosci w TV

This commit is contained in:
dm
2025-12-12 14:58:13 +01:00
parent 0f17daee17
commit ee51e0816d
29 changed files with 1975 additions and 455 deletions

View File

@@ -1,13 +0,0 @@
import type { APIRoute } from "astro";
import { getDb } from "./db";
type CityRow = { city: string };
export const GET: APIRoute = async () => {
const db = getDb();
const rows = db.prepare("SELECT DISTINCT city FROM ranges ORDER BY city").all() as CityRow[];
return new Response(JSON.stringify(rows.map((x) => x.city)), {
headers: { "Content-Type": "application/json" },
});
};

View File

@@ -3,7 +3,7 @@ import Database from "better-sqlite3";
const DB_PATH =
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
let db: Database.Database;
let db;
export function getDb() {
if (!db) {

View File

@@ -0,0 +1,54 @@
import Database from "better-sqlite3";
const DB_PATH = process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
export async function GET({ url }) {
const db = new Database(DB_PATH, { readonly: true });
try {
const packageId = Number(url.searchParams.get("packageId") || 0);
if (!packageId) {
return new Response(
JSON.stringify({ ok: false, error: "MISSING_PACKAGE_ID" }),
{ status: 400, headers: { "Content-Type": "application/json; charset=utf-8" } }
);
}
const rows = db
.prepare(
`
SELECT
a.id AS id,
a.name AS name,
a.type AS type,
CAST(o.price AS REAL) AS price
FROM jambox_package_addon_options o
JOIN internet_addons a
ON a.id = o.addon_id
WHERE o.package_id = ?
ORDER BY a.type, a.name
`
)
.all(packageId);
return new Response(
JSON.stringify({
ok: true,
count: rows.length,
data: rows,
}),
{
status: 200,
headers: { "Content-Type": "application/json; charset=utf-8" },
}
);
} catch (err) {
console.error("❌ Błąd w /api/jambox/addons:", err);
return new Response(
JSON.stringify({ ok: false, error: err.message || "DB_ERROR" }),
{ status: 500, headers: { "Content-Type": "application/json; charset=utf-8" } }
);
} finally {
db.close();
}
}

View File

@@ -1,143 +1,117 @@
// src/pages/api/jambox/base-packages.js
//import { getDb } from "../db.js";
import Database from "better-sqlite3";
const DB_PATH =
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
function getDb() {
return new Database(DB_PATH, { readonly: true });
}
/**
* GET /api/jambox/base-packages
* ?source=PLUS|EVIO
* &building=1|2 (1=jednorodzinny, 2=wielorodzinny)
* &contract=1|2 (1=24m, 2=bezterminowa)
* GET /api/jambox/base-packages?source=PLUS|EVIO|ALL&building=1|2&contract=1|2
*/
export function GET({ url }) {
const sourceParam = url.searchParams.get("source");
const source = sourceParam ? sourceParam.toUpperCase() : null;
const sourceParam = url.searchParams.get("source") || "PLUS";
const buildingParam = url.searchParams.get("building");
const contractParam = url.searchParams.get("contract");
const building = buildingParam ? Number(buildingParam) : null;
const contract = contractParam ? Number(contractParam) : null;
const building = buildingParam ? Number(buildingParam) : 1;
const contract = contractParam ? Number(contractParam) : 1;
const db = getDb();
try {
let rows = [];
const hasVariant =
Number.isInteger(building) && Number.isInteger(contract);
const rows = db
.prepare(
`
SELECT
p.id AS package_id,
p.source AS package_source,
p.tid AS package_tid,
p.name AS package_name,
p.slug AS package_slug,
p.sort_order AS package_sort_order,
p.updated_at AS package_updated_at,
if (source === "PLUS" || source === "EVIO") {
if (hasVariant) {
// pakiety + ceny dla danego budynku/umowy
const stmt = db.prepare(
`
SELECT
p.id,
p.source,
p.tid,
p.name,
p.slug,
p.sort_order,
p.updated_at,
pr.price_monthly,
pr.price_installation,
pr.currency
FROM jambox_base_packages p
LEFT JOIN jambox_base_package_prices pr
ON pr.package_id = p.id
AND pr.building_type = ?
AND pr.contract_type = ?
WHERE p.source = ?
ORDER BY p.sort_order ASC, p.name ASC;
`.trim()
);
rows = stmt.all(building, contract, source);
} else {
// tylko pakiety, bez cen
const stmt = db.prepare(
`
SELECT
p.id,
p.source,
p.tid,
p.name,
p.slug,
p.sort_order,
p.updated_at
FROM jambox_base_packages p
WHERE p.source = ?
ORDER BY p.sort_order ASC, p.name ASC;
`.trim()
);
rows = stmt.all(source);
pr.price_monthly AS price_monthly,
pr.price_installation AS price_installation,
f.id AS feature_id,
f.label AS feature_label,
fv.value AS feature_value
FROM jambox_base_packages p
LEFT JOIN jambox_base_package_prices pr
ON pr.package_id = p.id
AND pr.building_type = ?
AND pr.contract_type = ?
LEFT JOIN jambox_package_feature_values fv
ON fv.package_id = p.id
LEFT JOIN internet_features f
ON f.id = fv.feature_id
WHERE (? = 'ALL' OR p.source = ?)
ORDER BY p.sort_order ASC, p.id ASC, f.id ASC;
`.trim()
)
.all(building, contract, sourceParam, sourceParam);
// grupowanie jak w /api/internet/plans
const byPackage = new Map();
for (const row of rows) {
if (!byPackage.has(row.package_id)) {
byPackage.set(row.package_id, {
id: row.package_id,
source: row.package_source,
tid: row.package_tid,
name: row.package_name,
slug: row.package_slug,
sort_order: row.package_sort_order,
updated_at: row.package_updated_at,
price_monthly: row.price_monthly,
price_installation: row.price_installation,
features: [],
});
}
} else {
// bez filtra source (raczej nie użyjesz, ale niech będzie poprawnie)
if (hasVariant) {
const stmt = db.prepare(
`
SELECT
p.id,
p.source,
p.tid,
p.name,
p.slug,
p.sort_order,
p.updated_at,
pr.price_monthly,
pr.price_installation,
pr.currency
FROM jambox_base_packages p
LEFT JOIN jambox_base_package_prices pr
ON pr.package_id = p.id
AND pr.building_type = ?
AND pr.contract_type = ?
ORDER BY p.source ASC, p.sort_order ASC, p.name ASC;
`.trim()
);
rows = stmt.all(building, contract);
} else {
const stmt = db.prepare(
`
SELECT
p.id,
p.source,
p.tid,
p.name,
p.slug,
p.sort_order,
p.updated_at
FROM jambox_base_packages p
ORDER BY p.source ASC, p.sort_order ASC, p.name ASC;
`.trim()
);
rows = stmt.all();
if (row.feature_id) {
byPackage.get(row.package_id).features.push({
id: row.feature_id,
label: row.feature_label,
value: row.feature_value,
});
}
}
const data = Array.from(byPackage.values());
return new Response(
JSON.stringify({
ok: true,
source: source ?? "ALL",
source: sourceParam,
building,
contract,
count: rows.length,
data: rows,
count: data.length,
data,
}),
{
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "public, max-age=60",
"Cache-Control": "public, max-age=30",
},
}
);
} catch (err) {
console.error("❌ Błąd odczytu z bazy jambox_base_packages:", err);
console.error("❌ Błąd w /api/jambox/base-packages:", err);
return new Response(
JSON.stringify({ ok: false, error: "DB_ERROR" }),
{
@@ -147,7 +121,5 @@ export function GET({ url }) {
},
}
);
} finally {
db.close();
}
}

View File

@@ -0,0 +1,64 @@
// src/pages/api/jambox/channels.js
//import { getDb } from "../db.js";
import Database from "better-sqlite3";
const DB_PATH =
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
function getDb() {
return new Database(DB_PATH, { readonly: true });
}
export function GET({ url }) {
const packageIdParam = url.searchParams.get("packageId");
const packageId = Number(packageIdParam);
if (!packageId) {
return new Response(
JSON.stringify({ ok: false, error: "INVALID_PACKAGE_ID" }),
{
status: 400,
headers: { "Content-Type": "application/json; charset=utf-8" },
}
);
}
const db = getDb();
try {
const rows = db
.prepare(
`
SELECT
number,
name,
description,
logo_url,
guaranteed
FROM jambox_package_channels
WHERE package_id = ?
ORDER BY number ASC;
`.trim()
)
.all(packageId);
return new Response(
JSON.stringify({ ok: true, data: rows }),
{
status: 200,
headers: { "Content-Type": "application/json; charset=utf-8" },
}
);
} catch (err) {
console.error("❌ Błąd w /api/jambox/channels:", err);
return new Response(
JSON.stringify({ ok: false, error: "DB_ERROR" }),
{
status: 500,
headers: { "Content-Type": "application/json; charset=utf-8" },
}
);
}
}

View File

@@ -0,0 +1,51 @@
import Database from "better-sqlite3";
const DB_PATH = process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
export async function GET({ url }) {
const db = new Database(DB_PATH, { readonly: true });
try {
const packageId = Number(url.searchParams.get("packageId") || 0);
if (!packageId) {
return new Response(
JSON.stringify({ ok: false, error: "MISSING_PACKAGE_ID" }),
{ status: 400, headers: { "Content-Type": "application/json; charset=utf-8" } }
);
}
const rows = db
.prepare(
`
SELECT
a.tid AS tid,
a.name AS name,
a.kind AS kind,
a.is_active AS is_active,
CAST(p.price AS REAL) AS price,
p.currency AS currency,
a.description AS description
FROM jambox_tv_addon_prices p
JOIN jambox_tv_addons a
ON a.tid = p.addon_tid
WHERE p.package_id = ?
AND a.is_active = 1
ORDER BY a.kind, a.name
`
)
.all(packageId);
return new Response(
JSON.stringify({ ok: true, count: rows.length, data: rows }),
{ status: 200, headers: { "Content-Type": "application/json; charset=utf-8" } }
);
} catch (err) {
console.error("❌ Błąd w /api/jambox/tv-addons:", err);
return new Response(
JSON.stringify({ ok: false, error: err.message || "DB_ERROR" }),
{ status: 500, headers: { "Content-Type": "application/json; charset=utf-8" } }
);
} finally {
db.close();
}
}

View File

@@ -1,7 +1,6 @@
import Database from "better-sqlite3";
const DB_PATH =
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
const DB_PATH = process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
export async function GET() {
const db = new Database(DB_PATH, { readonly: true });
@@ -69,7 +68,7 @@ export async function GET() {
}
);
} catch (err) {
console.error("Błąd w /api/phone/plans:", err);
console.error("Błąd w /api/phone/plans:", err);
return new Response(
JSON.stringify({
ok: false,

View File

@@ -0,0 +1,15 @@
import { getDb } from "../db.js";
export async function GET() {
const db = getDb();
const rows = db
.prepare("SELECT DISTINCT city FROM ranges ORDER BY city")
.all();
const cities = rows.map((row) => row.city);
return new Response(JSON.stringify(cities), {
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}

View File

@@ -1,21 +1,21 @@
import type { APIRoute } from "astro";
import { getDb } from "./db";
import { getDb } from "../db.js";
function normalize(s: string) {
function normalize(s) {
return s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
}
type CityRow = { city: string };
export const GET: APIRoute = async ({ request }) => {
export async function GET({ request }) {
const q = new URL(request.url).searchParams.get("q")?.trim() || "";
if (q.length < 2)
return new Response("[]", { headers: { "Content-Type": "application/json" } });
if (q.length < 2) {
return new Response("[]", {
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}
const db = getDb();
const rows = db.prepare(`SELECT DISTINCT city FROM ranges`).all() as CityRow[];
const rows = db.prepare(`SELECT DISTINCT city FROM ranges`).all();
const nq = normalize(q);
@@ -25,6 +25,6 @@ export const GET: APIRoute = async ({ request }) => {
.map((r) => r.city);
return new Response(JSON.stringify(result), {
headers: { "Content-Type": "application/json" },
headers: { "Content-Type": "application/json; charset=utf-8" },
});
};
}

View File

@@ -1,13 +1,13 @@
import type { APIRoute } from "astro";
import { getDb } from "./db";
import { getDb } from "../db.js";
export const GET: APIRoute = async ({ request }) => {
export async function GET({ request }) {
const city = new URL(request.url).searchParams.get("city")?.trim() || "";
if (!city)
if (!city) {
return new Response(JSON.stringify({ hasStreets: false }), {
headers: { "Content-Type": "application/json" },
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}
const db = getDb();
@@ -18,10 +18,12 @@ export const GET: APIRoute = async ({ request }) => {
WHERE LOWER(city) = LOWER(?)
AND TRIM(street) <> ''`
)
.get(city) as { cnt: number };
.get(city);
return new Response(
JSON.stringify({ hasStreets: row.cnt > 0 }),
{ headers: { "Content-Type": "application/json" } }
{
headers: { "Content-Type": "application/json; charset=utf-8" },
}
);
};
}

View File

@@ -1,7 +1,7 @@
import type { APIRoute } from "astro";
import { getDb } from "./db";
// src/pages/api/range/check-address.js (przykładowa nazwa)
import { getDb } from "../db.js";
export const POST: APIRoute = async ({ request }) => {
export async function POST({ request }) {
const { city, street, number } = await request.json();
const db = getDb();
@@ -17,12 +17,13 @@ export const POST: APIRoute = async ({ request }) => {
)
.get(city, street || "", number || "");
if (!row)
if (!row) {
return new Response(JSON.stringify({ ok: false }), {
headers: { "Content-Type": "application/json" },
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}
return new Response(JSON.stringify({ ok: true, result: row }), {
headers: { "Content-Type": "application/json" },
headers: { "Content-Type": "application/json; charset=utf-8" },
});
};
}

View File

@@ -0,0 +1,40 @@
import { getDb } from "../db.js";
function normalize(s) {
return s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
}
export async function GET({ request }) {
const url = new URL(request.url);
const city = url.searchParams.get("city")?.trim() || "";
const q = url.searchParams.get("q")?.trim() || "";
if (!city || q.length < 1) {
return new Response("[]", {
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}
const db = getDb();
const rows = db
.prepare(
`SELECT DISTINCT street
FROM ranges
WHERE LOWER(city) = LOWER(?)
AND TRIM(street) <> ''`
)
.all(city);
const pattern = normalize(q);
const filtered = rows
.map((r) => r.street)
.filter((street) => normalize(street).includes(pattern))
.slice(0, 20);
return new Response(JSON.stringify(filtered), {
headers: { "Content-Type": "application/json; charset=utf-8" },
});
}

View File

@@ -1,38 +0,0 @@
import type { APIRoute } from "astro";
import { getDb } from "./db";
function normalize(s: string) {
return s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
}
type StreetRow = { street: string };
export const GET: APIRoute = async ({ request }) => {
const url = new URL(request.url);
const city = url.searchParams.get("city")?.trim() || "";
const q = url.searchParams.get("q")?.trim() || "";
if (!city || q.length < 1)
return new Response("[]", { headers: { "Content-Type": "application/json" } });
const db = getDb();
const rows = db.prepare(
`SELECT DISTINCT street
FROM ranges
WHERE LOWER(city) = LOWER(?)
AND TRIM(street) <> ''`
).all(city) as StreetRow[];
const pattern = normalize(q);
const filtered = rows
.map((s) => s.street)
.filter((s) => normalize(s).includes(pattern))
.slice(0, 20);
return new Response(JSON.stringify(filtered), {
headers: { "Content-Type": "application/json" },
});
};

View File

@@ -1,12 +1,13 @@
---
import DefaultLayout from "../../layouts/DefaultLayout.astro";
import OffersSwitches from "../../islands/Offers/OffersSwitches.jsx";
import OffersJamboxCards from "../../islands/jambox/OffersJamboxCards.jsx";
import Hero from "../../components/hero/Hero.astro";
import SectionRenderer from "../../components/sections/SectionRenderer.astro";
import Markdown from "../../islands/Markdown.jsx";
import Modal from "../../islands/Modal.jsx";
import OffersIsland from "../../islands/OffersIsland.jsx";
import JamboxMozliwosci from "../../components/sections/SectionJamboxMozliwosci.astro";
import JamboxBasePackages from "../../islands/Offers/JamboxBasePackages.jsx";
import yaml from "js-yaml";
import fs from "fs";
@@ -21,7 +22,7 @@ const page = yaml.load(
fs.readFileSync("./src/content/internet-telewizja/page.yaml", "utf8"),
);
const modalData = yaml.load(
fs.readFileSync("./src/content/internet-telewizja/modal.yaml", "utf8")
fs.readFileSync("./src/content/internet-telewizja/modal.yaml", "utf8"),
);
type Paragraph = {
@@ -37,31 +38,30 @@ const rest = page.paragraphs.slice(1);
---
<DefaultLayout seo={seo}>
<Hero {...hero} />
<!-- <Hero {...hero} /> -->
<section class="f-section">
<!-- <section class="f-section">
<div class="f-section-grid-single md:grid-cols-1">
{page.title.map((line: any) => <h1 class="f-section-title">{line}</h1>)}
{first.title && <h3>{first.title}</h3>}
<Markdown text={first.content} />
</div>
</section>
</section> -->
<section class="f-section">
<div class="f-section-grid-single md:grid-cols-1 max-w-6xl mx-auto">
<JamboxBasePackages
client:load
source=""
title=""/>
<OffersIsland client:load data={data} />
<section class="f-section">
<div class="f-section-grid-single md:grid-cols-1">
<h1 class="f-section-title">Telewizja z interentem</h1>
<div class="fuz-markdown max-w-none">
<p>Wybierz rodzaj budynku i czas trwania umowy</p>
</div>
<OffersSwitches client:load />
<OffersJamboxCards client:load />
<!-- <OffersIsland client:load data={data} /> -->
</div>
</section>
{
<!-- {
rest.map((p: Paragraph) => (
<section class="f-section">
<div class="f-section-grid-single md:grid-cols-1">
@@ -70,13 +70,11 @@ const rest = page.paragraphs.slice(1);
</div>
</section>
))
}
} -->
<SectionRenderer src="./src/content/internet-telewizja/section.yaml" />
<JamboxMozliwosci />
<Modal client:load modalData={modalData} />
<!-- <JamboxMozliwosci /> -->
<!-- <Modal client:load modalData={modalData} /> -->
</DefaultLayout>

View File

@@ -1,7 +1,7 @@
---
import DefaultLayout from "../../layouts/DefaultLayout.astro";
import SectionRenderer from "../../components/sections/SectionRenderer.astro";
import OffersPhoneCards from "../../islands/OffersPhoneCards.jsx";
import OffersPhoneCards from "../../islands/phone/OffersPhoneCards.jsx";
import yaml from "js-yaml";
import fs from "fs";
@@ -19,17 +19,5 @@ const seo = yaml.load(
</div>
</section>
<!-- <OffersIsland client:load data={data} /> -->
<!-- {
rest.map((p: Paragraph) => (
<section class="f-section">
<div class="f-section-grid-single md:grid-cols-1">
{p.title && <h3 class="f-section-title">{p.title}</h3>}
<Markdown text={p.content.replace(/\n/g, "\n\n")} />
</div>
</section>
))
} -->
<SectionRenderer src="./src/content/telefon/section.yaml" />
</DefaultLayout>