Use MUI, structure change, fix API
This commit is contained in:
parent
ef9f05e031
commit
8db8781f06
15 changed files with 831 additions and 350 deletions
45
frontend/components/App.js
Normal file
45
frontend/components/App.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { useState } from 'react';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import Container from '@mui/material/Container';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
|
||||
import NavbarComponent from './Navbar';
|
||||
import SearchComponent from './Search';
|
||||
import ResultComponent from './Result';
|
||||
|
||||
const darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
},
|
||||
});
|
||||
|
||||
export default 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 (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<SnackbarProvider>
|
||||
<NavbarComponent />
|
||||
<Container sx={{ marginY: "1rem" }}>
|
||||
<SearchComponent callback={handleSearch} />
|
||||
</Container>
|
||||
<Container sx={{ marginBottom: "2rem" }}>
|
||||
<ResultComponent zip={zip} city={city} />
|
||||
</Container>
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
25
frontend/components/Navbar.js
Normal file
25
frontend/components/Navbar.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import AppBar from '@mui/material/AppBar';
|
||||
import Box from '@mui/material/Box';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Place from '@mui/icons-material/Place';
|
||||
import Container from '@mui/material/Container';
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<AppBar position="static">
|
||||
<Container>
|
||||
<Toolbar>
|
||||
<Place sx={{ marginRight: ".5rem" }} />
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||
Chercher un code postal en Suisse
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Component;
|
||||
77
frontend/components/Result.js
Normal file
77
frontend/components/Result.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import ContentCopyRounded from '@mui/icons-material/ContentCopyRounded';
|
||||
import { useSnackbar } from 'notistack';
|
||||
|
||||
function Component({zip, city}) {
|
||||
const [zips, setZips] = useState([]);
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
const copyValue = (value) => {
|
||||
navigator.clipboard.writeText(value);
|
||||
enqueueSnackbar("Copié dans le presse-papier", {
|
||||
autoHideDuration: 2000,
|
||||
variant: "success",
|
||||
anchorOrigin: {
|
||||
vertical: "top",
|
||||
horizontal: "center"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (zip > 0 || city.length >= 4) {
|
||||
fetchResults(zip, city);
|
||||
} else {
|
||||
setZips([]);
|
||||
}
|
||||
}, [zip, city]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<List sx={{ width: '100%' }}>
|
||||
{zips.map((zip, i) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={i}
|
||||
secondaryAction={
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="Copier"
|
||||
onClick={() => {copyValue(`${zip.zip} ${zip.city}`)}}
|
||||
>
|
||||
<ContentCopyRounded />
|
||||
</IconButton>
|
||||
}
|
||||
disablePadding
|
||||
>
|
||||
<ListItemButton onClick={() => {copyValue(`${zip.zip} ${zip.city}`)}}>
|
||||
<ListItemText primary={`${zip.zip} ${zip.city}`} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Component;
|
||||
68
frontend/components/Search.js
Normal file
68
frontend/components/Search.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Search from '@mui/icons-material/Search';
|
||||
|
||||
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 (
|
||||
<>
|
||||
<TextField
|
||||
label="Rechercher un code postal ou une localité"
|
||||
variant="outlined"
|
||||
onChange={updateSearch}
|
||||
value={searchValue}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Search />
|
||||
</InputAdornment>
|
||||
)
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
marginTop: "1.5rem",
|
||||
marginBottom: "1.5rem"
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Component;
|
||||
Loading…
Add table
Add a link
Reference in a new issue