SPB Git

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

3.6 KB · 130 lines typescript
1// -----------------------------------------------------------------------------2// Lou-Ka — Location court terme (chalets, séjours, hébergements)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// ctapi.ts : types + client API /api/ct/* — sous-système séparé du long terme.5// -----------------------------------------------------------------------------67export interface CtDetails {8  spa?: boolean;9  pool?: boolean;10  waterfront?: boolean;11  sauna?: boolean;12  wifi?: boolean;13  fireplace?: boolean;14  ev_charger?: boolean;15  [k: string]: unknown;16}1718export interface CtListing {19  uid: string;20  source: string;21  external_id: string;22  url: string;23  title: string;24  property_type: string;25  address: string;26  city: string;27  region: string;28  price_night: number | null;29  price_label: string;30  capacity: number | null;31  bedrooms: number | null;32  beds: number | null;33  bathrooms: number | null;34  pets: string | null;          // "oui" | "non" | "conditions"35  citq: string | null;          // numéro d'enregistrement CITQ36  rating: number | null;        // sur 537  reviews: number | null;38  description: string;39  amenities: string[];40  details: CtDetails;41  images: string[];42  lat: number | null;43  lng: number | null;44  first_seen?: number;45  last_seen?: number;46  active?: number;47}4849export interface CtFacets {50  regions: { region: string; n: number }[];51  types: { type: string; n: number }[];52  sources: { source: string; n: number }[];53}5455export interface CtStats {56  total: number;57  sources: number;58  regions: number;59  avg_night: number | null;60  geocoded: number;61}6263export interface CtSource {64  id: string;65  name: string;66  url: string;67  category: string;68  status?: string;69  connector: boolean;70  active_listings: number;71  last_sync: number | null;72}7374export interface CtFilters {75  region?: string;76  city?: string;77  type?: string;78  source?: string;79  price_min?: string;80  price_max?: string;81  capacity_min?: string;82  bedrooms_min?: string;83  pets?: string;          // "oui"84  spa?: string;           // "1"85  waterfront?: string;    // "1"86  q?: string;87  sort?: string;          // recent | prix | prix_desc | note88}8990const CT_SOURCE_NAMES: Record<string, string> = {};91export function registerCtSourceNames(sources: CtSource[]) {92  for (const s of sources) CT_SOURCE_NAMES[s.id] = s.name;93}94export function ctSourceName(id: string): string {95  return CT_SOURCE_NAMES[id] ?? id;96}9798async function get<T>(path: string): Promise<T> {99  const ctrl = new AbortController();100  const timer = setTimeout(() => ctrl.abort(), 20000);101  try {102    const res = await fetch(path, { signal: ctrl.signal });103    if (!res.ok) throw new Error(`API ${res.status} — ${path}`);104    return (await res.json()) as T;105  } finally {106    clearTimeout(timer);107  }108}109110export function fetchCtListings(f: CtFilters, limit?: number, offset?: number) {111  const params = new URLSearchParams();112  for (const [k, v] of Object.entries(f)) if (v) params.set(k, v);113  if (limit != null) params.set("limit", String(limit));114  if (offset) params.set("offset", String(offset));115  return get<{ total: number; count: number; listings: CtListing[] }>(116    `/api/ct/listings?${params}`);117}118119export const fetchCtListing = (uid: string) =>120  get<CtListing>(`/api/ct/listings/${encodeURIComponent(uid)}`);121export const fetchCtFacets = () => get<CtFacets>("/api/ct/facets");122export const fetchCtStats = () => get<CtStats>("/api/ct/stats");123export const fetchCtSources = () =>124  get<{ sources: CtSource[] }>("/api/ct/sources");125126export const fmtNight = (p: number | null, label?: string) =>127  p != null128    ? `${p.toLocaleString("fr-CA", { maximumFractionDigits: 0 })} $`129    : label || "Prix sur demande";130
Files · 37f3f5ff498dd696dd199dc3582eafb361c118f2
Menu

Theme