add base front
This commit is contained in:
7
frontend/src/components/Footer/index.tsx
Normal file
7
frontend/src/components/Footer/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
12
frontend/src/components/Navbar/index.css
Normal file
12
frontend/src/components/Navbar/index.css
Normal file
@@ -0,0 +1,12 @@
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-self: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
a {
|
||||
gap: 1em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
12
frontend/src/components/Navbar/index.tsx
Normal file
12
frontend/src/components/Navbar/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NavLink } from "react-router";
|
||||
import { t } from "../../config/i18n";
|
||||
import "./index.css";
|
||||
export function Navbar() {
|
||||
return (
|
||||
<nav>
|
||||
<NavLink to="/">{t("home")}</NavLink>
|
||||
<NavLink to="/dashboard">{t("dashboard")}</NavLink>
|
||||
<NavLink to="/forms">{t("forms")}</NavLink>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
51
frontend/src/components/ShipmentCard/index.tsx
Normal file
51
frontend/src/components/ShipmentCard/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Group, NumberInput, Paper, Stack, Text } from "@mantine/core";
|
||||
import { useCallback, useState, type RefAttributes } from "react";
|
||||
|
||||
type Product = {
|
||||
name: string;
|
||||
price: number;
|
||||
priceKg: number;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export type ShipmentCardProps = {
|
||||
title: string;
|
||||
date: string;
|
||||
product: Product;
|
||||
}
|
||||
|
||||
export default function ShipmentCard({title, date, product}: ShipmentCardProps) {
|
||||
|
||||
const [ price, setPrice ] = useState<number>(0)
|
||||
|
||||
const calculatePrice = useCallback((value: number | string ) => {
|
||||
const numberValue = Number(value)
|
||||
const price = numberValue * product.price
|
||||
setPrice(price)
|
||||
}, [])
|
||||
return (
|
||||
<Paper shadow="xs" p="xl">
|
||||
<Group justify="space-between">
|
||||
<Text>{title}</Text>
|
||||
<Text>{date}</Text>
|
||||
</Group>
|
||||
<Text>{product.name}</Text>
|
||||
<Group>
|
||||
<Stack align="flex-start">
|
||||
<Text>{product.price} € / piece</Text>
|
||||
<Text>{product.priceKg} € / kilo</Text>
|
||||
</Stack>
|
||||
<NumberInput
|
||||
aria-label="select quantity"
|
||||
description={`Indiquez le nombre de ${product.unit}`}
|
||||
placeholder={`Quantité en ${product.unit}`}
|
||||
allowNegative={false}
|
||||
onChange={calculatePrice}
|
||||
/>
|
||||
<Text size="xs">
|
||||
{new Intl.NumberFormat('en-us', {minimumFractionDigits: 2}).format(price)}€
|
||||
</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
4
frontend/src/config/config.tsx
Normal file
4
frontend/src/config/config.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export const Config = {
|
||||
backend_uri: import.meta.env.VITE_API_URL,
|
||||
debug: import.meta.env.NODE_ENV === "development"
|
||||
}
|
||||
41
frontend/src/config/i18n.tsx
Normal file
41
frontend/src/config/i18n.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
/* eslint-disable @typescript-eslint/no-restricted-imports */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import i18next from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { Settings } from "luxon";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
import en from "../../locales/en.json";
|
||||
import fr from "../../locales/fr.json";
|
||||
import { Config } from "./config";
|
||||
|
||||
const resources = {
|
||||
en: { translation: en },
|
||||
fr: { translation: fr },
|
||||
};
|
||||
|
||||
i18next
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: resources,
|
||||
fallbackLng: "en",
|
||||
debug: Config.debug,
|
||||
detection: {
|
||||
caches: [],
|
||||
},
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
ns: ["translation"],
|
||||
defaultNS: "translation",
|
||||
})
|
||||
.then(() => {
|
||||
[Settings.defaultLocale] = i18next.language.split("-");
|
||||
});
|
||||
|
||||
export function t(message: string, params?: Record<string, any>) {
|
||||
return i18next.t(message, params);
|
||||
}
|
||||
|
||||
export default i18next;
|
||||
14
frontend/src/main.tsx
Normal file
14
frontend/src/main.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { RouterProvider } from "react-router";
|
||||
import { router } from "./router.tsx";
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import '@mantine/core/styles.css';
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<MantineProvider>
|
||||
<RouterProvider router={router} />
|
||||
</MantineProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
74
frontend/src/pages/ContractForm/index.tsx
Normal file
74
frontend/src/pages/ContractForm/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Flex, Grid, Select, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||
import { t } from "../../config/i18n";
|
||||
import { IconUser } from "@tabler/icons-react";
|
||||
import ShipmentCard from "../../components/ShipmentCard";
|
||||
|
||||
export function ContractForm() {
|
||||
return (
|
||||
<Flex
|
||||
w={{base: "100%", sm: "50%", lg: "60%"}}
|
||||
justify={"start"}
|
||||
align={"flex-start"}
|
||||
direction={"column"}
|
||||
>
|
||||
<Stack>
|
||||
<Title>{t("form contract")}</Title>
|
||||
<Text>{t("contract description that is rather long to show how text will be displayed even with unnecessary elements like this end of sentence")}</Text>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>{t("contact phase")}</Text>
|
||||
<Grid>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<TextInput
|
||||
radius="sm"
|
||||
label={t("firstname")}
|
||||
placeholder={t("firstname")}
|
||||
leftSection={<IconUser/>}
|
||||
/>
|
||||
<TextInput
|
||||
radius="sm"
|
||||
label={t("lastname")}
|
||||
placeholder={t("lastname")}
|
||||
leftSection={<IconUser/>}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<TextInput
|
||||
radius="sm"
|
||||
label={t("email")}
|
||||
placeholder={t("email")}
|
||||
leftSection={<IconUser/>}
|
||||
/>
|
||||
<TextInput
|
||||
radius="sm"
|
||||
label={t("phone")}
|
||||
placeholder={t("phone")}
|
||||
leftSection={<IconUser/>}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>{t("products reccurent phase")}</Text>
|
||||
<Select
|
||||
label={`${t("select reccurent product")} (${t("this product will be distributed for all shipments")})`}
|
||||
placeholder={t("select reccurent product")}
|
||||
data={["qwe", "qweqwe", "qweqweqwe"]}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>{t("products planned phase")}</Text>
|
||||
<ShipmentCard
|
||||
title="Shipment 1"
|
||||
date="2025-10-10"
|
||||
product={{
|
||||
name: "rognons de veau",
|
||||
price: 1.20,
|
||||
priceKg: 10.9,
|
||||
unit: "piece"
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
8
frontend/src/pages/Home/index.tsx
Normal file
8
frontend/src/pages/Home/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Text } from "@mantine/core";
|
||||
import { t } from "../../config/i18n";
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<Text>{t("test")}</Text>
|
||||
);
|
||||
}
|
||||
15
frontend/src/root.tsx
Normal file
15
frontend/src/root.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Outlet } from "react-router";
|
||||
import { Navbar } from "./components/Navbar";
|
||||
import { Footer } from "./components/Footer";
|
||||
|
||||
export default function Root() {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<main style={{display: "flex", justifyContent: "center"}}>
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
19
frontend/src/router.tsx
Normal file
19
frontend/src/router.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
createBrowserRouter,
|
||||
} from "react-router";
|
||||
|
||||
import Root from "./root";
|
||||
import { Home } from "./pages/Home";
|
||||
import { ContractForm } from "./pages/ContractForm";
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
Component: Root,
|
||||
// errorElement: <NotFound />,
|
||||
children: [
|
||||
{ index: true, Component: Home },
|
||||
{ path: "/forms", Component: ContractForm },
|
||||
],
|
||||
},
|
||||
]);
|
||||
5
frontend/src/theme.ts
Normal file
5
frontend/src/theme.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createTheme } from '@mantine/core';
|
||||
|
||||
export const theme = createTheme({
|
||||
/** Put your mantine theme override here */
|
||||
});
|
||||
Reference in New Issue
Block a user