add prettier code formater
This commit is contained in:
@@ -1,104 +1,103 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: '@typescript-eslint/parser',
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: "latest",
|
||||||
sourceType: 'module',
|
sourceType: "module",
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
jsx: true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
project: './tsconfig.json', // Required for type-aware rules
|
project: "./tsconfig.json", // Required for type-aware rules
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true,
|
es2021: true,
|
||||||
node: true,
|
node: true,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: ["react", "react-hooks", "@typescript-eslint", "jsx-a11y", "import", "unused-imports"],
|
||||||
'react',
|
extends: [
|
||||||
'react-hooks',
|
"eslint:recommended",
|
||||||
'@typescript-eslint',
|
"plugin:react/recommended",
|
||||||
'jsx-a11y',
|
"plugin:react-hooks/recommended",
|
||||||
'import',
|
"plugin:@typescript-eslint/recommended",
|
||||||
'unused-imports',
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
],
|
"plugin:jsx-a11y/recommended",
|
||||||
extends: [
|
"plugin:import/errors",
|
||||||
'eslint:recommended',
|
"plugin:import/warnings",
|
||||||
'plugin:react/recommended',
|
"plugin:import/typescript",
|
||||||
'plugin:react-hooks/recommended',
|
"prettier",
|
||||||
'plugin:@typescript-eslint/recommended',
|
],
|
||||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
rules: {
|
||||||
'plugin:jsx-a11y/recommended',
|
"@typescript-eslint/no-unused-vars": [
|
||||||
'plugin:import/errors',
|
"error",
|
||||||
'plugin:import/warnings',
|
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
||||||
'plugin:import/typescript',
|
],
|
||||||
'prettier',
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
],
|
"@typescript-eslint/explicit-function-return-type": ["error", { allowExpressions: false }],
|
||||||
rules: {
|
"@typescript-eslint/strict-boolean-expressions": "error",
|
||||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
'@typescript-eslint/no-explicit-any': 'error',
|
"@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
|
||||||
'@typescript-eslint/explicit-function-return-type': ['error', { allowExpressions: false }],
|
"@typescript-eslint/no-misused-promises": "error",
|
||||||
'@typescript-eslint/strict-boolean-expressions': 'error',
|
"@typescript-eslint/prefer-readonly": "error",
|
||||||
'@typescript-eslint/no-floating-promises': 'error',
|
"@typescript-eslint/explicit-module-boundary-types": "error",
|
||||||
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
|
"@typescript-eslint/typedef": [
|
||||||
'@typescript-eslint/no-misused-promises': 'error',
|
"error",
|
||||||
'@typescript-eslint/prefer-readonly': 'error',
|
{
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
arrayDestructuring: true,
|
||||||
'@typescript-eslint/typedef': [
|
arrowParameter: true,
|
||||||
'error',
|
memberVariableDeclaration: true,
|
||||||
{
|
objectDestructuring: true,
|
||||||
arrayDestructuring: true,
|
parameter: true,
|
||||||
arrowParameter: true,
|
propertyDeclaration: true,
|
||||||
memberVariableDeclaration: true,
|
variableDeclaration: true,
|
||||||
objectDestructuring: true,
|
variableDeclarationIgnoreFunction: false,
|
||||||
parameter: true,
|
},
|
||||||
propertyDeclaration: true,
|
],
|
||||||
variableDeclaration: true,
|
|
||||||
variableDeclarationIgnoreFunction: false,
|
"react/react-in-jsx-scope": "off",
|
||||||
},
|
"react/prop-types": "off",
|
||||||
],
|
"react/jsx-uses-react": "off",
|
||||||
|
"react/jsx-uses-vars": "error",
|
||||||
'react/react-in-jsx-scope': 'off',
|
"react/jsx-no-useless-fragment": "error",
|
||||||
'react/prop-types': 'off',
|
"react/self-closing-comp": "error",
|
||||||
'react/jsx-uses-react': 'off',
|
|
||||||
'react/jsx-uses-vars': 'error',
|
"react-hooks/rules-of-hooks": "error",
|
||||||
'react/jsx-no-useless-fragment': 'error',
|
"react-hooks/exhaustive-deps": "error",
|
||||||
'react/self-closing-comp': 'error',
|
|
||||||
|
"jsx-a11y/no-noninteractive-element-interactions": "error",
|
||||||
'react-hooks/rules-of-hooks': 'error',
|
"jsx-a11y/anchor-is-valid": "error",
|
||||||
'react-hooks/exhaustive-deps': 'error',
|
"jsx-a11y/click-events-have-key-events": "error",
|
||||||
|
|
||||||
'jsx-a11y/no-noninteractive-element-interactions': 'error',
|
"import/order": [
|
||||||
'jsx-a11y/anchor-is-valid': 'error',
|
"error",
|
||||||
'jsx-a11y/click-events-have-key-events': 'error',
|
{
|
||||||
|
groups: [
|
||||||
'import/order': [
|
["builtin", "external"],
|
||||||
'error',
|
["internal", "parent", "sibling", "index"],
|
||||||
{
|
],
|
||||||
groups: [['builtin', 'external'], ['internal', 'parent', 'sibling', 'index']],
|
"newlines-between": "always",
|
||||||
'newlines-between': 'always',
|
alphabetize: { order: "asc", caseInsensitive: true },
|
||||||
alphabetize: { order: 'asc', caseInsensitive: true },
|
},
|
||||||
},
|
],
|
||||||
],
|
"import/no-unresolved": "error",
|
||||||
'import/no-unresolved': 'error',
|
"import/no-duplicates": "error",
|
||||||
'import/no-duplicates': 'error',
|
|
||||||
|
"unused-imports/no-unused-imports-ts": "error",
|
||||||
'unused-imports/no-unused-imports-ts': 'error',
|
"no-console": ["warn", { allow: ["warn", "error"] }],
|
||||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
"no-debugger": "error",
|
||||||
'no-debugger': 'error',
|
eqeqeq: ["error", "always"],
|
||||||
'eqeqeq': ['error', 'always'],
|
curly: "error",
|
||||||
'curly': 'error',
|
semi: ["error", "always"],
|
||||||
'semi': ['error', 'always'],
|
quotes: ["error", "single", { avoidEscape: true }],
|
||||||
'quotes': ['error', 'single', { avoidEscape: true }],
|
"prefer-const": "error",
|
||||||
'prefer-const': 'error',
|
"no-var": "error",
|
||||||
'no-var': 'error',
|
},
|
||||||
},
|
settings: {
|
||||||
settings: {
|
react: {
|
||||||
react: {
|
version: "detect",
|
||||||
version: 'detect',
|
},
|
||||||
},
|
"import/resolver": {
|
||||||
'import/resolver': {
|
typescript: {},
|
||||||
typescript: {},
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|||||||
8
frontend/.prettierignore
Normal file
8
frontend/.prettierignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
public
|
||||||
|
*.lock
|
||||||
13
frontend/.prettierrc
Normal file
13
frontend/.prettierrc
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": false,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
||||||
@@ -17,57 +17,57 @@ If you are developing a production application, we recommend updating the config
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(["dist"]),
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
extends: [
|
extends: [
|
||||||
// Other configs...
|
// Other configs...
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
// Remove tseslint.configs.recommended and replace with this
|
||||||
tseslint.configs.recommendedTypeChecked,
|
tseslint.configs.recommendedTypeChecked,
|
||||||
// Alternatively, use this for stricter rules
|
// Alternatively, use this for stricter rules
|
||||||
tseslint.configs.strictTypeChecked,
|
tseslint.configs.strictTypeChecked,
|
||||||
// Optionally, add this for stylistic rules
|
// Optionally, add this for stylistic rules
|
||||||
tseslint.configs.stylisticTypeChecked,
|
tseslint.configs.stylisticTypeChecked,
|
||||||
|
|
||||||
// Other configs...
|
// Other configs...
|
||||||
],
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
// other options...
|
// other options...
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]);
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// eslint.config.js
|
// eslint.config.js
|
||||||
import reactX from 'eslint-plugin-react-x'
|
import reactX from "eslint-plugin-react-x";
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
import reactDom from "eslint-plugin-react-dom";
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(["dist"]),
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
extends: [
|
extends: [
|
||||||
// Other configs...
|
// Other configs...
|
||||||
// Enable lint rules for React
|
// Enable lint rules for React
|
||||||
reactX.configs['recommended-typescript'],
|
reactX.configs["recommended-typescript"],
|
||||||
// Enable lint rules for React DOM
|
// Enable lint rules for React DOM
|
||||||
reactDom.configs.recommended,
|
reactDom.configs.recommended,
|
||||||
],
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
// other options...
|
// other options...
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]);
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import js from '@eslint/js'
|
import js from "@eslint/js";
|
||||||
import globals from 'globals'
|
import globals from "globals";
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
import tseslint from 'typescript-eslint'
|
import tseslint from "typescript-eslint";
|
||||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(["dist"]),
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
extends: [
|
extends: [
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
tseslint.configs.recommended,
|
tseslint.configs.recommended,
|
||||||
reactHooks.configs.flat.recommended,
|
reactHooks.configs.flat.recommended,
|
||||||
reactRefresh.configs.vite,
|
reactRefresh.configs.vite,
|
||||||
],
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]);
|
||||||
])
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Amap Croix-luizet</title>
|
<title>Amap Croix-luizet</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -124,4 +124,4 @@
|
|||||||
"there is no contract for now": "there is no contract for now.",
|
"there is no contract for now": "there is no contract for now.",
|
||||||
"the product unit will be assigned to the quantity requested in the form": "the product unit will be assigned to the quantity requested in the form",
|
"the product unit will be assigned to the quantity requested in the form": "the product unit will be assigned to the quantity requested in the form",
|
||||||
"all theses informations are for contract generation": "all theses informations are for contract generation."
|
"all theses informations are for contract generation": "all theses informations are for contract generation."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,4 +136,4 @@
|
|||||||
"there is no contract for now": "Il n'y a pas de contrats pour le moment.",
|
"there is no contract for now": "Il n'y a pas de contrats pour le moment.",
|
||||||
"the product unit will be assigned to the quantity requested in the form": "L'unité de vente du produit définit l'unité associée a la quantité demandée dans le formulaire des amapiens.",
|
"the product unit will be assigned to the quantity requested in the form": "L'unité de vente du produit définit l'unité associée a la quantité demandée dans le formulaire des amapiens.",
|
||||||
"all theses informations are for contract generation": "ces informations sont nécéssaires pour la génération de contrat."
|
"all theses informations are for contract generation": "ces informations sont nécéssaires pour la génération de contrat."
|
||||||
}
|
}
|
||||||
|
|||||||
8253
frontend/package-lock.json
generated
8253
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,50 +1,52 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"format": "prettier . --write",
|
||||||
},
|
"preview": "vite preview"
|
||||||
"dependencies": {
|
},
|
||||||
"@mantine/core": "^8.3.14",
|
"dependencies": {
|
||||||
"@mantine/dates": "^8.3.14",
|
"@mantine/core": "^8.3.14",
|
||||||
"@mantine/form": "^8.3.14",
|
"@mantine/dates": "^8.3.14",
|
||||||
"@mantine/hooks": "^8.3.14",
|
"@mantine/form": "^8.3.14",
|
||||||
"@mantine/notifications": "^8.3.14",
|
"@mantine/hooks": "^8.3.14",
|
||||||
"@tabler/icons": "^3.36.1",
|
"@mantine/notifications": "^8.3.14",
|
||||||
"@tabler/icons-react": "^3.36.1",
|
"@tabler/icons": "^3.36.1",
|
||||||
"@tanstack/react-query": "^5.90.20",
|
"@tabler/icons-react": "^3.36.1",
|
||||||
"capitalize": "^2.0.4",
|
"@tanstack/react-query": "^5.90.20",
|
||||||
"dayjs": "^1.11.19",
|
"capitalize": "^2.0.4",
|
||||||
"i18next": "^25.8.4",
|
"dayjs": "^1.11.19",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next": "^25.8.4",
|
||||||
"luxon": "^3.7.2",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"react": "^19.2.0",
|
"luxon": "^3.7.2",
|
||||||
"react-dom": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-i18next": "^16.5.4",
|
"react-dom": "^19.2.0",
|
||||||
"react-router": "^7.13.0"
|
"react-i18next": "^16.5.4",
|
||||||
},
|
"react-router": "^7.13.0"
|
||||||
"devDependencies": {
|
},
|
||||||
"@eslint/js": "^9.39.1",
|
"devDependencies": {
|
||||||
"@types/capitalize": "^2.0.2",
|
"@eslint/js": "^9.39.1",
|
||||||
"@types/luxon": "^3.7.1",
|
"@types/capitalize": "^2.0.2",
|
||||||
"@types/node": "^24.10.1",
|
"@types/luxon": "^3.7.1",
|
||||||
"@types/react": "^19.2.7",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react": "^19.2.7",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@types/react-dom": "^19.2.3",
|
||||||
"eslint": "^9.39.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"globals": "^16.5.0",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"postcss": "^8.5.6",
|
"globals": "^16.5.0",
|
||||||
"postcss-preset-mantine": "^1.18.0",
|
"postcss": "^8.5.6",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-preset-mantine": "^1.18.0",
|
||||||
"typescript": "~5.9.3",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"typescript-eslint": "^8.48.0",
|
"prettier": "3.8.1",
|
||||||
"vite": "^7.3.1"
|
"typescript": "~5.9.3",
|
||||||
}
|
"typescript-eslint": "^8.48.0",
|
||||||
|
"vite": "^7.3.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
'postcss-preset-mantine': {},
|
"postcss-preset-mantine": {},
|
||||||
'postcss-simple-vars': {
|
"postcss-simple-vars": {
|
||||||
variables: {
|
variables: {
|
||||||
'mantine-breakpoint-xs': '36em',
|
"mantine-breakpoint-xs": "36em",
|
||||||
'mantine-breakpoint-sm': '48em',
|
"mantine-breakpoint-sm": "48em",
|
||||||
'mantine-breakpoint-md': '62em',
|
"mantine-breakpoint-md": "62em",
|
||||||
'mantine-breakpoint-lg': '75em',
|
"mantine-breakpoint-lg": "75em",
|
||||||
'mantine-breakpoint-xl': '88em',
|
"mantine-breakpoint-xl": "88em",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return <footer></footer>;
|
||||||
<footer>
|
}
|
||||||
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,39 +1,30 @@
|
|||||||
import { Badge, Box, Group, Paper, Text, Title } from "@mantine/core";
|
import { Badge, Box, Group, Paper, Text, Title } from "@mantine/core";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import type { Form } from "@/services/resources/forms";
|
import type { Form } from "@/services/resources/forms";
|
||||||
|
|
||||||
export type FormCardProps = {
|
export type FormCardProps = {
|
||||||
form: Form;
|
form: Form;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function FormCard({form}: FormCardProps) {
|
export function FormCard({ form }: FormCardProps) {
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper shadow="xl" p="xl" miw={{ base: "100vw", md: "25vw", lg: "20vw" }}>
|
||||||
shadow="xl"
|
<Box
|
||||||
p="xl"
|
component={Link}
|
||||||
miw={{base: "100vw", md: "25vw", lg:"20vw"}}
|
to={`/form/${form.id}`}
|
||||||
>
|
style={{ textDecoration: "none", color: "black" }}
|
||||||
<Box
|
>
|
||||||
component={Link}
|
<Group justify="space-between" wrap="nowrap">
|
||||||
to={`/form/${form.id}`}
|
<Title order={3} textWrap="wrap" lineClamp={1}>
|
||||||
style={{textDecoration: "none", color: "black"}}
|
{form.name}
|
||||||
>
|
</Title>
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Badge>{form.season}</Badge>
|
||||||
<Title
|
</Group>
|
||||||
order={3}
|
<Group justify="space-between">
|
||||||
textWrap="wrap"
|
<Text>{form.productor.name}</Text>
|
||||||
lineClamp={1}
|
<Text>{form.referer.name}</Text>
|
||||||
>
|
</Group>
|
||||||
{form.name}
|
</Box>
|
||||||
</Title>
|
</Paper>
|
||||||
<Badge>{form.season}</Badge>
|
);
|
||||||
</Group>
|
}
|
||||||
<Group justify="space-between">
|
|
||||||
<Text>{form.productor.name}</Text>
|
|
||||||
<Text>{form.referer.name}</Text>
|
|
||||||
</Group>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,49 +1,49 @@
|
|||||||
import { Group, MultiSelect } from "@mantine/core";
|
import { Group, MultiSelect } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export type FilterFormsProps = {
|
export type FilterFormsProps = {
|
||||||
seasons: string[];
|
seasons: string[];
|
||||||
productors: string[];
|
productors: string[];
|
||||||
filters: URLSearchParams;
|
filters: URLSearchParams;
|
||||||
onFilterChange: (values: string[], filter: string) => void;
|
onFilterChange: (values: string[], filter: string) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function FilterForms({
|
export default function FilterForms({
|
||||||
seasons,
|
seasons,
|
||||||
productors,
|
productors,
|
||||||
filters,
|
filters,
|
||||||
onFilterChange
|
onFilterChange,
|
||||||
}: FilterFormsProps) {
|
}: FilterFormsProps) {
|
||||||
const defaultProductors = useMemo(() => {
|
const defaultProductors = useMemo(() => {
|
||||||
return filters.getAll("productors")
|
return filters.getAll("productors");
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
const defaultSeasons = useMemo(() => {
|
const defaultSeasons = useMemo(() => {
|
||||||
return filters.getAll("seasons")
|
return filters.getAll("seasons");
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by season", {capfirst: true})}
|
aria-label={t("filter by season", { capfirst: true })}
|
||||||
placeholder={t("filter by season", {capfirst: true})}
|
placeholder={t("filter by season", { capfirst: true })}
|
||||||
data={seasons}
|
data={seasons}
|
||||||
defaultValue={defaultSeasons}
|
defaultValue={defaultSeasons}
|
||||||
onChange={(values: string[]) => {
|
onChange={(values: string[]) => {
|
||||||
onFilterChange(values, 'seasons')
|
onFilterChange(values, "seasons");
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by productor", {capfirst: true})}
|
aria-label={t("filter by productor", { capfirst: true })}
|
||||||
placeholder={t("filter by productor", {capfirst: true})}
|
placeholder={t("filter by productor", { capfirst: true })}
|
||||||
data={productors}
|
data={productors}
|
||||||
defaultValue={defaultProductors}
|
defaultValue={defaultProductors}
|
||||||
onChange={(values: string[]) => {
|
onChange={(values: string[]) => {
|
||||||
onFilterChange(values, 'productors')
|
onFilterChange(values, "productors");
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { Button, Group, Modal, NumberInput, Select, TextInput, type ModalBaseProps } from "@mantine/core";
|
import {
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Modal,
|
||||||
|
NumberInput,
|
||||||
|
Select,
|
||||||
|
TextInput,
|
||||||
|
type ModalBaseProps,
|
||||||
|
} from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { DatePickerInput } from "@mantine/dates";
|
import { DatePickerInput } from "@mantine/dates";
|
||||||
import { IconCancel, IconEdit, IconPlus } from "@tabler/icons-react";
|
import { IconCancel, IconEdit, IconPlus } from "@tabler/icons-react";
|
||||||
@@ -10,16 +18,11 @@ import type { Form, FormInputs } from "@/services/resources/forms";
|
|||||||
export type FormModalProps = ModalBaseProps & {
|
export type FormModalProps = ModalBaseProps & {
|
||||||
currentForm?: Form;
|
currentForm?: Form;
|
||||||
handleSubmit: (form: FormInputs, id?: number) => void;
|
handleSubmit: (form: FormInputs, id?: number) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function FormModal({
|
export default function FormModal({ opened, onClose, currentForm, handleSubmit }: FormModalProps) {
|
||||||
opened,
|
const { data: productors } = useGetProductors();
|
||||||
onClose,
|
const { data: users } = useGetUsers();
|
||||||
currentForm,
|
|
||||||
handleSubmit
|
|
||||||
}: FormModalProps) {
|
|
||||||
const {data: productors} = useGetProductors();
|
|
||||||
const {data: users} = useGetUsers();
|
|
||||||
|
|
||||||
const form = useForm<FormInputs>({
|
const form = useForm<FormInputs>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -33,115 +36,138 @@ export default function FormModal({
|
|||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
name: (value) =>
|
name: (value) =>
|
||||||
!value ? `${t("a name", {capfirst: true})} ${t('is required')}` : null,
|
!value ? `${t("a name", { capfirst: true })} ${t("is required")}` : null,
|
||||||
season: (value) =>
|
season: (value) =>
|
||||||
!value ? `${t("a season", {capfirst: true})} ${t('is required')}` : null,
|
!value ? `${t("a season", { capfirst: true })} ${t("is required")}` : null,
|
||||||
start: (value) =>
|
start: (value) =>
|
||||||
!value ? `${t("a start date", {capfirst: true})} ${t('is required')}` : null,
|
!value ? `${t("a start date", { capfirst: true })} ${t("is required")}` : null,
|
||||||
end: (value) =>
|
end: (value) =>
|
||||||
!value ? `${t("a end date", {capfirst: true})} ${t('is required')}` : null,
|
!value ? `${t("a end date", { capfirst: true })} ${t("is required")}` : null,
|
||||||
productor_id: (value) =>
|
productor_id: (value) =>
|
||||||
!value ? `${t("a productor", {capfirst: true})} ${t('is required')}` : null,
|
!value ? `${t("a productor", { capfirst: true })} ${t("is required")}` : null,
|
||||||
referer_id: (value) =>
|
referer_id: (value) =>
|
||||||
!value ? `${t("a referer", {capfirst: true})} ${t('is required')}` : null
|
!value ? `${t("a referer", { capfirst: true })} ${t("is required")}` : null,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const usersSelect = useMemo(() => {
|
const usersSelect = useMemo(() => {
|
||||||
return users?.map(user => ({value: String(user.id), label: `${user.name}`}))
|
return users?.map((user) => ({
|
||||||
|
value: String(user.id),
|
||||||
|
label: `${user.name}`,
|
||||||
|
}));
|
||||||
}, [users]);
|
}, [users]);
|
||||||
|
|
||||||
const productorsSelect = useMemo(() => {
|
const productorsSelect = useMemo(() => {
|
||||||
return productors?.map(prod => ({value: String(prod.id), label: `${prod.name}`}))
|
return productors?.map((prod) => ({
|
||||||
|
value: String(prod.id),
|
||||||
|
label: `${prod.name}`,
|
||||||
|
}));
|
||||||
}, [productors]);
|
}, [productors]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={currentForm ? t("edit form", {capfirst: true}) : t('create form', {capfirst: true})}
|
title={
|
||||||
|
currentForm
|
||||||
|
? t("edit form", { capfirst: true })
|
||||||
|
: t("create form", { capfirst: true })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t("form name", {capfirst: true})}
|
label={t("form name", { capfirst: true })}
|
||||||
placeholder={t("form name", {capfirst: true})}
|
placeholder={t("form name", { capfirst: true })}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
{...form.getInputProps('name')}
|
{...form.getInputProps("name")}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t("contract season", {capfirst: true})}
|
label={t("contract season", { capfirst: true })}
|
||||||
placeholder={t("contract season", {capfirst: true})}
|
placeholder={t("contract season", { capfirst: true })}
|
||||||
description={t("contract season recommandation", {capfirst: true})}
|
description={t("contract season recommandation", { capfirst: true })}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
{...form.getInputProps('season')}
|
{...form.getInputProps("season")}
|
||||||
/>
|
/>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<DatePickerInput
|
<DatePickerInput
|
||||||
label={t("start date", {capfirst: true})}
|
label={t("start date", { capfirst: true })}
|
||||||
placeholder={t("start date", {capfirst: true})}
|
placeholder={t("start date", { capfirst: true })}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
{...form.getInputProps('start')}
|
{...form.getInputProps("start")}
|
||||||
/>
|
/>
|
||||||
<DatePickerInput
|
<DatePickerInput
|
||||||
label={t("end date", {capfirst: true})}
|
label={t("end date", { capfirst: true })}
|
||||||
placeholder={t("end date", {capfirst: true})}
|
placeholder={t("end date", { capfirst: true })}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
{...form.getInputProps('end')}
|
{...form.getInputProps("end")}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<Select
|
<Select
|
||||||
label={t("referer", {capfirst: true})}
|
label={t("referer", { capfirst: true })}
|
||||||
placeholder={t("referer", {capfirst: true})}
|
placeholder={t("referer", { capfirst: true })}
|
||||||
nothingFoundMessage={t("nothing found", {capfirst: true})}
|
nothingFoundMessage={t("nothing found", { capfirst: true })}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
clearable
|
clearable
|
||||||
allowDeselect
|
allowDeselect
|
||||||
searchable
|
searchable
|
||||||
data={usersSelect || []}
|
data={usersSelect || []}
|
||||||
{...form.getInputProps('referer_id')}
|
{...form.getInputProps("referer_id")}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label={t("productor", {capfirst: true})}
|
label={t("productor", { capfirst: true })}
|
||||||
placeholder={t("productor", {capfirst: true})}
|
placeholder={t("productor", { capfirst: true })}
|
||||||
nothingFoundMessage={t("nothing found", {capfirst: true})}
|
nothingFoundMessage={t("nothing found", { capfirst: true })}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
clearable
|
clearable
|
||||||
allowDeselect
|
allowDeselect
|
||||||
searchable
|
searchable
|
||||||
data={productorsSelect || []}
|
data={productorsSelect || []}
|
||||||
{...form.getInputProps('productor_id')}
|
{...form.getInputProps("productor_id")}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label={t("minimum shipment value", {capfirst: true})}
|
label={t("minimum shipment value", { capfirst: true })}
|
||||||
placeholder={t("minimum shipment value", {capfirst: true})}
|
placeholder={t("minimum shipment value", { capfirst: true })}
|
||||||
description={t("some contracts require a minimum value per shipment, ignore this field if it's not the case", {capfirst: true})}
|
description={t(
|
||||||
|
"some contracts require a minimum value per shipment, ignore this field if it's not the case",
|
||||||
|
{ capfirst: true },
|
||||||
|
)}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
{...form.getInputProps('minimum_shipment_value')}
|
{...form.getInputProps("minimum_shipment_value")}
|
||||||
/>
|
/>
|
||||||
<Group mt="sm" justify="space-between">
|
<Group mt="sm" justify="space-between">
|
||||||
<Button
|
<Button
|
||||||
variant="filled"
|
variant="filled"
|
||||||
color="red"
|
color="red"
|
||||||
aria-label={t("cancel", {capfirst: true})}
|
aria-label={t("cancel", { capfirst: true })}
|
||||||
leftSection={<IconCancel/>}
|
leftSection={<IconCancel />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.clearErrors();
|
form.clearErrors();
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>{t("cancel", {capfirst: true})}</Button>
|
>
|
||||||
|
{t("cancel", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="filled"
|
variant="filled"
|
||||||
aria-label={currentForm ? t("edit form", {capfirst: true}) : t('create form', {capfirst: true})}
|
aria-label={
|
||||||
leftSection={currentForm ? <IconEdit/> : <IconPlus/>}
|
currentForm
|
||||||
|
? t("edit form", { capfirst: true })
|
||||||
|
: t("create form", { capfirst: true })
|
||||||
|
}
|
||||||
|
leftSection={currentForm ? <IconEdit /> : <IconPlus />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.validate();
|
form.validate();
|
||||||
if (form.isValid()) {
|
if (form.isValid()) {
|
||||||
handleSubmit(form.getValues(), currentForm?.id)
|
handleSubmit(form.getValues(), currentForm?.id);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>{currentForm ? t("edit form", {capfirst: true}) : t('create form', {capfirst: true})}</Button>
|
>
|
||||||
|
{currentForm
|
||||||
|
? t("edit form", { capfirst: true })
|
||||||
|
: t("create form", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
import { useDeleteForm} from "@/services/api";
|
import { useDeleteForm } from "@/services/api";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import type { Form } from "@/services/resources/forms";
|
import type { Form } from "@/services/resources/forms";
|
||||||
|
|
||||||
export type FormRowProps = {
|
export type FormRowProps = {
|
||||||
form: Form;
|
form: Form;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function FormRow({
|
export default function FormRow({ form }: FormRowProps) {
|
||||||
form,
|
|
||||||
}: FormRowProps) {
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const deleteMutation = useDeleteForm();
|
const deleteMutation = useDeleteForm();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -25,31 +23,33 @@ export default function FormRow({
|
|||||||
<Table.Td>{form.productor.name}</Table.Td>
|
<Table.Td>{form.productor.name}</Table.Td>
|
||||||
<Table.Td>{form.referer.name}</Table.Td>
|
<Table.Td>{form.referer.name}</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Tooltip label={t("edit productor", {capfirst: true})}>
|
<Tooltip label={t("edit productor", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/forms/${form.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
navigate(
|
||||||
|
`/dashboard/forms/${form.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t("remove productor", {capfirst: true})}>
|
<Tooltip label={t("remove productor", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteMutation.mutate(form.id);
|
deleteMutation.mutate(form.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX/>
|
<IconX />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,24 @@
|
|||||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||||
import { IconInfoCircle } from "@tabler/icons-react";
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
|
|
||||||
export type InputLabelProps = {
|
export type InputLabelProps = {
|
||||||
label: string;
|
label: string;
|
||||||
info: string;
|
info: string;
|
||||||
isRequired?: boolean;
|
isRequired?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function InputLabel({label, info, isRequired}: InputLabelProps) {
|
export function InputLabel({ label, info, isRequired }: InputLabelProps) {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
<div style={{ display: "flex", alignItems: "center", gap: 4 }}>
|
||||||
<Tooltip label={info}>
|
<Tooltip label={info}>
|
||||||
<ActionIcon variant="transparent" size="xs" color="gray">
|
<ActionIcon variant="transparent" size="xs" color="gray">
|
||||||
<IconInfoCircle size={16}/>
|
<IconInfoCircle size={16} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span>
|
<span>
|
||||||
{label}
|
{label}
|
||||||
{
|
{isRequired ? <span style={{ color: "red" }}> *</span> : null}
|
||||||
isRequired ?
|
</span>
|
||||||
<span style={{ color: 'red' }}> *</span> : null
|
</div>
|
||||||
}
|
);
|
||||||
</span>
|
}
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ nav {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navLink {
|
.navLink {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,28 +8,24 @@ export function Navbar() {
|
|||||||
return (
|
return (
|
||||||
<nav>
|
<nav>
|
||||||
<Group>
|
<Group>
|
||||||
<NavLink
|
<NavLink className={"navLink"} aria-label={t("home")} to="/">
|
||||||
className={"navLink"}
|
{t("home", { capfirst: true })}
|
||||||
aria-label={t('home')}
|
|
||||||
to="/"
|
|
||||||
>
|
|
||||||
{t("home", {capfirst: true})}
|
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
className={"navLink"}
|
className={"navLink"}
|
||||||
aria-label={t('dashboard')}
|
aria-label={t("dashboard")}
|
||||||
to="/dashboard/productors"
|
to="/dashboard/productors"
|
||||||
>
|
>
|
||||||
{t("dashboard", {capfirst: true})}
|
{t("dashboard", { capfirst: true })}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Group>
|
</Group>
|
||||||
<NavLink
|
<NavLink
|
||||||
className={"navLink"}
|
className={"navLink"}
|
||||||
aria-label={t("login with keycloak")}
|
aria-label={t("login with keycloak")}
|
||||||
to={`${Config.backend_uri}/auth/login`}
|
to={`${Config.backend_uri}/auth/login`}
|
||||||
>
|
>
|
||||||
{t("login with keycloak", {capfirst: true})}
|
{t("login with keycloak", { capfirst: true })}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
import { Group, MultiSelect } from "@mantine/core";
|
import { Group, MultiSelect } from "@mantine/core";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
|
|
||||||
export type ProductorsFiltersProps = {
|
export type ProductorsFiltersProps = {
|
||||||
names: string[];
|
names: string[];
|
||||||
types: string[];
|
types: string[];
|
||||||
filters: URLSearchParams;
|
filters: URLSearchParams;
|
||||||
onFilterChange: (values: string[], filter: string) => void;
|
onFilterChange: (values: string[], filter: string) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function ProductorsFilter({
|
export default function ProductorsFilter({
|
||||||
names,
|
names,
|
||||||
types,
|
types,
|
||||||
filters,
|
filters,
|
||||||
onFilterChange
|
onFilterChange,
|
||||||
}: ProductorsFiltersProps) {
|
}: ProductorsFiltersProps) {
|
||||||
const defaultNames = useMemo(() => {
|
const defaultNames = useMemo(() => {
|
||||||
return filters.getAll("names")
|
return filters.getAll("names");
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
const defaultTypes = useMemo(() => {
|
const defaultTypes = useMemo(() => {
|
||||||
return filters.getAll("types")
|
return filters.getAll("types");
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by name", {capfirst: true})}
|
aria-label={t("filter by name", { capfirst: true })}
|
||||||
placeholder={t("filter by name", {capfirst: true})}
|
placeholder={t("filter by name", { capfirst: true })}
|
||||||
data={names}
|
data={names}
|
||||||
defaultValue={defaultNames}
|
defaultValue={defaultNames}
|
||||||
onChange={(values: string[]) => {
|
onChange={(values: string[]) => {
|
||||||
onFilterChange(values, 'names')
|
onFilterChange(values, "names");
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
/>
|
/>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by type", {capfirst: true})}
|
aria-label={t("filter by type", { capfirst: true })}
|
||||||
placeholder={t("filter by type", {capfirst: true})}
|
placeholder={t("filter by type", { capfirst: true })}
|
||||||
data={types}
|
data={types}
|
||||||
defaultValue={defaultTypes}
|
defaultValue={defaultTypes}
|
||||||
onChange={(values: string[]) => {
|
onChange={(values: string[]) => {
|
||||||
onFilterChange(values, 'types')
|
onFilterChange(values, "types");
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,130 +1,151 @@
|
|||||||
import { Button, Group, Modal, MultiSelect, TextInput, Title, type ModalBaseProps } from "@mantine/core";
|
import {
|
||||||
import { t } from "@/config/i18n";
|
Button,
|
||||||
import { useForm } from "@mantine/form";
|
Group,
|
||||||
import { IconCancel } from "@tabler/icons-react";
|
Modal,
|
||||||
import { PaymentMethods, type Productor, type ProductorInputs } from "@/services/resources/productors";
|
MultiSelect,
|
||||||
|
TextInput,
|
||||||
export type ProductorModalProps = ModalBaseProps & {
|
Title,
|
||||||
currentProductor?: Productor;
|
type ModalBaseProps,
|
||||||
handleSubmit: (productor: ProductorInputs, id?: number) => void;
|
} from "@mantine/core";
|
||||||
}
|
import { t } from "@/config/i18n";
|
||||||
|
import { useForm } from "@mantine/form";
|
||||||
export function ProductorModal({
|
import { IconCancel } from "@tabler/icons-react";
|
||||||
opened,
|
import {
|
||||||
onClose,
|
PaymentMethods,
|
||||||
currentProductor,
|
type Productor,
|
||||||
handleSubmit
|
type ProductorInputs,
|
||||||
}: ProductorModalProps) {
|
} from "@/services/resources/productors";
|
||||||
const form = useForm<ProductorInputs>({
|
|
||||||
initialValues: {
|
export type ProductorModalProps = ModalBaseProps & {
|
||||||
name: currentProductor?.name ?? "",
|
currentProductor?: Productor;
|
||||||
address: currentProductor?.address ?? "",
|
handleSubmit: (productor: ProductorInputs, id?: number) => void;
|
||||||
payment_methods: currentProductor?.payment_methods ?? [],
|
};
|
||||||
type: currentProductor?.type ?? "",
|
|
||||||
},
|
export function ProductorModal({
|
||||||
validate: {
|
opened,
|
||||||
name: (value) =>
|
onClose,
|
||||||
!value ? `${t("name", {capfirst: true})} ${t("is required")}` : null,
|
currentProductor,
|
||||||
address: (value) =>
|
handleSubmit,
|
||||||
!value ? `${t("address", {capfirst: true})} ${t("is required")}` : null,
|
}: ProductorModalProps) {
|
||||||
type: (value) =>
|
const form = useForm<ProductorInputs>({
|
||||||
!value ? `${t("type", {capfirst: true})} ${t("is required")}` : null
|
initialValues: {
|
||||||
}
|
name: currentProductor?.name ?? "",
|
||||||
});
|
address: currentProductor?.address ?? "",
|
||||||
|
payment_methods: currentProductor?.payment_methods ?? [],
|
||||||
return (
|
type: currentProductor?.type ?? "",
|
||||||
<Modal
|
},
|
||||||
opened={opened}
|
validate: {
|
||||||
onClose={onClose}
|
name: (value) =>
|
||||||
title={t("create productor", {capfirst: true})}
|
!value ? `${t("name", { capfirst: true })} ${t("is required")}` : null,
|
||||||
>
|
address: (value) =>
|
||||||
<Title order={4}>{t("Informations", {capfirst: true})}</Title>
|
!value ? `${t("address", { capfirst: true })} ${t("is required")}` : null,
|
||||||
<TextInput
|
type: (value) =>
|
||||||
label={t("productor name", {capfirst: true})}
|
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
|
||||||
placeholder={t("productor name", {capfirst: true})}
|
},
|
||||||
radius="sm"
|
});
|
||||||
withAsterisk
|
|
||||||
{...form.getInputProps("name")}
|
return (
|
||||||
/>
|
<Modal opened={opened} onClose={onClose} title={t("create productor", { capfirst: true })}>
|
||||||
<TextInput
|
<Title order={4}>{t("Informations", { capfirst: true })}</Title>
|
||||||
label={t("productor type", {capfirst: true})}
|
<TextInput
|
||||||
placeholder={t("productor type", {capfirst: true})}
|
label={t("productor name", { capfirst: true })}
|
||||||
radius="sm"
|
placeholder={t("productor name", { capfirst: true })}
|
||||||
withAsterisk
|
radius="sm"
|
||||||
{...form.getInputProps("type")}
|
withAsterisk
|
||||||
/>
|
{...form.getInputProps("name")}
|
||||||
<TextInput
|
/>
|
||||||
label={t("productor address", {capfirst: true})}
|
<TextInput
|
||||||
placeholder={t("productor address", {capfirst: true})}
|
label={t("productor type", { capfirst: true })}
|
||||||
radius="sm"
|
placeholder={t("productor type", { capfirst: true })}
|
||||||
withAsterisk
|
radius="sm"
|
||||||
{...form.getInputProps("address")}
|
withAsterisk
|
||||||
/>
|
{...form.getInputProps("type")}
|
||||||
<MultiSelect
|
/>
|
||||||
label={t("payment methods", {capfirst: true})}
|
<TextInput
|
||||||
placeholder={t("payment methods", {capfirst: true})}
|
label={t("productor address", { capfirst: true })}
|
||||||
radius="sm"
|
placeholder={t("productor address", { capfirst: true })}
|
||||||
withAsterisk
|
radius="sm"
|
||||||
data={PaymentMethods}
|
withAsterisk
|
||||||
clearable
|
{...form.getInputProps("address")}
|
||||||
searchable
|
/>
|
||||||
value={form.values.payment_methods.map(p => p.name)}
|
<MultiSelect
|
||||||
onChange={(names) => {
|
label={t("payment methods", { capfirst: true })}
|
||||||
form.setFieldValue("payment_methods", names.map(name => {
|
placeholder={t("payment methods", { capfirst: true })}
|
||||||
const existing = form.values.payment_methods.find(p => p.name === name);
|
radius="sm"
|
||||||
return existing ?? {
|
withAsterisk
|
||||||
name,
|
data={PaymentMethods}
|
||||||
details: ""
|
clearable
|
||||||
};
|
searchable
|
||||||
}));
|
value={form.values.payment_methods.map((p) => p.name)}
|
||||||
}}
|
onChange={(names) => {
|
||||||
/>
|
form.setFieldValue(
|
||||||
{
|
"payment_methods",
|
||||||
form.values.payment_methods.map((method, index) => (
|
names.map((name) => {
|
||||||
<TextInput
|
const existing = form.values.payment_methods.find(
|
||||||
key={index}
|
(p) => p.name === name,
|
||||||
label={
|
);
|
||||||
method.name === "cheque" ?
|
return (
|
||||||
t("order name", {capfirst: true}) :
|
existing ?? {
|
||||||
method.name === "transfer" ?
|
name,
|
||||||
t("IBAN") :
|
details: "",
|
||||||
t("details", {capfirst: true})
|
}
|
||||||
}
|
);
|
||||||
placeholder={
|
}),
|
||||||
method.name === "cheque" ?
|
);
|
||||||
t("order name", {capfirst: true}) :
|
}}
|
||||||
method.name === "transfer" ?
|
/>
|
||||||
t("IBAN") :
|
{form.values.payment_methods.map((method, index) => (
|
||||||
t("details", {capfirst: true})
|
<TextInput
|
||||||
}
|
key={index}
|
||||||
{...form.getInputProps(
|
label={
|
||||||
`payment_methods.${index}.details`
|
method.name === "cheque"
|
||||||
)}
|
? t("order name", { capfirst: true })
|
||||||
/>
|
: method.name === "transfer"
|
||||||
))
|
? t("IBAN")
|
||||||
}
|
: t("details", { capfirst: true })
|
||||||
<Group mt="sm" justify="space-between">
|
}
|
||||||
<Button
|
placeholder={
|
||||||
variant="filled"
|
method.name === "cheque"
|
||||||
color="red"
|
? t("order name", { capfirst: true })
|
||||||
aria-label={t("cancel", {capfirst: true})}
|
: method.name === "transfer"
|
||||||
leftSection={<IconCancel/>}
|
? t("IBAN")
|
||||||
onClick={() => {
|
: t("details", { capfirst: true })
|
||||||
form.clearErrors();
|
}
|
||||||
onClose();
|
{...form.getInputProps(`payment_methods.${index}.details`)}
|
||||||
}}
|
/>
|
||||||
>{t("cancel", {capfirst: true})}</Button>
|
))}
|
||||||
<Button
|
<Group mt="sm" justify="space-between">
|
||||||
variant="filled"
|
<Button
|
||||||
aria-label={currentProductor ? t("edit productor", {capfirst: true}) : t("create productor", {capfirst: true})}
|
variant="filled"
|
||||||
onClick={() => {
|
color="red"
|
||||||
form.validate();
|
aria-label={t("cancel", { capfirst: true })}
|
||||||
if (form.isValid()) {
|
leftSection={<IconCancel />}
|
||||||
handleSubmit(form.getValues(), currentProductor?.id)
|
onClick={() => {
|
||||||
}
|
form.clearErrors();
|
||||||
}}
|
onClose();
|
||||||
>{currentProductor ? t("edit productor", {capfirst: true}) : t("create productor", {capfirst: true})}</Button>
|
}}
|
||||||
</Group>
|
>
|
||||||
</Modal>
|
{t("cancel", { capfirst: true })}
|
||||||
);
|
</Button>
|
||||||
}
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
aria-label={
|
||||||
|
currentProductor
|
||||||
|
? t("edit productor", { capfirst: true })
|
||||||
|
: t("create productor", { capfirst: true })
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
form.validate();
|
||||||
|
if (form.isValid()) {
|
||||||
|
handleSubmit(form.getValues(), currentProductor?.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentProductor
|
||||||
|
? t("edit productor", { capfirst: true })
|
||||||
|
: t("create productor", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,62 +1,59 @@
|
|||||||
import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import type { Productor } from "@/services/resources/productors";
|
import type { Productor } from "@/services/resources/productors";
|
||||||
import { useDeleteProductor } from "@/services/api";
|
import { useDeleteProductor } from "@/services/api";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
|
||||||
export type ProductorRowProps = {
|
export type ProductorRowProps = {
|
||||||
productor: Productor;
|
productor: Productor;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function ProductorRow({
|
export default function ProductorRow({ productor }: ProductorRowProps) {
|
||||||
productor,
|
const [searchParams] = useSearchParams();
|
||||||
}: ProductorRowProps) {
|
const deleteMutation = useDeleteProductor();
|
||||||
const [searchParams] = useSearchParams();
|
const navigate = useNavigate();
|
||||||
const deleteMutation = useDeleteProductor();
|
|
||||||
const navigate = useNavigate();
|
return (
|
||||||
|
<Table.Tr key={productor.id}>
|
||||||
return (
|
<Table.Td>{productor.name}</Table.Td>
|
||||||
<Table.Tr key={productor.id}>
|
<Table.Td>{productor.type}</Table.Td>
|
||||||
<Table.Td>{productor.name}</Table.Td>
|
<Table.Td>{productor.address}</Table.Td>
|
||||||
<Table.Td>{productor.type}</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td>{productor.address}</Table.Td>
|
{productor.payment_methods.map((value) => (
|
||||||
<Table.Td>
|
<Badge key={value.name} ml="xs">
|
||||||
{
|
{t(value.name, { capfirst: true })}
|
||||||
productor.payment_methods.map((value) =>(
|
</Badge>
|
||||||
<Badge key={value.name} ml="xs">
|
))}
|
||||||
{t(value.name, {capfirst: true})}
|
</Table.Td>
|
||||||
</Badge>
|
<Table.Td>
|
||||||
))
|
<Tooltip label={t("edit productor", { capfirst: true })}>
|
||||||
}
|
<ActionIcon
|
||||||
</Table.Td>
|
size="sm"
|
||||||
<Table.Td>
|
mr="5"
|
||||||
<Tooltip label={t("edit productor", {capfirst: true})}>
|
onClick={(e) => {
|
||||||
<ActionIcon
|
e.stopPropagation();
|
||||||
size="sm"
|
navigate(
|
||||||
mr="5"
|
`/dashboard/productors/${productor.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
onClick={(e) => {
|
);
|
||||||
e.stopPropagation();
|
}}
|
||||||
navigate(`/dashboard/productors/${productor.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
>
|
||||||
}}
|
<IconEdit />
|
||||||
>
|
</ActionIcon>
|
||||||
<IconEdit/>
|
</Tooltip>
|
||||||
</ActionIcon>
|
<Tooltip label={t("remove productor", { capfirst: true })}>
|
||||||
</Tooltip>
|
<ActionIcon
|
||||||
<Tooltip label={t("remove productor", {capfirst: true})}>
|
color="red"
|
||||||
<ActionIcon
|
size="sm"
|
||||||
color="red"
|
mr="5"
|
||||||
size="sm"
|
onClick={() => {
|
||||||
mr="5"
|
deleteMutation.mutate(productor.id);
|
||||||
onClick={() => {
|
}}
|
||||||
deleteMutation.mutate(productor.id);
|
>
|
||||||
}}
|
<IconX />
|
||||||
>
|
</ActionIcon>
|
||||||
<IconX/>
|
</Tooltip>
|
||||||
</ActionIcon>
|
</Table.Td>
|
||||||
</Tooltip>
|
</Table.Tr>
|
||||||
</Table.Td>
|
);
|
||||||
</Table.Tr>
|
}
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
import { Group, MultiSelect } from "@mantine/core";
|
import { Group, MultiSelect } from "@mantine/core";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
|
|
||||||
export type ProductsFiltersProps = {
|
export type ProductsFiltersProps = {
|
||||||
names: string[];
|
names: string[];
|
||||||
productors: string[];
|
productors: string[];
|
||||||
filters: URLSearchParams;
|
filters: URLSearchParams;
|
||||||
onFilterChange: (values: string[], filter: string) => void;
|
onFilterChange: (values: string[], filter: string) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function ProductsFilters({
|
export default function ProductsFilters({
|
||||||
names,
|
names,
|
||||||
productors,
|
productors,
|
||||||
filters,
|
filters,
|
||||||
onFilterChange
|
onFilterChange,
|
||||||
}: ProductsFiltersProps) {
|
}: ProductsFiltersProps) {
|
||||||
const defaultNames = useMemo(() => {
|
const defaultNames = useMemo(() => {
|
||||||
return filters.getAll("names")
|
return filters.getAll("names");
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
const defaultProductors = useMemo(() => {
|
const defaultProductors = useMemo(() => {
|
||||||
return filters.getAll("productors")
|
return filters.getAll("productors");
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by name", {capfirst: true})}
|
aria-label={t("filter by name", { capfirst: true })}
|
||||||
placeholder={t("filter by name", {capfirst: true})}
|
placeholder={t("filter by name", { capfirst: true })}
|
||||||
data={names}
|
data={names}
|
||||||
defaultValue={defaultNames}
|
defaultValue={defaultNames}
|
||||||
onChange={(values: string[]) => {
|
onChange={(values: string[]) => {
|
||||||
onFilterChange(values, 'names')
|
onFilterChange(values, "names");
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
/>
|
/>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by productor", {capfirst: true})}
|
aria-label={t("filter by productor", { capfirst: true })}
|
||||||
placeholder={t("filter by productor", {capfirst: true})}
|
placeholder={t("filter by productor", { capfirst: true })}
|
||||||
data={productors}
|
data={productors}
|
||||||
defaultValue={defaultProductors}
|
defaultValue={defaultProductors}
|
||||||
onChange={(values: string[]) => {
|
onChange={(values: string[]) => {
|
||||||
onFilterChange(values, 'productors')
|
onFilterChange(values, "productors");
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,37 +8,34 @@ export type ProductFormProps = {
|
|||||||
inputForm: UseFormReturnType<Record<string, string | number>>;
|
inputForm: UseFormReturnType<Record<string, string | number>>;
|
||||||
product: Product;
|
product: Product;
|
||||||
shipment?: Shipment;
|
shipment?: Shipment;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function ProductForm({
|
export function ProductForm({ inputForm, product, shipment }: ProductFormProps) {
|
||||||
inputForm,
|
|
||||||
product,
|
|
||||||
shipment,
|
|
||||||
}: ProductFormProps) {
|
|
||||||
return (
|
return (
|
||||||
<Group mb="sm" grow>
|
<Group mb="sm" grow>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label={
|
label={`${product.name}
|
||||||
`${product.name}
|
|
||||||
${product.quantity || ""}${product.quantity ? product.quantity_unit : ""}
|
${product.quantity || ""}${product.quantity ? product.quantity_unit : ""}
|
||||||
${
|
${
|
||||||
product.price ?
|
product.price
|
||||||
Intl.NumberFormat(
|
? Intl.NumberFormat("fr-FR", {
|
||||||
"fr-FR",
|
style: "currency",
|
||||||
{style: "currency", currency: "EUR"}
|
currency: "EUR",
|
||||||
).format(product.price) :
|
}).format(product.price)
|
||||||
product.price_kg && Intl.NumberFormat(
|
: product.price_kg &&
|
||||||
"fr-FR",
|
Intl.NumberFormat("fr-FR", {
|
||||||
{style: "currency", currency: "EUR"}
|
style: "currency",
|
||||||
).format(product.price_kg)
|
currency: "EUR",
|
||||||
|
}).format(product.price_kg)
|
||||||
}
|
}
|
||||||
${product.price ? `/ ${t(ProductUnit[product.unit])}` : "/ kg"}`
|
${product.price ? `/ ${t(ProductUnit[product.unit])}` : "/ kg"}`}
|
||||||
}
|
description={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t(ProductUnit[product.unit])}`}
|
||||||
description={`${t("enter quantity", {capfirst: true})} ${t('in')} ${t(ProductUnit[product.unit])}`}
|
|
||||||
aria-label={t("enter quantity")}
|
aria-label={t("enter quantity")}
|
||||||
placeholder={`${t("enter quantity", {capfirst: true})} ${t('in')} ${t(ProductUnit[product.unit])}`}
|
placeholder={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t(ProductUnit[product.unit])}`}
|
||||||
{...inputForm.getInputProps(shipment ? `planned-${shipment.id}-${product.id}` : `recurrent-${product.id}`)}
|
{...inputForm.getInputProps(
|
||||||
|
shipment ? `planned-${shipment.id}-${product.id}` : `recurrent-${product.id}`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,163 +1,196 @@
|
|||||||
import { Button, Group, Modal, NumberInput, Select, TextInput, Title, type ModalBaseProps } from "@mantine/core";
|
import {
|
||||||
import { t } from "@/config/i18n";
|
Button,
|
||||||
import { useForm } from "@mantine/form";
|
Group,
|
||||||
import { IconCancel } from "@tabler/icons-react";
|
Modal,
|
||||||
import { ProductQuantityUnit, ProductUnit, type Product, type ProductInputs } from "@/services/resources/products";
|
NumberInput,
|
||||||
import { useMemo } from "react";
|
Select,
|
||||||
import { useGetProductors } from "@/services/api";
|
TextInput,
|
||||||
import { InputLabel } from "@/components/Label";
|
Title,
|
||||||
|
type ModalBaseProps,
|
||||||
export type ProductModalProps = ModalBaseProps & {
|
} from "@mantine/core";
|
||||||
currentProduct?: Product;
|
import { t } from "@/config/i18n";
|
||||||
handleSubmit: (product: ProductInputs, id?: number) => void;
|
import { useForm } from "@mantine/form";
|
||||||
}
|
import { IconCancel } from "@tabler/icons-react";
|
||||||
|
import {
|
||||||
export function ProductModal({
|
ProductQuantityUnit,
|
||||||
opened,
|
ProductUnit,
|
||||||
onClose,
|
type Product,
|
||||||
currentProduct,
|
type ProductInputs,
|
||||||
handleSubmit
|
} from "@/services/resources/products";
|
||||||
}: ProductModalProps) {
|
import { useMemo } from "react";
|
||||||
const {data: productors} = useGetProductors();
|
import { useGetProductors } from "@/services/api";
|
||||||
const form = useForm<ProductInputs>({
|
import { InputLabel } from "@/components/Label";
|
||||||
initialValues: {
|
|
||||||
name: currentProduct?.name ?? "",
|
export type ProductModalProps = ModalBaseProps & {
|
||||||
unit: currentProduct?.unit ?? null,
|
currentProduct?: Product;
|
||||||
price: currentProduct?.price ?? null,
|
handleSubmit: (product: ProductInputs, id?: number) => void;
|
||||||
price_kg: currentProduct?.price_kg ?? null,
|
};
|
||||||
quantity: currentProduct?.quantity ?? null,
|
|
||||||
quantity_unit: currentProduct?.quantity_unit ?? null,
|
export function ProductModal({ opened, onClose, currentProduct, handleSubmit }: ProductModalProps) {
|
||||||
type: currentProduct?.type ?? null,
|
const { data: productors } = useGetProductors();
|
||||||
productor_id: currentProduct ? String(currentProduct.productor.id) : null,
|
const form = useForm<ProductInputs>({
|
||||||
},
|
initialValues: {
|
||||||
validate: {
|
name: currentProduct?.name ?? "",
|
||||||
name: (value) =>
|
unit: currentProduct?.unit ?? null,
|
||||||
!value ? `${t("name", {capfirst: true})} ${t('is required')}` : null,
|
price: currentProduct?.price ?? null,
|
||||||
unit: (value) =>
|
price_kg: currentProduct?.price_kg ?? null,
|
||||||
!value ? `${t("unit", {capfirst: true})} ${t('is required')}` : null,
|
quantity: currentProduct?.quantity ?? null,
|
||||||
price: (value, values) =>
|
quantity_unit: currentProduct?.quantity_unit ?? null,
|
||||||
!value && !values.price_kg ? `${t("price or price_kg", {capfirst: true})} ${t('is required')}` : null,
|
type: currentProduct?.type ?? null,
|
||||||
price_kg: (value, values) =>
|
productor_id: currentProduct ? String(currentProduct.productor.id) : null,
|
||||||
!value && !values.price ? `${t("price or price_kg", {capfirst: true})} ${t('is required')}` : null,
|
},
|
||||||
type: (value) =>
|
validate: {
|
||||||
!value ? `${t("type", {capfirst: true})} ${t('is required')}` : null,
|
name: (value) =>
|
||||||
productor_id: (value) =>
|
!value ? `${t("name", { capfirst: true })} ${t("is required")}` : null,
|
||||||
!value ? `${t("productor", {capfirst: true})} ${t('is required')}` : null
|
unit: (value) =>
|
||||||
},
|
!value ? `${t("unit", { capfirst: true })} ${t("is required")}` : null,
|
||||||
});
|
price: (value, values) =>
|
||||||
|
!value && !values.price_kg
|
||||||
const productorsSelect = useMemo(() => {
|
? `${t("price or price_kg", { capfirst: true })} ${t("is required")}`
|
||||||
return productors?.map(productor => ({value: String(productor.id), label: `${productor.name}`}))
|
: null,
|
||||||
}, [productors]);
|
price_kg: (value, values) =>
|
||||||
|
!value && !values.price
|
||||||
return (
|
? `${t("price or price_kg", { capfirst: true })} ${t("is required")}`
|
||||||
<Modal
|
: null,
|
||||||
opened={opened}
|
type: (value) =>
|
||||||
onClose={onClose}
|
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
|
||||||
title={t("create product", {capfirst: true})}
|
productor_id: (value) =>
|
||||||
>
|
!value ? `${t("productor", { capfirst: true })} ${t("is required")}` : null,
|
||||||
<Title order={4}>{t("informations", {capfirst: true})}</Title>
|
},
|
||||||
<Select
|
});
|
||||||
label={t("productor", {capfirst: true})}
|
|
||||||
placeholder={t("productor")}
|
const productorsSelect = useMemo(() => {
|
||||||
nothingFoundMessage={t("nothing found", {capfirst: true})}
|
return productors?.map((productor) => ({
|
||||||
withAsterisk
|
value: String(productor.id),
|
||||||
clearable
|
label: `${productor.name}`,
|
||||||
searchable
|
}));
|
||||||
data={productorsSelect || []}
|
}, [productors]);
|
||||||
{...form.getInputProps('productor_id')}
|
|
||||||
/>
|
return (
|
||||||
<Group grow>
|
<Modal opened={opened} onClose={onClose} title={t("create product", { capfirst: true })}>
|
||||||
<TextInput
|
<Title order={4}>{t("informations", { capfirst: true })}</Title>
|
||||||
label={t("product name", {capfirst: true})}
|
<Select
|
||||||
placeholder={t("product name", {capfirst: true})}
|
label={t("productor", { capfirst: true })}
|
||||||
radius="sm"
|
placeholder={t("productor")}
|
||||||
{...form.getInputProps('name')}
|
nothingFoundMessage={t("nothing found", { capfirst: true })}
|
||||||
/>
|
withAsterisk
|
||||||
<Select
|
clearable
|
||||||
label={
|
searchable
|
||||||
<InputLabel
|
data={productorsSelect || []}
|
||||||
label={t("product type", {capfirst: true})}
|
{...form.getInputProps("productor_id")}
|
||||||
info={t("recurrent product is for all shipments, planned product is for a specific shipment (see shipment form)", {capfirst: true})}
|
/>
|
||||||
isRequired
|
<Group grow>
|
||||||
/>
|
<TextInput
|
||||||
}
|
label={t("product name", { capfirst: true })}
|
||||||
placeholder={t("product type", {capfirst: true})}
|
placeholder={t("product name", { capfirst: true })}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
searchable
|
{...form.getInputProps("name")}
|
||||||
clearable
|
/>
|
||||||
data={[
|
<Select
|
||||||
{value: "1", label: t("planned", {capfirst: true})},
|
label={
|
||||||
{value: "2", label: t("recurrent", {capfirst: true})}
|
<InputLabel
|
||||||
]}
|
label={t("product type", { capfirst: true })}
|
||||||
{...form.getInputProps('type')}
|
info={t(
|
||||||
/>
|
"recurrent product is for all shipments, planned product is for a specific shipment (see shipment form)",
|
||||||
</Group>
|
{ capfirst: true },
|
||||||
<Select
|
)}
|
||||||
label={t("product unit", {capfirst: true})}
|
isRequired
|
||||||
placeholder={t("product unit", {capfirst: true})}
|
/>
|
||||||
description={t("the product unit will be assigned to the quantity requested in the form", { capfirst: true})}
|
}
|
||||||
radius="sm"
|
placeholder={t("product type", { capfirst: true })}
|
||||||
withAsterisk
|
radius="sm"
|
||||||
searchable
|
searchable
|
||||||
clearable
|
clearable
|
||||||
data={Object.entries(ProductUnit).map(([key, value]) => ({value: key, label: t(value, {capfirst: true})}))}
|
data={[
|
||||||
{...form.getInputProps('unit')}
|
{ value: "1", label: t("planned", { capfirst: true }) },
|
||||||
/>
|
{ value: "2", label: t("recurrent", { capfirst: true }) },
|
||||||
<Group grow>
|
]}
|
||||||
<NumberInput
|
{...form.getInputProps("type")}
|
||||||
label={t("product price", {capfirst: true})}
|
/>
|
||||||
placeholder={t("product price", {capfirst: true})}
|
</Group>
|
||||||
radius="sm"
|
<Select
|
||||||
{...form.getInputProps('price')}
|
label={t("product unit", { capfirst: true })}
|
||||||
/>
|
placeholder={t("product unit", { capfirst: true })}
|
||||||
<NumberInput
|
description={t(
|
||||||
label={t("product price kg", {capfirst: true})}
|
"the product unit will be assigned to the quantity requested in the form",
|
||||||
placeholder={t("product price kg", {capfirst: true})}
|
{ capfirst: true },
|
||||||
radius="sm"
|
)}
|
||||||
{...form.getInputProps('price_kg')}
|
radius="sm"
|
||||||
/>
|
withAsterisk
|
||||||
</Group>
|
searchable
|
||||||
<Group grow>
|
clearable
|
||||||
<NumberInput
|
data={Object.entries(ProductUnit).map(([key, value]) => ({
|
||||||
label={t("product quantity", {capfirst: true})}
|
value: key,
|
||||||
placeholder={t("product quantity", {capfirst: true})}
|
label: t(value, { capfirst: true }),
|
||||||
radius="sm"
|
}))}
|
||||||
{...form.getInputProps('quantity', {capfirst: true})}
|
{...form.getInputProps("unit")}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Group grow>
|
||||||
label={t("product quantity unit", {capfirst: true})}
|
<NumberInput
|
||||||
placeholder={t("product quantity unit", {capfirst: true})}
|
label={t("product price", { capfirst: true })}
|
||||||
radius="sm"
|
placeholder={t("product price", { capfirst: true })}
|
||||||
clearable
|
radius="sm"
|
||||||
searchable
|
{...form.getInputProps("price")}
|
||||||
data={Object.entries(ProductQuantityUnit).map(([key, value]) => ({value: key, label: t(value, {capfirst: true})}))}
|
/>
|
||||||
{...form.getInputProps('quantity_unit', {capfirst: true})}
|
<NumberInput
|
||||||
/>
|
label={t("product price kg", { capfirst: true })}
|
||||||
|
placeholder={t("product price kg", { capfirst: true })}
|
||||||
</Group>
|
radius="sm"
|
||||||
<Group mt="sm" justify="space-between">
|
{...form.getInputProps("price_kg")}
|
||||||
<Button
|
/>
|
||||||
variant="filled"
|
</Group>
|
||||||
color="red"
|
<Group grow>
|
||||||
aria-label={t("cancel", {capfirst: true})}
|
<NumberInput
|
||||||
leftSection={<IconCancel/>}
|
label={t("product quantity", { capfirst: true })}
|
||||||
onClick={() => {
|
placeholder={t("product quantity", { capfirst: true })}
|
||||||
form.clearErrors();
|
radius="sm"
|
||||||
onClose();
|
{...form.getInputProps("quantity", { capfirst: true })}
|
||||||
}}
|
/>
|
||||||
>{t("cancel", {capfirst: true})}</Button>
|
<Select
|
||||||
<Button
|
label={t("product quantity unit", { capfirst: true })}
|
||||||
variant="filled"
|
placeholder={t("product quantity unit", { capfirst: true })}
|
||||||
aria-label={currentProduct ? t("edit product", {capfirst: true}) : t('create product', {capfirst: true})}
|
radius="sm"
|
||||||
onClick={() => {
|
clearable
|
||||||
form.validate();
|
searchable
|
||||||
if (form.isValid()) {
|
data={Object.entries(ProductQuantityUnit).map(([key, value]) => ({
|
||||||
handleSubmit(form.getValues(), currentProduct?.id)
|
value: key,
|
||||||
}
|
label: t(value, { capfirst: true }),
|
||||||
}}
|
}))}
|
||||||
>{currentProduct ? t("edit product", {capfirst: true}) : t('create product', {capfirst: true})}</Button>
|
{...form.getInputProps("quantity_unit", { capfirst: true })}
|
||||||
</Group>
|
/>
|
||||||
</Modal>
|
</Group>
|
||||||
);
|
<Group mt="sm" justify="space-between">
|
||||||
}
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
color="red"
|
||||||
|
aria-label={t("cancel", { capfirst: true })}
|
||||||
|
leftSection={<IconCancel />}
|
||||||
|
onClick={() => {
|
||||||
|
form.clearErrors();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("cancel", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
aria-label={
|
||||||
|
currentProduct
|
||||||
|
? t("edit product", { capfirst: true })
|
||||||
|
: t("create product", { capfirst: true })
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
form.validate();
|
||||||
|
if (form.isValid()) {
|
||||||
|
handleSubmit(form.getValues(), currentProduct?.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentProduct
|
||||||
|
? t("edit product", { capfirst: true })
|
||||||
|
: t("create product", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,73 +1,72 @@
|
|||||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { ProductType, ProductUnit, type Product } from "@/services/resources/products";
|
import { ProductType, ProductUnit, type Product } from "@/services/resources/products";
|
||||||
import { useDeleteProduct } from "@/services/api";
|
import { useDeleteProduct } from "@/services/api";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
|
||||||
export type ProductRowProps = {
|
export type ProductRowProps = {
|
||||||
product: Product;
|
product: Product;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function ProductRow({
|
export default function ProductRow({ product }: ProductRowProps) {
|
||||||
product,
|
const [searchParams] = useSearchParams();
|
||||||
}: ProductRowProps) {
|
const deleteMutation = useDeleteProduct();
|
||||||
const [searchParams] = useSearchParams();
|
const navigate = useNavigate();
|
||||||
const deleteMutation = useDeleteProduct();
|
|
||||||
const navigate = useNavigate();
|
return (
|
||||||
|
<Table.Tr key={product.id}>
|
||||||
return (
|
<Table.Td>{product.name}</Table.Td>
|
||||||
<Table.Tr key={product.id}>
|
<Table.Td>{t(ProductType[product.type])}</Table.Td>
|
||||||
<Table.Td>{product.name}</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td>{t(ProductType[product.type])}</Table.Td>
|
{product.price
|
||||||
<Table.Td>
|
? Intl.NumberFormat("fr-FR", {
|
||||||
{
|
style: "currency",
|
||||||
product.price ?
|
currency: "EUR",
|
||||||
Intl.NumberFormat(
|
}).format(product.price)
|
||||||
"fr-FR",
|
: null}
|
||||||
{style: "currency", currency: "EUR"}
|
</Table.Td>
|
||||||
).format(product.price) : null
|
<Table.Td>
|
||||||
}
|
{product.price_kg
|
||||||
</Table.Td>
|
? `${Intl.NumberFormat("fr-FR", {
|
||||||
<Table.Td>
|
style: "currency",
|
||||||
{
|
currency: "EUR",
|
||||||
product.price_kg ?
|
}).format(product.price_kg)}/kg`
|
||||||
`${Intl.NumberFormat(
|
: null}
|
||||||
"fr-FR",
|
</Table.Td>
|
||||||
{style: "currency", currency: "EUR"}
|
<Table.Td>
|
||||||
).format(product.price_kg)}/kg` : null
|
{product.quantity}
|
||||||
|
{product.quantity_unit}
|
||||||
}
|
</Table.Td>
|
||||||
</Table.Td>
|
<Table.Td>{t(ProductUnit[product.unit], { capfirst: true })}</Table.Td>
|
||||||
<Table.Td>{product.quantity}{product.quantity_unit}</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td>{t(ProductUnit[product.unit], {capfirst: true})}</Table.Td>
|
<Tooltip label={t("edit product", { capfirst: true })}>
|
||||||
<Table.Td>
|
<ActionIcon
|
||||||
<Tooltip label={t("edit product", {capfirst: true})}>
|
size="sm"
|
||||||
<ActionIcon
|
mr="5"
|
||||||
size="sm"
|
onClick={(e) => {
|
||||||
mr="5"
|
e.stopPropagation();
|
||||||
onClick={(e) => {
|
navigate(
|
||||||
e.stopPropagation();
|
`/dashboard/products/${product.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
navigate(`/dashboard/products/${product.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t("remove product", {capfirst: true})}>
|
<Tooltip label={t("remove product", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteMutation.mutate(product.id);
|
deleteMutation.mutate(product.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX/>
|
<IconX />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
import { Group, MultiSelect } from "@mantine/core";
|
import { Group, MultiSelect } from "@mantine/core";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
|
|
||||||
export type ShipmentFiltersProps = {
|
export type ShipmentFiltersProps = {
|
||||||
names: string[];
|
names: string[];
|
||||||
forms: string[];
|
forms: string[];
|
||||||
filters: URLSearchParams;
|
filters: URLSearchParams;
|
||||||
onFilterChange: (values: string[], filter: string) => void;
|
onFilterChange: (values: string[], filter: string) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function ShipmentsFilters({
|
export default function ShipmentsFilters({
|
||||||
names,
|
names,
|
||||||
forms,
|
forms,
|
||||||
filters,
|
filters,
|
||||||
onFilterChange
|
onFilterChange,
|
||||||
}: ShipmentFiltersProps) {
|
}: ShipmentFiltersProps) {
|
||||||
const defaultNames = useMemo(() => {
|
const defaultNames = useMemo(() => {
|
||||||
return filters.getAll("names")
|
return filters.getAll("names");
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
const defaultForms = useMemo(() => {
|
const defaultForms = useMemo(() => {
|
||||||
return filters.getAll("forms")
|
return filters.getAll("forms");
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by name", {capfirst: true})}
|
aria-label={t("filter by name", { capfirst: true })}
|
||||||
placeholder={t("filter by name", {capfirst: true})}
|
placeholder={t("filter by name", { capfirst: true })}
|
||||||
data={names}
|
data={names}
|
||||||
defaultValue={defaultNames}
|
defaultValue={defaultNames}
|
||||||
onChange={(values: string[]) => {
|
onChange={(values: string[]) => {
|
||||||
onFilterChange(values, 'names')
|
onFilterChange(values, "names");
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
/>
|
/>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by form", {capfirst: true})}
|
aria-label={t("filter by form", { capfirst: true })}
|
||||||
placeholder={t("filter by form", {capfirst: true})}
|
placeholder={t("filter by form", { capfirst: true })}
|
||||||
data={forms}
|
data={forms}
|
||||||
defaultValue={defaultForms}
|
defaultValue={defaultForms}
|
||||||
onChange={(values: string[]) => {
|
onChange={(values: string[]) => {
|
||||||
onFilterChange(values, 'forms')
|
onFilterChange(values, "forms");
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type ShipmentFormProps = {
|
|||||||
shipment: Shipment;
|
shipment: Shipment;
|
||||||
minimumPrice?: number | null;
|
minimumPrice?: number | null;
|
||||||
index: number;
|
index: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function ShipmentForm({
|
export default function ShipmentForm({
|
||||||
shipment,
|
shipment,
|
||||||
@@ -20,20 +20,16 @@ export default function ShipmentForm({
|
|||||||
minimumPrice,
|
minimumPrice,
|
||||||
}: ShipmentFormProps) {
|
}: ShipmentFormProps) {
|
||||||
const shipmentPrice = useMemo(() => {
|
const shipmentPrice = useMemo(() => {
|
||||||
const values = Object
|
const values = Object.entries(inputForm.getValues()).filter(
|
||||||
.entries(inputForm.getValues())
|
([key]) => key.includes("planned") && key.split("-")[1] === String(shipment.id),
|
||||||
.filter(([key]) =>
|
);
|
||||||
key.includes("planned") &&
|
|
||||||
key.split("-")[1] === String(shipment.id)
|
|
||||||
);
|
|
||||||
return computePrices(values, shipment.products);
|
return computePrices(values, shipment.products);
|
||||||
}, [inputForm, shipment.products, shipment.id]);
|
}, [inputForm, shipment.products, shipment.id]);
|
||||||
|
|
||||||
const priceRequirement = useMemo(() => {
|
const priceRequirement = useMemo(() => {
|
||||||
if (!minimumPrice)
|
if (!minimumPrice) return false;
|
||||||
return false;
|
return minimumPrice ? shipmentPrice < minimumPrice : true;
|
||||||
return minimumPrice ? shipmentPrice < minimumPrice : true
|
}, [shipmentPrice, minimumPrice]);
|
||||||
}, [shipmentPrice, minimumPrice])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion.Item value={String(index)}>
|
<Accordion.Item value={String(index)}>
|
||||||
@@ -41,44 +37,38 @@ export default function ShipmentForm({
|
|||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text>{shipment.name}</Text>
|
<Text>{shipment.name}</Text>
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text c={priceRequirement ? "red" : "green"}>{
|
<Text c={priceRequirement ? "red" : "green"}>
|
||||||
Intl.NumberFormat(
|
{Intl.NumberFormat("fr-FR", {
|
||||||
"fr-FR",
|
style: "currency",
|
||||||
{style: "currency", currency: "EUR"}
|
currency: "EUR",
|
||||||
).format(shipmentPrice)
|
}).format(shipmentPrice)}
|
||||||
}</Text>
|
</Text>
|
||||||
{
|
{priceRequirement ? (
|
||||||
priceRequirement ?
|
<Text c="red" size="sm">
|
||||||
<Text c="red"size="sm">
|
{`${t("minimum price for this shipment should be at least", { capfirst: true })} ${minimumPrice}€`}
|
||||||
{`${t("minimum price for this shipment should be at least", {capfirst: true})} ${minimumPrice}€`}
|
</Text>
|
||||||
</Text> :
|
) : null}
|
||||||
null
|
|
||||||
}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
<Text mr="lg">
|
<Text mr="lg">
|
||||||
{`${
|
{`${new Date(shipment.date).toLocaleDateString("fr-FR", {
|
||||||
new Date(shipment.date).toLocaleDateString("fr-FR", {
|
weekday: "long",
|
||||||
weekday: "long",
|
year: "numeric",
|
||||||
year: "numeric",
|
month: "long",
|
||||||
month: "long",
|
day: "numeric",
|
||||||
day: "numeric",
|
})}`}
|
||||||
})
|
|
||||||
}`}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
{
|
{shipment?.products.map((product) => (
|
||||||
shipment?.products.map((product) => (
|
|
||||||
<ProductForm
|
<ProductForm
|
||||||
key={product.id}
|
key={product.id}
|
||||||
product={product}
|
product={product}
|
||||||
shipment={shipment}
|
shipment={shipment}
|
||||||
inputForm={inputForm}
|
inputForm={inputForm}
|
||||||
/>
|
/>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,120 +1,146 @@
|
|||||||
import { Button, Group, Modal, MultiSelect, Select, TextInput, type ModalBaseProps } from "@mantine/core";
|
import {
|
||||||
import { t } from "@/config/i18n";
|
Button,
|
||||||
import { DatePickerInput } from "@mantine/dates";
|
Group,
|
||||||
import { IconCancel } from "@tabler/icons-react";
|
Modal,
|
||||||
import { useForm } from "@mantine/form";
|
MultiSelect,
|
||||||
import { useMemo } from "react";
|
Select,
|
||||||
import { type Shipment, type ShipmentInputs } from "@/services/resources/shipments";
|
TextInput,
|
||||||
import { useGetForms, useGetProductors, useGetProducts } from "@/services/api";
|
type ModalBaseProps,
|
||||||
|
} from "@mantine/core";
|
||||||
export type ShipmentModalProps = ModalBaseProps & {
|
import { t } from "@/config/i18n";
|
||||||
currentShipment?: Shipment;
|
import { DatePickerInput } from "@mantine/dates";
|
||||||
handleSubmit: (shipment: ShipmentInputs, id?: number) => void;
|
import { IconCancel } from "@tabler/icons-react";
|
||||||
}
|
import { useForm } from "@mantine/form";
|
||||||
|
import { useMemo } from "react";
|
||||||
export default function ShipmentModal({
|
import { type Shipment, type ShipmentInputs } from "@/services/resources/shipments";
|
||||||
opened,
|
import { useGetForms, useGetProductors, useGetProducts } from "@/services/api";
|
||||||
onClose,
|
|
||||||
currentShipment,
|
export type ShipmentModalProps = ModalBaseProps & {
|
||||||
handleSubmit
|
currentShipment?: Shipment;
|
||||||
}: ShipmentModalProps) {
|
handleSubmit: (shipment: ShipmentInputs, id?: number) => void;
|
||||||
const form = useForm<ShipmentInputs>({
|
};
|
||||||
initialValues: {
|
|
||||||
name: currentShipment?.name ?? "",
|
export default function ShipmentModal({
|
||||||
date: currentShipment?.date ?? null,
|
opened,
|
||||||
form_id: currentShipment ? String(currentShipment?.form_id) : "",
|
onClose,
|
||||||
product_ids: currentShipment?.products.map((el) => (String(el.id))) ?? []
|
currentShipment,
|
||||||
},
|
handleSubmit,
|
||||||
validate: {
|
}: ShipmentModalProps) {
|
||||||
name: (value) =>
|
const form = useForm<ShipmentInputs>({
|
||||||
!value ? `${t("a name", {capfirst: true})} ${t('is required')}` : null,
|
initialValues: {
|
||||||
date: (value) =>
|
name: currentShipment?.name ?? "",
|
||||||
!value ? `${t("a shipment date", {capfirst: true})} ${t('is required')}` : null,
|
date: currentShipment?.date ?? null,
|
||||||
form_id: (value) =>
|
form_id: currentShipment ? String(currentShipment?.form_id) : "",
|
||||||
!value ? `${t("a form", {capfirst: true})} ${t('is required')}` : null,
|
product_ids: currentShipment?.products.map((el) => String(el.id)) ?? [],
|
||||||
}
|
},
|
||||||
});
|
validate: {
|
||||||
|
name: (value) =>
|
||||||
const { data: allForms } = useGetForms();
|
!value ? `${t("a name", { capfirst: true })} ${t("is required")}` : null,
|
||||||
const { data: allProducts } = useGetProducts(new URLSearchParams("types=1"));
|
date: (value) =>
|
||||||
const { data: allProductors } = useGetProductors()
|
!value ? `${t("a shipment date", { capfirst: true })} ${t("is required")}` : null,
|
||||||
|
form_id: (value) =>
|
||||||
const formsSelect = useMemo(() => {
|
!value ? `${t("a form", { capfirst: true })} ${t("is required")}` : null,
|
||||||
return allForms?.map(currentForm => ({value: String(currentForm.id), label: `${currentForm.name} ${currentForm.season}`}))
|
},
|
||||||
}, [allForms]);
|
});
|
||||||
|
|
||||||
const productsSelect = useMemo(() => {
|
const { data: allForms } = useGetForms();
|
||||||
if (!allProducts)
|
const { data: allProducts } = useGetProducts(new URLSearchParams("types=1"));
|
||||||
return;
|
const { data: allProductors } = useGetProductors();
|
||||||
return allProductors?.map(productor => {
|
|
||||||
return {
|
const formsSelect = useMemo(() => {
|
||||||
group: productor.name,
|
return allForms?.map((currentForm) => ({
|
||||||
items: allProducts
|
value: String(currentForm.id),
|
||||||
.filter((product) => product.productor.id === productor.id)
|
label: `${currentForm.name} ${currentForm.season}`,
|
||||||
.map((product) => ({value: String(product.id), label: `${product.name}`}))
|
}));
|
||||||
}
|
}, [allForms]);
|
||||||
});
|
|
||||||
}, [allProductors, allProducts]);
|
const productsSelect = useMemo(() => {
|
||||||
|
if (!allProducts) return;
|
||||||
return (
|
return allProductors?.map((productor) => {
|
||||||
<Modal
|
return {
|
||||||
opened={opened}
|
group: productor.name,
|
||||||
onClose={onClose}
|
items: allProducts
|
||||||
title={currentShipment ? t("edit shipment") : t('create shipment')}
|
.filter((product) => product.productor.id === productor.id)
|
||||||
>
|
.map((product) => ({
|
||||||
<TextInput
|
value: String(product.id),
|
||||||
label={t("shipment name", {capfirst: true})}
|
label: `${product.name}`,
|
||||||
placeholder={t("shipment name", {capfirst: true})}
|
})),
|
||||||
radius="sm"
|
};
|
||||||
withAsterisk
|
});
|
||||||
{...form.getInputProps('name')}
|
}, [allProductors, allProducts]);
|
||||||
/>
|
|
||||||
<DatePickerInput
|
return (
|
||||||
label={t("shipment date", {capfirst: true})}
|
<Modal
|
||||||
placeholder={t("shipment date", {capfirst: true})}
|
opened={opened}
|
||||||
withAsterisk
|
onClose={onClose}
|
||||||
{...form.getInputProps('date')}
|
title={currentShipment ? t("edit shipment") : t("create shipment")}
|
||||||
/>
|
>
|
||||||
<Select
|
<TextInput
|
||||||
label={t("shipment form", {capfirst: true})}
|
label={t("shipment name", { capfirst: true })}
|
||||||
placeholder={t("shipment form", {capfirst: true})}
|
placeholder={t("shipment name", { capfirst: true })}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
data={formsSelect || []}
|
withAsterisk
|
||||||
clearable
|
{...form.getInputProps("name")}
|
||||||
withAsterisk
|
/>
|
||||||
{...form.getInputProps('form_id')}
|
<DatePickerInput
|
||||||
/>
|
label={t("shipment date", { capfirst: true })}
|
||||||
<MultiSelect
|
placeholder={t("shipment date", { capfirst: true })}
|
||||||
label={t("shipment products", {capfirst: true})}
|
withAsterisk
|
||||||
placeholder={t("shipment products", {capfirst: true})}
|
{...form.getInputProps("date")}
|
||||||
description={t("shipment products is necessary only for planned products (if all products are recurrent leave empty)", {capfirst: true})}
|
/>
|
||||||
data={productsSelect || []}
|
<Select
|
||||||
clearable
|
label={t("shipment form", { capfirst: true })}
|
||||||
searchable
|
placeholder={t("shipment form", { capfirst: true })}
|
||||||
{...form.getInputProps('product_ids')}
|
radius="sm"
|
||||||
/>
|
data={formsSelect || []}
|
||||||
<Group mt="sm" justify="space-between">
|
clearable
|
||||||
<Button
|
withAsterisk
|
||||||
variant="filled"
|
{...form.getInputProps("form_id")}
|
||||||
color="red"
|
/>
|
||||||
aria-label={t("cancel", {capfirst: true})}
|
<MultiSelect
|
||||||
leftSection={<IconCancel/>}
|
label={t("shipment products", { capfirst: true })}
|
||||||
onClick={() => {
|
placeholder={t("shipment products", { capfirst: true })}
|
||||||
form.clearErrors();
|
description={t(
|
||||||
onClose();
|
"shipment products is necessary only for planned products (if all products are recurrent leave empty)",
|
||||||
}}
|
{ capfirst: true },
|
||||||
>{t("cancel", {capfirst: true})}</Button>
|
)}
|
||||||
<Button
|
data={productsSelect || []}
|
||||||
variant="filled"
|
clearable
|
||||||
aria-label={currentShipment ? t("edit shipment", {capfirst: true}) : t('create shipment', {capfirst: true})}
|
searchable
|
||||||
onClick={() => {
|
{...form.getInputProps("product_ids")}
|
||||||
form.validate();
|
/>
|
||||||
if (form.isValid()) {
|
<Group mt="sm" justify="space-between">
|
||||||
handleSubmit(form.getValues(), currentShipment?.id)
|
<Button
|
||||||
}
|
variant="filled"
|
||||||
}}
|
color="red"
|
||||||
>{currentShipment ? t("edit shipment", {capfirst: true}) : t('create shipment', {capfirst: true})}</Button>
|
aria-label={t("cancel", { capfirst: true })}
|
||||||
</Group>
|
leftSection={<IconCancel />}
|
||||||
</Modal>
|
onClick={() => {
|
||||||
);
|
form.clearErrors();
|
||||||
}
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("cancel", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
aria-label={
|
||||||
|
currentShipment
|
||||||
|
? t("edit shipment", { capfirst: true })
|
||||||
|
: t("create shipment", { capfirst: true })
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
form.validate();
|
||||||
|
if (form.isValid()) {
|
||||||
|
handleSubmit(form.getValues(), currentShipment?.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentShipment
|
||||||
|
? t("edit shipment", { capfirst: true })
|
||||||
|
: t("create shipment", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,52 +1,52 @@
|
|||||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
import { useDeleteShipment} from "@/services/api";
|
import { useDeleteShipment } from "@/services/api";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import type { Shipment } from "@/services/resources/shipments";
|
import type { Shipment } from "@/services/resources/shipments";
|
||||||
|
|
||||||
export type ShipmentRowProps = {
|
export type ShipmentRowProps = {
|
||||||
shipment: Shipment;
|
shipment: Shipment;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function ShipmentRow({
|
export default function ShipmentRow({ shipment }: ShipmentRowProps) {
|
||||||
shipment,
|
const [searchParams] = useSearchParams();
|
||||||
}: ShipmentRowProps) {
|
const deleteMutation = useDeleteShipment();
|
||||||
const [searchParams] = useSearchParams();
|
const navigate = useNavigate();
|
||||||
const deleteMutation = useDeleteShipment();
|
|
||||||
const navigate = useNavigate();
|
return (
|
||||||
|
<Table.Tr key={shipment.id}>
|
||||||
return (
|
<Table.Td>{shipment.name}</Table.Td>
|
||||||
<Table.Tr key={shipment.id}>
|
<Table.Td>{shipment.date}</Table.Td>
|
||||||
<Table.Td>{shipment.name}</Table.Td>
|
<Table.Td>{`${shipment.form.name} ${shipment.form.season}`}</Table.Td>
|
||||||
<Table.Td>{shipment.date}</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td>{`${shipment.form.name} ${shipment.form.season}`}</Table.Td>
|
<Tooltip label={t("edit productor", { capfirst: true })}>
|
||||||
<Table.Td>
|
<ActionIcon
|
||||||
<Tooltip label={t("edit productor", {capfirst: true})}>
|
size="sm"
|
||||||
<ActionIcon
|
mr="5"
|
||||||
size="sm"
|
onClick={(e) => {
|
||||||
mr="5"
|
e.stopPropagation();
|
||||||
onClick={(e) => {
|
navigate(
|
||||||
e.stopPropagation();
|
`/dashboard/shipments/${shipment.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
navigate(`/dashboard/shipments/${shipment.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t("remove productor", {capfirst: true})}>
|
<Tooltip label={t("remove productor", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteMutation.mutate(shipment.id);
|
deleteMutation.mutate(shipment.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX/>
|
<IconX />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,30 @@
|
|||||||
import { Group, MultiSelect } from "@mantine/core";
|
import { Group, MultiSelect } from "@mantine/core";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
|
|
||||||
export type UserFiltersProps = {
|
export type UserFiltersProps = {
|
||||||
names: string[];
|
names: string[];
|
||||||
filters: URLSearchParams;
|
filters: URLSearchParams;
|
||||||
onFilterChange: (values: string[], filter: string) => void;
|
onFilterChange: (values: string[], filter: string) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function UserFilters({
|
export default function UserFilters({ names, filters, onFilterChange }: UserFiltersProps) {
|
||||||
names,
|
const defaultNames = useMemo(() => {
|
||||||
filters,
|
return filters.getAll("names");
|
||||||
onFilterChange
|
}, [filters]);
|
||||||
}: UserFiltersProps) {
|
|
||||||
const defaultNames = useMemo(() => {
|
return (
|
||||||
return filters.getAll("names")
|
<Group>
|
||||||
}, [filters]);
|
<MultiSelect
|
||||||
|
aria-label={t("filter by name", { capfirst: true })}
|
||||||
return (
|
placeholder={t("filter by name", { capfirst: true })}
|
||||||
<Group>
|
data={names}
|
||||||
<MultiSelect
|
defaultValue={defaultNames}
|
||||||
aria-label={t("filter by name", {capfirst: true})}
|
onChange={(values: string[]) => {
|
||||||
placeholder={t("filter by name", {capfirst: true})}
|
onFilterChange(values, "names");
|
||||||
data={names}
|
}}
|
||||||
defaultValue={defaultNames}
|
clearable
|
||||||
onChange={(values: string[]) => {
|
/>
|
||||||
onFilterChange(values, 'names')
|
</Group>
|
||||||
}}
|
);
|
||||||
clearable
|
}
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,76 +1,77 @@
|
|||||||
import { Button, Group, Modal, TextInput, Title, type ModalBaseProps } from "@mantine/core";
|
import { Button, Group, Modal, TextInput, Title, type ModalBaseProps } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { IconCancel } from "@tabler/icons-react";
|
import { IconCancel } from "@tabler/icons-react";
|
||||||
import { type User, type UserInputs } from "@/services/resources/users";
|
import { type User, type UserInputs } from "@/services/resources/users";
|
||||||
|
|
||||||
export type UserModalProps = ModalBaseProps & {
|
export type UserModalProps = ModalBaseProps & {
|
||||||
currentUser?: User;
|
currentUser?: User;
|
||||||
handleSubmit: (user: UserInputs, id?: number) => void;
|
handleSubmit: (user: UserInputs, id?: number) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function UserModal({
|
export function UserModal({ opened, onClose, currentUser, handleSubmit }: UserModalProps) {
|
||||||
opened,
|
const form = useForm<UserInputs>({
|
||||||
onClose,
|
initialValues: {
|
||||||
currentUser,
|
name: currentUser?.name ?? "",
|
||||||
handleSubmit
|
email: currentUser?.email ?? "",
|
||||||
}: UserModalProps) {
|
},
|
||||||
const form = useForm<UserInputs>({
|
validate: {
|
||||||
initialValues: {
|
name: (value) =>
|
||||||
name: currentUser?.name ?? "",
|
!value ? `${t("name", { capfirst: true })} ${t("is required")}` : null,
|
||||||
email: currentUser?.email ?? ""
|
email: (value) =>
|
||||||
},
|
!value ? `${t("email", { capfirst: true })} ${t("is required")}` : null,
|
||||||
validate: {
|
},
|
||||||
name: (value) =>
|
});
|
||||||
!value ? `${t("name", {capfirst: true})} ${t('is required')}` : null,
|
|
||||||
email: (value) =>
|
return (
|
||||||
!value ? `${t("email", {capfirst: true})} ${t('is required')}` : null,
|
<Modal opened={opened} onClose={onClose} title={t("create user", { capfirst: true })}>
|
||||||
}
|
<Title order={4}>{t("informations", { capfirst: true })}</Title>
|
||||||
});
|
<TextInput
|
||||||
|
label={t("user name", { capfirst: true })}
|
||||||
return (
|
placeholder={t("user name", { capfirst: true })}
|
||||||
<Modal
|
radius="sm"
|
||||||
opened={opened}
|
withAsterisk
|
||||||
onClose={onClose}
|
{...form.getInputProps("name")}
|
||||||
title={t("create user", {capfirst: true})}
|
/>
|
||||||
>
|
<TextInput
|
||||||
<Title order={4}>{t("informations", {capfirst: true})}</Title>
|
label={t("user email", { capfirst: true })}
|
||||||
<TextInput
|
placeholder={t("user email", { capfirst: true })}
|
||||||
label={t("user name", {capfirst: true})}
|
radius="sm"
|
||||||
placeholder={t("user name", {capfirst: true})}
|
withAsterisk
|
||||||
radius="sm"
|
{...form.getInputProps("email")}
|
||||||
withAsterisk
|
/>
|
||||||
{...form.getInputProps('name')}
|
<Group mt="sm" justify="space-between">
|
||||||
/>
|
<Button
|
||||||
<TextInput
|
variant="filled"
|
||||||
label={t("user email", {capfirst: true})}
|
color="red"
|
||||||
placeholder={t("user email", {capfirst: true})}
|
aria-label={t("cancel", { capfirst: true })}
|
||||||
radius="sm"
|
leftSection={<IconCancel />}
|
||||||
withAsterisk
|
onClick={() => {
|
||||||
{...form.getInputProps('email')}
|
form.clearErrors();
|
||||||
/>
|
onClose();
|
||||||
<Group mt="sm" justify="space-between">
|
}}
|
||||||
<Button
|
>
|
||||||
variant="filled"
|
{t("cancel", { capfirst: true })}
|
||||||
color="red"
|
</Button>
|
||||||
aria-label={t("cancel", {capfirst: true})}
|
<Button
|
||||||
leftSection={<IconCancel/>}
|
variant="filled"
|
||||||
onClick={() => {
|
aria-label={
|
||||||
form.clearErrors();
|
currentUser
|
||||||
onClose();
|
? t("edit user", { capfirst: true })
|
||||||
}}
|
: t("create user", { capfirst: true })
|
||||||
>{t("cancel", {capfirst: true})}</Button>
|
}
|
||||||
<Button
|
onClick={() => {
|
||||||
variant="filled"
|
form.validate();
|
||||||
aria-label={currentUser ? t("edit user", {capfirst: true}) : t('create user', {capfirst: true})}
|
if (form.isValid()) {
|
||||||
onClick={() => {
|
handleSubmit(form.getValues(), currentUser?.id);
|
||||||
form.validate();
|
}
|
||||||
if (form.isValid()) {
|
}}
|
||||||
handleSubmit(form.getValues(), currentUser?.id)
|
>
|
||||||
}
|
{currentUser
|
||||||
}}
|
? t("edit user", { capfirst: true })
|
||||||
>{currentUser ? t("edit user", {capfirst: true}) : t('create user', {capfirst: true})}</Button>
|
: t("create user", { capfirst: true })}
|
||||||
</Group>
|
</Button>
|
||||||
</Modal>
|
</Group>
|
||||||
);
|
</Modal>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,52 +1,51 @@
|
|||||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { type User } from "@/services/resources/users";
|
import { type User } from "@/services/resources/users";
|
||||||
import { useDeleteUser } from "@/services/api";
|
import { useDeleteUser } from "@/services/api";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
|
||||||
export type UserRowProps = {
|
export type UserRowProps = {
|
||||||
user: User;
|
user: User;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function UserRow({
|
export default function UserRow({ user }: UserRowProps) {
|
||||||
user,
|
const [searchParams] = useSearchParams();
|
||||||
}: UserRowProps) {
|
const deleteMutation = useDeleteUser();
|
||||||
const [searchParams] = useSearchParams();
|
const navigate = useNavigate();
|
||||||
const deleteMutation = useDeleteUser();
|
|
||||||
const navigate = useNavigate();
|
return (
|
||||||
|
<Table.Tr key={user.id}>
|
||||||
return (
|
<Table.Td>{user.name}</Table.Td>
|
||||||
<Table.Tr key={user.id}>
|
<Table.Td>{user.email}</Table.Td>
|
||||||
<Table.Td>{user.name}</Table.Td>
|
<Table.Td>
|
||||||
<Table.Td>{user.email}</Table.Td>
|
<Tooltip label={t("edit user", { capfirst: true })}>
|
||||||
<Table.Td>
|
<ActionIcon
|
||||||
<Tooltip label={t("edit user", {capfirst: true})}>
|
size="sm"
|
||||||
<ActionIcon
|
mr="5"
|
||||||
size="sm"
|
onClick={(e) => {
|
||||||
mr="5"
|
e.stopPropagation();
|
||||||
onClick={(e) => {
|
navigate(
|
||||||
e.stopPropagation();
|
`/dashboard/users/${user.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
navigate(`/dashboard/users/${user.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t("remove user", {capfirst: true})}>
|
<Tooltip label={t("remove user", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteMutation.mutate(user.id);
|
deleteMutation.mutate(user.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX/>
|
<IconX />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const Config = {
|
export const Config = {
|
||||||
backend_uri: import.meta.env.VITE_API_URL,
|
backend_uri: import.meta.env.VITE_API_URL,
|
||||||
debug: import.meta.env.NODE_ENV === "development"
|
debug: import.meta.env.NODE_ENV === "development",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -8,46 +8,42 @@ import fr from "@/../locales/fr.json";
|
|||||||
import { Config } from "@/config/config";
|
import { Config } from "@/config/config";
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
en: { translation: en },
|
en: { translation: en },
|
||||||
fr: { translation: fr },
|
fr: { translation: fr },
|
||||||
};
|
};
|
||||||
|
|
||||||
i18next
|
i18next
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources: resources,
|
resources: resources,
|
||||||
fallbackLng: "fr",
|
fallbackLng: "fr",
|
||||||
debug: Config.debug,
|
debug: Config.debug,
|
||||||
detection: {
|
detection: {
|
||||||
caches: [],
|
caches: [],
|
||||||
},
|
},
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
},
|
||||||
ns: ["translation"],
|
ns: ["translation"],
|
||||||
defaultNS: "translation",
|
defaultNS: "translation",
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
[Settings.defaultLocale] = i18next.language.split("-");
|
[Settings.defaultLocale] = i18next.language.split("-");
|
||||||
|
|
||||||
i18next.services.formatter?.add(
|
|
||||||
"capfirst",
|
|
||||||
(value) => {
|
|
||||||
if (typeof value !== "string" || !value.length) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
i18next.services.formatter?.add("capfirst", (value) => {
|
||||||
|
if (typeof value !== "string" || !value.length) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export function t(message: string, params?: Record<string, unknown>) {
|
export function t(message: string, params?: Record<string, unknown>) {
|
||||||
const result = i18next.t(message, params);
|
const result = i18next.t(message, params);
|
||||||
if (params?.capfirst && typeof result === "string" && result.length) {
|
if (params?.capfirst && typeof result === "string" && result.length) {
|
||||||
return result.charAt(0).toUpperCase() + result.slice(1);
|
return result.charAt(0).toUpperCase() + result.slice(1);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default i18next;
|
export default i18next;
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ import { RouterProvider } from "react-router";
|
|||||||
import { router } from "@/router.tsx";
|
import { router } from "@/router.tsx";
|
||||||
import { MantineProvider } from "@mantine/core";
|
import { MantineProvider } from "@mantine/core";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import '@mantine/core/styles.css';
|
import "@mantine/core/styles.css";
|
||||||
import '@mantine/dates/styles.css';
|
import "@mantine/dates/styles.css";
|
||||||
import '@mantine/notifications/styles.css';
|
import "@mantine/notifications/styles.css";
|
||||||
import { Notifications } from "@mantine/notifications";
|
import { Notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<MantineProvider>
|
<MantineProvider>
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</StrictMode>
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,268 +1,281 @@
|
|||||||
import { ProductForm } from "@/components/Products/Form";
|
import { ProductForm } from "@/components/Products/Form";
|
||||||
import ShipmentForm from "@/components/Shipments/Form";
|
import ShipmentForm from "@/components/Shipments/Form";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useCreateContract, useGetForm } from "@/services/api";
|
import { useCreateContract, useGetForm } from "@/services/api";
|
||||||
import { type Product } from "@/services/resources/products";
|
import { type Product } from "@/services/resources/products";
|
||||||
import { Accordion, Button, Group, List, Loader, Overlay, Stack, Text, TextInput, Title } from "@mantine/core";
|
import {
|
||||||
import { useForm } from "@mantine/form";
|
Accordion,
|
||||||
import { IconMail, IconPhone, IconUser } from "@tabler/icons-react";
|
Button,
|
||||||
import { useCallback, useMemo, useRef } from "react";
|
Group,
|
||||||
import { useParams } from "react-router";
|
List,
|
||||||
import { computePrices } from "./price";
|
Loader,
|
||||||
|
Overlay,
|
||||||
export function Contract() {
|
Stack,
|
||||||
const { id } = useParams();
|
Text,
|
||||||
const { data: form } = useGetForm(Number(id), {enabled: !!id});
|
TextInput,
|
||||||
const inputForm = useForm<Record<string, number | string>>({
|
Title,
|
||||||
initialValues: {
|
} from "@mantine/core";
|
||||||
firstname: "",
|
import { useForm } from "@mantine/form";
|
||||||
lastname: "",
|
import { IconMail, IconPhone, IconUser } from "@tabler/icons-react";
|
||||||
email: "",
|
import { useCallback, useMemo, useRef } from "react";
|
||||||
phone: "",
|
import { useParams } from "react-router";
|
||||||
},
|
import { computePrices } from "./price";
|
||||||
validate: {
|
|
||||||
firstname: (value) => !value ? `${t("a firstname", {capfirst: true})} ${t("is required")}` : null,
|
export function Contract() {
|
||||||
lastname: (value) => !value ? `${t("a lastname", {capfirst: true})} ${t("is required")}` : null,
|
const { id } = useParams();
|
||||||
email: (value) => !value ? `${t("a email", {capfirst: true})} ${t("is required")}` : null,
|
const { data: form } = useGetForm(Number(id), { enabled: !!id });
|
||||||
phone: (value) => !value ? `${t("a phone", {capfirst: true})} ${t("is required")}` : null,
|
const inputForm = useForm<Record<string, number | string>>({
|
||||||
}
|
initialValues: {
|
||||||
});
|
firstname: "",
|
||||||
|
lastname: "",
|
||||||
const createContractMutation = useCreateContract();
|
email: "",
|
||||||
|
phone: "",
|
||||||
const productsRecurent = useMemo(() => {
|
},
|
||||||
return form?.productor?.products.filter((el) => el.type === "2")
|
validate: {
|
||||||
}, [form]);
|
firstname: (value) =>
|
||||||
|
!value ? `${t("a firstname", { capfirst: true })} ${t("is required")}` : null,
|
||||||
const shipments = useMemo(() => {
|
lastname: (value) =>
|
||||||
return form?.shipments;
|
!value ? `${t("a lastname", { capfirst: true })} ${t("is required")}` : null,
|
||||||
}, [form]);
|
email: (value) =>
|
||||||
|
!value ? `${t("a email", { capfirst: true })} ${t("is required")}` : null,
|
||||||
const allProducts = useMemo(() => {
|
phone: (value) =>
|
||||||
return form?.productor?.products;
|
!value ? `${t("a phone", { capfirst: true })} ${t("is required")}` : null,
|
||||||
}, [form])
|
},
|
||||||
|
});
|
||||||
const price = useMemo(() => {
|
|
||||||
if (!allProducts) {
|
const createContractMutation = useCreateContract();
|
||||||
return 0;
|
|
||||||
}
|
const productsRecurent = useMemo(() => {
|
||||||
const values = Object.entries(inputForm.getValues());
|
return form?.productor?.products.filter((el) => el.type === "2");
|
||||||
return computePrices(values, allProducts, form?.shipments.length);
|
}, [form]);
|
||||||
}, [inputForm, allProducts, form?.shipments]);
|
|
||||||
|
const shipments = useMemo(() => {
|
||||||
const inputRefs = useRef<Record<string, HTMLInputElement | null>>({
|
return form?.shipments;
|
||||||
firstname: null,
|
}, [form]);
|
||||||
lastname: null,
|
|
||||||
email: null,
|
const allProducts = useMemo(() => {
|
||||||
phone: null
|
return form?.productor?.products;
|
||||||
});
|
}, [form]);
|
||||||
|
|
||||||
const isShipmentsMinimumValue = useCallback(() => {
|
const price = useMemo(() => {
|
||||||
if (!form)
|
if (!allProducts) {
|
||||||
return false;
|
return 0;
|
||||||
const shipmentErrors = form.shipments
|
}
|
||||||
.map((shipment) => {
|
const values = Object.entries(inputForm.getValues());
|
||||||
const total = computePrices(
|
return computePrices(values, allProducts, form?.shipments.length);
|
||||||
Object.entries(inputForm.getValues()),
|
}, [inputForm, allProducts, form?.shipments]);
|
||||||
shipment.products
|
|
||||||
);
|
const inputRefs = useRef<Record<string, HTMLInputElement | null>>({
|
||||||
if (total < (form?.minimum_shipment_value || 0)) {
|
firstname: null,
|
||||||
return shipment.id;
|
lastname: null,
|
||||||
}
|
email: null,
|
||||||
return null;
|
phone: null,
|
||||||
})
|
});
|
||||||
.filter(Boolean);
|
|
||||||
return shipmentErrors.length === 0;
|
const isShipmentsMinimumValue = useCallback(() => {
|
||||||
}, [form, inputForm]);
|
if (!form) return false;
|
||||||
|
const shipmentErrors = form.shipments
|
||||||
const withDefaultValues = useCallback((values: Record<string, number | string>) => {
|
.map((shipment) => {
|
||||||
if (!productsRecurent || !form)
|
const total = computePrices(
|
||||||
return {};
|
Object.entries(inputForm.getValues()),
|
||||||
const result = {...values};
|
shipment.products,
|
||||||
|
);
|
||||||
productsRecurent.forEach((product: Product) => {
|
if (total < (form?.minimum_shipment_value || 0)) {
|
||||||
const key = `recurrent-${product.id}`;
|
return shipment.id;
|
||||||
if (result[key] === undefined || result[key] === "") {
|
}
|
||||||
result[key] = 0;
|
return null;
|
||||||
}
|
})
|
||||||
});
|
.filter(Boolean);
|
||||||
|
return shipmentErrors.length === 0;
|
||||||
form.shipments.forEach((shipment) => {
|
}, [form, inputForm]);
|
||||||
shipment.products.forEach((product) => {
|
|
||||||
const key = `planned-${shipment.id}-${product.id}`;
|
const withDefaultValues = useCallback(
|
||||||
if (result[key] === undefined || result[key] === "") {
|
(values: Record<string, number | string>) => {
|
||||||
result[key] = 0;
|
if (!productsRecurent || !form) return {};
|
||||||
}
|
const result = { ...values };
|
||||||
})
|
|
||||||
});
|
productsRecurent.forEach((product: Product) => {
|
||||||
|
const key = `recurrent-${product.id}`;
|
||||||
return result;
|
if (result[key] === undefined || result[key] === "") {
|
||||||
}, [productsRecurent, form]);
|
result[key] = 0;
|
||||||
|
}
|
||||||
const handleSubmit = useCallback(async () => {
|
});
|
||||||
const errors = inputForm.validate();
|
|
||||||
if (!form) {
|
form.shipments.forEach((shipment) => {
|
||||||
return;
|
shipment.products.forEach((product) => {
|
||||||
}
|
const key = `planned-${shipment.id}-${product.id}`;
|
||||||
if (inputForm.isValid() && isShipmentsMinimumValue()) {
|
if (result[key] === undefined || result[key] === "") {
|
||||||
const contract = {
|
result[key] = 0;
|
||||||
form_id: form.id,
|
}
|
||||||
contract: withDefaultValues(inputForm.getValues()),
|
});
|
||||||
}
|
});
|
||||||
await createContractMutation.mutateAsync(contract);
|
|
||||||
} else {
|
return result;
|
||||||
const firstErrorField = Object.keys(errors.errors)[0];
|
},
|
||||||
const ref = inputRefs.current[firstErrorField];
|
[productsRecurent, form],
|
||||||
ref?.scrollIntoView({behavior: "smooth", block: "center"});
|
);
|
||||||
}
|
|
||||||
}, [inputForm, inputRefs, isShipmentsMinimumValue, form, createContractMutation, withDefaultValues]);
|
const handleSubmit = useCallback(async () => {
|
||||||
|
const errors = inputForm.validate();
|
||||||
|
if (!form) {
|
||||||
if (!form || !shipments || !productsRecurent)
|
return;
|
||||||
return (
|
}
|
||||||
<Group
|
if (inputForm.isValid() && isShipmentsMinimumValue()) {
|
||||||
align="center"
|
const contract = {
|
||||||
justify="center"
|
form_id: form.id,
|
||||||
h="80vh"
|
contract: withDefaultValues(inputForm.getValues()),
|
||||||
w="100%"
|
};
|
||||||
>
|
await createContractMutation.mutateAsync(contract);
|
||||||
<Loader color="pink"/>
|
} else {
|
||||||
</Group>
|
const firstErrorField = Object.keys(errors.errors)[0];
|
||||||
);
|
const ref = inputRefs.current[firstErrorField];
|
||||||
|
ref?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
}
|
||||||
return (
|
}, [
|
||||||
<Stack w={{base: "100%", md: "80%", lg: "50%"}}>
|
inputForm,
|
||||||
<Title order={2}>{form.name}</Title>
|
inputRefs,
|
||||||
<Title order={3}>{t("informations", {capfirst: true})}</Title>
|
isShipmentsMinimumValue,
|
||||||
<Text size="sm">
|
form,
|
||||||
{t("all theses informations are for contract generation", {capfirst: true})}
|
createContractMutation,
|
||||||
</Text>
|
withDefaultValues,
|
||||||
<Group grow>
|
]);
|
||||||
<TextInput
|
|
||||||
label={t("firstname", {capfirst: true})}
|
if (!form || !shipments || !productsRecurent)
|
||||||
placeholder={t("firstname", {capfirst: true})}
|
return (
|
||||||
radius="sm"
|
<Group align="center" justify="center" h="80vh" w="100%">
|
||||||
withAsterisk
|
<Loader color="pink" />
|
||||||
required
|
</Group>
|
||||||
leftSection={<IconUser/>}
|
);
|
||||||
{...inputForm.getInputProps('firstname')}
|
|
||||||
ref={(el) => {inputRefs.current.firstname = el}}
|
return (
|
||||||
/>
|
<Stack w={{ base: "100%", md: "80%", lg: "50%" }}>
|
||||||
<TextInput
|
<Title order={2}>{form.name}</Title>
|
||||||
label={t("lastname", {capfirst: true})}
|
<Title order={3}>{t("informations", { capfirst: true })}</Title>
|
||||||
placeholder={t("lastname", {capfirst: true})}
|
<Text size="sm">
|
||||||
radius="sm"
|
{t("all theses informations are for contract generation", {
|
||||||
withAsterisk
|
capfirst: true,
|
||||||
required
|
})}
|
||||||
leftSection={<IconUser/>}
|
</Text>
|
||||||
{...inputForm.getInputProps('lastname')}
|
<Group grow>
|
||||||
ref={(el) => {inputRefs.current.lastname = el}}
|
<TextInput
|
||||||
/>
|
label={t("firstname", { capfirst: true })}
|
||||||
</Group>
|
placeholder={t("firstname", { capfirst: true })}
|
||||||
<Group grow>
|
radius="sm"
|
||||||
<TextInput
|
withAsterisk
|
||||||
label={t("email", {capfirst: true})}
|
required
|
||||||
placeholder={t("email", {capfirst: true})}
|
leftSection={<IconUser />}
|
||||||
radius="sm"
|
{...inputForm.getInputProps("firstname")}
|
||||||
withAsterisk
|
ref={(el) => {
|
||||||
required
|
inputRefs.current.firstname = el;
|
||||||
leftSection={<IconMail/>}
|
}}
|
||||||
{...inputForm.getInputProps('email')}
|
/>
|
||||||
ref={(el) => {inputRefs.current.email = el}}
|
<TextInput
|
||||||
/>
|
label={t("lastname", { capfirst: true })}
|
||||||
<TextInput
|
placeholder={t("lastname", { capfirst: true })}
|
||||||
label={t("phone", {capfirst: true})}
|
radius="sm"
|
||||||
placeholder={t("phone", {capfirst: true})}
|
withAsterisk
|
||||||
radius="sm"
|
required
|
||||||
withAsterisk
|
leftSection={<IconUser />}
|
||||||
required
|
{...inputForm.getInputProps("lastname")}
|
||||||
leftSection={<IconPhone/>}
|
ref={(el) => {
|
||||||
{...inputForm.getInputProps('phone')}
|
inputRefs.current.lastname = el;
|
||||||
ref={(el) => {inputRefs.current.phone = el}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<Title order={3}>{t('shipments', {capfirst: true})}</Title>
|
<Group grow>
|
||||||
<Text>{`${t("there is", {capfirst: true})} ${shipments.length} ${shipments.length > 1 ? t("shipments") : t("shipment")} ${t("for this contract")}`}</Text>
|
<TextInput
|
||||||
<List>
|
label={t("email", { capfirst: true })}
|
||||||
{
|
placeholder={t("email", { capfirst: true })}
|
||||||
shipments.map(shipment => (
|
radius="sm"
|
||||||
<List.Item key={shipment.id}>{`${shipment.name} :
|
withAsterisk
|
||||||
${
|
required
|
||||||
new Date(shipment.date).toLocaleDateString("fr-FR", {
|
leftSection={<IconMail />}
|
||||||
weekday: "long",
|
{...inputForm.getInputProps("email")}
|
||||||
year: "numeric",
|
ref={(el) => {
|
||||||
month: "long",
|
inputRefs.current.email = el;
|
||||||
day: "numeric",
|
}}
|
||||||
})
|
/>
|
||||||
}`}
|
<TextInput
|
||||||
</List.Item>
|
label={t("phone", { capfirst: true })}
|
||||||
))
|
placeholder={t("phone", { capfirst: true })}
|
||||||
}
|
radius="sm"
|
||||||
</List>
|
withAsterisk
|
||||||
{
|
required
|
||||||
productsRecurent.length > 0 ?
|
leftSection={<IconPhone />}
|
||||||
<>
|
{...inputForm.getInputProps("phone")}
|
||||||
<Title order={3}>{t('recurrent products', {capfirst: true})}</Title>
|
ref={(el) => {
|
||||||
<Text size="sm">{t('your selection in this category will apply for all shipments', {capfirst: true})}</Text>
|
inputRefs.current.phone = el;
|
||||||
{
|
}}
|
||||||
productsRecurent.map((product) => (
|
/>
|
||||||
<ProductForm
|
</Group>
|
||||||
key={product.id}
|
<Title order={3}>{t("shipments", { capfirst: true })}</Title>
|
||||||
product={product}
|
<Text>{`${t("there is", { capfirst: true })} ${shipments.length} ${shipments.length > 1 ? t("shipments") : t("shipment")} ${t("for this contract")}`}</Text>
|
||||||
inputForm={inputForm}
|
<List>
|
||||||
/>
|
{shipments.map((shipment) => (
|
||||||
))
|
<List.Item key={shipment.id}>
|
||||||
}
|
{`${shipment.name} :
|
||||||
</> :
|
${new Date(shipment.date).toLocaleDateString("fr-FR", {
|
||||||
null
|
weekday: "long",
|
||||||
}
|
year: "numeric",
|
||||||
{
|
month: "long",
|
||||||
shipments.some(shipment => shipment.products.length > 0) ?
|
day: "numeric",
|
||||||
<>
|
})}`}
|
||||||
<Title order={3}>{t("planned products")}</Title>
|
</List.Item>
|
||||||
<Text>{t("select products per shipment")}</Text>
|
))}
|
||||||
<Accordion defaultValue={"0"}>
|
</List>
|
||||||
{
|
{productsRecurent.length > 0 ? (
|
||||||
shipments.map((shipment, index) => (
|
<>
|
||||||
<ShipmentForm
|
<Title order={3}>{t("recurrent products", { capfirst: true })}</Title>
|
||||||
minimumPrice={form.minimum_shipment_value}
|
<Text size="sm">
|
||||||
shipment={shipment}
|
{t("your selection in this category will apply for all shipments", {
|
||||||
index={index}
|
capfirst: true,
|
||||||
inputForm={inputForm}
|
})}
|
||||||
key={shipment.id}
|
</Text>
|
||||||
/>
|
{productsRecurent.map((product) => (
|
||||||
))
|
<ProductForm key={product.id} product={product} inputForm={inputForm} />
|
||||||
}
|
))}
|
||||||
</Accordion>
|
</>
|
||||||
</> :
|
) : null}
|
||||||
null
|
{shipments.some((shipment) => shipment.products.length > 0) ? (
|
||||||
}
|
<>
|
||||||
<Overlay
|
<Title order={3}>{t("planned products")}</Title>
|
||||||
bg={"lightGray"}
|
<Text>{t("select products per shipment")}</Text>
|
||||||
h="10vh"
|
<Accordion defaultValue={"0"}>
|
||||||
p="sm"
|
{shipments.map((shipment, index) => (
|
||||||
style={{
|
<ShipmentForm
|
||||||
display: "flex",
|
minimumPrice={form.minimum_shipment_value}
|
||||||
justifyContent: "space-between",
|
shipment={shipment}
|
||||||
alignItems: "center",
|
index={index}
|
||||||
position: "sticky",
|
inputForm={inputForm}
|
||||||
bottom: "0px",
|
key={shipment.id}
|
||||||
}}
|
/>
|
||||||
>
|
))}
|
||||||
<Text>{
|
</Accordion>
|
||||||
t("total", {capfirst: true})} : {Intl.NumberFormat(
|
</>
|
||||||
"fr-FR",
|
) : null}
|
||||||
{style: "currency", currency: "EUR"}
|
<Overlay
|
||||||
).format(price)}
|
bg={"lightGray"}
|
||||||
</Text>
|
h="10vh"
|
||||||
<Button
|
p="sm"
|
||||||
aria-label={t('submit contract')}
|
style={{
|
||||||
onClick={handleSubmit}
|
display: "flex",
|
||||||
>
|
justifyContent: "space-between",
|
||||||
{t('submit contract')}
|
alignItems: "center",
|
||||||
</Button>
|
position: "sticky",
|
||||||
</Overlay>
|
bottom: "0px",
|
||||||
</Stack>
|
}}
|
||||||
)
|
>
|
||||||
}
|
<Text>
|
||||||
|
{t("total", { capfirst: true })} :{" "}
|
||||||
|
{Intl.NumberFormat("fr-FR", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "EUR",
|
||||||
|
}).format(price)}
|
||||||
|
</Text>
|
||||||
|
<Button aria-label={t("submit contract")} onClick={handleSubmit}>
|
||||||
|
{t("submit contract")}
|
||||||
|
</Button>
|
||||||
|
</Overlay>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
import type { Product } from "@/services/resources/products";
|
import type { Product } from "@/services/resources/products";
|
||||||
|
|
||||||
export function computePrices(values: [string, string | number][], products: Product[], nbShipment?: number) {
|
export function computePrices(
|
||||||
return values.reduce((prev, [key, value]) => {
|
values: [string, string | number][],
|
||||||
const keyArray = key.split("-");
|
products: Product[],
|
||||||
const productId = Number(keyArray[keyArray.length - 1]);
|
nbShipment?: number,
|
||||||
const product = products.find((product) => product.id === productId);
|
) {
|
||||||
if (!product) {
|
return values.reduce((prev, [key, value]) => {
|
||||||
return prev + 0;
|
const keyArray = key.split("-");
|
||||||
}
|
const productId = Number(keyArray[keyArray.length - 1]);
|
||||||
const isRecurent = key.includes("recurrent") && nbShipment;
|
const product = products.find((product) => product.id === productId);
|
||||||
const productPrice = Number(product.price || product.price_kg);
|
if (!product) {
|
||||||
const productQuantityUnit = product.unit === "2" ? 1 : 1000;
|
return prev + 0;
|
||||||
const productQuantity = Number(product.price ? Number(value) : Number(value) / productQuantityUnit);
|
}
|
||||||
return(prev + productPrice * productQuantity * (isRecurent ? nbShipment : 1));
|
const isRecurent = key.includes("recurrent") && nbShipment;
|
||||||
}, 0);
|
const productPrice = Number(product.price || product.price_kg);
|
||||||
}
|
const productQuantityUnit = product.unit === "2" ? 1 : 1000;
|
||||||
|
const productQuantity = Number(
|
||||||
|
product.price ? Number(value) : Number(value) / productQuantityUnit,
|
||||||
|
);
|
||||||
|
return prev + productPrice * productQuantity * (isRecurent ? nbShipment : 1);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import { Tabs } from "@mantine/core";
|
import { Tabs } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router";
|
import { Outlet, useLocation, useNavigate } from "react-router";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
w={{base: "100%", md: "80%", lg: "60%"}}
|
w={{ base: "100%", md: "80%", lg: "60%" }}
|
||||||
orientation={"horizontal"}
|
orientation={"horizontal"}
|
||||||
value={location.pathname.split('/')[2]}
|
value={location.pathname.split("/")[2]}
|
||||||
defaultValue={"productors"}
|
defaultValue={"productors"}
|
||||||
onChange={(value) => navigate(`/dashboard/${value}`)}
|
onChange={(value) => navigate(`/dashboard/${value}`)}
|
||||||
>
|
>
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab value="productors">{t("productors", {capfirst: true})}</Tabs.Tab>
|
<Tabs.Tab value="productors">{t("productors", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="products">{t("products", {capfirst: true})}</Tabs.Tab>
|
<Tabs.Tab value="products">{t("products", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="forms">{t("forms", {capfirst: true})}</Tabs.Tab>
|
<Tabs.Tab value="forms">{t("forms", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="shipments">{t("shipments", {capfirst: true})}</Tabs.Tab>
|
<Tabs.Tab value="shipments">{t("shipments", { capfirst: true })}</Tabs.Tab>
|
||||||
{/* <Tabs.Tab value="templates">{t("templates", {capfirst: true})}</Tabs.Tab> */}
|
{/* <Tabs.Tab value="templates">{t("templates", {capfirst: true})}</Tabs.Tab> */}
|
||||||
<Tabs.Tab value="users">{t("users", {capfirst: true})}</Tabs.Tab>
|
<Tabs.Tab value="users">{t("users", { capfirst: true })}</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Outlet/>
|
<Outlet />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,170 +1,169 @@
|
|||||||
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core";
|
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core";
|
||||||
import { useCreateForm, useEditForm, useGetForm, useGetForms } from "@/services/api";
|
import { useCreateForm, useEditForm, useGetForm, useGetForms } from "@/services/api";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
import { IconPlus } from "@tabler/icons-react";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import FormModal from "@/components/Forms/Modal";
|
import FormModal from "@/components/Forms/Modal";
|
||||||
import FormRow from "@/components/Forms/Row";
|
import FormRow from "@/components/Forms/Row";
|
||||||
import type { Form, FormInputs } from "@/services/resources/forms";
|
import type { Form, FormInputs } from "@/services/resources/forms";
|
||||||
import FilterForms from "@/components/Forms/Filter";
|
import FilterForms from "@/components/Forms/Filter";
|
||||||
|
|
||||||
export function Forms() {
|
export function Forms() {
|
||||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const isCreate = location.pathname === "/dashboard/forms/create";
|
const isCreate = location.pathname === "/dashboard/forms/create";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const isEdit = location.pathname.includes("/edit");
|
||||||
|
|
||||||
const editId = useMemo(() => {
|
const editId = useMemo(() => {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
return location.pathname.split("/")[3]
|
return location.pathname.split("/")[3];
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}, [location, isEdit])
|
}, [location, isEdit]);
|
||||||
|
|
||||||
const closeModal = useCallback(() => {
|
const closeModal = useCallback(() => {
|
||||||
navigate(`/dashboard/forms${searchParams ? `?${searchParams.toString()}` : ""}`);
|
navigate(`/dashboard/forms${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}, [navigate, searchParams]);
|
}, [navigate, searchParams]);
|
||||||
|
|
||||||
const { isPending, data } = useGetForms(searchParams);
|
const { isPending, data } = useGetForms(searchParams);
|
||||||
const { data: currentForm } = useGetForm(Number(editId), { enabled: !!editId });
|
const { data: currentForm } = useGetForm(Number(editId), {
|
||||||
|
enabled: !!editId,
|
||||||
const { data: allForms } = useGetForms();
|
});
|
||||||
|
|
||||||
const seasons = useMemo(() => {
|
const { data: allForms } = useGetForms();
|
||||||
return allForms?.map((form: Form) => (form.season))
|
|
||||||
.filter((season, index, array) => array.indexOf(season) === index)
|
const seasons = useMemo(() => {
|
||||||
}, [allForms])
|
return allForms
|
||||||
|
?.map((form: Form) => form.season)
|
||||||
const productors = useMemo(() => {
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
return allForms?.map((form: Form) => (form.productor.name))
|
}, [allForms]);
|
||||||
.filter((productor, index, array) => array.indexOf(productor) === index)
|
|
||||||
}, [allForms])
|
const productors = useMemo(() => {
|
||||||
|
return allForms
|
||||||
const createFormMutation = useCreateForm();
|
?.map((form: Form) => form.productor.name)
|
||||||
const editFormMutation = useEditForm();
|
.filter((productor, index, array) => array.indexOf(productor) === index);
|
||||||
|
}, [allForms]);
|
||||||
const handleCreateForm = useCallback(async (form: FormInputs) => {
|
|
||||||
if (!form.start || !form.end)
|
const createFormMutation = useCreateForm();
|
||||||
return;
|
const editFormMutation = useEditForm();
|
||||||
await createFormMutation.mutateAsync({
|
|
||||||
...form,
|
const handleCreateForm = useCallback(
|
||||||
start: form?.start,
|
async (form: FormInputs) => {
|
||||||
end: form?.end,
|
if (!form.start || !form.end) return;
|
||||||
productor_id: Number(form.productor_id),
|
await createFormMutation.mutateAsync({
|
||||||
referer_id: Number(form.referer_id),
|
...form,
|
||||||
minimum_shipment_value: Number(form.minimum_shipment_value),
|
start: form?.start,
|
||||||
});
|
end: form?.end,
|
||||||
closeModal();
|
productor_id: Number(form.productor_id),
|
||||||
}, [createFormMutation, closeModal]);
|
referer_id: Number(form.referer_id),
|
||||||
|
minimum_shipment_value: Number(form.minimum_shipment_value),
|
||||||
const handleEditForm = useCallback(async (form: FormInputs, id?: number) => {
|
});
|
||||||
if (!id)
|
closeModal();
|
||||||
return;
|
},
|
||||||
await editFormMutation.mutateAsync({
|
[createFormMutation, closeModal],
|
||||||
id: id,
|
);
|
||||||
form: {
|
|
||||||
...form,
|
const handleEditForm = useCallback(
|
||||||
start: form.start,
|
async (form: FormInputs, id?: number) => {
|
||||||
end: form.end,
|
if (!id) return;
|
||||||
productor_id: Number(form.productor_id),
|
await editFormMutation.mutateAsync({
|
||||||
referer_id: Number(form.referer_id),
|
id: id,
|
||||||
minimum_shipment_value: Number(form.minimum_shipment_value),
|
form: {
|
||||||
}
|
...form,
|
||||||
});
|
start: form.start,
|
||||||
closeModal();
|
end: form.end,
|
||||||
}, [editFormMutation, closeModal]);
|
productor_id: Number(form.productor_id),
|
||||||
|
referer_id: Number(form.referer_id),
|
||||||
const onFilterChange = useCallback((
|
minimum_shipment_value: Number(form.minimum_shipment_value),
|
||||||
values: string[],
|
},
|
||||||
filter: string
|
});
|
||||||
) => {
|
closeModal();
|
||||||
setSearchParams(prev => {
|
},
|
||||||
const params = new URLSearchParams(prev);
|
[editFormMutation, closeModal],
|
||||||
params.delete(filter)
|
);
|
||||||
|
|
||||||
values.forEach(value => {
|
const onFilterChange = useCallback(
|
||||||
params.append(filter, value);
|
(values: string[], filter: string) => {
|
||||||
});
|
setSearchParams((prev) => {
|
||||||
|
const params = new URLSearchParams(prev);
|
||||||
return params;
|
params.delete(filter);
|
||||||
});
|
|
||||||
}, [setSearchParams])
|
values.forEach((value) => {
|
||||||
|
params.append(filter, value);
|
||||||
|
});
|
||||||
if (!data || isPending)
|
|
||||||
return (
|
return params;
|
||||||
<Group
|
});
|
||||||
align="center"
|
},
|
||||||
justify="center"
|
[setSearchParams],
|
||||||
h="80vh"
|
);
|
||||||
w="100%"
|
|
||||||
>
|
if (!data || isPending)
|
||||||
<Loader color="pink"/>
|
return (
|
||||||
</Group>
|
<Group align="center" justify="center" h="80vh" w="100%">
|
||||||
);
|
<Loader color="pink" />
|
||||||
|
</Group>
|
||||||
return (
|
);
|
||||||
<Stack>
|
|
||||||
<Group justify="space-between">
|
return (
|
||||||
<Title order={2}>{t("all forms", {capfirst: true})}</Title>
|
<Stack>
|
||||||
<Tooltip label={t("create new form", {capfirst: true})}>
|
<Group justify="space-between">
|
||||||
<ActionIcon
|
<Title order={2}>{t("all forms", { capfirst: true })}</Title>
|
||||||
onClick={(e) => {
|
<Tooltip label={t("create new form", { capfirst: true })}>
|
||||||
e.stopPropagation();
|
<ActionIcon
|
||||||
navigate(`/dashboard/forms/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
onClick={(e) => {
|
||||||
}}
|
e.stopPropagation();
|
||||||
>
|
navigate(
|
||||||
<IconPlus/>
|
`/dashboard/forms/create${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
</ActionIcon>
|
);
|
||||||
</Tooltip>
|
}}
|
||||||
<FormModal
|
>
|
||||||
key={`${currentForm?.id}_create`}
|
<IconPlus />
|
||||||
opened={isCreate}
|
</ActionIcon>
|
||||||
onClose={closeModal}
|
</Tooltip>
|
||||||
handleSubmit={handleCreateForm}
|
<FormModal
|
||||||
/>
|
key={`${currentForm?.id}_create`}
|
||||||
</Group>
|
opened={isCreate}
|
||||||
<FilterForms
|
onClose={closeModal}
|
||||||
productors={productors || []}
|
handleSubmit={handleCreateForm}
|
||||||
seasons={seasons || []}
|
/>
|
||||||
filters={searchParams}
|
</Group>
|
||||||
onFilterChange={onFilterChange}
|
<FilterForms
|
||||||
/>
|
productors={productors || []}
|
||||||
<FormModal
|
seasons={seasons || []}
|
||||||
key={`${currentForm?.id}_edit`}
|
filters={searchParams}
|
||||||
opened={isEdit}
|
onFilterChange={onFilterChange}
|
||||||
onClose={closeModal}
|
/>
|
||||||
currentForm={currentForm}
|
<FormModal
|
||||||
handleSubmit={handleEditForm}
|
key={`${currentForm?.id}_edit`}
|
||||||
/>
|
opened={isEdit}
|
||||||
<ScrollArea type="auto">
|
onClose={closeModal}
|
||||||
<Table striped>
|
currentForm={currentForm}
|
||||||
<Table.Thead>
|
handleSubmit={handleEditForm}
|
||||||
<Table.Tr>
|
/>
|
||||||
<Table.Th>{t("name", {capfirst: true})}</Table.Th>
|
<ScrollArea type="auto">
|
||||||
<Table.Th>{t("type", {capfirst: true})}</Table.Th>
|
<Table striped>
|
||||||
<Table.Th>{t("start", {capfirst: true})}</Table.Th>
|
<Table.Thead>
|
||||||
<Table.Th>{t("end", {capfirst: true})}</Table.Th>
|
<Table.Tr>
|
||||||
<Table.Th>{t("productor", {capfirst: true})}</Table.Th>
|
<Table.Th>{t("name", { capfirst: true })}</Table.Th>
|
||||||
<Table.Th>{t("referer", {capfirst: true})}</Table.Th>
|
<Table.Th>{t("type", { capfirst: true })}</Table.Th>
|
||||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
<Table.Th>{t("start", { capfirst: true })}</Table.Th>
|
||||||
</Table.Tr>
|
<Table.Th>{t("end", { capfirst: true })}</Table.Th>
|
||||||
</Table.Thead>
|
<Table.Th>{t("productor", { capfirst: true })}</Table.Th>
|
||||||
<Table.Tbody>
|
<Table.Th>{t("referer", { capfirst: true })}</Table.Th>
|
||||||
{
|
<Table.Th>{t("actions", { capfirst: true })}</Table.Th>
|
||||||
data.map((form) => (
|
</Table.Tr>
|
||||||
<FormRow
|
</Table.Thead>
|
||||||
form={form}
|
<Table.Tbody>
|
||||||
key={form.id}
|
{data.map((form) => (
|
||||||
/>
|
<FormRow form={form} key={form.id} />
|
||||||
))
|
))}
|
||||||
}
|
</Table.Tbody>
|
||||||
</Table.Tbody>
|
</Table>
|
||||||
</Table>
|
</ScrollArea>
|
||||||
</ScrollArea>
|
</Stack>
|
||||||
</Stack>
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ export function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap="md" wrap="wrap" justify="center">
|
<Flex gap="md" wrap="wrap" justify="center">
|
||||||
{
|
{allForms && allForms?.length > 0 ? (
|
||||||
allForms && allForms?.length > 0 ?
|
allForms.map((form: Form) => <FormCard form={form} key={form.id} />)
|
||||||
allForms.map((form: Form) => (
|
) : (
|
||||||
<FormCard form={form} key={form.id}/>
|
<Text mt="lg" size="lg">
|
||||||
)) :
|
{t("there is no contract for now", { capfirst: true })}
|
||||||
<Text mt="lg" size="lg">{t("there is no contract for now",{capfirst: true})}</Text>
|
</Text>
|
||||||
}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { ActionIcon, Stack, Text, Title, Tooltip } from "@mantine/core";
|
import { ActionIcon, Stack, Text, Title, Tooltip } from "@mantine/core";
|
||||||
import { IconHome } from "@tabler/icons-react";
|
import { IconHome } from "@tabler/icons-react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
export function NotFound() {
|
export function NotFound() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<Stack justify="center" align="center">
|
<Stack justify="center" align="center">
|
||||||
<Title order={2}>{t("oops", {capfirst: true})}</Title>
|
<Title order={2}>{t("oops", { capfirst: true })}</Title>
|
||||||
<Text>{t('this page does not exists', {capfirst: true})}</Text>
|
<Text>{t("this page does not exists", { capfirst: true })}</Text>
|
||||||
<Tooltip label={t('back to home', {capfirst: true})}>
|
<Tooltip label={t("back to home", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
aria-label={t("back to home", {capfirst: true})}
|
aria-label={t("back to home", { capfirst: true })}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate('/')
|
navigate("/");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconHome/>
|
<IconHome />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,148 +1,157 @@
|
|||||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useCreateProductor, useEditProductor, useGetProductor, useGetProductors } from "@/services/api";
|
import {
|
||||||
import { IconPlus } from "@tabler/icons-react";
|
useCreateProductor,
|
||||||
import ProductorRow from "@/components/Productors/Row";
|
useEditProductor,
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
useGetProductor,
|
||||||
import { ProductorModal } from "@/components/Productors/Modal";
|
useGetProductors,
|
||||||
import { useCallback, useMemo } from "react";
|
} from "@/services/api";
|
||||||
import type { Productor, ProductorInputs } from "@/services/resources/productors";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
import ProductorsFilters from "@/components/Productors/Filter";
|
import ProductorRow from "@/components/Productors/Row";
|
||||||
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
export default function Productors() {
|
import { ProductorModal } from "@/components/Productors/Modal";
|
||||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
import { useCallback, useMemo } from "react";
|
||||||
const location = useLocation();
|
import type { Productor, ProductorInputs } from "@/services/resources/productors";
|
||||||
const navigate = useNavigate();
|
import ProductorsFilters from "@/components/Productors/Filter";
|
||||||
|
|
||||||
const isCreate = location.pathname === "/dashboard/productors/create";
|
export default function Productors() {
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const location = useLocation();
|
||||||
const editId = useMemo(() => {
|
const navigate = useNavigate();
|
||||||
if (isEdit) {
|
|
||||||
return location.pathname.split("/")[3]
|
const isCreate = location.pathname === "/dashboard/productors/create";
|
||||||
}
|
const isEdit = location.pathname.includes("/edit");
|
||||||
return null
|
|
||||||
}, [location, isEdit])
|
const editId = useMemo(() => {
|
||||||
|
if (isEdit) {
|
||||||
const closeModal = useCallback(() => {
|
return location.pathname.split("/")[3];
|
||||||
navigate(`/dashboard/productors${searchParams ? `?${searchParams.toString()}` : ""}`);
|
}
|
||||||
}, [navigate, searchParams]);
|
return null;
|
||||||
|
}, [location, isEdit]);
|
||||||
const { data: productors, isPending } = useGetProductors(searchParams);
|
|
||||||
const { data: currentProductor } = useGetProductor(Number(editId), { enabled: !!editId });
|
const closeModal = useCallback(() => {
|
||||||
const { data: allProductors } = useGetProductors();
|
navigate(`/dashboard/productors${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
|
}, [navigate, searchParams]);
|
||||||
const names = useMemo(() => {
|
|
||||||
return allProductors?.map((productor: Productor) => (productor.name))
|
const { data: productors, isPending } = useGetProductors(searchParams);
|
||||||
.filter((season, index, array) => array.indexOf(season) === index)
|
const { data: currentProductor } = useGetProductor(Number(editId), {
|
||||||
}, [allProductors])
|
enabled: !!editId,
|
||||||
|
});
|
||||||
const types = useMemo(() => {
|
const { data: allProductors } = useGetProductors();
|
||||||
return allProductors?.map((productor: Productor) => (productor.type))
|
|
||||||
.filter((productor, index, array) => array.indexOf(productor) === index)
|
const names = useMemo(() => {
|
||||||
}, [allProductors])
|
return allProductors
|
||||||
|
?.map((productor: Productor) => productor.name)
|
||||||
const createProductorMutation = useCreateProductor();
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
const editProductorMutation = useEditProductor();
|
}, [allProductors]);
|
||||||
|
|
||||||
const handleCreateProductor = useCallback(async (productor: ProductorInputs) => {
|
const types = useMemo(() => {
|
||||||
await createProductorMutation.mutateAsync({
|
return allProductors
|
||||||
...productor
|
?.map((productor: Productor) => productor.type)
|
||||||
});
|
.filter((productor, index, array) => array.indexOf(productor) === index);
|
||||||
closeModal();
|
}, [allProductors]);
|
||||||
}, [createProductorMutation, closeModal]);
|
|
||||||
|
const createProductorMutation = useCreateProductor();
|
||||||
const handleEditProductor = useCallback(async (productor: ProductorInputs, id?: number) => {
|
const editProductorMutation = useEditProductor();
|
||||||
if (!id)
|
|
||||||
return;
|
const handleCreateProductor = useCallback(
|
||||||
await editProductorMutation.mutateAsync({
|
async (productor: ProductorInputs) => {
|
||||||
id: id,
|
await createProductorMutation.mutateAsync({
|
||||||
productor: productor
|
...productor,
|
||||||
});
|
});
|
||||||
closeModal();
|
closeModal();
|
||||||
}, [editProductorMutation, closeModal]);
|
},
|
||||||
|
[createProductorMutation, closeModal],
|
||||||
const onFilterChange = useCallback((values: string[], filter: string) => {
|
);
|
||||||
setSearchParams(prev => {
|
|
||||||
const params = new URLSearchParams(prev);
|
const handleEditProductor = useCallback(
|
||||||
params.delete(filter)
|
async (productor: ProductorInputs, id?: number) => {
|
||||||
|
if (!id) return;
|
||||||
values.forEach(value => {
|
await editProductorMutation.mutateAsync({
|
||||||
params.append(filter, value);
|
id: id,
|
||||||
});
|
productor: productor,
|
||||||
return params;
|
});
|
||||||
});
|
closeModal();
|
||||||
}, [setSearchParams])
|
},
|
||||||
|
[editProductorMutation, closeModal],
|
||||||
if (!productors || isPending)
|
);
|
||||||
return (
|
|
||||||
<Group
|
const onFilterChange = useCallback(
|
||||||
align="center"
|
(values: string[], filter: string) => {
|
||||||
justify="center"
|
setSearchParams((prev) => {
|
||||||
h="80vh"
|
const params = new URLSearchParams(prev);
|
||||||
w="100%"
|
params.delete(filter);
|
||||||
>
|
|
||||||
<Loader color="pink"/>
|
values.forEach((value) => {
|
||||||
</Group>
|
params.append(filter, value);
|
||||||
);
|
});
|
||||||
|
return params;
|
||||||
return (
|
});
|
||||||
<Stack>
|
},
|
||||||
<Group justify="space-between">
|
[setSearchParams],
|
||||||
<Title order={2}>{t("all productors", {capfirst: true})}</Title>
|
);
|
||||||
<Tooltip label={t("create productor", {capfirst: true})}>
|
|
||||||
<ActionIcon
|
if (!productors || isPending)
|
||||||
onClick={(e) => {
|
return (
|
||||||
e.stopPropagation();
|
<Group align="center" justify="center" h="80vh" w="100%">
|
||||||
navigate(`/dashboard/productors/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
<Loader color="pink" />
|
||||||
}}
|
</Group>
|
||||||
>
|
);
|
||||||
<IconPlus/>
|
|
||||||
</ActionIcon>
|
return (
|
||||||
</Tooltip>
|
<Stack>
|
||||||
<ProductorModal
|
<Group justify="space-between">
|
||||||
key={`${currentProductor?.id}_create`}
|
<Title order={2}>{t("all productors", { capfirst: true })}</Title>
|
||||||
opened={isCreate}
|
<Tooltip label={t("create productor", { capfirst: true })}>
|
||||||
onClose={closeModal}
|
<ActionIcon
|
||||||
handleSubmit={handleCreateProductor}
|
onClick={(e) => {
|
||||||
/>
|
e.stopPropagation();
|
||||||
<ProductorModal
|
navigate(
|
||||||
key={`${currentProductor?.id}_edit`}
|
`/dashboard/productors/create${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
opened={isEdit}
|
);
|
||||||
onClose={closeModal}
|
}}
|
||||||
currentProductor={currentProductor}
|
>
|
||||||
handleSubmit={handleEditProductor}
|
<IconPlus />
|
||||||
/>
|
</ActionIcon>
|
||||||
</Group>
|
</Tooltip>
|
||||||
<ProductorsFilters
|
<ProductorModal
|
||||||
names={names || []}
|
key={`${currentProductor?.id}_create`}
|
||||||
types={types || []}
|
opened={isCreate}
|
||||||
filters={searchParams}
|
onClose={closeModal}
|
||||||
onFilterChange={onFilterChange}
|
handleSubmit={handleCreateProductor}
|
||||||
/>
|
/>
|
||||||
<ScrollArea type="auto">
|
<ProductorModal
|
||||||
<Table striped>
|
key={`${currentProductor?.id}_edit`}
|
||||||
<Table.Thead>
|
opened={isEdit}
|
||||||
<Table.Tr>
|
onClose={closeModal}
|
||||||
<Table.Th>{t("name", {capfirst: true})}</Table.Th>
|
currentProductor={currentProductor}
|
||||||
<Table.Th>{t("type", {capfirst: true})}</Table.Th>
|
handleSubmit={handleEditProductor}
|
||||||
<Table.Th>{t("address", {capfirst: true})}</Table.Th>
|
/>
|
||||||
<Table.Th>{t("payment methods", {capfirst: true})}</Table.Th>
|
</Group>
|
||||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
<ProductorsFilters
|
||||||
</Table.Tr>
|
names={names || []}
|
||||||
</Table.Thead>
|
types={types || []}
|
||||||
<Table.Tbody>
|
filters={searchParams}
|
||||||
{
|
onFilterChange={onFilterChange}
|
||||||
productors.map((productor) => (
|
/>
|
||||||
<ProductorRow
|
<ScrollArea type="auto">
|
||||||
productor={productor}
|
<Table striped>
|
||||||
key={productor.id}
|
<Table.Thead>
|
||||||
/>
|
<Table.Tr>
|
||||||
))
|
<Table.Th>{t("name", { capfirst: true })}</Table.Th>
|
||||||
}
|
<Table.Th>{t("type", { capfirst: true })}</Table.Th>
|
||||||
</Table.Tbody>
|
<Table.Th>{t("address", { capfirst: true })}</Table.Th>
|
||||||
</Table>
|
<Table.Th>{t("payment methods", { capfirst: true })}</Table.Th>
|
||||||
</ScrollArea>
|
<Table.Th>{t("actions", { capfirst: true })}</Table.Th>
|
||||||
</Stack>
|
</Table.Tr>
|
||||||
);
|
</Table.Thead>
|
||||||
}
|
<Table.Tbody>
|
||||||
|
{productors.map((productor) => (
|
||||||
|
<ProductorRow productor={productor} key={productor.id} />
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,148 +1,156 @@
|
|||||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useCreateProduct, useEditProduct, useGetProduct, useGetProducts } from "@/services/api";
|
import { useCreateProduct, useEditProduct, useGetProduct, useGetProducts } from "@/services/api";
|
||||||
import { IconPlus } from "@tabler/icons-react";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
import ProductRow from "@/components/Products/Row";
|
import ProductRow from "@/components/Products/Row";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
import { ProductModal } from "@/components/Products/Modal";
|
import { ProductModal } from "@/components/Products/Modal";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { productCreateFromProductInputs, type Product, type ProductInputs } from "@/services/resources/products";
|
import {
|
||||||
import ProductsFilters from "@/components/Products/Filter";
|
productCreateFromProductInputs,
|
||||||
|
type Product,
|
||||||
export default function Products() {
|
type ProductInputs,
|
||||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
} from "@/services/resources/products";
|
||||||
const location = useLocation();
|
import ProductsFilters from "@/components/Products/Filter";
|
||||||
const navigate = useNavigate();
|
|
||||||
|
export default function Products() {
|
||||||
const isCreate = location.pathname === "/dashboard/products/create";
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
const editId = useMemo(() => {
|
|
||||||
if (isEdit) {
|
const isCreate = location.pathname === "/dashboard/products/create";
|
||||||
return location.pathname.split("/")[3]
|
const isEdit = location.pathname.includes("/edit");
|
||||||
}
|
|
||||||
return null
|
const editId = useMemo(() => {
|
||||||
}, [location, isEdit])
|
if (isEdit) {
|
||||||
|
return location.pathname.split("/")[3];
|
||||||
const closeModal = useCallback(() => {
|
}
|
||||||
navigate(`/dashboard/products${searchParams ? `?${searchParams.toString()}` : ""}`);
|
return null;
|
||||||
}, [navigate, searchParams]);
|
}, [location, isEdit]);
|
||||||
|
|
||||||
const { data: products, isPending } = useGetProducts(searchParams);
|
const closeModal = useCallback(() => {
|
||||||
const { data: currentProduct } = useGetProduct(Number(editId), { enabled: !!editId });
|
navigate(`/dashboard/products${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
const { data: allProducts } = useGetProducts();
|
}, [navigate, searchParams]);
|
||||||
|
|
||||||
const names = useMemo(() => {
|
const { data: products, isPending } = useGetProducts(searchParams);
|
||||||
return allProducts?.map((product: Product) => (product.name))
|
const { data: currentProduct } = useGetProduct(Number(editId), {
|
||||||
.filter((season, index, array) => array.indexOf(season) === index)
|
enabled: !!editId,
|
||||||
}, [allProducts])
|
});
|
||||||
|
const { data: allProducts } = useGetProducts();
|
||||||
const productors = useMemo(() => {
|
|
||||||
return allProducts?.map((product: Product) => (product.productor.name))
|
const names = useMemo(() => {
|
||||||
.filter((productor, index, array) => array.indexOf(productor) === index)
|
return allProducts
|
||||||
}, [allProducts])
|
?.map((product: Product) => product.name)
|
||||||
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
const createProductMutation = useCreateProduct();
|
}, [allProducts]);
|
||||||
const editProductMutation = useEditProduct();
|
|
||||||
|
const productors = useMemo(() => {
|
||||||
const handleCreateProduct = useCallback(async (product: ProductInputs) => {
|
return allProducts
|
||||||
await createProductMutation.mutateAsync(productCreateFromProductInputs(product));
|
?.map((product: Product) => product.productor.name)
|
||||||
closeModal();
|
.filter((productor, index, array) => array.indexOf(productor) === index);
|
||||||
}, [createProductMutation, closeModal]);
|
}, [allProducts]);
|
||||||
|
|
||||||
const handleEditProduct = useCallback(async (product: ProductInputs, id?: number) => {
|
const createProductMutation = useCreateProduct();
|
||||||
if (!id)
|
const editProductMutation = useEditProduct();
|
||||||
return;
|
|
||||||
await editProductMutation.mutateAsync({
|
const handleCreateProduct = useCallback(
|
||||||
id: id,
|
async (product: ProductInputs) => {
|
||||||
product: productCreateFromProductInputs(product)
|
await createProductMutation.mutateAsync(productCreateFromProductInputs(product));
|
||||||
});
|
closeModal();
|
||||||
closeModal();
|
},
|
||||||
}, [editProductMutation, closeModal]);
|
[createProductMutation, closeModal],
|
||||||
|
);
|
||||||
const onFilterChange = useCallback((values: string[], filter: string) => {
|
|
||||||
setSearchParams(prev => {
|
const handleEditProduct = useCallback(
|
||||||
const params = new URLSearchParams(prev);
|
async (product: ProductInputs, id?: number) => {
|
||||||
params.delete(filter);
|
if (!id) return;
|
||||||
|
await editProductMutation.mutateAsync({
|
||||||
values.forEach(value => {
|
id: id,
|
||||||
params.append(filter, value);
|
product: productCreateFromProductInputs(product),
|
||||||
});
|
});
|
||||||
return params;
|
closeModal();
|
||||||
});
|
},
|
||||||
}, [setSearchParams])
|
[editProductMutation, closeModal],
|
||||||
|
);
|
||||||
if (!products || isPending)
|
|
||||||
return (
|
const onFilterChange = useCallback(
|
||||||
<Group
|
(values: string[], filter: string) => {
|
||||||
align="center"
|
setSearchParams((prev) => {
|
||||||
justify="center"
|
const params = new URLSearchParams(prev);
|
||||||
h="80vh"
|
params.delete(filter);
|
||||||
w="100%"
|
|
||||||
>
|
values.forEach((value) => {
|
||||||
<Loader color="pink"/>
|
params.append(filter, value);
|
||||||
</Group>
|
});
|
||||||
);
|
return params;
|
||||||
|
});
|
||||||
return (
|
},
|
||||||
<Stack>
|
[setSearchParams],
|
||||||
<Group justify="space-between">
|
);
|
||||||
<Title order={2}>{t("all products", {capfirst: true})}</Title>
|
|
||||||
<Tooltip label={t("create product", {capfirst: true})}>
|
if (!products || isPending)
|
||||||
<ActionIcon
|
return (
|
||||||
onClick={(e) => {
|
<Group align="center" justify="center" h="80vh" w="100%">
|
||||||
e.stopPropagation();
|
<Loader color="pink" />
|
||||||
navigate(`/dashboard/products/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
</Group>
|
||||||
}}
|
);
|
||||||
>
|
|
||||||
<IconPlus/>
|
return (
|
||||||
</ActionIcon>
|
<Stack>
|
||||||
</Tooltip>
|
<Group justify="space-between">
|
||||||
<ProductModal
|
<Title order={2}>{t("all products", { capfirst: true })}</Title>
|
||||||
key={`${currentProduct?.id}_create`}
|
<Tooltip label={t("create product", { capfirst: true })}>
|
||||||
opened={isCreate}
|
<ActionIcon
|
||||||
onClose={closeModal}
|
onClick={(e) => {
|
||||||
handleSubmit={handleCreateProduct}
|
e.stopPropagation();
|
||||||
/>
|
navigate(
|
||||||
</Group>
|
`/dashboard/products/create${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
<ProductsFilters
|
);
|
||||||
productors = {productors || []}
|
}}
|
||||||
names={names || []}
|
>
|
||||||
filters={searchParams}
|
<IconPlus />
|
||||||
onFilterChange={onFilterChange}
|
</ActionIcon>
|
||||||
/>
|
</Tooltip>
|
||||||
<ProductModal
|
<ProductModal
|
||||||
key={`${currentProduct?.id}_edit`}
|
key={`${currentProduct?.id}_create`}
|
||||||
opened={isEdit}
|
opened={isCreate}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
currentProduct={currentProduct}
|
handleSubmit={handleCreateProduct}
|
||||||
handleSubmit={handleEditProduct}
|
/>
|
||||||
/>
|
</Group>
|
||||||
<ScrollArea type="auto">
|
<ProductsFilters
|
||||||
<Table striped>
|
productors={productors || []}
|
||||||
<Table.Thead>
|
names={names || []}
|
||||||
<Table.Tr>
|
filters={searchParams}
|
||||||
<Table.Th>{t("name", {capfirst: true})}</Table.Th>
|
onFilterChange={onFilterChange}
|
||||||
<Table.Th>{t("type", {capfirst: true})}</Table.Th>
|
/>
|
||||||
<Table.Th>{t("price", {capfirst: true})}</Table.Th>
|
<ProductModal
|
||||||
<Table.Th>{t("priceKg", {capfirst: true})}</Table.Th>
|
key={`${currentProduct?.id}_edit`}
|
||||||
<Table.Th>{t("quantity", {capfirst: true})}</Table.Th>
|
opened={isEdit}
|
||||||
<Table.Th>{t("unit", {capfirst: true})}</Table.Th>
|
onClose={closeModal}
|
||||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
currentProduct={currentProduct}
|
||||||
</Table.Tr>
|
handleSubmit={handleEditProduct}
|
||||||
</Table.Thead>
|
/>
|
||||||
<Table.Tbody>
|
<ScrollArea type="auto">
|
||||||
{
|
<Table striped>
|
||||||
products.map((product) => (
|
<Table.Thead>
|
||||||
<ProductRow
|
<Table.Tr>
|
||||||
product={product}
|
<Table.Th>{t("name", { capfirst: true })}</Table.Th>
|
||||||
key={product.id}
|
<Table.Th>{t("type", { capfirst: true })}</Table.Th>
|
||||||
/>
|
<Table.Th>{t("price", { capfirst: true })}</Table.Th>
|
||||||
))
|
<Table.Th>{t("priceKg", { capfirst: true })}</Table.Th>
|
||||||
}
|
<Table.Th>{t("quantity", { capfirst: true })}</Table.Th>
|
||||||
</Table.Tbody>
|
<Table.Th>{t("unit", { capfirst: true })}</Table.Th>
|
||||||
</Table>
|
<Table.Th>{t("actions", { capfirst: true })}</Table.Th>
|
||||||
</ScrollArea>
|
</Table.Tr>
|
||||||
</Stack>
|
</Table.Thead>
|
||||||
);
|
<Table.Tbody>
|
||||||
}
|
{products.map((product) => (
|
||||||
|
<ProductRow product={product} key={product.id} />
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,145 +1,158 @@
|
|||||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useCreateShipment, useEditShipment, useGetShipment, useGetShipments } from "@/services/api";
|
import {
|
||||||
import { IconPlus } from "@tabler/icons-react";
|
useCreateShipment,
|
||||||
import ShipmentRow from "@/components/Shipments/Row";
|
useEditShipment,
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
useGetShipment,
|
||||||
import { useCallback, useMemo } from "react";
|
useGetShipments,
|
||||||
import { shipmentCreateFromShipmentInputs, type Shipment, type ShipmentInputs } from "@/services/resources/shipments";
|
} from "@/services/api";
|
||||||
import ShipmentModal from "@/components/Shipments/Modal";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
import ShipmentsFilters from "@/components/Shipments/Filter";
|
import ShipmentRow from "@/components/Shipments/Row";
|
||||||
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
export default function Shipments() {
|
import { useCallback, useMemo } from "react";
|
||||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
import {
|
||||||
const location = useLocation();
|
shipmentCreateFromShipmentInputs,
|
||||||
const navigate = useNavigate();
|
type Shipment,
|
||||||
|
type ShipmentInputs,
|
||||||
const isCreate = location.pathname === "/dashboard/shipments/create";
|
} from "@/services/resources/shipments";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
import ShipmentModal from "@/components/Shipments/Modal";
|
||||||
|
import ShipmentsFilters from "@/components/Shipments/Filter";
|
||||||
const editId = useMemo(() => {
|
|
||||||
if (isEdit) {
|
export default function Shipments() {
|
||||||
return location.pathname.split("/")[3]
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
}
|
const location = useLocation();
|
||||||
return null
|
const navigate = useNavigate();
|
||||||
}, [location, isEdit])
|
|
||||||
|
const isCreate = location.pathname === "/dashboard/shipments/create";
|
||||||
const closeModal = useCallback(() => {
|
const isEdit = location.pathname.includes("/edit");
|
||||||
navigate(`/dashboard/shipments${searchParams ? `?${searchParams.toString()}` : ""}`);
|
|
||||||
}, [navigate, searchParams]);
|
const editId = useMemo(() => {
|
||||||
|
if (isEdit) {
|
||||||
const { data: shipments, isPending } = useGetShipments(searchParams);
|
return location.pathname.split("/")[3];
|
||||||
const { data: currentShipment } = useGetShipment(Number(editId), { enabled: !!editId });
|
}
|
||||||
const { data: allShipments } = useGetShipments();
|
return null;
|
||||||
|
}, [location, isEdit]);
|
||||||
const names = useMemo(() => {
|
|
||||||
return allShipments?.map((shipment: Shipment) => (shipment.name))
|
const closeModal = useCallback(() => {
|
||||||
.filter((season, index, array) => array.indexOf(season) === index)
|
navigate(`/dashboard/shipments${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}, [allShipments])
|
}, [navigate, searchParams]);
|
||||||
|
|
||||||
const forms = useMemo(() => {
|
const { data: shipments, isPending } = useGetShipments(searchParams);
|
||||||
return allShipments?.map((shipment: Shipment) => (shipment.form.name))
|
const { data: currentShipment } = useGetShipment(Number(editId), {
|
||||||
.filter((season, index, array) => array.indexOf(season) === index)
|
enabled: !!editId,
|
||||||
}, [allShipments])
|
});
|
||||||
|
const { data: allShipments } = useGetShipments();
|
||||||
const createShipmentMutation = useCreateShipment();
|
|
||||||
const editShipmentMutation = useEditShipment();
|
const names = useMemo(() => {
|
||||||
|
return allShipments
|
||||||
const handleCreateShipment = useCallback(async (shipment: ShipmentInputs) => {
|
?.map((shipment: Shipment) => shipment.name)
|
||||||
await createShipmentMutation.mutateAsync(shipmentCreateFromShipmentInputs(shipment));
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
closeModal();
|
}, [allShipments]);
|
||||||
}, [createShipmentMutation, closeModal]);
|
|
||||||
|
const forms = useMemo(() => {
|
||||||
const handleEditShipment = useCallback(async (shipment: ShipmentInputs, id?: number) => {
|
return allShipments
|
||||||
if (!id)
|
?.map((shipment: Shipment) => shipment.form.name)
|
||||||
return;
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
await editShipmentMutation.mutateAsync({
|
}, [allShipments]);
|
||||||
id: id,
|
|
||||||
shipment: shipmentCreateFromShipmentInputs(shipment)
|
const createShipmentMutation = useCreateShipment();
|
||||||
});
|
const editShipmentMutation = useEditShipment();
|
||||||
closeModal();
|
|
||||||
}, [editShipmentMutation, closeModal]);
|
const handleCreateShipment = useCallback(
|
||||||
|
async (shipment: ShipmentInputs) => {
|
||||||
const onFilterChange = useCallback((values: string[], filter: string) => {
|
await createShipmentMutation.mutateAsync(shipmentCreateFromShipmentInputs(shipment));
|
||||||
setSearchParams(prev => {
|
closeModal();
|
||||||
const params = new URLSearchParams(prev);
|
},
|
||||||
params.delete(filter)
|
[createShipmentMutation, closeModal],
|
||||||
|
);
|
||||||
values.forEach(value => {
|
|
||||||
params.append(filter, value);
|
const handleEditShipment = useCallback(
|
||||||
});
|
async (shipment: ShipmentInputs, id?: number) => {
|
||||||
return params;
|
if (!id) return;
|
||||||
});
|
await editShipmentMutation.mutateAsync({
|
||||||
}, [setSearchParams])
|
id: id,
|
||||||
|
shipment: shipmentCreateFromShipmentInputs(shipment),
|
||||||
if (!shipments || isPending)
|
});
|
||||||
return (
|
closeModal();
|
||||||
<Group
|
},
|
||||||
align="center"
|
[editShipmentMutation, closeModal],
|
||||||
justify="center"
|
);
|
||||||
h="80vh"
|
|
||||||
w="100%"
|
const onFilterChange = useCallback(
|
||||||
>
|
(values: string[], filter: string) => {
|
||||||
<Loader color="pink"/>
|
setSearchParams((prev) => {
|
||||||
</Group>
|
const params = new URLSearchParams(prev);
|
||||||
);
|
params.delete(filter);
|
||||||
|
|
||||||
return (
|
values.forEach((value) => {
|
||||||
<Stack>
|
params.append(filter, value);
|
||||||
<Group justify="space-between">
|
});
|
||||||
<Title order={2}>{t("all shipments", {capfirst: true})}</Title>
|
return params;
|
||||||
<Tooltip label={t("create shipment", {capfirst: true})}>
|
});
|
||||||
<ActionIcon
|
},
|
||||||
onClick={(e) => {
|
[setSearchParams],
|
||||||
e.stopPropagation();
|
);
|
||||||
navigate(`/dashboard/shipments/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
|
||||||
}}
|
if (!shipments || isPending)
|
||||||
>
|
return (
|
||||||
<IconPlus/>
|
<Group align="center" justify="center" h="80vh" w="100%">
|
||||||
</ActionIcon>
|
<Loader color="pink" />
|
||||||
</Tooltip>
|
</Group>
|
||||||
<ShipmentModal
|
);
|
||||||
key={`${currentShipment?.id}_create`}
|
|
||||||
opened={isCreate}
|
return (
|
||||||
onClose={closeModal}
|
<Stack>
|
||||||
handleSubmit={handleCreateShipment}
|
<Group justify="space-between">
|
||||||
/>
|
<Title order={2}>{t("all shipments", { capfirst: true })}</Title>
|
||||||
<ShipmentModal
|
<Tooltip label={t("create shipment", { capfirst: true })}>
|
||||||
key={`${currentShipment?.id}_edit`}
|
<ActionIcon
|
||||||
opened={isEdit}
|
onClick={(e) => {
|
||||||
onClose={closeModal}
|
e.stopPropagation();
|
||||||
currentShipment={currentShipment}
|
navigate(
|
||||||
handleSubmit={handleEditShipment}
|
`/dashboard/shipments/create${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
/>
|
);
|
||||||
</Group>
|
}}
|
||||||
<ShipmentsFilters
|
>
|
||||||
forms={forms || []}
|
<IconPlus />
|
||||||
names={names || []}
|
</ActionIcon>
|
||||||
filters={searchParams}
|
</Tooltip>
|
||||||
onFilterChange={onFilterChange}
|
<ShipmentModal
|
||||||
/>
|
key={`${currentShipment?.id}_create`}
|
||||||
<ScrollArea type="auto">
|
opened={isCreate}
|
||||||
<Table striped>
|
onClose={closeModal}
|
||||||
<Table.Thead>
|
handleSubmit={handleCreateShipment}
|
||||||
<Table.Tr>
|
/>
|
||||||
<Table.Th>{t("name", {capfirst: true})}</Table.Th>
|
<ShipmentModal
|
||||||
<Table.Th>{t("date", {capfirst: true})}</Table.Th>
|
key={`${currentShipment?.id}_edit`}
|
||||||
<Table.Th>{t("formulare", {capfirst: true})}</Table.Th>
|
opened={isEdit}
|
||||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
onClose={closeModal}
|
||||||
</Table.Tr>
|
currentShipment={currentShipment}
|
||||||
</Table.Thead>
|
handleSubmit={handleEditShipment}
|
||||||
<Table.Tbody>
|
/>
|
||||||
{
|
</Group>
|
||||||
shipments.map((shipment) => (
|
<ShipmentsFilters
|
||||||
<ShipmentRow
|
forms={forms || []}
|
||||||
shipment={shipment}
|
names={names || []}
|
||||||
key={shipment.id}
|
filters={searchParams}
|
||||||
/>
|
onFilterChange={onFilterChange}
|
||||||
))
|
/>
|
||||||
}
|
<ScrollArea type="auto">
|
||||||
</Table.Tbody>
|
<Table striped>
|
||||||
</Table>
|
<Table.Thead>
|
||||||
</ScrollArea>
|
<Table.Tr>
|
||||||
</Stack>
|
<Table.Th>{t("name", { capfirst: true })}</Table.Th>
|
||||||
);
|
<Table.Th>{t("date", { capfirst: true })}</Table.Th>
|
||||||
}
|
<Table.Th>{t("formulare", { capfirst: true })}</Table.Th>
|
||||||
|
<Table.Th>{t("actions", { capfirst: true })}</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{shipments.map((shipment) => (
|
||||||
|
<ShipmentRow shipment={shipment} key={shipment.id} />
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export default function Templates() {
|
export default function Templates() {
|
||||||
return (
|
return <></>;
|
||||||
<></>
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,139 +1,142 @@
|
|||||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useCreateUser, useEditUser, useGetUser, useGetUsers } from "@/services/api";
|
import { useCreateUser, useEditUser, useGetUser, useGetUsers } from "@/services/api";
|
||||||
import { IconPlus } from "@tabler/icons-react";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
import UserRow from "@/components/Users/Row";
|
import UserRow from "@/components/Users/Row";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
import { UserModal } from "@/components/Users/Modal";
|
import { UserModal } from "@/components/Users/Modal";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { type User, type UserInputs } from "@/services/resources/users";
|
import { type User, type UserInputs } from "@/services/resources/users";
|
||||||
import UsersFilters from "@/components/Users/Filter";
|
import UsersFilters from "@/components/Users/Filter";
|
||||||
|
|
||||||
export default function Users() {
|
export default function Users() {
|
||||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const isCreate = location.pathname === "/dashboard/users/create";
|
const isCreate = location.pathname === "/dashboard/users/create";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const isEdit = location.pathname.includes("/edit");
|
||||||
|
|
||||||
const editId = useMemo(() => {
|
const editId = useMemo(() => {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
return location.pathname.split("/")[3]
|
return location.pathname.split("/")[3];
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}, [location, isEdit])
|
}, [location, isEdit]);
|
||||||
|
|
||||||
const closeModal = useCallback(() => {
|
const closeModal = useCallback(() => {
|
||||||
navigate(`/dashboard/users${searchParams ? `?${searchParams.toString()}` : ""}`);
|
navigate(`/dashboard/users${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}, [navigate, searchParams]);
|
}, [navigate, searchParams]);
|
||||||
|
|
||||||
const {data: users, isPending} = useGetUsers(searchParams);
|
const { data: users, isPending } = useGetUsers(searchParams);
|
||||||
const { data: currentUser } = useGetUser(Number(editId), { enabled: !!editId });
|
const { data: currentUser } = useGetUser(Number(editId), {
|
||||||
|
enabled: !!editId,
|
||||||
const {data: allUsers } = useGetUsers();
|
});
|
||||||
|
|
||||||
const names = useMemo(() => {
|
const { data: allUsers } = useGetUsers();
|
||||||
return allUsers?.map((user: User) => (user.name))
|
|
||||||
.filter((season, index, array) => array.indexOf(season) === index)
|
const names = useMemo(() => {
|
||||||
}, [allUsers])
|
return allUsers
|
||||||
|
?.map((user: User) => user.name)
|
||||||
const createUserMutation = useCreateUser();
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
const editUserMutation = useEditUser();
|
}, [allUsers]);
|
||||||
|
|
||||||
const handleCreateUser = useCallback(async (user: UserInputs) => {
|
const createUserMutation = useCreateUser();
|
||||||
await createUserMutation.mutateAsync(user);
|
const editUserMutation = useEditUser();
|
||||||
closeModal();
|
|
||||||
}, [createUserMutation, closeModal]);
|
const handleCreateUser = useCallback(
|
||||||
|
async (user: UserInputs) => {
|
||||||
const handleEditUser = useCallback(async (user: UserInputs, id?: number) => {
|
await createUserMutation.mutateAsync(user);
|
||||||
if (!id)
|
closeModal();
|
||||||
return;
|
},
|
||||||
await editUserMutation.mutateAsync({
|
[createUserMutation, closeModal],
|
||||||
id: id,
|
);
|
||||||
user: user
|
|
||||||
});
|
const handleEditUser = useCallback(
|
||||||
closeModal();
|
async (user: UserInputs, id?: number) => {
|
||||||
}, [editUserMutation, closeModal]);
|
if (!id) return;
|
||||||
|
await editUserMutation.mutateAsync({
|
||||||
const onFilterChange = useCallback((values: string[], filter: string) => {
|
id: id,
|
||||||
setSearchParams(prev => {
|
user: user,
|
||||||
const params = new URLSearchParams(prev);
|
});
|
||||||
params.delete(filter);
|
closeModal();
|
||||||
|
},
|
||||||
values.forEach(value => {
|
[editUserMutation, closeModal],
|
||||||
params.append(filter, value);
|
);
|
||||||
});
|
|
||||||
return params;
|
const onFilterChange = useCallback(
|
||||||
});
|
(values: string[], filter: string) => {
|
||||||
}, [setSearchParams])
|
setSearchParams((prev) => {
|
||||||
|
const params = new URLSearchParams(prev);
|
||||||
if (!users || isPending)
|
params.delete(filter);
|
||||||
return (
|
|
||||||
<Group
|
values.forEach((value) => {
|
||||||
align="center"
|
params.append(filter, value);
|
||||||
justify="center"
|
});
|
||||||
h="80vh"
|
return params;
|
||||||
w="100%"
|
});
|
||||||
>
|
},
|
||||||
<Loader color="pink"/>
|
[setSearchParams],
|
||||||
</Group>
|
);
|
||||||
);
|
|
||||||
|
if (!users || isPending)
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Group align="center" justify="center" h="80vh" w="100%">
|
||||||
<Group justify="space-between">
|
<Loader color="pink" />
|
||||||
<Title order={2}>{t("all users", {capfirst: true})}</Title>
|
</Group>
|
||||||
<Tooltip label={t("create user", {capfirst: true})}>
|
);
|
||||||
<ActionIcon
|
|
||||||
onClick={(e) => {
|
return (
|
||||||
e.stopPropagation();
|
<Stack>
|
||||||
navigate(`/dashboard/users/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
<Group justify="space-between">
|
||||||
}}
|
<Title order={2}>{t("all users", { capfirst: true })}</Title>
|
||||||
>
|
<Tooltip label={t("create user", { capfirst: true })}>
|
||||||
<IconPlus/>
|
<ActionIcon
|
||||||
</ActionIcon>
|
onClick={(e) => {
|
||||||
</Tooltip>
|
e.stopPropagation();
|
||||||
<UserModal
|
navigate(
|
||||||
key={`${currentUser?.id}_create`}
|
`/dashboard/users/create${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
opened={isCreate}
|
);
|
||||||
onClose={closeModal}
|
}}
|
||||||
handleSubmit={handleCreateUser}
|
>
|
||||||
/>
|
<IconPlus />
|
||||||
<UserModal
|
</ActionIcon>
|
||||||
key={`${currentUser?.id}_edit`}
|
</Tooltip>
|
||||||
opened={isEdit}
|
<UserModal
|
||||||
onClose={closeModal}
|
key={`${currentUser?.id}_create`}
|
||||||
currentUser={currentUser}
|
opened={isCreate}
|
||||||
handleSubmit={handleEditUser}
|
onClose={closeModal}
|
||||||
/>
|
handleSubmit={handleCreateUser}
|
||||||
</Group>
|
/>
|
||||||
<UsersFilters
|
<UserModal
|
||||||
names={names || []}
|
key={`${currentUser?.id}_edit`}
|
||||||
filters={searchParams}
|
opened={isEdit}
|
||||||
onFilterChange={onFilterChange}
|
onClose={closeModal}
|
||||||
/>
|
currentUser={currentUser}
|
||||||
<ScrollArea type="auto">
|
handleSubmit={handleEditUser}
|
||||||
<Table striped>
|
/>
|
||||||
<Table.Thead>
|
</Group>
|
||||||
<Table.Tr>
|
<UsersFilters
|
||||||
<Table.Th>{t("name", {capfirst: true})}</Table.Th>
|
names={names || []}
|
||||||
<Table.Th>{t("email", {capfirst: true})}</Table.Th>
|
filters={searchParams}
|
||||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
onFilterChange={onFilterChange}
|
||||||
</Table.Tr>
|
/>
|
||||||
</Table.Thead>
|
<ScrollArea type="auto">
|
||||||
<Table.Tbody>
|
<Table striped>
|
||||||
{
|
<Table.Thead>
|
||||||
users.map((user) => (
|
<Table.Tr>
|
||||||
<UserRow
|
<Table.Th>{t("name", { capfirst: true })}</Table.Th>
|
||||||
user={user}
|
<Table.Th>{t("email", { capfirst: true })}</Table.Th>
|
||||||
key={user.id}
|
<Table.Th>{t("actions", { capfirst: true })}</Table.Th>
|
||||||
/>
|
</Table.Tr>
|
||||||
))
|
</Table.Thead>
|
||||||
}
|
<Table.Tbody>
|
||||||
</Table.Tbody>
|
{users.map((user) => (
|
||||||
</Table>
|
<UserRow user={user} key={user.id} />
|
||||||
</ScrollArea>
|
))}
|
||||||
</Stack>
|
</Table.Tbody>
|
||||||
);
|
</Table>
|
||||||
}
|
</ScrollArea>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { Navbar } from "@/components/Navbar";
|
|||||||
import { Footer } from "@/components/Footer";
|
import { Footer } from "@/components/Footer";
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main style={{display: "flex", justifyContent: "center"}}>
|
<main style={{ display: "flex", justifyContent: "center" }}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import {
|
import { createBrowserRouter } from "react-router";
|
||||||
createBrowserRouter,
|
|
||||||
} from "react-router";
|
|
||||||
|
|
||||||
import Root from "@/root";
|
import Root from "@/root";
|
||||||
import { Home } from "@/pages/Home";
|
import { Home } from "@/pages/Home";
|
||||||
@@ -14,35 +12,36 @@ import { Contract } from "./pages/Contract";
|
|||||||
import { NotFound } from "./pages/NotFound";
|
import { NotFound } from "./pages/NotFound";
|
||||||
|
|
||||||
export const router = createBrowserRouter([
|
export const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
Component: Root,
|
Component: Root,
|
||||||
errorElement: <NotFound />,
|
errorElement: <NotFound />,
|
||||||
children: [
|
|
||||||
{ index: true, Component: Home },
|
|
||||||
{ path: "/forms", Component: Forms },
|
|
||||||
{
|
|
||||||
path: "/dashboard", Component: Dashboard,
|
|
||||||
children: [
|
children: [
|
||||||
{ path: "productors", Component: Productors },
|
{ index: true, Component: Home },
|
||||||
{ path: "productors/create", Component: Productors },
|
{ path: "/forms", Component: Forms },
|
||||||
{ path: "productors/:id/edit", Component: Productors },
|
{
|
||||||
{ path: "products", Component: Products },
|
path: "/dashboard",
|
||||||
{ path: "products/create", Component: Products },
|
Component: Dashboard,
|
||||||
{ path: "products/:id/edit", Component: Products },
|
children: [
|
||||||
// { path: "templates", Component: Templates },
|
{ path: "productors", Component: Productors },
|
||||||
{ path: "users", Component: Users },
|
{ path: "productors/create", Component: Productors },
|
||||||
{ path: "users/create", Component: Users },
|
{ path: "productors/:id/edit", Component: Productors },
|
||||||
{ path: "users/:id/edit", Component: Users },
|
{ path: "products", Component: Products },
|
||||||
{ path: "forms", Component: Forms },
|
{ path: "products/create", Component: Products },
|
||||||
{ path: "forms/:id/edit", Component: Forms },
|
{ path: "products/:id/edit", Component: Products },
|
||||||
{ path: "forms/create", Component: Forms },
|
// { path: "templates", Component: Templates },
|
||||||
{ path: "shipments", Component: Shipments },
|
{ path: "users", Component: Users },
|
||||||
{ path: "shipments/:id/edit", Component: Shipments },
|
{ path: "users/create", Component: Users },
|
||||||
{ path: "shipments/create", Component: Shipments },
|
{ path: "users/:id/edit", Component: Users },
|
||||||
]
|
{ path: "forms", Component: Forms },
|
||||||
},
|
{ path: "forms/:id/edit", Component: Forms },
|
||||||
{ path: "/form/:id", Component: Contract},
|
{ path: "forms/create", Component: Forms },
|
||||||
],
|
{ path: "shipments", Component: Shipments },
|
||||||
},
|
{ path: "shipments/:id/edit", Component: Shipments },
|
||||||
|
{ path: "shipments/create", Component: Shipments },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ path: "/form/:id", Component: Contract },
|
||||||
|
],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
import { useMutation, useQuery, useQueryClient,type DefinedInitialDataOptions,type UseQueryResult } from "@tanstack/react-query";
|
import {
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
type DefinedInitialDataOptions,
|
||||||
|
type UseQueryResult,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
import { Config } from "@/config/config";
|
import { Config } from "@/config/config";
|
||||||
import type { Form, FormCreate, FormEditPayload } from "@/services/resources/forms";
|
import type { Form, FormCreate, FormEditPayload } from "@/services/resources/forms";
|
||||||
import type { Shipment, ShipmentCreate, ShipmentEditPayload } from "@/services/resources/shipments";
|
import type { Shipment, ShipmentCreate, ShipmentEditPayload } from "@/services/resources/shipments";
|
||||||
import type { Productor, ProductorCreate, ProductorEditPayload } from "@/services/resources/productors";
|
import type {
|
||||||
|
Productor,
|
||||||
|
ProductorCreate,
|
||||||
|
ProductorEditPayload,
|
||||||
|
} from "@/services/resources/productors";
|
||||||
import type { User, UserCreate, UserEditPayload } from "@/services/resources/users";
|
import type { User, UserCreate, UserEditPayload } from "@/services/resources/users";
|
||||||
import type { Product, ProductCreate, ProductEditPayload } from "./resources/products";
|
import type { Product, ProductCreate, ProductEditPayload } from "./resources/products";
|
||||||
import type { ContractCreate } from "./resources/contracts";
|
import type { ContractCreate } from "./resources/contracts";
|
||||||
@@ -10,559 +20,558 @@ import { notifications } from "@mantine/notifications";
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
|
|
||||||
export function useGetShipments(filters?: URLSearchParams): UseQueryResult<Shipment[], Error> {
|
export function useGetShipments(filters?: URLSearchParams): UseQueryResult<Shipment[], Error> {
|
||||||
const queryString = filters?.toString()
|
const queryString = filters?.toString();
|
||||||
return useQuery<Shipment[]>({
|
return useQuery<Shipment[]>({
|
||||||
queryKey: ['shipments', queryString],
|
queryKey: ["shipments", queryString],
|
||||||
queryFn: () => (
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/shipments${filters ? `?${queryString}` : ""}`)
|
fetch(`${Config.backend_uri}/shipments${filters ? `?${queryString}` : ""}`).then(
|
||||||
.then((res) => res.json())
|
(res) => res.json(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetShipment(id?: number, options?: Partial<DefinedInitialDataOptions<Shipment, Error, Shipment, readonly unknown[]>>): UseQueryResult<Shipment, Error> {
|
export function useGetShipment(
|
||||||
|
id?: number,
|
||||||
|
options?: Partial<DefinedInitialDataOptions<Shipment, Error, Shipment, readonly unknown[]>>,
|
||||||
|
): UseQueryResult<Shipment, Error> {
|
||||||
return useQuery<Shipment>({
|
return useQuery<Shipment>({
|
||||||
queryKey: ['shipment'],
|
queryKey: ["shipment"],
|
||||||
queryFn: () => (
|
queryFn: () => fetch(`${Config.backend_uri}/shipments/${id}`).then((res) => res.json()),
|
||||||
fetch(`${Config.backend_uri}/shipments/${id}`)
|
enabled: !!id,
|
||||||
.then((res) => res.json())
|
|
||||||
),
|
|
||||||
enabled: !!id,
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateShipment() {
|
export function useCreateShipment() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newShipment: ShipmentCreate) => {
|
mutationFn: (newShipment: ShipmentCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/shipments`, {
|
return fetch(`${Config.backend_uri}/shipments`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newShipment),
|
body: JSON.stringify(newShipment),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully created shipment", {capfirst: true}),
|
message: t("successfully created shipment", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['shipments'] })
|
await queryClient.invalidateQueries({ queryKey: ["shipments"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing shipment`, {capfirst: true}),
|
message: error?.message || t(`error editing shipment`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEditShipment() {
|
export function useEditShipment() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({shipment, id}: ShipmentEditPayload) => {
|
mutationFn: ({ shipment, id }: ShipmentEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/shipments/${id}`, {
|
return fetch(`${Config.backend_uri}/shipments/${id}`, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(shipment),
|
body: JSON.stringify(shipment),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully edited shipment", {capfirst: true}),
|
message: t("successfully edited shipment", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['shipments'] })
|
await queryClient.invalidateQueries({ queryKey: ["shipments"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing shipment`, {capfirst: true}),
|
message: error?.message || t(`error editing shipment`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteShipment() {
|
export function useDeleteShipment() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/shipments/${id}`, {
|
return fetch(`${Config.backend_uri}/shipments/${id}`, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully deleted shipment", {capfirst: true}),
|
message: t("successfully deleted shipment", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['shipments'] })
|
await queryClient.invalidateQueries({ queryKey: ["shipments"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error deleting shipment`, {capfirst: true}),
|
message: error?.message || t(`error deleting shipment`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetProductors(filters?: URLSearchParams): UseQueryResult<Productor[], Error> {
|
export function useGetProductors(filters?: URLSearchParams): UseQueryResult<Productor[], Error> {
|
||||||
const queryString = filters?.toString()
|
const queryString = filters?.toString();
|
||||||
return useQuery<Productor[]>({
|
return useQuery<Productor[]>({
|
||||||
queryKey: ['productors', queryString],
|
queryKey: ["productors", queryString],
|
||||||
queryFn: () => (
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`)
|
fetch(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`).then(
|
||||||
.then((res) => res.json())
|
(res) => res.json(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetProductor(id?: number, options?: Partial<DefinedInitialDataOptions<Productor, Error, Productor, readonly unknown[]>>) {
|
export function useGetProductor(
|
||||||
|
id?: number,
|
||||||
|
options?: Partial<DefinedInitialDataOptions<Productor, Error, Productor, readonly unknown[]>>,
|
||||||
|
) {
|
||||||
return useQuery<Productor>({
|
return useQuery<Productor>({
|
||||||
queryKey: ['productor'],
|
queryKey: ["productor"],
|
||||||
queryFn: () => (
|
queryFn: () => fetch(`${Config.backend_uri}/productors/${id}`).then((res) => res.json()),
|
||||||
fetch(`${Config.backend_uri}/productors/${id}`)
|
enabled: !!id,
|
||||||
.then((res) => res.json())
|
|
||||||
),
|
|
||||||
enabled: !!id,
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateProductor() {
|
export function useCreateProductor() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newProductor: ProductorCreate) => {
|
mutationFn: (newProductor: ProductorCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/productors`, {
|
return fetch(`${Config.backend_uri}/productors`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newProductor),
|
body: JSON.stringify(newProductor),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully created productor", {capfirst: true}),
|
message: t("successfully created productor", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['productors'] })
|
await queryClient.invalidateQueries({ queryKey: ["productors"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing productor`, {capfirst: true}),
|
message: error?.message || t(`error editing productor`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEditProductor() {
|
export function useEditProductor() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({productor, id}: ProductorEditPayload) => {
|
mutationFn: ({ productor, id }: ProductorEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/productors/${id}`, {
|
return fetch(`${Config.backend_uri}/productors/${id}`, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(productor),
|
body: JSON.stringify(productor),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully edited productor", {capfirst: true}),
|
message: t("successfully edited productor", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['productors'] })
|
await queryClient.invalidateQueries({ queryKey: ["productors"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing productor`, {capfirst: true}),
|
message: error?.message || t(`error editing productor`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteProductor() {
|
export function useDeleteProductor() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/productors/${id}`, {
|
return fetch(`${Config.backend_uri}/productors/${id}`, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully deleted productor", {capfirst: true}),
|
message: t("successfully deleted productor", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['productors'] })
|
await queryClient.invalidateQueries({ queryKey: ["productors"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error deleting productor`, {capfirst: true}),
|
message: error?.message || t(`error deleting productor`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetForm(id?: number, options?: Partial<DefinedInitialDataOptions<Form, Error, Form, readonly unknown[]>>) {
|
export function useGetForm(
|
||||||
|
id?: number,
|
||||||
|
options?: Partial<DefinedInitialDataOptions<Form, Error, Form, readonly unknown[]>>,
|
||||||
|
) {
|
||||||
return useQuery<Form>({
|
return useQuery<Form>({
|
||||||
queryKey: ['form'],
|
queryKey: ["form"],
|
||||||
queryFn: () => (
|
queryFn: () => fetch(`${Config.backend_uri}/forms/${id}`).then((res) => res.json()),
|
||||||
fetch(`${Config.backend_uri}/forms/${id}`)
|
enabled: !!id,
|
||||||
.then((res) => res.json())
|
|
||||||
),
|
|
||||||
enabled: !!id,
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], Error> {
|
export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], Error> {
|
||||||
const queryString = filters?.toString()
|
const queryString = filters?.toString();
|
||||||
return useQuery<Form[]>({
|
return useQuery<Form[]>({
|
||||||
queryKey: ['forms', queryString],
|
queryKey: ["forms", queryString],
|
||||||
queryFn: () => (
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`)
|
fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`).then((res) =>
|
||||||
.then((res) => res.json())
|
res.json(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateForm() {
|
export function useCreateForm() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newForm: FormCreate) => {
|
mutationFn: (newForm: FormCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/forms`, {
|
return fetch(`${Config.backend_uri}/forms`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newForm),
|
body: JSON.stringify(newForm),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await queryClient.invalidateQueries({ queryKey: ['forms'] })
|
await queryClient.invalidateQueries({ queryKey: ["forms"] });
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteForm() {
|
export function useDeleteForm() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/forms/${id}`, {
|
return fetch(`${Config.backend_uri}/forms/${id}`, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully deleted form", {capfirst: true}),
|
message: t("successfully deleted form", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['forms'] })
|
await queryClient.invalidateQueries({ queryKey: ["forms"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error deleting form`, {capfirst: true}),
|
message: error?.message || t(`error deleting form`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEditForm() {
|
export function useEditForm() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({id, form}: FormEditPayload) => {
|
mutationFn: ({ id, form }: FormEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/forms/${id}`, {
|
return fetch(`${Config.backend_uri}/forms/${id}`, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(form),
|
body: JSON.stringify(form),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully edited form", {capfirst: true}),
|
message: t("successfully edited form", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['forms'] })
|
await queryClient.invalidateQueries({ queryKey: ["forms"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing form`, {capfirst: true}),
|
message: error?.message || t(`error editing form`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetProduct(id?: number, options?: Partial<DefinedInitialDataOptions<Product, Error, Product, readonly unknown[]>>) {
|
export function useGetProduct(
|
||||||
|
id?: number,
|
||||||
|
options?: Partial<DefinedInitialDataOptions<Product, Error, Product, readonly unknown[]>>,
|
||||||
|
) {
|
||||||
return useQuery<Product>({
|
return useQuery<Product>({
|
||||||
queryKey: ['product'],
|
queryKey: ["product"],
|
||||||
queryFn: () => (
|
queryFn: () => fetch(`${Config.backend_uri}/products/${id}`).then((res) => res.json()),
|
||||||
fetch(`${Config.backend_uri}/products/${id}`)
|
enabled: !!id,
|
||||||
.then((res) => res.json())
|
|
||||||
),
|
|
||||||
enabled: !!id,
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetProducts(filters?: URLSearchParams): UseQueryResult<Product[], Error> {
|
export function useGetProducts(filters?: URLSearchParams): UseQueryResult<Product[], Error> {
|
||||||
const queryString = filters?.toString()
|
const queryString = filters?.toString();
|
||||||
return useQuery<Product[]>({
|
return useQuery<Product[]>({
|
||||||
queryKey: ['products', queryString],
|
queryKey: ["products", queryString],
|
||||||
queryFn: () => (
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/products${filters ? `?${queryString}` : ""}`)
|
fetch(`${Config.backend_uri}/products${filters ? `?${queryString}` : ""}`).then((res) =>
|
||||||
.then((res) => res.json())
|
res.json(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateProduct() {
|
export function useCreateProduct() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newProduct: ProductCreate) => {
|
mutationFn: (newProduct: ProductCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/products`, {
|
return fetch(`${Config.backend_uri}/products`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newProduct),
|
body: JSON.stringify(newProduct),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully created product", {capfirst: true}),
|
message: t("successfully created product", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['products'] })
|
await queryClient.invalidateQueries({ queryKey: ["products"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing product`, {capfirst: true}),
|
message: error?.message || t(`error editing product`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteProduct() {
|
export function useDeleteProduct() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/products/${id}`, {
|
return fetch(`${Config.backend_uri}/products/${id}`, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully deleted product", {capfirst: true}),
|
message: t("successfully deleted product", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['products'] })
|
await queryClient.invalidateQueries({ queryKey: ["products"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error deleting product`, {capfirst: true}),
|
message: error?.message || t(`error deleting product`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEditProduct() {
|
export function useEditProduct() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({id, product}: ProductEditPayload) => {
|
mutationFn: ({ id, product }: ProductEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/products/${id}`, {
|
return fetch(`${Config.backend_uri}/products/${id}`, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(product),
|
body: JSON.stringify(product),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully edited product", {capfirst: true}),
|
message: t("successfully edited product", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['products'] })
|
await queryClient.invalidateQueries({ queryKey: ["products"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing product`, {capfirst: true}),
|
message: error?.message || t(`error editing product`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetUser(id?: number, options?: Partial<DefinedInitialDataOptions<User, Error, User, readonly unknown[]>>) {
|
export function useGetUser(
|
||||||
|
id?: number,
|
||||||
|
options?: Partial<DefinedInitialDataOptions<User, Error, User, readonly unknown[]>>,
|
||||||
|
) {
|
||||||
return useQuery<User>({
|
return useQuery<User>({
|
||||||
queryKey: ['user'],
|
queryKey: ["user"],
|
||||||
queryFn: () => (
|
queryFn: () => fetch(`${Config.backend_uri}/users/${id}`).then((res) => res.json()),
|
||||||
fetch(`${Config.backend_uri}/users/${id}`)
|
enabled: !!id,
|
||||||
.then((res) => res.json())
|
|
||||||
),
|
|
||||||
enabled: !!id,
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetUsers(filters?: URLSearchParams): UseQueryResult<User[], Error> {
|
export function useGetUsers(filters?: URLSearchParams): UseQueryResult<User[], Error> {
|
||||||
const queryString = filters?.toString()
|
const queryString = filters?.toString();
|
||||||
return useQuery<User[]>({
|
return useQuery<User[]>({
|
||||||
queryKey: ['users', queryString],
|
queryKey: ["users", queryString],
|
||||||
queryFn: () => (
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/users${filters ? `?${queryString}` : ""}`)
|
fetch(`${Config.backend_uri}/users${filters ? `?${queryString}` : ""}`).then((res) =>
|
||||||
.then((res) => res.json())
|
res.json(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateUser() {
|
export function useCreateUser() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newUser: UserCreate) => {
|
mutationFn: (newUser: UserCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/users`, {
|
return fetch(`${Config.backend_uri}/users`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newUser),
|
body: JSON.stringify(newUser),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully created user", {capfirst: true}),
|
message: t("successfully created user", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['users'] })
|
await queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing user`, {capfirst: true}),
|
message: error?.message || t(`error editing user`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteUser() {
|
export function useDeleteUser() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/users/${id}`, {
|
return fetch(`${Config.backend_uri}/users/${id}`, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully deleted user", {capfirst: true}),
|
message: t("successfully deleted user", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['users'] })
|
await queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error deleting user`, {capfirst: true}),
|
message: error?.message || t(`error deleting user`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEditUser() {
|
export function useEditUser() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({id, user}: UserEditPayload) => {
|
mutationFn: ({ id, user }: UserEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/users/${id}`, {
|
return fetch(`${Config.backend_uri}/users/${id}`, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(user),
|
body: JSON.stringify(user),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("success", {capfirst: true}),
|
title: t("success", { capfirst: true }),
|
||||||
message: t("successfully edited user", {capfirst: true}),
|
message: t("successfully edited user", { capfirst: true }),
|
||||||
});
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['users'] })
|
await queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t("error", {capfirst: true}),
|
title: t("error", { capfirst: true }),
|
||||||
message: error?.message || t(`error editing user`, {capfirst: true}),
|
message: error?.message || t(`error editing user`, { capfirst: true }),
|
||||||
color: "red"
|
color: "red",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function useCreateContract() {
|
export function useCreateContract() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newContract: ContractCreate) => {
|
mutationFn: (newContract: ContractCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/contracts`, {
|
return fetch(`${Config.backend_uri}/contracts`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newContract),
|
body: JSON.stringify(newContract),
|
||||||
}).then(async (res) => await res.blob());
|
}).then(async (res) => await res.blob());
|
||||||
@@ -575,6 +584,6 @@ export function useCreateContract() {
|
|||||||
link.click();
|
link.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
await queryClient.invalidateQueries({ queryKey: ["contracts"] });
|
await queryClient.invalidateQueries({ queryKey: ["contracts"] });
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type ContractCreate = {
|
export type ContractCreate = {
|
||||||
form_id: number;
|
form_id: number;
|
||||||
contract: Record<string, string | number | null>;
|
contract: Record<string, string | number | null>;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
import type { Productor } from "@/services/resources/productors";
|
import type { Productor } from "@/services/resources/productors";
|
||||||
import type { Shipment } from "@/services/resources/shipments";
|
import type { Shipment } from "@/services/resources/shipments";
|
||||||
import type { User } from "@/services/resources/users";
|
import type { User } from "@/services/resources/users";
|
||||||
|
|
||||||
export type Form = {
|
export type Form = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
season: string;
|
season: string;
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
productor: Productor;
|
productor: Productor;
|
||||||
referer: User;
|
referer: User;
|
||||||
shipments: Shipment[];
|
shipments: Shipment[];
|
||||||
minimum_shipment_value: number | null;
|
minimum_shipment_value: number | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type FormCreate = {
|
export type FormCreate = {
|
||||||
name: string;
|
name: string;
|
||||||
season: string;
|
season: string;
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
productor_id: number;
|
productor_id: number;
|
||||||
referer_id: number;
|
referer_id: number;
|
||||||
minimum_shipment_value: number | null;
|
minimum_shipment_value: number | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type FormEdit = {
|
export type FormEdit = {
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
season?: string | null;
|
season?: string | null;
|
||||||
start?: string | null;
|
start?: string | null;
|
||||||
end?: string | null;
|
end?: string | null;
|
||||||
productor_id?: number | null;
|
productor_id?: number | null;
|
||||||
referer_id?: number | null;
|
referer_id?: number | null;
|
||||||
minimum_shipment_value: number | null;
|
minimum_shipment_value: number | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type FormEditPayload = {
|
export type FormEditPayload = {
|
||||||
id: number;
|
id: number;
|
||||||
form: FormEdit;
|
form: FormEdit;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type FormInputs = {
|
export type FormInputs = {
|
||||||
name: string;
|
name: string;
|
||||||
season: string;
|
season: string;
|
||||||
start: string | null;
|
start: string | null;
|
||||||
end: string | null;
|
end: string | null;
|
||||||
productor_id: string;
|
productor_id: string;
|
||||||
referer_id: string;
|
referer_id: string;
|
||||||
minimum_shipment_value: number | string | null;
|
minimum_shipment_value: number | string | null;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import type { Product } from "./products";
|
import type { Product } from "./products";
|
||||||
|
|
||||||
export const PaymentMethods = [
|
export const PaymentMethods = [
|
||||||
{value: "cheque", label: t("cheque", {capfirst: true})},
|
{ value: "cheque", label: t("cheque", { capfirst: true }) },
|
||||||
{value: "transfer", label: t("transfer", {capfirst: true})},
|
{ value: "transfer", label: t("transfer", { capfirst: true }) },
|
||||||
]
|
];
|
||||||
|
|
||||||
export type PaymentMethod = {
|
export type PaymentMethod = {
|
||||||
name: string;
|
name: string;
|
||||||
details: string;
|
details: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Productor = {
|
export type Productor = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
payment_methods: PaymentMethod[];
|
payment_methods: PaymentMethod[];
|
||||||
type: string;
|
type: string;
|
||||||
products: Product[]
|
products: Product[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProductorCreate = {
|
export type ProductorCreate = {
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
payment_methods: PaymentMethod[];
|
payment_methods: PaymentMethod[];
|
||||||
type: string;
|
type: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProductorEdit = {
|
export type ProductorEdit = {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
address: string | null;
|
address: string | null;
|
||||||
payment_methods: PaymentMethod[];
|
payment_methods: PaymentMethod[];
|
||||||
type: string | null;
|
type: string | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProductorInputs = {
|
export type ProductorInputs = {
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
type: string;
|
type: string;
|
||||||
payment_methods: PaymentMethod[];
|
payment_methods: PaymentMethod[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProductorEditPayload = {
|
export type ProductorEditPayload = {
|
||||||
productor: ProductorEdit;
|
productor: ProductorEdit;
|
||||||
id: number;
|
id: number;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,100 +1,106 @@
|
|||||||
import type { Productor } from "@/services/resources/productors";
|
import type { Productor } from "@/services/resources/productors";
|
||||||
import type { Shipment } from "@/services/resources/shipments";
|
import type { Shipment } from "@/services/resources/shipments";
|
||||||
|
|
||||||
type ProductTypeKey = "1" | "2";
|
type ProductTypeKey = "1" | "2";
|
||||||
type ProductUnitKey = "1" | "2" | "3";
|
type ProductUnitKey = "1" | "2" | "3";
|
||||||
|
|
||||||
export const ProductType = {
|
export const ProductType = {
|
||||||
"1": "planned",
|
"1": "planned",
|
||||||
"2": "recurrent",
|
"2": "recurrent",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProductUnit = {
|
export const ProductUnit = {
|
||||||
"1": "grams",
|
"1": "grams",
|
||||||
"2": "kilo",
|
"2": "kilo",
|
||||||
"3": "piece",
|
"3": "piece",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProductQuantityUnit = {
|
export const ProductQuantityUnit = {
|
||||||
"ml": "mililiter",
|
ml: "mililiter",
|
||||||
"L": "liter",
|
L: "liter",
|
||||||
"g": "grams",
|
g: "grams",
|
||||||
"kg": "kilo"
|
kg: "kilo",
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Product = {
|
export type Product = {
|
||||||
id: number;
|
id: number;
|
||||||
productor: Productor;
|
productor: Productor;
|
||||||
name: string;
|
name: string;
|
||||||
unit: ProductUnitKey;
|
unit: ProductUnitKey;
|
||||||
price: number | null;
|
price: number | null;
|
||||||
price_kg: number | null;
|
price_kg: number | null;
|
||||||
quantity: number | null;
|
quantity: number | null;
|
||||||
quantity_unit: string | null;
|
quantity_unit: string | null;
|
||||||
type: ProductTypeKey;
|
type: ProductTypeKey;
|
||||||
shipments: Shipment[];
|
shipments: Shipment[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProductCreate = {
|
export type ProductCreate = {
|
||||||
productor_id: number;
|
productor_id: number;
|
||||||
name: string;
|
name: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
price: number | null;
|
price: number | null;
|
||||||
price_kg: number | null;
|
price_kg: number | null;
|
||||||
quantity: number | null;
|
quantity: number | null;
|
||||||
quantity_unit: string | null;
|
quantity_unit: string | null;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProductEdit = {
|
export type ProductEdit = {
|
||||||
productor_id: number | null;
|
productor_id: number | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
unit: string | null;
|
unit: string | null;
|
||||||
price: number | null;
|
price: number | null;
|
||||||
price_kg: number | null;
|
price_kg: number | null;
|
||||||
quantity: number | null;
|
quantity: number | null;
|
||||||
quantity_unit: string | null;
|
quantity_unit: string | null;
|
||||||
type: string | null;
|
type: string | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProductInputs = {
|
export type ProductInputs = {
|
||||||
productor_id: string | null;
|
productor_id: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
unit: string | null;
|
unit: string | null;
|
||||||
price: number | string | null;
|
price: number | string | null;
|
||||||
price_kg: number | string | null;
|
price_kg: number | string | null;
|
||||||
quantity: number | string | null;
|
quantity: number | string | null;
|
||||||
quantity_unit: string | null;
|
quantity_unit: string | null;
|
||||||
type: string | null;
|
type: string | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProductEditPayload = {
|
export type ProductEditPayload = {
|
||||||
product: ProductEdit;
|
product: ProductEdit;
|
||||||
id: number;
|
id: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function productToProductInputs(product: Product): ProductInputs {
|
export function productToProductInputs(product: Product): ProductInputs {
|
||||||
return {
|
return {
|
||||||
productor_id: String(product.productor.id),
|
productor_id: String(product.productor.id),
|
||||||
name: product.name,
|
name: product.name,
|
||||||
unit: product.unit,
|
unit: product.unit,
|
||||||
price: product.price,
|
price: product.price,
|
||||||
price_kg: product.price_kg,
|
price_kg: product.price_kg,
|
||||||
quantity: product.quantity,
|
quantity: product.quantity,
|
||||||
quantity_unit: product.quantity_unit,
|
quantity_unit: product.quantity_unit,
|
||||||
type: product.type,
|
type: product.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function productCreateFromProductInputs(productInput: ProductInputs): ProductCreate {
|
export function productCreateFromProductInputs(productInput: ProductInputs): ProductCreate {
|
||||||
return {
|
return {
|
||||||
productor_id: Number(productInput.productor_id)!,
|
productor_id: Number(productInput.productor_id)!,
|
||||||
name: productInput.name,
|
name: productInput.name,
|
||||||
unit: productInput.unit!,
|
unit: productInput.unit!,
|
||||||
price: productInput.price === "" || !productInput.price ? null : Number(productInput.price),
|
price: productInput.price === "" || !productInput.price ? null : Number(productInput.price),
|
||||||
price_kg: productInput.price_kg === "" || !productInput.price_kg ? null : Number(productInput.price_kg),
|
price_kg:
|
||||||
quantity: productInput.quantity === "" || !productInput.quantity ? null : Number(productInput.quantity),
|
productInput.price_kg === "" || !productInput.price_kg
|
||||||
quantity_unit: productInput.quantity_unit,
|
? null
|
||||||
type: productInput.type!,
|
: Number(productInput.price_kg),
|
||||||
}
|
quantity:
|
||||||
}
|
productInput.quantity === "" || !productInput.quantity
|
||||||
|
? null
|
||||||
|
: Number(productInput.quantity),
|
||||||
|
quantity_unit: productInput.quantity_unit,
|
||||||
|
type: productInput.type!,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,54 +1,54 @@
|
|||||||
import type { Form } from "./forms";
|
import type { Form } from "./forms";
|
||||||
import type { Product } from "./products";
|
import type { Product } from "./products";
|
||||||
|
|
||||||
export type Shipment = {
|
export type Shipment = {
|
||||||
name: string;
|
name: string;
|
||||||
date: string;
|
date: string;
|
||||||
id: number;
|
id: number;
|
||||||
form: Form;
|
form: Form;
|
||||||
form_id: number;
|
form_id: number;
|
||||||
products: Product[];
|
products: Product[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ShipmentCreate = {
|
export type ShipmentCreate = {
|
||||||
name: string;
|
name: string;
|
||||||
date: string;
|
date: string;
|
||||||
form_id: number;
|
form_id: number;
|
||||||
product_ids: number[];
|
product_ids: number[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ShipmentEdit = {
|
export type ShipmentEdit = {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
date: string | null;
|
date: string | null;
|
||||||
form_id: number | null;
|
form_id: number | null;
|
||||||
product_ids: number[];
|
product_ids: number[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ShipmentEditPayload = {
|
export type ShipmentEditPayload = {
|
||||||
id: number;
|
id: number;
|
||||||
shipment: ShipmentEdit;
|
shipment: ShipmentEdit;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ShipmentInputs = {
|
export type ShipmentInputs = {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
date: string | null;
|
date: string | null;
|
||||||
form_id: string | null;
|
form_id: string | null;
|
||||||
product_ids: string[];
|
product_ids: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export function shipmentToShipmentInputs(shipment: Shipment): ShipmentInputs {
|
export function shipmentToShipmentInputs(shipment: Shipment): ShipmentInputs {
|
||||||
return {
|
return {
|
||||||
...shipment,
|
...shipment,
|
||||||
form_id: String(shipment.form_id),
|
form_id: String(shipment.form_id),
|
||||||
product_ids: shipment.products.map((el) => (String(el.id)))
|
product_ids: shipment.products.map((el) => String(el.id)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shipmentCreateFromShipmentInputs(shipmentInput: ShipmentInputs): ShipmentCreate {
|
export function shipmentCreateFromShipmentInputs(shipmentInput: ShipmentInputs): ShipmentCreate {
|
||||||
return {
|
return {
|
||||||
name: shipmentInput.name!,
|
name: shipmentInput.name!,
|
||||||
date: shipmentInput.date!,
|
date: shipmentInput.date!,
|
||||||
form_id: Number(shipmentInput.form_id),
|
form_id: Number(shipmentInput.form_id),
|
||||||
product_ids: shipmentInput.product_ids.map(el => (Number(el))),
|
product_ids: shipmentInput.product_ids.map((el) => Number(el)),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import type { Product } from "@/services/resources/products";
|
import type { Product } from "@/services/resources/products";
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
products: Product[];
|
products: Product[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type UserInputs = {
|
export type UserInputs = {
|
||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type UserCreate ={
|
export type UserCreate = {
|
||||||
email: string | null;
|
email: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type UserEdit ={
|
export type UserEdit = {
|
||||||
email: string | null;
|
email: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type UserEditPayload = {
|
export type UserEditPayload = {
|
||||||
user: UserEdit;
|
user: UserEdit;
|
||||||
id: number;
|
id: number;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createTheme } from '@mantine/core';
|
import { createTheme } from "@mantine/core";
|
||||||
|
|
||||||
export const theme = createTheme({
|
export const theme = createTheme({
|
||||||
/** Put your mantine theme override here */
|
/** Put your mantine theme override here */
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"types": ["vite/client"],
|
"types": ["vite/client"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true,
|
"noUncheckedSideEffectImports": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
{
|
{
|
||||||
"files": [],
|
"files": [],
|
||||||
"references": [
|
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
||||||
{ "path": "./tsconfig.app.json" },
|
|
||||||
{ "path": "./tsconfig.node.json" }
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
"target": "ES2023",
|
"target": "ES2023",
|
||||||
"lib": ["ES2023"],
|
"lib": ["ES2023"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'src'),
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
server: {
|
||||||
server: {
|
watch: {
|
||||||
watch: {
|
usePolling: true,
|
||||||
usePolling: true,
|
},
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
})
|
|
||||||
|
|||||||
Reference in New Issue
Block a user