Initial commit
This commit is contained in:
commit
ef9f05e031
12 changed files with 3801 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
frontend/node_modules
|
||||
frontend/.parcel-cache
|
||||
public/assets/*
|
||||
!public/assets/.gitkeep
|
||||
45
frontend/package.json
Normal file
45
frontend/package.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"source": "src/main.js",
|
||||
"targets": {
|
||||
"default": {
|
||||
"distDir": "../public/assets"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "parcel watch",
|
||||
"build": "parcel build"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.5.2",
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"buffer": "^5.5.0||^6.0.0",
|
||||
"parcel": "^2.13.3",
|
||||
"process": "^0.11.10"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@swc/core",
|
||||
"core-js",
|
||||
"deasync",
|
||||
"lmdb",
|
||||
"msgpackr-extract",
|
||||
"parcel-bundler"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.8.1",
|
||||
"bootstrap": "^5.3.3",
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap": "^2.10.9",
|
||||
"react-bootstrap-icons": "^1.11.5",
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
}
|
||||
3530
frontend/pnpm-lock.yaml
generated
Normal file
3530
frontend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
32
frontend/src/components/App.js
Normal file
32
frontend/src/components/App.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { useState } from 'react';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import NavbarComponent from './Navbar';
|
||||
import SearchComponent from './Search';
|
||||
import ResultComponent from './Result';
|
||||
|
||||
export function App() {
|
||||
const [zip, setZip] = useState(0);
|
||||
const [city, setCity] = useState("");
|
||||
|
||||
function handleSearch(value, zip, city) {
|
||||
setZip(zip);
|
||||
setCity(city);
|
||||
|
||||
if (window.history.replaceState) {
|
||||
value = encodeURIComponent(value);
|
||||
window.history.replaceState({}, null, `?s=${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavbarComponent />
|
||||
<Container className="my-4">
|
||||
<SearchComponent callback={handleSearch} />
|
||||
</Container>
|
||||
<Container className="my-4">
|
||||
<ResultComponent zip={zip} city={city} />
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
14
frontend/src/components/Navbar.js
Normal file
14
frontend/src/components/Navbar.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import Container from 'react-bootstrap/Container';
|
||||
import Navbar from 'react-bootstrap/Navbar';
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<Navbar expand="lg" className="navbar-dark bg-danger">
|
||||
<Container>
|
||||
<Navbar.Brand>Codes postaux | <b>Suisse</b></Navbar.Brand>
|
||||
</Container>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
export default Component;
|
||||
42
frontend/src/components/Result.js
Normal file
42
frontend/src/components/Result.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { Card } from 'react-bootstrap';
|
||||
|
||||
function Component({zip, city}) {
|
||||
const [zips, setZips] = useState(Object);
|
||||
|
||||
const fetchResults = (zip, city) => {
|
||||
city = encodeURIComponent(city);
|
||||
axios.get(`/api/query.php?zip=${zip}&city=${city}`)
|
||||
.then(function (response) {
|
||||
if (response.data.zips) {
|
||||
setZips(response.data.zips);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (zip > 0 || city.length >= 4) {
|
||||
fetchResults(zip, city);
|
||||
} else {
|
||||
setZips((new Object));
|
||||
}
|
||||
}, [zip, city]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(zips).map((zip, i) => {
|
||||
return (
|
||||
<Card body key={i} className="mb-2">
|
||||
<b>{zip[0]}</b> {zip[1]}
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Component;
|
||||
58
frontend/src/components/Search.js
Normal file
58
frontend/src/components/Search.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { useEffect, useState } from 'npm:react';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import InputGroup from 'react-bootstrap/InputGroup';
|
||||
import { Search } from 'react-bootstrap-icons';
|
||||
|
||||
function Component({callback}) {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const updateSearch = event => {
|
||||
var value = event.target.value;
|
||||
setSearchValue(value);
|
||||
parseSearch(value);
|
||||
}
|
||||
|
||||
const parseSearch = value => {
|
||||
var zip = 0;
|
||||
var findZip = value.match(/[0-9]{4}/g);
|
||||
if (findZip) {
|
||||
zip = parseInt(findZip[0]);
|
||||
}
|
||||
|
||||
var city = "";
|
||||
var findCity = value.replace(/[0-9]/g, '');
|
||||
if (findCity.length > 0) {
|
||||
city = findCity.trim();
|
||||
}
|
||||
|
||||
callback(value, zip, city);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const paramsString = window.location.search;
|
||||
const searchParams = new URLSearchParams(paramsString);
|
||||
const value = searchParams.get("s");
|
||||
|
||||
if (value && value.length > 0) {
|
||||
setSearchValue(value);
|
||||
parseSearch(value);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InputGroup size="lg">
|
||||
<InputGroup.Text>
|
||||
<Search />
|
||||
</InputGroup.Text>
|
||||
<Form.Control
|
||||
placeholder="Rechercher un code postal ou une localité"
|
||||
onChange={updateSearch}
|
||||
value={searchValue}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Component;
|
||||
0
frontend/src/main.css
Normal file
0
frontend/src/main.css
Normal file
9
frontend/src/main.js
Normal file
9
frontend/src/main.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import "npm:bootstrap/dist/css/bootstrap.css"
|
||||
import "./main.css"
|
||||
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "./components/App";
|
||||
|
||||
const container = document.getElementById("app");
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
54
public/api/query.php
Normal file
54
public/api/query.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
function getParam(string $name, string $default = ""): string {
|
||||
if (isset($_GET[$name]) && !empty($_GET[$name])) {
|
||||
return (string)$_GET[$name];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
function getParamInt(string $name, int $default = 0): int {
|
||||
if (isset($_GET[$name]) && !empty($_GET[$name])) {
|
||||
return (int)$_GET[$name];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
$zip = getParamInt("zip", 0);
|
||||
$city = getParam("city", "");
|
||||
$time = time();
|
||||
|
||||
$url = "https://service.post.ch/zopa/app/api/addresschecker/v1/zips?limit=9999&noCache={$time}";
|
||||
|
||||
if (strlen($city) > 0) {
|
||||
$url .= "&city={$city}";
|
||||
}
|
||||
|
||||
if ($zip >= 1000 && $zip <= 9999) {
|
||||
$url .= "&zip={$zip}";
|
||||
}
|
||||
|
||||
$response = file_get_contents($url);
|
||||
$json = json_decode($response);
|
||||
|
||||
$zips = [];
|
||||
if (isset($json->zips) && !empty($json->zips)) {
|
||||
foreach ($json->zips as $zip) {
|
||||
if (!isset($zip->zip) || empty($zip->zip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($zip->city27) || empty($zip->city27)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$zips[(int)$zip->zip] = $zip->city27;
|
||||
}
|
||||
}
|
||||
|
||||
header("Content-Type: application/json");
|
||||
echo json_encode([
|
||||
"zips" => $zips
|
||||
], JSON_PRETTY_PRINT);
|
||||
0
public/assets/.gitkeep
Normal file
0
public/assets/.gitkeep
Normal file
13
public/index.html
Normal file
13
public/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Codes postaux | Suisse</title>
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/assets/main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue