--- /dev/null
+# These are some examples of commonly ignored file patterns.
+# You should customize this list as applicable to your project.
+# Learn more about .gitignore:
+# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
+
+# Node artifact files
+node_modules/
+dist/
+build/
+
+# Compiled Java class files
+*.class
+
+# Compiled Python bytecode
+*.py[cod]
+
+# Log files
+*.log
+
+# Package files
+*.jar
+
+# Maven
+target/
+dist/
+
+# JetBrains IDE
+.idea/
+
+# Unit test reports
+TEST*.xml
+
+# Generated by MacOS
+.DS_Store
+
+# Generated by Windows
+Thumbs.db
+
+# Applications
+*.app
+*.exe
+*.war
+
+# Large media files
+*.mp4
+*.tiff
+*.avi
+*.flv
+*.mov
+*.wmv
+
+html/resources/css/
+html/resources/js/
+package-lock.json
--- /dev/null
+{
+ "bracketSpacing": true,
+ "printWidth": 140,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "tabWidth": 2,
+ "useTabs": false
+}
--- /dev/null
+{
+}
\ No newline at end of file
--- /dev/null
+# Come funziona
+
+Per utilizzare questo archetipo come base per i tuoi progetti segui i seguenti passaggi:
+
+- crea un fork del progetto
+- esegui `nvm use` per impostare la versione di node.js corretta
+- esegui `npm install` per installare le dipendenze
+- esegui `npm start` per avviare il server di sviluppo
+- **divertiti**
--- /dev/null
+image: google/cloud-sdk:412.0.0-slim
+
+definitions:
+ steps:
+ - step: &build
+ image: node:latest
+ name: Build
+ caches:
+ - node
+ script:
+ - source scripts/set-env.sh
+ - export VERSION="${REVISION}.${BITBUCKET_BUILD_NUMBER}"
+ - 'echo "{ \"version\": \"${VERSION}\" }" > ./src/build.json'
+ - npm install
+ - npm run build
+ artifacts:
+ - build/**
+ - step: &publish
+ name: Publish
+ trigger: automatic
+ services:
+ - docker
+ script:
+ - source scripts/set-env.sh
+ - export MODULE=web
+ - export VERSION="${REVISION}.${BITBUCKET_BUILD_NUMBER}"
+ - export IMAGE_BASE="eu.gcr.io/${PROJECT_ID}/${NAMESPACE}-${MODULE}"
+ - export IMAGE="${IMAGE_BASE}:${VERSION}"
+ - cat scripts/service-account.json.base64 | base64 --decode >> build/service-account.json
+ - gcloud auth activate-service-account --key-file build/service-account.json
+ - gcloud config set project ${PROJECT_ID}
+ - gcloud config set compute/zone europe-west1-b
+ - gcloud auth configure-docker
+ - docker build -t ${IMAGE} -f ./scripts/docker/Dockerfile .
+ - docker push ${IMAGE}
+ - docker tag $IMAGE "${IMAGE_BASE}:latest"
+ - docker push "${IMAGE_BASE}:latest"
+ - git tag -a "${VERSION}" -m "version ${VERSION}"
+ - git push origin "${VERSION}"
+ artifacts:
+ - build/**
+ - step: &deploy
+ name: Deploy
+ script:
+ - source scripts/set-env.sh
+ - gcloud auth activate-service-account --key-file build/service-account.json
+ - rm build/service-account.json
+ - gsutil -m cp -r build/* ${BUCKET}
+pipelines:
+ branches:
+ develop:
+ - step: *build
+ - step: *publish
+ - step: *deploy
+ master:
+ - step: *build
+ - step: *publish
\ No newline at end of file
--- /dev/null
+const webpack = require('webpack');
+const WorkBoxPlugin = require('workbox-webpack-plugin');
+
+module.exports = function override(config) {
+ config.resolve.fallback = {
+ process: require.resolve('process/browser'),
+ // zlib: require.resolve('browserify-zlib'),
+ stream: require.resolve('stream-browserify'),
+ crypto: require.resolve('crypto-browserify'),
+ util: require.resolve('util'),
+ buffer: require.resolve('buffer')
+ // asset: require.resolve('assert')
+ };
+
+ // https://stackoverflow.com/questions/69135310/workaround-for-cache-size-limit-in-create-react-app-pwa-service-worker
+ config.plugins.forEach((plugin) => {
+ if (plugin instanceof WorkBoxPlugin.InjectManifest) {
+ plugin.config.maximumFileSizeToCacheInBytes = 50 * 1024 * 1024;
+ }
+ });
+
+ config.plugins = [
+ ...config.plugins,
+ new webpack.ProvidePlugin({
+ process: 'process/browser.js',
+ Buffer: ['buffer', 'Buffer']
+ })
+ ];
+
+ return config;
+};
--- /dev/null
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "commonjs",
+ "baseUrl": "src"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+}
--- /dev/null
+{
+ "name": "edera",
+ "description": "Manufacturing Execution System - Applica Software Guru",
+ "version": "1.0.0",
+ "private": true,
+ "dependencies": {
+ "@ant-design/icons": "file:../../modules/react-admin/node_modules/@ant-design/icons",
+ "@applica-software-guru/crud-client": "^1.1",
+ "@applica-software-guru/iam-client": "^1.1",
+ "@applica-software-guru/react-admin": "file:../../modules/react-admin",
+ "highcharts": "^11.2.0",
+ "highcharts-react-official": "^3.2.1",
+ "@mui/icons-material": "file:../../modules/react-admin/node_modules/@mui/icons-material",
+ "@mui/material": "file:../../modules/react-admin/node_modules/@mui/material",
+ "@react-pdf/renderer": "^3.1.12",
+ "dayjs": "^1.11.10",
+ "react": "file:../../modules/react-admin/node_modules/react",
+ "react-admin": "file:../../modules/react-admin/node_modules/react-admin",
+ "react-app-rewired": "^2.2.1",
+ "react-dom": "file:../../modules/react-admin/node_modules/react-dom",
+ "react-hook-form": "file:../../modules/react-admin/node_modules/react-hook-form",
+ "react-router": "file:../../modules/react-admin/node_modules/react-router",
+ "react-router-dom": "file:../../modules/react-admin/node_modules/react-router-dom",
+ "react-scripts": "^5.0.1",
+ "react-sticky-box": "file:../../modules/react-admin/node_modules/react-sticky-box",
+ "react-query": "file:../../modules/react-admin/node_modules/react-query",
+ "react-apexcharts": "^1.4.1",
+ "web-vitals": "^3.0.3",
+ "apexcharts": "^3.44.0",
+ "qrcode": "^1.5.3"
+ },
+ "scripts": {
+ "start": "react-app-rewired start",
+ "build": "react-app-rewired build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "babel": {
+ "presets": [
+ "@babel/preset-react"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "resolutions": {
+ "@svgr/webpack": "6.4.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.19.3",
+ "@babel/eslint-parser": "^7.19.1",
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "buffer": "^6.0.3",
+ "crypto-browserify": "^3.12.0",
+ "eslint": "^8.25.0",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-config-react-app": "7.0.1",
+ "eslint-plugin-flowtype": "^8.0.3",
+ "eslint-plugin-import": "^2.26.0",
+ "eslint-plugin-jsx-a11y": "^6.6.1",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-react": "^7.31.10",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "prettier": "^2.7.1",
+ "process": "^0.11.10",
+ "react-error-overlay": "6.0.9",
+ "stream-browserify": "^3.0.0"
+ }
+}
--- /dev/null
+{
+ "name": "web",
+ "description": "Web Template - Applica Software Guru",
+ "version": "1.0.0",
+ "private": true,
+ "dependencies": {
+ "@applica-software-guru/crud-client": "^1.1",
+ "@applica-software-guru/iam-client": "^1.1",
+ "@applica-software-guru/react-admin": "^1.4",
+ "@react-pdf/renderer": "^3.1.14",
+ "apexcharts": "^3.44.0",
+ "highcharts": "^11.2.0",
+ "highcharts-react-official": "^3.2.1",
+ "qrcode": "^1.5.3",
+ "react": "^18.2.0",
+ "react-apexcharts": "^1.4.1",
+ "react-app-rewired": "^2.2.1",
+ "react-dom": "^18.2.0",
+ "react-scripts": "^5.0.1",
+ "web-vitals": "^3.0.3"
+ },
+ "scripts": {
+ "start": "react-app-rewired start",
+ "build": "react-app-rewired build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": ["react-app", "react-app/jest"]
+ },
+ "babel": {
+ "presets": ["@babel/preset-react"]
+ },
+ "browserslist": {
+ "production": [">0.2%", "not dead", "not op_mini all"],
+ "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
+ },
+ "resolutions": {
+ "@svgr/webpack": "6.4.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.19.3",
+ "@babel/eslint-parser": "^7.19.1",
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "buffer": "^6.0.3",
+ "crypto-browserify": "^3.12.0",
+ "eslint": "^8.25.0",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-config-react-app": "7.0.1",
+ "eslint-plugin-flowtype": "^8.0.3",
+ "eslint-plugin-import": "^2.26.0",
+ "eslint-plugin-jsx-a11y": "^6.6.1",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-react": "^7.31.10",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "prettier": "^2.7.1",
+ "process": "^0.11.10",
+ "react-error-overlay": "6.0.9",
+ "stream-browserify": "^3.0.0"
+ }
+}
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="theme-color" content="#ffffff" />
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
+ <meta name="description" content="Edera" />
+ <link href="%PUBLIC_URL%/favicon-light.png" rel="apple-touch-icon" media="(prefers-color-scheme: light)" />
+ <link href="%PUBLIC_URL%/favicon-dark.png" rel="apple-touch-icon" media="(prefers-color-scheme: dark)" />
+ <link href="%PUBLIC_URL%/favicon-light.png" rel="icon" media="(prefers-color-scheme: light)" />
+ <link href="%PUBLIC_URL%/favicon-dark.png" rel="icon" media="(prefers-color-scheme: dark)" />
+ <!--
+ Notice the use of %PUBLIC_URL% in the tags above.
+ It will be replaced with the URL of the `public` folder during the build.
+ Only files inside the `public` folder can be referenced from the HTML.
+
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+ work correctly both with client-side routing and a non-root public URL.
+ Learn how to configure a non-root public URL by running `npm run build`.
+ -->
+ <title>Edera</title>
+
+ <link rel="preconnect" href="https://fonts.gstatic.com" />
+ <link
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap&family=Public+Sans:wght@400;500;600;700"
+ rel="stylesheet"
+ />
+
+ <!-- this is to resolve issue in old safari browser in tablet -->
+ <script src="https://cdn.jsdelivr.net/npm/resize-observer-polyfill@1.5.1/dist/ResizeObserver.min.js"></script>
+ </head>
+ <body>
+ <noscript>You need to enable JavaScript to run this app.</noscript>
+ <div id="root"></div>
+ <!--
+ This HTML file is a template.
+ If you open it directly in the browser, you will see an empty page.
+
+ You can add webfonts, meta tags, or analytics to this file.
+ The build step will place the bundled scripts into the <body> tag.
+
+ To begin the development, run `npm start` or `yarn start`.
+ To create a production bundle, use `npm run build` or `yarn build`.
+ -->
+ </body>
+</html>
--- /dev/null
+FROM httpd:2
+ARG PORT=80
+
+EXPOSE $PORT
+
+ADD ./build/ /usr/local/apache2/htdocs/
\ No newline at end of file
--- /dev/null
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: app-ingress-web
+ annotations:
+ cert-manager.io/issuer: letsencrypt
+ nginx.ingress.kubernetes.io/use-regex: 'true'
+ nginx.ingress.kubernetes.io/rewrite-target: /edera-web/$1
+ nginx.ingress.kubernetes.io/backend-protocol: 'HTTPS'
+ nginx.ingress.kubernetes.io/upstream-vhost: 'storage.googleapis.com'
+ kubernetes.io/ingress.class: 'nginx'
+ kubernetes.io/ingress.allow-http: 'true'
+ nginx.ingress.kubernetes.io/server-snippet: |
+ location ~ ^/$ {
+ rewrite ^.*$ /index.html last;
+ }
+spec:
+ ingressClassName: nginx
+ tls:
+ - hosts:
+ - edera.applica.guru
+ secretName: letsencrypt
+ rules:
+ - host: edera.applica.guru
+ http:
+ paths:
+ - path: /(?!api)(.*)
+ pathType: ImplementationSpecific
+ backend:
+ service:
+ name: edera-bucket
+ port:
+ number: 443
\ No newline at end of file
--- /dev/null
+apiVersion: v1
+kind: Service
+metadata:
+ name: edera-bucket
+spec:
+ type: ExternalName
+ externalName: storage.googleapis.com
--- /dev/null
+ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAiYXBwbGljYS1n
+ZW5lcmFsIiwKICAicHJpdmF0ZV9rZXlfaWQiOiAiNDk3YzZmNTU5N2Y0NTI3NWY3ZTlmZTQwMzFj
+NGI4MjllMjY4NzEyMSIsCiAgInByaXZhdGVfa2V5IjogIi0tLS0tQkVHSU4gUFJJVkFURSBLRVkt
+LS0tLVxuTUlJRXZnSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2d3Z2dTa0FnRUFBb0lCQVFD
+dDRrY1B3UVJ6bUQ3b1xucHlVQVBlTnBnS01TWGxTM25CYmVtSUFlcmk3dm5GeHJIMFV4RlJndmRN
+VTlzRTJ6L0h5WXNTMGNnR1R3Y01Fd1xuQlVVYVF3UWpUMFVxdlBrN3ByY2lOWURSTXVNQXY1MW5Y
+WkpRZ0RYeitMSG15VldhaE1VRTlMczM4YzJxeUFpalxuVTBjakhqTmxsMmp3S2J5OWJVNUVUcWlT
+eEtxdE1FRVlwSUFzV2kvWG1iekx6NlVtSnladm56ZCtkRFNRQTU3aFxuY0NsOEFTdlBpQTdIeXVz
+b1lzVWJ2S2xRS0tmcndtV3NBUllEVjdRc0t6bEpsR0ViWE1sWlozdzB1VCszVkxXOVxuTWMyS1Fo
+SGpvaWpoTW9yRjdVdjVWMjJrTWkyL2V4RkI3L3ErbllCejU5K1MxUXZlRmpCd3E5L0gwQTRyTXpU
+a1xubWZDYzhjMWZBZ01CQUFFQ2dnRUFOR1dsdlRrUUl0Y2pTYzhvSnJEL2lLaXpPeE5DMnd0Rmx2
+RUVWbnB0ZVZXNFxuUWExc0Y3VEFFM2pRQU4xU0pPVDJGTHI3R1lZVkpLRU5qZTlnbWQvTTdPanpz
+a084cEwyQm5PVGJldTZuR2ZBalxudWVTbjlPc1ZsdjEvZWtoOEs3Skxma2xTNnpKSm8rZGdOdnNl
+eWhYTkxoVllrVm82WGlpRWQ2L3VPei9aSUpPVVxuTFlHN1hPVHExNWhKcmZaM2trdDBBRldJc2NK
+SXVFbFBlUTJOaDVWY3VSeVJwUVQwaFA0NUlRUTZFdElBWXp4OVxuUnRGUWFhd2tpWVFqZ0RkelFO
+Qm1tUHN2ZWc4Y2JkQXlWZVlxcitaVkFiNk1pWjJ2N01RR0hyeEFBcnhSeXRMZlxuMmd2VUl0K3pv
+dmJBemo3Q3h0YjUvS3JDdHUwMC9aWTcwNzRxTExnR1NRS0JnUUR2NXRkNmxWbUg5cXl3TThTc1xu
+NnA5NE5lTVFrbU9vME41SjBUUisxTGFITnBkdjBaY3B0bEJDS0lCTXorVTBBc0p0cG11dlpGTGdH
+cmp6U3E2cFxuVFlGUis0Mlc1NjJGMkoxWmlKZmdnWDdubWp3TmhUNzdrdWUwbjVWbE1iTHBUN0Iz
+ajB6V3Ftc0hqZ09lZWhDblxueEIwOXpKcXJEYm96MkV3UGVvRkhDVjB5dHdLQmdRQzVqVmpJd21F
+d01PU2g5UHF3QzJ5SVBYeS81UkpGTG4yL1xuNWxzUmhRbkkrTkxTZUJjWnRIV1BiOWcyNVF6SFVV
+dWRRNGhiYWpHaDM1R2t5SGtIcjJ5MGVjdTFUd2pVTitGMFxubzdhejRzekk4a3kzRk9sRUpvdXM4
+enFNQWsrYnhFR1hrUUNkWmFiL0ltS0svR1dRK21RcXlDV0RtUkFvR3NVcVxuTEJNdUhKOXltUUtC
+Z0YrRnJBRGNYT1RkWEk5Z1hZeDRjM3piQUFtR01IWjBqRDRhTmV2V2FNTllBbDU4dHRMZVxuREFE
+N3ZYSllTU3czZVJGTjlZekZ4cFlETGVkNXNpZ3BlemVZa1Iwb0xKaWgwcTFtelFxUXBXWTBySHE1
+dG9WWFxuVGpsR1hhY0liZk9tVGw2Y3lYeWtLSysrWlVTQjJBWGsrYnUwcjFVeXh4U0RxRzExV3Vw
+ZEdTWHJBb0dCQUo2YVxucm9CMGZvU2wxbGlGd2Q3RzlRK0RsMldqMWJraTQwUXNFRDNxZlJHM2R1
+V0cxeUFXdThKT3RQOC9UR3YzRm00blxuc3ArSkowR1ppN0hSMW5wMlBiSUt4ZENGN1NNUlhQckpr
+YnN6cXg0ODFzeEw2SlJqYWxMOFdWZ2lCWkE4OG1BdlxuQnRxRGNIcDNGc3A4c2doNXJ6Tk9mNXA4
+Tkc1RGE3TC9sNmw3dCtOSkFvR0JBT2d6MHM1WGErRzZLTmlVN0IySFxuc0l2MmRNMzZvNlZ3a21P
+T3FjdCtkVS8ycG12ZGM4aFpDbFZGYXBQeWZ4djFqVnNjSDUrZG5FaFJIaTEzYXdIU1xuVzc3MnQ5
+WUdBNWpGb2dnakVPRU5QbVR2QUwxd01NL0ExRCtmanVaWVArOEVDaEU0QmFmbVN3WWZGVzJRQmxE
+eFxuZlhoOHNmT0FnMjhuMEc1QWNGcXZVSFJNXG4tLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tXG4i
+LAogICJjbGllbnRfZW1haWwiOiAiYml0YnVja2V0LXBpcGVsaW5lc0BhcHBsaWNhLWdlbmVyYWwu
+aWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAogICJjbGllbnRfaWQiOiAiMTA4Mzk0ODE1Njc3NDgx
+NDI2MjYwIiwKICAiYXV0aF91cmkiOiAiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1
+dGgyL2F1dGgiLAogICJ0b2tlbl91cmkiOiAiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20v
+dG9rZW4iLAogICJhdXRoX3Byb3ZpZGVyX3g1MDlfY2VydF91cmwiOiAiaHR0cHM6Ly93d3cuZ29v
+Z2xlYXBpcy5jb20vb2F1dGgyL3YxL2NlcnRzIiwKICAiY2xpZW50X3g1MDlfY2VydF91cmwiOiAi
+aHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9iaXRidWNr
+ZXQtcGlwZWxpbmVzJTQwYXBwbGljYS1nZW5lcmFsLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwK
+ICAidW5pdmVyc2VfZG9tYWluIjogImdvb2dsZWFwaXMuY29tIgp9Cg==
\ No newline at end of file
--- /dev/null
+export PROJECT_ID=applica-general
+export PROJECT_NAME=applica-general
+export PROFILE=production
+export NAMESPACE=edera
+export REVISION=1.0
+export BUCKET=gs://edera-web
\ No newline at end of file
--- /dev/null
+import '@applica-software-guru/react-admin/style.css';
+
+import * as entities from './entities';
+
+import { API_URL, APP_NAME, COPY } from './config';
+import { ActivatePage, ApplicaAdmin, HttpError, RecoverPage, RegisterPage, Resource } from '@applica-software-guru/react-admin';
+import { CustomPage, RFIDDeviceTrackPage, RedirectPage } from 'components/pages';
+import { createAttachmentsParser, createDataProvider } from '@applica-software-guru/crud-client';
+
+import { CustomRoutes } from 'ra-core';
+import { Route } from 'react-router-dom';
+import build from './build.json';
+import { createAuthProvider } from '@applica-software-guru/iam-client';
+import dayjs from 'dayjs';
+import duration from 'dayjs/plugin/duration';
+import menu from './menu';
+import relativeTime from 'dayjs/plugin/relativeTime';
+import theme from './theme';
+
+dayjs.extend(duration);
+dayjs.extend(relativeTime);
+
+const authProvider = createAuthProvider({ apiUrl: API_URL });
+const dataProvider = createDataProvider({
+ apiUrl: API_URL,
+ getHeaders: () => authProvider.getHeaders(),
+ getToken: () => authProvider.getToken(),
+ attachmentsParser: createAttachmentsParser(),
+ HttpErrorClass: HttpError
+});
+const App = () => {
+ return (
+ <ApplicaAdmin
+ theme={theme}
+ apiUrl={API_URL}
+ authProvider={authProvider}
+ dataProvider={dataProvider}
+ defaultLocale="it"
+ menu={menu}
+ name={APP_NAME}
+ copy={COPY}
+ dashboard={RedirectPage}
+ version={build.version}
+ enablePasswordRecover
+ enableNotification
+ >
+ <Resource name="entities/notification" {...entities.notification} />
+ <Resource name="entities/user" {...entities.user} />
+ <Resource name="entities/i18n-message" {...entities.i18nMessage} />
+ <Resource name="entities/device" {...entities.device} />
+ <Resource name="entities/audit-log" {...entities.auditLog} />
+ <Resource name="entities/rfid-device" {...entities.rfidDevice} />
+ <Resource name="entities/customer" {...entities.customer} />
+ <Resource name="entities/activity-type" {...entities.activityType} />
+ <Resource name="entities/supplier" {...entities.supplier} />
+ <Resource name="entities/equipment-type" {...entities.equipmentType} />
+ <Resource name="entities/price-list" {...entities.priceList} />
+ <Resource name="entities/protocol" {...entities.protocol} />
+ <Resource name="entities/equipment" {...entities.equipment} />
+ <Resource name="entities/supply" {...entities.supply} />
+ <Resource name="entities/activity" {...entities.activity} />
+ <Resource name="entities/maintenance" {...entities.maintenance} />
+ <Resource name="entities/rfid-device-track" {...entities.rfidDeviceTrack} />
+ <Resource name="entities/docx-template" {...entities.docxTemplate} />
+ <CustomRoutes>
+ <Route path="/custom-page" element={<CustomPage />} />
+ <Route path="/position" element={<RFIDDeviceTrackPage />} />
+ </CustomRoutes>
+ <CustomRoutes noLayout>
+ <Route path="/register" element={<RegisterPage name={APP_NAME} copy={COPY} version={build.version} />} />
+ <Route path="/recover" element={<RecoverPage name={APP_NAME} copy={COPY} version={build.version} />} />
+ <Route path="/activate/:token" element={<ActivatePage name={APP_NAME} copy={COPY} version={build.version} />} />
+ </CustomRoutes>
+ <Resource name="entities/city" />
+ </ApplicaAdmin>
+ );
+};
+
+export default App;
--- /dev/null
+{ "version": "0.0.0" }
--- /dev/null
+export * from './ra-lists';
+export * from './ra-inputs';
+export * from './ra-fields';
+export * from './ra-forms';
+export * from './ra-buttons';
+export * from './ra-details';
--- /dev/null
+import { MainCard } from '@applica-software-guru/react-admin';
+
+const FixedMainCard = ({ children, ...props }) => (
+ <MainCard
+ {...props}
+ sx={{
+ '&.MuiToolbar-root': {
+ border: 0
+ }
+ }}
+ content={true}
+ >
+ {children}
+ </MainCard>
+);
+
+export default FixedMainCard;
--- /dev/null
+export { default as FixedMainCard } from './FixedMainCard';
--- /dev/null
+import { Box, Typography } from '@mui/material';
+
+import { useTranslate } from '@applica-software-guru/react-admin';
+
+const CustomPage = () => {
+ const translate = useTranslate();
+ return (
+ <Box>
+ <Typography variant="h1">{translate('ra.custom_pages.welcome.title')}</Typography>
+ <Typography variant="body1">{translate('ra.custom_pages.welcome.subtitle')}</Typography>
+ </Box>
+ );
+};
+
+export default CustomPage;
--- /dev/null
+import { Stack } from '@mui/material';
+import { useThemeConfig } from '@applica-software-guru/react-admin';
+import RFIDDeviceTrackSection from './charts/sections';
+
+const RFIDDeviceTrackPage = () => {
+ const { spacing } = useThemeConfig();
+ return (
+ <Stack direction="column" spacing={spacing}>
+ <RFIDDeviceTrackSection />
+ </Stack>
+ );
+};
+
+export default RFIDDeviceTrackPage;
--- /dev/null
+import { Navigate } from 'react-router';
+import { useEffect } from 'react';
+import { useRedirect } from '../../hooks';
+const RedirectPage = () => {
+ const { get: getRedirect } = useRedirect();
+ const redirect = getRedirect();
+ useEffect(() => {
+ if (redirect) {
+ document.location.href = redirect;
+ }
+ }, [redirect]);
+
+ return <Navigate to="/entities/notification" />;
+};
+
+export default RedirectPage;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactApexChart from 'react-apexcharts';
+const BaseChart = ({ series, type = 'bar', sparkline = true, height = 150, options }) => {
+ const chartOptions = options || {
+ chart: {
+ sparkline: {
+ enabled: sparkline
+ },
+ height: 'auto',
+ type,
+ toolbar: {
+ show: false
+ }
+ },
+ stroke: {
+ curve: 'straight'
+ },
+
+ dataLabels: {
+ enabled: false
+ },
+
+ plotOptions: {
+ [type]: {
+ columnWidth: '80%'
+ }
+ },
+ xaxis: {
+ labels: {
+ show: false
+ }
+ },
+ yaxis: {
+ labels: {
+ show: false
+ }
+ },
+
+ tooltip: {
+ fixed: {
+ enabled: false
+ },
+ x: {
+ show: false
+ },
+ marker: {
+ show: false
+ }
+ }
+ };
+
+ return <ReactApexChart options={chartOptions} series={series} type={type} height={height} />;
+};
+
+BaseChart.propTypes = {
+ type: PropTypes.string.isRequired,
+ sparkline: PropTypes.bool,
+ series: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string,
+ data: PropTypes.any
+ })
+ )
+};
+
+export default BaseChart;
--- /dev/null
+import { Chip, Stack, Typography } from '@mui/material';
+import { FallOutlined, RiseOutlined } from '@ant-design/icons';
+
+import { MainCard } from '@applica-software-guru/react-admin';
+import React from 'react';
+
+const percentageFormatter = new Intl.NumberFormat('it-IT', {
+ style: 'percent',
+ maximumFractionDigits: 2
+});
+
+const formatPercentage = (v) => percentageFormatter.format(v / 100);
+
+const BaseChartCard = ({ title, total, trend, children }) => {
+ return (
+ <MainCard>
+ <Stack>
+ <Typography>{title}</Typography>
+ <Stack spacing={1.5} direction="row" alignItems="center">
+ <Typography variant="h4" color="inherit">
+ {total}
+ </Typography>
+ {trend && (
+ <Chip
+ color={trend > 0 ? 'primary' : 'error'}
+ variant="combined"
+ icon={
+ trend > 0 ? (
+ <RiseOutlined style={{ fontSize: '0.75rem', color: 'inherit' }} />
+ ) : (
+ <FallOutlined style={{ fontSize: '0.75rem', color: 'inherit' }} />
+ )
+ }
+ label={formatPercentage(trend)}
+ size="small"
+ />
+ )}
+ </Stack>
+ {children}
+ </Stack>
+ </MainCard>
+ );
+};
+
+export default BaseChartCard;
--- /dev/null
+import { LoadingIndicator, MainCard } from '@applica-software-guru/react-admin';
+import { Stack, Typography } from '@mui/material';
+
+const DataMainCard = (props) => {
+ const { isFetching, title, data, unit } = props;
+ return (
+ <MainCard>
+ <Stack>
+ <Typography>{title}</Typography>
+ <Stack spacing={1.5} direction="row" alignItems="center">
+ <Typography variant="h4" color="inherit">
+ {isFetching ? <LoadingIndicator /> : `${data} ${unit}`}
+ </Typography>
+ </Stack>
+ </Stack>
+ </MainCard>
+ );
+};
+
+export default DataMainCard;
--- /dev/null
+import { useTranslate } from '@applica-software-guru/react-admin';
+import { Box, Typography } from '@mui/material';
+import WarningIcon from '@mui/icons-material/Warning';
+
+const WarningIconBig = () => {
+ return <WarningIcon sx={{ fontSize: 100, textAlign: 'center' }} />;
+};
+
+const EmptyAlert = ({ visible = true }) => {
+ const translate = useTranslate();
+ if (!visible) return null;
+ return (
+ <Box
+ sx={{ mt: 4, mb: 4 }}
+ justifyContent={'center'}
+ display={'flex'}
+ flexDirection={'column'}
+ alignItems={'center'}
+ textAlign={'center'}
+ >
+ <WarningIconBig />
+ <Typography>{translate('resources.entities/usage.tabs.chart.no-data-description')}</Typography>
+ </Box>
+ );
+};
+
+export default EmptyAlert;
--- /dev/null
+import { Form, useThemeConfig } from '@applica-software-guru/react-admin';
+import React, { useEffect, useRef } from 'react';
+
+import { Grid } from '@mui/material';
+import PropTypes from 'prop-types';
+import { useWatch } from 'react-hook-form';
+
+const FilterFormContent = ({ children, onChange }) => {
+ const { spacing } = useThemeConfig();
+ const values = useWatch();
+ const didMount = useRef(false);
+ useEffect(() => {
+ if (!didMount.current) {
+ didMount.current = true;
+ return;
+ }
+ onChange(values);
+ }, [values, onChange]);
+
+ return (
+ <Grid container spacing={spacing} direction="row-reverse">
+ {React.Children.map(children, (child) => (
+ <Grid item lg={3} md={3} sm={12} xs={12}>
+ {React.cloneElement(child, { fullWidth: true, display: 'legend' })}
+ </Grid>
+ ))}
+ </Grid>
+ );
+};
+
+const FilterForm = ({ children, onChange }) => {
+ const handleSubmit = (values) => console.log(values);
+ return (
+ <Form onSubmit={handleSubmit}>
+ <FilterFormContent children={children} onChange={onChange} />
+ </Form>
+ );
+};
+
+FilterForm.propTypes = {
+ children: PropTypes.node,
+ onChange: PropTypes.func.isRequired
+};
+
+export default FilterForm;
--- /dev/null
+import React, { useMemo } from 'react';
+import BaseChartCard from './BaseChartCard';
+import { useTranslate } from 'react-admin';
+import HighchartsReact from 'highcharts-react-official';
+import Highcharts from 'highcharts';
+
+const RFIDDeviceTrackChart = ({ data }) => {
+ const translate = useTranslate();
+
+ const { options, loading } = useMemo(() => {
+ if (!data || !data.tagsAndGatewaysNames || !data.latestTrackedSupplies) {
+ return { options: {}, loading: true };
+ }
+
+ const { tagsAndGatewaysNames, latestTrackedSupplies } = data;
+
+ const gatewaysSorted = tagsAndGatewaysNames.points[0]?.gateways?.sort((a, b) => a.gatewayName[0].localeCompare(b.gatewayName[0])) || [];
+ const gateways = gatewaysSorted.reduce((acc, gateway) => ({ ...acc, [gateway.gatewayId]: gateway.gatewayName[0] }), {});
+ const gatewayIndexes = Object.values(gateways);
+
+ const tagsSorted = tagsAndGatewaysNames.points[0]?.tags?.sort((a, b) => a.tagName[0].localeCompare(b.tagName[0])) || [];
+ const tags = tagsSorted.reduce((acc, tag) => ({ ...acc, [tag.tagId]: tag.tagName[0] }), {});
+ const tagsIndexes = Object.values(tags);
+
+ const seriesData = (latestTrackedSupplies?.points || []).map((track) => ({
+ x: tagsIndexes.indexOf(tags[track.latestRfidTrack.tagId]),
+ y: gatewayIndexes.indexOf(gateways[track.latestRfidTrack.gatewayId])
+ }));
+
+ const options = {
+ chart: {
+ type: 'scatter',
+ zoomType: 'xy'
+ },
+ credits: {
+ enabled: false
+ },
+ title: {
+ text: ''
+ },
+ xAxis: {
+ title: {
+ text: 'Tag'
+ },
+ categories: tagsIndexes,
+ labels: {
+ rotation: -45
+ }
+ },
+ yAxis: {
+ title: {
+ text: 'Gateway'
+ },
+ categories: gatewayIndexes
+ },
+ series: [
+ {
+ name: 'Movimenti RFID',
+ color: 'rgb(0, 100, 255)',
+ data: seriesData
+ }
+ ]
+ };
+
+ return { options };
+ }, [data]);
+
+ if (loading) {
+ return null;
+ }
+ return (
+ <BaseChartCard title={translate('tracking.stats.rfid.title')}>
+ <HighchartsReact highcharts={Highcharts} options={options} />
+ </BaseChartCard>
+ );
+};
+
+export default RFIDDeviceTrackChart;
--- /dev/null
+import React, { useMemo } from 'react';
+import Chart from 'react-apexcharts';
+import dayjs from 'dayjs';
+import { useTranslate } from '@applica-software-guru/react-admin';
+import _ from 'lodash';
+import { BaseChartCard, EmptyAlert } from '.';
+
+const formatExponentialToFixed = (number) => {
+ const num = Number(number);
+ if (num === 0) return '0.00';
+ return num.toFixed(2);
+};
+
+const UsageChart = ({ data }) => {
+ const translate = useTranslate();
+ const { hasData, options, series } = useMemo(() => {
+ const points = _.get(data, 'customUsage.points', []);
+ const hasData = points.length > 0;
+ const isHourlyData = points.some((point) => point.totalUsageHours != null);
+
+ const series = [
+ {
+ name: translate(isHourlyData ? 'reporting.stats.usageHours' : 'reporting.stats.usageDays'),
+ data:
+ points.length > 0
+ ? points.map((point) => {
+ const timestamp = dayjs(
+ new Date(point.year, isHourlyData ? point.month - 1 : point.month, isHourlyData ? point.day : null)
+ ).valueOf();
+ const usageValue = point?.total;
+ const formattedValue = usageValue != null ? formatExponentialToFixed(usageValue) : 0;
+ return { x: timestamp, y: formattedValue };
+ })
+ : [{ x: dayjs().valueOf(), y: null }]
+ }
+ ];
+
+ const options = {
+ chart: {
+ id: 'basic-line',
+ height: 'auto'
+ },
+ xaxis: {
+ type: 'datetime',
+ labels: {
+ formatter: (val) => dayjs(val).format(isHourlyData ? 'MMM DD' : 'MMM YYYY')
+ }
+ },
+ yaxis: {
+ title: {
+ text: translate(isHourlyData ? 'reporting.stats.usageHours' : 'reporting.stats.usageDays')
+ }
+ },
+ tooltip: {
+ x: {
+ format: 'MMM DD, YYYY'
+ }
+ }
+ };
+
+ return { options, series, hasData };
+ }, [data, translate]);
+
+ if (!hasData) {
+ return <EmptyAlert />;
+ }
+
+ return (
+ <BaseChartCard>
+ <Chart options={options} series={series} type="line" height="300" />
+ </BaseChartCard>
+ );
+};
+
+export default UsageChart;
--- /dev/null
+import { useTranslate } from '@applica-software-guru/react-admin';
+import _ from 'lodash';
+import { useMemo } from 'react';
+import dayjs from 'dayjs';
+import { BaseChartCard, EmptyAlert } from '.';
+import Chart from 'react-apexcharts';
+import { useTheme } from '@mui/material/styles';
+
+const formatExponentialToFixed = (number) => {
+ const num = Number(number);
+ if (num === 0) return '0.00';
+ return num.toFixed(2);
+};
+
+const UsageMLBreakChart = ({ data }) => {
+ const translate = useTranslate();
+ const theme = useTheme();
+
+ const { hasData, options, series } = useMemo(() => {
+ const primaryColor = theme.palette.primary.main;
+ const secondaryColor = theme.palette.secondary.main;
+ const dangerColor = theme.palette.error.main;
+ const points = _.get(data, 'predictions.points', []);
+ const hasData = points.some((p) => p.prediction === 1);
+ const series = [
+ {
+ name: translate('reporting.stats.predictions.accumulated'),
+ data: points
+ ?.filter((p) => !p.broken)
+ .map((point) => ({
+ x: dayjs(new Date(point.year, point.month - 1, point.day)).valueOf(),
+ y: formatExponentialToFixed(point?.accumulated)
+ }))
+ },
+ {
+ name: translate('reporting.stats.predictions.broken'),
+ data: points
+ ?.filter((point) => point?.broken)
+ .map((point) => ({
+ x: dayjs(new Date(point.year, point.month - 1, point.day)).valueOf(),
+ y: formatExponentialToFixed(point?.accumulated)
+ }))
+ }
+ ];
+ if (series[0]?.data.length > 0 && series[1]?.data.length > 0) {
+ series.push({
+ data: [series[0]?.data[series[0]?.data.length - 1], series[1]?.data[0]]
+ });
+ }
+ const breakpointInHours = _.get(data, 'breakpointInHours', 0);
+ const options = {
+ chart: {
+ id: 'basic-line',
+ height: 'auto'
+ },
+ xaxis: {
+ type: 'datetime',
+ labels: {
+ formatter: (val) => dayjs(val).format('YYYY-MM-DD')
+ }
+ },
+ tooltip: {
+ x: {
+ format: 'MMM DD, YYYY'
+ }
+ },
+ stroke: { width: 2 },
+ markers: {
+ size: 8
+ },
+ annotations: {
+ yaxis: [
+ {
+ y: breakpointInHours,
+ borderColor: dangerColor,
+ label: {
+ borderColor: dangerColor,
+ style: {
+ color: '#fff',
+ background: dangerColor
+ },
+ text: translate('reporting.stats.breakPredictions.line'),
+ position: 'center'
+ }
+ }
+ ]
+ },
+ colors: [primaryColor, secondaryColor, dangerColor]
+ };
+
+ return { hasData, options, series };
+ }, [data, translate, theme]);
+
+ if (!hasData) {
+ return <EmptyAlert />;
+ }
+
+ return (
+ <BaseChartCard>
+ <Chart options={options} series={series} type="line" height="300" />
+ </BaseChartCard>
+ );
+};
+
+export default UsageMLBreakChart;
--- /dev/null
+import { useTranslate } from '@applica-software-guru/react-admin';
+import _ from 'lodash';
+import { useMemo } from 'react';
+import dayjs from 'dayjs';
+import { BaseChartCard, EmptyAlert } from '.';
+import Chart from 'react-apexcharts';
+import { useTheme } from '@mui/material/styles';
+
+const formatExponentialToFixed = (number) => {
+ const num = Number(number);
+ if (num === 0) return '0.00';
+ return num.toFixed(2);
+};
+
+const UsageMLHourlyChart = ({ data }) => {
+ const translate = useTranslate();
+ const theme = useTheme();
+
+ const { hasData, options, series } = useMemo(() => {
+ const primaryColor = theme.palette.primary.main;
+ const dangerColor = theme.palette.error.main;
+ const points = _.get(data, 'predictions.points', []);
+ const hasData = points.some((p) => p.prediction === 1);
+ const series = [
+ {
+ name: translate('reporting.stats.predictions.history'),
+ data: points
+ ?.filter((point) => point.prediction === 0)
+ .map((point) => ({
+ x: dayjs(new Date(point.year, point.month - 1, point.day)).valueOf(),
+ y: formatExponentialToFixed(point?.total)
+ }))
+ },
+ {
+ name: translate('reporting.stats.predictions.future'),
+ data: points
+ ?.filter((point) => point.prediction === 1)
+ .map((point) => ({
+ x: dayjs(new Date(point.year, point.month - 1, point.day)).valueOf(),
+ y: formatExponentialToFixed(point?.total)
+ }))
+ }
+ ];
+ const options = {
+ chart: {
+ id: 'basic-bar',
+ height: 'auto'
+ },
+ xaxis: {
+ type: 'datetime',
+ labels: {
+ formatter: (val) => dayjs(val).format('YYYY-MM-DD')
+ }
+ },
+ yaxis: {
+ title: {
+ text: translate('reporting.stats.predictions.usageHours')
+ }
+ },
+ tooltip: {
+ x: {
+ format: 'MMM DD, YYYY'
+ }
+ },
+ colors: [primaryColor, dangerColor]
+ };
+
+ return { hasData, options, series };
+ }, [data, translate, theme]);
+
+ if (!hasData) {
+ return <EmptyAlert />;
+ }
+
+ return (
+ <BaseChartCard>
+ <Chart options={options} series={series} type="bar" height="300" />
+ </BaseChartCard>
+ );
+};
+
+export default UsageMLHourlyChart;
--- /dev/null
+export { default as UsageChart } from './UsageChart';
+export { default as BaseChartCard } from './BaseChartCard';
+export { default as DataMainCard } from './DataMainCard';
+export { default as FilterForm } from './FilterForm';
+export { default as RFIDDeviceTrackChart } from './RFIDDeviceTrackChart';
+export { default as UsageMLHourlyChart } from './UsageMLHourlyChart';
+export { default as EmptyAlert } from './EmptyAlert';
+export { default as useReportData } from './useReportData';
+export { default as UsageMLBreakChart } from './UsageMLBreakChart';
--- /dev/null
+import { MainCard, useThemeConfig, useTranslate } from '@applica-software-guru/react-admin';
+import useReportData from '../useReportData';
+import { Grid, Alert, Typography } from '@mui/material';
+import DataMainCard from '../DataMainCard';
+import { Fragment, useCallback, useState } from 'react';
+import { CustomerOfficeAutocompleteInput } from 'components/ra-inputs';
+import { FilterForm, RFIDDeviceTrackChart } from '..';
+
+const formatExponentialToFixed = (number) => {
+ const num = Number(number);
+ if (isNaN(num) || num === 0) return '0.00';
+ return num.toFixed(2);
+};
+
+const RFIDDeviceTrackSection = () => {
+ const { spacing } = useThemeConfig();
+ const translate = useTranslate();
+ const [filters, setFilters] = useState({});
+ const handleChange = useCallback((filters) => setFilters(filters), [setFilters]);
+ const { data, isFetching } = useReportData({
+ officeId: filters.officeId,
+ type: 'supply-tracks'
+ });
+
+ const fakeData = {
+ latestTrackedSupplies: {
+ points: [],
+ count: 0
+ },
+
+ tagsAndGatewaysNames: {
+ points: [
+ {
+ gateways: [],
+ _id: null,
+ tags: []
+ }
+ ],
+ count: 1
+ }
+ };
+
+ return (
+ <Fragment>
+ <MainCard title={translate('ra.position.report.title')}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12}>
+ <FilterForm onChange={handleChange}>
+ <CustomerOfficeAutocompleteInput source="officeId" label="reporting.stats.filters.office" />
+ </FilterForm>
+ </Grid>
+ <Grid item sm={3} xs={12}>
+ <DataMainCard
+ isFetching={isFetching}
+ title={translate('reporting.stats.track.monthly-movement.title')}
+ data={(!isFetching && data?.monthMovementTracking?.points[0]?.totalTrackedRFID) || 0}
+ unit=""
+ />
+ </Grid>
+ <Grid item sm={3} xs={12}>
+ <DataMainCard
+ isFetching={isFetching}
+ title={translate('reporting.stats.track.yearly-movement.title')}
+ data={(!isFetching && data?.yearMovementTracking?.points[0]?.totalTrackedRFID) || 0}
+ unit=""
+ />
+ </Grid>
+ <Grid item sm={3} xs={12}>
+ <DataMainCard
+ isFetching={isFetching}
+ title={translate('reporting.stats.track.total-monthly-tracked.title')}
+ data={(!isFetching && data?.monthlyTotalTrackedRFID?.points[0]?.totalUniqueSupplyId) || 0}
+ unit=""
+ />
+ </Grid>
+ <Grid item sm={3} xs={12}>
+ <DataMainCard
+ isFetching={isFetching}
+ title={translate('reporting.stats.track.monthly-total-tracked-percentage.title')}
+ data={!isFetching && formatExponentialToFixed(data?.monthlyTotalTrackedRFIDPercentage?.points[0]?.trackingPercentage)}
+ unit="%"
+ />
+ </Grid>
+ {!data && (
+ <Grid item sm={12} xs={12}>
+ <Alert severity="warning" sx={{ marginTop: 1, marginBottom: 2 }}>
+ <Typography variant="body1">{translate('ra.position.waiting-for-data')}</Typography>
+ </Alert>
+ </Grid>
+ )}
+ </Grid>
+ <Grid item sm={12} xs={12} sx={{ marginTop: 2 }}>
+ <RFIDDeviceTrackChart data={isFetching ? fakeData : data} />
+ </Grid>
+ </MainCard>
+ </Fragment>
+ );
+};
+
+export default RFIDDeviceTrackSection;
--- /dev/null
+import RFIDDeviceTrackSection from './RFIDDeviceTrackSection';
+
+export default RFIDDeviceTrackSection;
--- /dev/null
+import dayjs from 'dayjs';
+import { useDataProvider, useNotify } from '@applica-software-guru/react-admin';
+import { useQuery } from 'react-query';
+
+const useReportData = ({ supplyId, from, to, trigger, type, officeId }) => {
+ const dataProvider = useDataProvider();
+ const notify = useNotify();
+ const fetchData = () => {
+ if (from && to && dayjs(from).isAfter(dayjs(to))) {
+ notify('ra.error.invalid-date-range', 'error');
+ return Promise.resolve(null);
+ }
+ const params = {
+ from: from || dayjs().startOf('month').add(1, 'hours').toISOString(),
+ to: dayjs(to).add(1, 'day').toISOString() || dayjs().endOf('month').add(1, 'hours').toISOString(),
+ supplyId: supplyId,
+ officeId: officeId
+ };
+ return dataProvider.post(`report/${type}/execute`, params).then(({ data: { data } }) => data);
+ };
+ const shouldFetch = !!supplyId || !!officeId;
+ return useQuery(['reportData', supplyId, officeId, trigger], fetchData, { enabled: shouldFetch });
+};
+
+export default useReportData;
--- /dev/null
+import CustomPage from './CustomPage';
+import RedirectPage from './RedirectPage';
+import RFIDDeviceTrackPage from './RFIDDeviceTrackPage';
+export { CustomPage, RedirectPage, RFIDDeviceTrackPage };
--- /dev/null
+import { Document, Page, StyleSheet, View, Image, Text } from '@react-pdf/renderer';
+import React from 'react';
+
+const styles = StyleSheet.create({
+ page: {
+ fontFamily: 'Helvetica'
+ }
+});
+
+// <Text style={{ fontSize: 5 }}>Approvvigionamento: {supplyName}</Text>
+
+const SupplyPdf = ({ qrCodeDataUrl, serialNumber }) => (
+ <Document>
+ <Page size="A8" style={styles.page}>
+ <View>
+ <Image src={qrCodeDataUrl} />
+ <Text style={{ fontSize: 20, textAlign: 'center' }}>SN: {serialNumber}</Text>
+ </View>
+ </Page>
+ </Document>
+);
+
+export default SupplyPdf;
--- /dev/null
+import SupplyPdf from './SupplyPdf';
+export { SupplyPdf };
--- /dev/null
+import { useRecordContext, useTranslate } from '@applica-software-guru/react-admin';
+import { Alert, AlertTitle, Typography } from '@mui/material';
+import dayjs from 'dayjs';
+const SupplyLastTrackAlert = () => {
+ const record = useRecordContext();
+ const translate = useTranslate();
+ if (!record?.latestRfidTrack) {
+ return null;
+ }
+ return (
+ <Alert severity="info" sx={{ marginTop: 1 }}>
+ <AlertTitle dangerouslySetInnerHTML={{ __html: translate('resources.entities/supply.rfid-device-last-position.title') }} />
+ <Typography
+ dangerouslySetInnerHTML={{
+ __html: translate('resources.entities/supply.rfid-device-last-position', {
+ area: record?.latestArea?.name,
+ ts: dayjs(record?.latestRfidTrack?.ts).format(translate('app.date_format.long'))
+ })
+ }}
+ />
+ </Alert>
+ );
+};
+
+export default SupplyLastTrackAlert;
--- /dev/null
+export { default as SupplyLastTrackAlert } from './SupplyLastTrackAlert';
--- /dev/null
+import { Button } from '@mui/material';
+import KeyboardReturnIcon from '@mui/icons-material/KeyboardReturn';
+import { Link } from 'react-router-dom';
+import { useTranslate } from '@applica-software-guru/react-admin';
+
+const EntityBackButton = ({ type, id, label }) => {
+ const translate = useTranslate();
+ return (
+ <Button startIcon={<KeyboardReturnIcon />} variant="outlined" size="medium" component={Link} to={`/entities/${type}/${id}`}>
+ {translate(label) || translate('resources.entities/supply.actions.backToFather')}
+ </Button>
+ );
+};
+
+export default EntityBackButton;
--- /dev/null
+import { Button } from '@applica-software-guru/react-admin';
+import KeyboardReturnIcon from '@mui/icons-material/KeyboardReturn';
+import { Link } from 'react-router-dom';
+
+const EquipmentBackButton = ({ equipmentId }) => {
+ return (
+ <Button
+ startIcon={<KeyboardReturnIcon />}
+ label={'resources.entities/supply.actions.backToFather'}
+ variant="outlined"
+ size="medium"
+ component={Link}
+ to={`/entities/equipment/${equipmentId}`}
+ />
+ );
+};
+
+export default EquipmentBackButton;
--- /dev/null
+import { Button } from '@applica-software-guru/react-admin';
+import { Link } from 'react-router-dom';
+import KeyboardReturnIcon from '@mui/icons-material/KeyboardReturn';
+
+const EquipmentTypeBackButton = ({ parentId }) => {
+ return (
+ <Button
+ startIcon={<KeyboardReturnIcon />}
+ label={'resources.entities/equipmentType.actions.backToFather'}
+ variant="outlined"
+ size="medium"
+ component={Link}
+ to={`/entities/equipment-type/${parentId}`}
+ />
+ );
+};
+
+export default EquipmentTypeBackButton;
--- /dev/null
+// GenerateQRCodePdfButton.js
+import React, { useState } from 'react';
+import { Button, useNotify, useRecordContext } from '@applica-software-guru/react-admin';
+import { pdf } from '@react-pdf/renderer';
+import QRCode from 'qrcode';
+import { SupplyPdf } from 'components/pdf';
+
+const GenerateQRCodePdfButton = () => {
+ const [loading, setLoading] = useState(false);
+ const notify = useNotify();
+ const record = useRecordContext();
+
+ const handleClick = async () => {
+ if (!record || !record.id) {
+ notify('Error: no record found.');
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const qrCodeValue = `${window.location.protocol}//${window.location.hostname}${
+ window.location.port ? `:${window.location.port}` : ''
+ }/#/entities/supply/${record.id}/show`;
+ const qrCodeDataUrl = await QRCode.toDataURL(qrCodeValue, {
+ errorCorrectionLevel: 'H',
+ type: 'image/png',
+ width: 20
+ });
+
+ const asPdf = pdf(<SupplyPdf qrCodeDataUrl={qrCodeDataUrl} serialNumber={record?.serialNumber} />);
+ const blob = await asPdf.toBlob();
+
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'supply-details.pdf';
+ document.body.appendChild(a);
+ a.click();
+ URL.revokeObjectURL(url);
+ a.remove();
+ } catch (error) {
+ notify(`ra.error.pdf-generation: ${error.message}`, 'warning');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <Button
+ variant="contained"
+ size="medium"
+ color="secondary"
+ onClick={handleClick}
+ disabled={loading || !record || !record.id}
+ label={loading ? 'Generating...' : 'Download PDF'}
+ />
+ );
+};
+
+export default GenerateQRCodePdfButton;
--- /dev/null
+import { Button } from '@applica-software-guru/react-admin';
+import { Link } from 'react-router-dom';
+import KeyboardReturnIcon from '@mui/icons-material/KeyboardReturn';
+
+const PriceListBackButton = ({ supplierId, tab }) => {
+ return (
+ <Button
+ startIcon={<KeyboardReturnIcon />}
+ label={'resources.entities/price-list.actions.back-to-supplier'}
+ variant="outlined"
+ size="medium"
+ component={Link}
+ to={`/entities/supplier/${supplierId}${tab ? `/${tab}` : ''}`}
+ />
+ );
+};
+
+export default PriceListBackButton;
--- /dev/null
+import { useAuthProvider, useDataProvider, usePopoverState, useRecordContext, useTranslate } from '@applica-software-guru/react-admin';
+import { useEffect, useState } from 'react';
+import { Typography, Button, ButtonGroup, Menu, MenuList, MenuItem } from '@mui/material';
+import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
+import PrintIcon from '@mui/icons-material/Print';
+import React from 'react';
+import PropTypes from 'prop-types';
+import { API_URL } from '../../config';
+
+const PrintDocxButton = ({ type }) => {
+ const { anchorEl, open, handleClose, handleClick } = usePopoverState();
+ const [token, setToken] = useState(null);
+ const translate = useTranslate();
+ const record = useRecordContext();
+ const dataProvider = useDataProvider();
+ const authProvider = useAuthProvider();
+
+ const [options, setOptions] = useState([]);
+ useEffect(() => {
+ dataProvider.get(`docx/templates/${type}`).then(({ data }) => setOptions(data));
+ authProvider.getToken().then((token) => setToken(token));
+ }, [dataProvider, authProvider, setOptions, type]);
+
+ if (options.length === 0 || !record?.id || !token) {
+ return null;
+ }
+
+ return (
+ <React.Fragment>
+ <ButtonGroup variant="contained" color="secondary">
+ <Button onClick={handleClick}>
+ <PrintIcon />
+ <Typography sx={{ pl: 1, pr: 1 }}>{translate('ra.actions.print_docx')}</Typography>
+ <ArrowDropDownIcon />
+ </Button>
+ </ButtonGroup>
+ <Menu open={open} anchorEl={anchorEl} onClose={handleClose}>
+ <MenuList autoFocusItem>
+ {options.map((option) => (
+ <MenuItem
+ key={option.id}
+ onClick={handleClose}
+ component={Button}
+ target="_blank"
+ href={`${API_URL}/docx/generate/${type}/${option.id}/${record?.id}.docx?_token=${token}`}
+ >
+ {option.name}
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Menu>
+ </React.Fragment>
+ );
+};
+
+PrintDocxButton.propTypes = {
+ type: PropTypes.string.isRequired
+};
+
+export default PrintDocxButton;
--- /dev/null
+import { Button } from '@mui/material';
+import { useNavigate } from 'react-router-dom';
+import { useTranslate } from '@applica-software-guru/react-admin';
+
+const RegisterMaintenanceButton = (props) => {
+ const navigate = useNavigate();
+ const translate = useTranslate();
+ const { record } = props;
+ const handleClick = () => {
+ navigate('/entities/maintenance/create', {
+ state: {
+ record: {
+ supplyId: record?.id
+ }
+ }
+ });
+ };
+
+ return (
+ <Button variant="outlined" color="primary" onClick={handleClick} fullWidth>
+ {translate('resources.entities/supply.show.register-maintenance')}
+ </Button>
+ );
+};
+
+export default RegisterMaintenanceButton;
--- /dev/null
+import { useTranslate } from '@applica-software-guru/react-admin';
+import { Button } from '@mui/material';
+import { useNavigate } from 'react-router-dom';
+
+const RequestAssistanceButton = (props) => {
+ const navigate = useNavigate();
+ const translate = useTranslate();
+ const { record } = props;
+ const handleClick = () => {
+ navigate('/entities/activity/create', {
+ state: {
+ record: {
+ supplierId: record?.supplier?.id,
+ customerId: record?.customer?.id,
+ supplyId: record?.id,
+ supply: {
+ supplierId: record?.supplier?.id
+ },
+ customer: {
+ id: record?.customer?.id
+ }
+ }
+ }
+ });
+ };
+
+ return (
+ <Button variant="contained" color="error" onClick={handleClick} fullWidth>
+ {translate('resources.entities/supply.show.request-assistance')}
+ </Button>
+ );
+};
+
+export default RequestAssistanceButton;
--- /dev/null
+import React, { useState } from 'react';
+import { useDataProvider, useNotify, useTranslate } from '@applica-software-guru/react-admin';
+import PlayCircleFilledIcon from '@mui/icons-material/PlayCircleFilled';
+import StopCircleIcon from '@mui/icons-material/StopCircle';
+import { Button } from '@mui/material';
+import { get } from 'lodash';
+
+const StartAndStopUsageButton = ({ record, setStatus, setColor }) => {
+ const dataProvider = useDataProvider();
+ const translate = useTranslate();
+ const notify = useNotify();
+ const [status, setUsageStatus] = useState(get(record, 'usageStatus', 'STOPPED') || 'STOPPED');
+
+ const handleStart = () => {
+ dataProvider
+ .get(`supply/supply-start/${record?.id}`)
+ .then(() => {
+ setUsageStatus('RUNNING');
+ setColor('green');
+ setStatus('RUNNING');
+ notify('resources.entities.supply.notifications.started', { type: 'info' });
+ })
+ .catch(() => {
+ notify('ra.notification.http_error', { type: 'error' });
+ });
+ };
+
+ const handleStop = async () => {
+ dataProvider
+ .get(`supply/supply-stop/${record?.id}`)
+ .then(() => {
+ setUsageStatus('STOPPED');
+ setColor('red');
+ setStatus('STOPPED');
+ notify('resources.entities.supply.notifications.stopped', { type: 'info' });
+ })
+ .catch(() => notify('ra.notification.http_error', { type: 'error' }));
+ };
+ return status === 'STOPPED' || status === null ? (
+ <Button variant="outlined" color="secondary" onClick={handleStart} startIcon={<PlayCircleFilledIcon />} fullWidth>
+ {translate('resources.entities/supply.show.status-stopped')}
+ </Button>
+ ) : (
+ <Button variant="outlined" color="secondary" onClick={handleStop} startIcon={<StopCircleIcon />} fullWidth>
+ {translate('resources.entities/supply.show.status-running')}
+ </Button>
+ );
+};
+
+export default StartAndStopUsageButton;
--- /dev/null
+import { Button } from '@applica-software-guru/react-admin';
+import KeyboardReturnIcon from '@mui/icons-material/KeyboardReturn';
+import { Link } from 'react-router-dom';
+
+const SupplierBackButton = ({ supplierId, tab }) => {
+ return (
+ <Button
+ startIcon={<KeyboardReturnIcon />}
+ label={'resources.entities/equipment.actions.backToFather'}
+ variant="outlined"
+ size="medium"
+ component={Link}
+ to={`/entities/supplier/${supplierId}${tab ? `/${tab}` : ''}`}
+ />
+ );
+};
+
+export default SupplierBackButton;
--- /dev/null
+export { default as PriceListBackButton } from './PriceListBackButton';
+export { default as SupplierBackButton } from './SupplierBackButton';
+export { default as GenerateQRCodePdfButton } from './GenerateQRCodePdfButton';
+export { default as RequestAssistanceButton } from './RequestAssistanceButton';
+export { default as RegisterMaintenanceButton } from './RegisterMaintenanceButton';
+export { default as StartAndStopUsageButton } from './StartAndStopUsageButton';
+export { default as EntityBackButton } from './EntityBackButton';
+export { default as PrintDocxButton } from './PrintDocxButton';
--- /dev/null
+import 'dayjs/locale/it';
+import 'dayjs/locale/en';
+import { Card, Grid, Typography } from '@mui/material';
+import {
+ EditButton,
+ LongForm,
+ MainCard,
+ Show,
+ TopToolbar,
+ useGetIdentity,
+ usePermissions,
+ useRecordContext,
+ useThemeConfig,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import {
+ FileOutlined,
+ InboxOutlined,
+ InfoCircleOutlined,
+ LineChartOutlined,
+ PlayCircleOutlined,
+ ScheduleOutlined,
+ SolutionOutlined,
+ UnorderedListOutlined,
+ GlobalOutlined
+} from '@ant-design/icons';
+import React, { useEffect, useState } from 'react';
+import { RegisterMaintenanceButton, RequestAssistanceButton, StartAndStopUsageButton } from 'components/ra-buttons';
+
+import CircleIcon from '@mui/icons-material/Circle';
+import { get } from 'lodash';
+import { useRedirect } from '../../hooks';
+import {
+ ActivityTab,
+ DetailsTab,
+ EquipmentAttachmentTab,
+ ProtocolTab,
+ RFIDDeviceTab,
+ SupplyTab,
+ UsageRecordTab,
+ UsageStatisticsTab
+} from './supply';
+
+const SupplyShowActions = () => {
+ const { permissions, isLoading } = usePermissions();
+ const isEditable = !isLoading && permissions.includes('supply:edit');
+
+ return <TopToolbar>{isEditable && <EditButton />}</TopToolbar>;
+};
+
+const STATUS_COLOR = {
+ RUNNING: 'green',
+ STOPPED: 'red'
+};
+
+const SupplyStatusSidebar = ({ status, color }) => {
+ const translate = useTranslate();
+
+ return (
+ <MainCard sx={{ textAlign: 'center' }}>
+ <Typography>
+ {translate(`ra.supply.card-status.${status}`)} <CircleIcon style={{ color: color, fontSize: 'small' }} />
+ </Typography>
+ </MainCard>
+ );
+};
+
+const SupplyShowContext = () => {
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ const { identity } = useGetIdentity();
+ const { set: setRedirect } = useRedirect();
+ const [status, setStatus] = useState(get(record, 'usageStatus', 'STOPPED') || 'STOPPED');
+ const [color, setColor] = useState(get(STATUS_COLOR, status, 'red') || 'red');
+
+ useEffect(() => {
+ if (!identity?.email) {
+ setRedirect(document.location.href);
+ }
+ const newStatus = get(record, 'usageStatus', 'STOPPED') || 'STOPPED';
+ setStatus(newStatus);
+ setColor(get(STATUS_COLOR, newStatus, 'red'));
+ }, [identity, setRedirect, record]);
+
+ return (
+ <LongForm>
+ <LongForm.SidebarSection visibility="xs" position={LongForm.SidebarSectionPosition.TOP}>
+ <SupplyStatusSidebar status={status} color={color} />
+ </LongForm.SidebarSection>
+ <LongForm.Tab icon={<InfoCircleOutlined />} id="details" label="resources.entities/supply.tabs.details">
+ <DetailsTab />
+ </LongForm.Tab>
+ <LongForm.Tab icon={<InboxOutlined />} id="supply" label="resources.entities/supply.tabs.supply">
+ <SupplyTab />
+ </LongForm.Tab>
+ <LongForm.Tab icon={<SolutionOutlined />} id="protocol" label="resources.entities/supply.tabs.protocol">
+ <ProtocolTab />
+ </LongForm.Tab>
+ <LongForm.Tab icon={<FileOutlined />} id="equipment-attachment" label="resources.entities/supply.tabs.equipment-attachment">
+ <EquipmentAttachmentTab />
+ </LongForm.Tab>
+ <LongForm.Tab icon={<ScheduleOutlined />} id="activity" label="resources.entities/supply.tabs.activity">
+ <ActivityTab />
+ </LongForm.Tab>
+ <LongForm.Tab icon={<GlobalOutlined />} id="rfid" label="resources.entities/rfid-device-track.tabs.list">
+ <RFIDDeviceTab />
+ </LongForm.Tab>
+ <LongForm.Group icon={<PlayCircleOutlined />} id="usage" label="resources.entities/supply.group.usage">
+ <LongForm.Tab icon={<UnorderedListOutlined />} id="register" label="resources.entities/usage.tabs.record">
+ <UsageRecordTab />
+ </LongForm.Tab>
+ <LongForm.Tab icon={<LineChartOutlined />} id="chart" label="resources.entities/usage.tabs.chart">
+ <UsageStatisticsTab />
+ </LongForm.Tab>
+ </LongForm.Group>
+ <LongForm.SidebarSection position={LongForm.SidebarSectionPosition.BOTTOM} visibility="xs">
+ <Card sx={{ marginTop: 3, backgroundColor: 'transparent' }}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <StartAndStopUsageButton record={record} setColor={setColor} setStatus={setStatus} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <RequestAssistanceButton record={record} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <RegisterMaintenanceButton record={record} />
+ </Grid>
+ </Grid>
+ </Card>
+ </LongForm.SidebarSection>
+ </LongForm>
+ );
+};
+
+const SupplyShow = () => {
+ return (
+ <Show actions={<SupplyShowActions />}>
+ <SupplyShowContext />
+ </Show>
+ );
+};
+
+export default SupplyShow;
--- /dev/null
+import SupplyShow from './SupplyShow';
+export { SupplyShow };
--- /dev/null
+import {
+ Datagrid,
+ DateField,
+ ReferenceField,
+ ReferenceManyInput,
+ SearchInput,
+ SelectInput,
+ TextField,
+ usePermissions,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import { ActivityStatusField } from 'components/ra-fields';
+import { FixedMainCard } from 'components/layout';
+import { ActivityTypeAutocompleteInput, CustomerAutocompleteInput } from 'components/ra-inputs';
+import { ACTIVITY_STATUS } from 'config';
+
+const ActivityTab = () => {
+ const translate = useTranslate();
+ const { permissions, isLoading } = usePermissions();
+ const filtersList = [
+ <SearchInput source="keyword" alwaysOn />,
+ <ActivityTypeAutocompleteInput source="activityTypeId" alwaysOn display="legend" />,
+ <SelectInput source="status" display="legend" alwaysOn choices={ACTIVITY_STATUS} />
+ ];
+
+ if (!isLoading && permissions.includes('customer:list')) {
+ filtersList.push(<CustomerAutocompleteInput source="customerId" alwaysOn display="legend" />);
+ }
+ return (
+ <FixedMainCard title={translate('resources.entities/supply.show.activity')}>
+ <ReferenceManyInput reference="entities/activity" target="supplyId" filters={filtersList}>
+ <Datagrid bulkActionButtons={false} rowClick="edit">
+ {!isLoading && permissions.includes('supplier:list') && (
+ <ReferenceField source="supply.supplierId" reference="entities/supplier" link={false} sortable={false}>
+ <TextField source="businessName" />
+ </ReferenceField>
+ )}
+ {!isLoading && permissions.includes('customer:list') && <TextField source="customer.name" sortable={false} />}
+ {!isLoading && permissions.includes('equipment:list') && (
+ <ReferenceField source="supply.equipmentId" reference="entities/equipment" link={false} sortable={false}>
+ <TextField source="equipmentType.name" />
+ </ReferenceField>
+ )}
+ <TextField source="activityType.description" sortable={false} />
+ <TextField source="title" />
+ <TextField source="user.name" sortable={false} />
+ <DateField source="date" />
+ <DateField source="updated" showTime />
+ <ActivityStatusField source="status" />
+ </Datagrid>
+ </ReferenceManyInput>
+ </FixedMainCard>
+ );
+};
+
+export default ActivityTab;
--- /dev/null
+import { Grid } from '@mui/material';
+import {
+ FunctionField,
+ MainCard,
+ ReadonlyField,
+ ReferenceField,
+ usePermissions,
+ useRecordContext,
+ useThemeConfig,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import React, { Fragment } from 'react';
+import { SupplyLastTrackAlert } from '../../ra-alerts';
+
+const DetailsTab = () => {
+ const translate = useTranslate();
+ const { permissions, isLoading } = usePermissions();
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ return (
+ <MainCard title={translate('resources.entities/supply.tabs.details')}>
+ <Grid container spacing={spacing}>
+ {!isLoading && permissions.includes('supplier:list') && (
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="supplier.businessName" />
+ </Grid>
+ )}
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="equipmentType.name" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="protocol.title" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="customer.name" />
+ </Grid>
+ {!isLoading && permissions.includes('rfid-device:list') && record?.rfidDeviceId && (
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="rfidDevice.name" />
+ </Grid>
+ )}
+ {!isLoading && permissions.includes('customer:new') && record?.areaId && (
+ <Fragment>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="area.officeId">
+ <ReferenceField source="area.officeId" reference="entities/customer-office" link={false}>
+ <FunctionField render={(record) => `${record.city.name} - ${record.address}`} />
+ </ReferenceField>
+ </ReadonlyField>
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="area.name" />
+ </Grid>
+ </Fragment>
+ )}
+ </Grid>
+ {!isLoading && permissions.includes('rfid-device:list') && <SupplyLastTrackAlert />}
+ </MainCard>
+ );
+};
+
+export default DetailsTab;
--- /dev/null
+import { AttachmentField, Datagrid, ReferenceManyField, TextField, useTranslate } from '@applica-software-guru/react-admin';
+import { FixedMainCard } from 'components/layout';
+
+const EquipmentAttachmentTab = () => {
+ const translate = useTranslate();
+ return (
+ <FixedMainCard title={translate('resources.entities/supply.show.equipment-attachment')}>
+ <ReferenceManyField reference="entities/equipment-attachment" source="equipmentId" target="equipmentId">
+ <Datagrid bulkActionButtons={false}>
+ <TextField source="description" />
+ <AttachmentField source="attachment" title="attachment.name" sortable={false} />
+ </Datagrid>
+ </ReferenceManyField>
+ </FixedMainCard>
+ );
+};
+
+export default EquipmentAttachmentTab;
--- /dev/null
+import { Grid } from '@mui/material';
+import { MainCard, ReadonlyField, useThemeConfig, useTranslate } from '@applica-software-guru/react-admin';
+
+const ProtocolTab = () => {
+ const translate = useTranslate();
+ const { spacing } = useThemeConfig();
+ return (
+ <MainCard title={translate('resources.entities/supply.show.protocol-info')}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="protocol.title" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="protocol.description" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="protocol.protocolNumber" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="protocol.protocolDate" />
+ </Grid>
+ </Grid>
+ </MainCard>
+ );
+};
+
+export default ProtocolTab;
--- /dev/null
+import { Datagrid, DateField, ReferenceField, ReferenceManyInput, TextField, useTranslate } from '@applica-software-guru/react-admin';
+import { FixedMainCard } from 'components/layout';
+import { RFIDDeviceAutocomplete, CustomerAreaAutocompleteInput } from 'components/ra-inputs';
+
+const RFIDDeviceTab = () => {
+ const translate = useTranslate();
+ return (
+ <FixedMainCard title={translate('resources.entities/rfid-device-track.tabs.list')}>
+ <ReferenceManyInput
+ reference="entities/rfid-device-track"
+ target="supplyId"
+ filters={[
+ <CustomerAreaAutocompleteInput source="areaId" display="legend" alwaysOn />,
+ <RFIDDeviceAutocomplete source="gatewayId" display="legend" type="GATEWAY" alwaysOn />
+ ]}
+ sort={{ field: 'ts', order: 'DESC' }}
+ >
+ <Datagrid bulkActionButtons={null}>
+ <TextField source="gateway.name" sortable={false} />
+ <ReferenceField source="areaId" reference="entities/customer-area" sortable={false}>
+ <TextField source="name" />
+ </ReferenceField>
+ <TextField source="additionalData" />
+ <DateField source="ts" showTime />
+ </Datagrid>
+ </ReferenceManyInput>
+ </FixedMainCard>
+ );
+};
+
+export default RFIDDeviceTab;
--- /dev/null
+import { Grid } from '@mui/material';
+import { MainCard, ReadonlyField, useThemeConfig, useTranslate } from '@applica-software-guru/react-admin';
+
+const SupplyTab = () => {
+ const translate = useTranslate();
+ const { spacing } = useThemeConfig();
+ return (
+ <MainCard title={translate('resources.entities/supply.show.supply-info')}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="equipmentType.name" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="quantity" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="orderDate" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="deliveryDate" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="serialNumber" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="ddtNumber" />
+ </Grid>
+ </Grid>
+ </MainCard>
+ );
+};
+
+export default SupplyTab;
--- /dev/null
+import 'dayjs/locale/it';
+import 'dayjs/locale/en';
+import { Datagrid, DateField, FunctionField, ReferenceManyField, useLocaleState, useTranslate } from '@applica-software-guru/react-admin';
+
+import { FixedMainCard } from 'components/layout';
+import dayjs from 'dayjs';
+
+const UsageRecordTab = () => {
+ const translate = useTranslate();
+ const locale = useLocaleState()[0];
+ return (
+ <FixedMainCard title={translate('resources.entities/usage.tabs.record')}>
+ <ReferenceManyField reference="entities/usage" target="supplyId" perPage={10}>
+ <Datagrid bulkActionButtons={false}>
+ <DateField source="start" showTime />
+ <DateField source="stop" showTime />
+ <FunctionField
+ label="resources.entities/supply.fields.duration"
+ render={({ start, stop }) => {
+ if (!stop) {
+ return translate('resources.entities/supply.fields.duration.running');
+ }
+ return dayjs
+ .duration(dayjs(stop).diff(dayjs(start)))
+ .locale(locale)
+ .humanize();
+ }}
+ />
+ </Datagrid>
+ </ReferenceManyField>
+ </FixedMainCard>
+ );
+};
+
+export default UsageRecordTab;
--- /dev/null
+import { DataMainCard, UsageChart, UsageMLBreakChart, UsageMLHourlyChart, useReportData } from 'components/pages/charts';
+import { DateInput, MainCard, useRecordContext, useThemeConfig, useTranslate } from '@applica-software-guru/react-admin';
+import { Grid, Button, Collapse, Alert } from '@mui/material';
+import { useWatch } from 'react-hook-form';
+import dayjs from 'dayjs';
+import { Fragment, useState } from 'react';
+import AutorenewIcon from '@mui/icons-material/Autorenew';
+
+const fakeData = {
+ customUsage: {
+ points: [
+ {
+ month: dayjs().month(),
+ year: dayjs().year(),
+ day: dayjs().day(),
+ total: 1
+ }
+ ],
+ count: 1
+ }
+};
+
+const convertDateToISO = (date) => {
+ if (!date) {
+ return null;
+ }
+ const day = dayjs(date);
+ const dayWithTime = day.hour(0).minute(0).second(0);
+ return dayWithTime.add(1, 'hours').toISOString();
+};
+
+const formatExponentialToFixed = (number) => {
+ const num = Number(number);
+ if (isNaN(num) || num === 0) return '0.00';
+ return num.toFixed(2);
+};
+
+const UsageStatisticsTab = () => {
+ const record = useRecordContext();
+ const { spacing } = useThemeConfig();
+ const translate = useTranslate();
+ const from = useWatch({ name: 'from' });
+ const to = useWatch({ name: 'to' });
+ const [trigger, setTrigger] = useState(false);
+ const handleClick = () => {
+ setTrigger((prev) => !prev);
+ };
+ const { data, isFetching } = useReportData({
+ supplyId: record?.id,
+ from: to ? convertDateToISO(from) : undefined,
+ to: to ? convertDateToISO(to) : undefined,
+ trigger: trigger,
+ type: 'supply'
+ });
+
+ return (
+ <Fragment>
+ <MainCard title={translate('resources.entities/usage.tabs.chart')} divider={false}>
+ <Grid container spacing={spacing}>
+ <Grid item sm={3} xs={12}>
+ <DataMainCard
+ isFetching={isFetching}
+ title={translate('reporting.stats.supply.monthly-usage.title')}
+ data={!isFetching && formatExponentialToFixed(data?.monthUsage?.points[0]?.totalUsageHours)}
+ unit={translate('reporting.stats.unit.hours')}
+ />
+ </Grid>
+ <Grid item sm={3} xs={12}>
+ <DataMainCard
+ isFetching={isFetching}
+ title={translate('reporting.stats.supply.yearly-usage.title')}
+ data={!isFetching && formatExponentialToFixed(data?.yearUsage?.points[0]?.totalUsageDays)}
+ unit={translate('reporting.stats.unit.days')}
+ />
+ </Grid>
+ <Grid item sm={3} xs={12}>
+ <DataMainCard
+ isFetching={isFetching}
+ title={translate('reporting.stats.supply.average-monthly-usage.title')}
+ data={(!isFetching && data?.averageMonthUsage?.points[0]?.count) || 0}
+ unit=""
+ />
+ </Grid>
+ <Grid item sm={3} xs={12}>
+ <DataMainCard
+ isFetching={isFetching}
+ title={translate('reporting.stats.supply.monthly-usage-percentage.title')}
+ data={!isFetching && formatExponentialToFixed(data?.monthlyUtilizationPercentage?.points[0]?.utilizationPercentage)}
+ unit="%"
+ />
+ </Grid>
+ <Grid item sm={5} xs={12}>
+ <DateInput source="from" />
+ </Grid>
+ <Grid item sm={5} xs={12}>
+ <DateInput source="to" />
+ </Grid>
+ <Grid item sm={2} xs={12} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginTop: 3 }}>
+ <Button variant="contained" size="medium" color="primary" onClick={handleClick}>
+ <AutorenewIcon />
+ </Button>
+ </Grid>
+ <Grid item sm={12} xs={12}>
+ <Collapse in={isFetching}>
+ <Alert severity="info">{translate('reporting.stats.loading')}</Alert>
+ </Collapse>
+ </Grid>
+ {!isFetching && (
+ <Grid item sm={12} xs={12}>
+ <UsageChart data={data} />
+ </Grid>
+ )}
+ </Grid>
+ </MainCard>
+ {!isFetching && (
+ <Fragment>
+ <MainCard sx={{ mt: 2 }} title={translate('resources.entities/usage.tabs.chart.predictions')} divider={false}>
+ <Grid container spacing={spacing}>
+ <Grid item sm={12} xs={12}>
+ <UsageMLHourlyChart data={data} />
+ </Grid>
+ </Grid>
+ </MainCard>
+ <MainCard sx={{ mt: 2 }} title={translate('resources.entities/usage.tabs.chart.breakPredictions')} divider={false}>
+ <Grid container spacing={spacing}>
+ <Grid item sm={12} xs={12}>
+ <UsageMLBreakChart data={isFetching ? fakeData : data} />
+ </Grid>
+ </Grid>
+ </MainCard>
+ </Fragment>
+ )}
+ </Fragment>
+ );
+};
+
+export default UsageStatisticsTab;
--- /dev/null
+import UsageStatisticsTab from './UsageStatisticsTab';
+import DetailsTab from './DetailsTab';
+import SupplyTab from './SupplyTab';
+import ProtocolTab from './ProtocolTab';
+import EquipmentAttachmentTab from './EquipmentAttachmentTab';
+import ActivityTab from './ActivityTab';
+import UsageRecordTab from './UsageRecordTab';
+import RFIDDeviceTab from './RFIDDeviceTab';
+
+export { UsageStatisticsTab, DetailsTab, SupplyTab, ProtocolTab, EquipmentAttachmentTab, ActivityTab, UsageRecordTab, RFIDDeviceTab };
--- /dev/null
+import { useRecordContext, useTranslate } from '@applica-software-guru/react-admin';
+
+import { Chip } from '@mui/material';
+import { get } from 'lodash';
+
+const STATUS_COLOR = {
+ OPEN: 'info',
+ CLOSED: 'error'
+};
+const ActivityStatusField = () => {
+ const translate = useTranslate();
+ const record = useRecordContext();
+ const status = get(record, 'status', 'new') || 'new';
+ const color = get(STATUS_COLOR, status, 'info');
+ return <Chip size="small" variant="combined" color={color} label={translate(`ra.activity.status.${status}`)} />;
+};
+
+export default ActivityStatusField;
--- /dev/null
+import { useRecordContext, useTranslate } from '@applica-software-guru/react-admin';
+
+import PropTypes from 'prop-types';
+import { Typography } from '@mui/material';
+import { get } from 'lodash';
+
+const TRANSATABLE_FIELDS = ['entity'];
+const AuditLogMessageField = ({ source }) => {
+ const translate = useTranslate();
+ const record = useRecordContext();
+ const args = get(record, `${source}.args`);
+ const message = get(record, `${source}.message`) || 'ra.audit_log.default_message';
+ const messageArgs =
+ args && args !== null
+ ? Object.keys(args).reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: TRANSATABLE_FIELDS.indexOf(key) !== -1 ? translate(get(args, key, ''), { smart_count: 1 }) : get(args, key, '')
+ }),
+ {}
+ )
+ : {
+ ...record,
+ method: record?.method || 'unknown'
+ };
+
+ return (
+ <Typography variant="body1" color="textSecondary" sx={{ minWidth: 300 }}>
+ {translate(message, messageArgs)}
+ </Typography>
+ );
+};
+
+AuditLogMessageField.propTypes = {
+ source: PropTypes.string.isRequired
+};
+
+export default AuditLogMessageField;
--- /dev/null
+import { ReferenceField, TextField } from '@applica-software-guru/react-admin';
+
+const CityField = (props) => (
+ <ReferenceField {...props} label="city" reference="entities/city">
+ <TextField source="name" />
+ </ReferenceField>
+);
+
+export default CityField;
--- /dev/null
+import { useFormState } from 'react-hook-form';
+import { Collapse, Alert, AlertTitle } from '@mui/material';
+import { useTranslate } from '@applica-software-guru/react-admin';
+
+const DirtyFormPrintAlertField = () => {
+ const { dirtyFields } = useFormState();
+ const isDirty = Object.keys(dirtyFields).length > 0;
+ const translate = useTranslate();
+
+ return (
+ <Collapse in={isDirty}>
+ <Alert severity="warning">
+ <AlertTitle>{translate('ra.form.docx_print.dirty')}</AlertTitle>
+ {translate('ra.form.docx_print.dirty_message')}
+ </Alert>
+ </Collapse>
+ );
+};
+
+export default DirtyFormPrintAlertField;
--- /dev/null
+import { FunctionField } from '@applica-software-guru/react-admin';
+import { get } from 'lodash';
+const formatMoney = (record, source) => {
+ const value = get(record, source);
+ if (typeof value === 'number') {
+ return `€ ${value.toFixed(2).replace('.', ',')}`;
+ }
+ return '';
+};
+
+const MoneyField = (props) => {
+ return <FunctionField {...props} render={(record) => formatMoney(record, props.source)} />;
+};
+
+export default MoneyField;
--- /dev/null
+import { Datagrid, DateField, ReferenceField, ReferenceManyInput, TextField } from '@applica-software-guru/react-admin';
+import { CustomerAreaAutocompleteInput, RFIDDeviceAutocomplete } from 'components/ra-inputs';
+
+const RFIDDeviceTrackManyField = () => {
+ return (
+ <ReferenceManyInput
+ reference="entities/rfid-device-track"
+ target="supplyId"
+ filters={[
+ <CustomerAreaAutocompleteInput source="areaId" display="legend" alwaysOn />,
+ <RFIDDeviceAutocomplete source="gatewayId" display="legend" type="GATEWAY" alwaysOn />
+ ]}
+ sort={{ field: 'ts', order: 'DESC' }}
+ >
+ <Datagrid bulkActionButtons={null}>
+ <TextField source="gateway.name" sortable={false} />
+ <ReferenceField source="areaId" reference="entities/customer-area" sortable={false}>
+ <TextField source="name" />
+ </ReferenceField>
+ <TextField source="additionalData" />
+ <DateField source="ts" showTime />
+ </Datagrid>
+ </ReferenceManyInput>
+ );
+};
+
+export default RFIDDeviceTrackManyField;
--- /dev/null
+import { useRecordContext, useTranslate } from '@applica-software-guru/react-admin';
+
+import { Chip } from '@mui/material';
+import { get } from 'lodash';
+
+const STATUS_COLOR = {
+ RUNNING: 'success',
+ STOPPED: 'secondary'
+};
+const SupplyStatusField = () => {
+ const record = useRecordContext();
+ const translate = useTranslate();
+ const status = get(record, 'usageStatus', 'STOPPED') || 'STOPPED';
+ const color = get(STATUS_COLOR, status, 'secondary');
+ return <Chip variant="combined" color={color} label={translate(`ra.supply.status.${status}`)} />;
+};
+
+export default SupplyStatusField;
--- /dev/null
+import { CoverField, useRecordContext } from '@applica-software-guru/react-admin';
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import { get } from 'lodash';
+
+const UserPictureField = ({ source, ...props }) => {
+ const record = useRecordContext();
+ const image = get(record, source);
+ if (!image) {
+ return null;
+ }
+ return <CoverField {...props} circle width={50} height={50} justify="flex-end" />;
+};
+
+UserPictureField.propTypes = {
+ source: PropTypes.string.isRequired
+};
+
+export default UserPictureField;
--- /dev/null
+export { default as AuditLogMessageField } from './AuditLogMessageField';
+export { default as UserPictureField } from './UserPictureField';
+export { default as CityField } from './CityField';
+export { default as MoneyField } from './MoneyField';
+export { default as ActivityStatusField } from './ActivityStatusField';
+export { default as SupplyStatusField } from './SupplyStatusField';
+export { default as RFIDDeviceTrackManyField } from './RFIDDeviceTrackManyField';
+export { default as DirtyFormPrintAlertField } from './DirtyFormPrintAlertField';
--- /dev/null
+import {
+ ActionsMenu,
+ CardForm,
+ DateInput,
+ DeleteWithConfirmButton,
+ ReadonlyField,
+ SaveButton,
+ SelectInput,
+ SmartTextInput,
+ TextInput,
+ Toolbar,
+ required,
+ useDataProvider,
+ useNotify,
+ usePermissions,
+ useRecordContext,
+ useResourceTitle,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+import {
+ ActivityTypeAutocompleteInput,
+ CustomerAutocompleteInput,
+ MaintenanceManyInput,
+ SupplierAutocompleteInput,
+ SupplyActivityAutocompleteInput
+} from 'components/ra-inputs';
+import { useLocation } from 'react-router-dom';
+import { Grid } from '@mui/material';
+import { EntityBackButton, PrintDocxButton } from 'components/ra-buttons';
+import queryString from 'query-string';
+import { useEffect, useState } from 'react';
+
+const ActivityForm = ({ activityStatus }) => {
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ const dataProvider = useDataProvider();
+ const notify = useNotify();
+ const currentDate = new Date().toISOString().slice(0, 10);
+ const title = useResourceTitle();
+ const { permissions, isLoading } = usePermissions();
+ const canEdit = !isLoading && permissions.includes('activity:new');
+ const canDelete = !isLoading && permissions.includes('activity:delete');
+ const location = useLocation();
+ const queryParams = queryString.parse(location.search);
+ const activityId = queryParams['activity-id'];
+ const activityTypeIdUrl = queryParams['activity-type-id'];
+ const [activity, setActivity] = useState(null);
+
+ useEffect(() => {
+ if (!activityId) return;
+
+ const fetchDetails = async () => {
+ try {
+ const { data } = await dataProvider.getOne('entities/activity', { id: activityId });
+ setActivity(data);
+ } catch (error) {
+ notify('ra.notification.http_error', 'error', {});
+ }
+ };
+
+ fetchDetails();
+ }, [activityId, dataProvider, notify]);
+
+ return (
+ <CardForm>
+ <CardForm.Section
+ toolbar={null}
+ title={title}
+ secondary={
+ record?.id &&
+ canDelete && (
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ )
+ }
+ >
+ <Grid container spacing={spacing}>
+ {record?.id && (
+ <Grid item xs={12} sm={12}>
+ <ReadonlyField source="user.name" />
+ </Grid>
+ )}
+ {!isLoading && permissions.includes('supplier:list') && (
+ <Grid item xs={12} sm={12}>
+ <SupplierAutocompleteInput
+ source="supply.supplierId"
+ defaultValue={activity?.supply.supplierId}
+ disabled={!canEdit}
+ validate={required()}
+ fullWidth
+ />
+ </Grid>
+ )}
+ {!isLoading && permissions.includes('customer:list') && (
+ <Grid item xs={12} sm={12}>
+ <CustomerAutocompleteInput
+ source="customer.id"
+ defaultValue={activity?.customer.id}
+ disabled={!canEdit}
+ validate={required()}
+ fullWidth
+ />
+ </Grid>
+ )}
+ {!isLoading && permissions.includes('supply:list') && (
+ <Grid item xs={12} sm={12}>
+ <SupplyActivityAutocompleteInput
+ source="supplyId"
+ defaultValue={activity?.supplyId}
+ disabled={!canEdit}
+ validate={required()}
+ fullWidth
+ />
+ </Grid>
+ )}
+ {!isLoading && permissions.includes('activity-type:list') && (
+ <Grid item xs={12} sm={12}>
+ <ActivityTypeAutocompleteInput
+ source="activityTypeId"
+ defaultValue={activityTypeIdUrl}
+ disabled={!canEdit}
+ validate={required()}
+ fullWidth
+ />
+ </Grid>
+ )}
+ <Grid item xs={12} sm={12}>
+ <TextInput source="title" validate={required()} disabled={!canEdit} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <SmartTextInput source="description" validate={required()} maxLength={500} disabled={!canEdit} multiline rows={3} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <DateInput source="date" defaultValue={currentDate} validate={required()} disabled={!canEdit} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <SelectInput
+ source="status"
+ validate={required()}
+ fullWidth
+ choices={activityStatus}
+ defaultValue={'OPEN'}
+ disabled={!canEdit}
+ />
+ </Grid>
+ </Grid>
+ <Toolbar>
+ {canEdit && <SaveButton />}
+ <PrintDocxButton type="activity" />
+ {record?.id && <EntityBackButton type="supply" id={record?.supplyId} label="resources.entities/activity.actions.backToFather" />}
+ </Toolbar>
+ </CardForm.Section>
+ {record?.id && (
+ <CardForm.Section title="resources.entities/maintenance.title">
+ <MaintenanceManyInput />
+ </CardForm.Section>
+ )}
+ </CardForm>
+ );
+};
+
+export default ActivityForm;
--- /dev/null
+import { AttachmentInput, SimpleForm, TextInput, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const ActivityTypeForm = () => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="description" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <AttachmentInput source="attachment" multiple={false} label={false} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default ActivityTypeForm;
--- /dev/null
+import { SimpleForm, SmartTextInput, TextInput, required, useRecordContext, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { CustomerOfficeAutocompleteInput, RFIDDeviceAutocomplete } from '../ra-inputs';
+import { Grid } from '@mui/material';
+
+const CustomerAreaForm = () => {
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ return (
+ <SimpleForm secondary={null}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12}>
+ <CustomerOfficeAutocompleteInput source="officeId" filter={{ customerId: record?.customerId }} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="name" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <SmartTextInput source="description" validate={required()} fullWidth maxLength={200} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <RFIDDeviceAutocomplete type="GATEWAY" source="rfidDeviceId" />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default CustomerAreaForm;
--- /dev/null
+import { BooleanInput, SimpleForm, TextInput, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const CustomerCreateForm = (props) => {
+ const { spacing } = useThemeConfig();
+
+ return (
+ <SimpleForm {...props} defaultValues={{ active: true }}>
+ <Grid container spacing={spacing}>
+ <Grid item lg={7} md={8} xs={12}>
+ <TextInput source="name" fullWidth />
+ </Grid>
+ <Grid item lg={3} md={4} xs={12}>
+ <TextInput source="vatCode" />
+ </Grid>
+ <Grid item lg={2} xs={12}>
+ <BooleanInput source="active" />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default CustomerCreateForm;
--- /dev/null
+import {
+ ActionsMenu,
+ BooleanInput,
+ DeleteWithConfirmButton,
+ LongForm,
+ MainCard,
+ SaveButton,
+ TextInput,
+ Toolbar,
+ required,
+ useRecordContext,
+ useResourceTitle,
+ useThemeConfig,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import { CustomerAreaManyInput, CustomerOfficeManyInput, CustomerReferentManyInput } from '../ra-inputs';
+import { HomeOutlined, InfoCircleOutlined, ShareAltOutlined, UserAddOutlined } from '@ant-design/icons';
+
+import { FixedMainCard } from '../layout';
+import { Grid } from '@mui/material';
+
+const CustomerEditForm = () => {
+ const { spacing } = useThemeConfig();
+ const translate = useTranslate();
+ const record = useRecordContext();
+ const title = useResourceTitle();
+ return (
+ <LongForm>
+ <LongForm.Tab
+ id="details"
+ icon={<InfoCircleOutlined />}
+ label="resources.entities/customer.tabs.details"
+ title={title}
+ secondary={
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ }
+ >
+ <MainCard title={translate('resources.entities/customer.tabs.details')}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12}>
+ <TextInput source="name" validate={required()} />
+ </Grid>
+ <Grid item lg={9} xs={12}>
+ <TextInput source="vatCode" validate={required()} />
+ </Grid>
+ <Grid item lg={3} xs={12}>
+ <BooleanInput source="active" />
+ </Grid>
+ </Grid>
+ <Toolbar>
+ <SaveButton />
+ </Toolbar>
+ </MainCard>
+ </LongForm.Tab>
+ {record?.id && (
+ <LongForm.Tab id="offices" icon={<HomeOutlined />} label="resources.entities/customer.tabs.offices">
+ <FixedMainCard>
+ <CustomerOfficeManyInput />
+ </FixedMainCard>
+ </LongForm.Tab>
+ )}
+ {record?.id && (
+ <LongForm.Tab id="areas" icon={<ShareAltOutlined />} label="resources.entities/customer.tabs.areas">
+ <FixedMainCard>
+ <CustomerAreaManyInput />
+ </FixedMainCard>
+ </LongForm.Tab>
+ )}
+ {record?.id && (
+ <LongForm.Tab id="referents" icon={<UserAddOutlined />} label="resources.entities/customer.tabs.referents">
+ <FixedMainCard>
+ <CustomerReferentManyInput />
+ </FixedMainCard>
+ </LongForm.Tab>
+ )}
+ </LongForm>
+ );
+};
+
+export default CustomerEditForm;
--- /dev/null
+import { BooleanInput, SimpleForm, TextInput, email, required, useThemeConfig } from '@applica-software-guru/react-admin';
+import { CityAutocompleteInput, NationAutocompleteInput, ProvinceAutocompleteInput, RegionAutocompleteInput } from '../ra-inputs';
+
+import { Grid } from '@mui/material';
+import PropTypes from 'prop-types';
+
+const CustomerOfficeForm = ({ modal, ...props }) => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm modal={modal} {...props}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={6}>
+ <NationAutocompleteInput source="nationId" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <RegionAutocompleteInput source="regionId" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ProvinceAutocompleteInput source="provinceId" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <CityAutocompleteInput filterByProvince={true} source="cityId" validate={required()} />
+ </Grid>
+ <Grid item xs={12}>
+ <TextInput source="address" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12}>
+ <TextInput source="email" validate={[required(), email()]} />
+ </Grid>
+ <Grid item xs={12}>
+ <BooleanInput source="headquarter" />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+CustomerOfficeForm.propTypes = {
+ modal: PropTypes.bool
+};
+
+CustomerOfficeForm.defaultProps = {
+ modal: true
+};
+
+export default CustomerOfficeForm;
--- /dev/null
+import { SimpleForm, TextInput, email, required, useRecordContext, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { CustomerOfficeAutocompleteInput } from '../ra-inputs';
+import { Grid } from '@mui/material';
+import PropTypes from 'prop-types';
+
+const CustomerReferentForm = ({ modal, ...props }) => {
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ return (
+ <SimpleForm modal={modal} {...props} defaultValues={{ active: true, required: true }}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12}>
+ <CustomerOfficeAutocompleteInput source="officeId" validate={required()} filter={{ customerId: record?.customerId }} />
+ </Grid>
+ <Grid item xs={6}>
+ <TextInput source="name" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={6}>
+ <TextInput source="surname" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={6}>
+ <TextInput source="email" validate={email()} fullWidth />
+ </Grid>
+ <Grid item xs={6}>
+ <TextInput source="mobile" fullWidth />
+ </Grid>
+ <Grid item xs={6}>
+ <TextInput source="unit" fullWidth />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+CustomerReferentForm.propTypes = {
+ modal: PropTypes.bool
+};
+
+CustomerReferentForm.defaultProps = {
+ modal: true
+};
+
+export default CustomerReferentForm;
--- /dev/null
+import { ReadonlyField, SimpleForm, TextInput, required, useRecordContext, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const DeviceForm = () => {
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ return (
+ <SimpleForm>
+ <Grid container spacing={spacing}>
+ {record?.id && (
+ <Grid item xs={12} sm={12}>
+ <ReadonlyField source="registrationDate" fullWidth />
+ </Grid>
+ )}
+ <Grid item xs={12} sm={12}>
+ <TextInput source="code" validate={required()} fullWidth />
+ </Grid>
+ {record?.id && (
+ <Grid item xs={12} sm={12}>
+ <TextInput source="secret" validate={required()} fullWidth />
+ </Grid>
+ )}
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default DeviceForm;
--- /dev/null
+import { AttachmentInput, SimpleForm, TextInput, required, useThemeConfig, useTranslate } from '@applica-software-guru/react-admin';
+import { useWatch } from 'react-hook-form';
+import { Grid, Typography, Collapse } from '@mui/material';
+import { DocxTemplateTypeSelectInput } from '../ra-inputs';
+import { API_URL } from '../../config';
+
+const TypeHelperLink = () => {
+ const type = useWatch({ name: 'type' });
+ const translate = useTranslate();
+
+ return (
+ <Collapse in={!!type}>
+ <Typography
+ variant="caption"
+ color="textSecondary"
+ sx={{ ml: 1, mr: 1 }}
+ dangerouslySetInnerHTML={{
+ __html: translate('resources.entities/docx-templates.fields.type.link', {
+ url: `${API_URL}/docx/helper/${type}.docx`
+ })
+ }}
+ />
+ </Collapse>
+ );
+};
+
+const DocxTemplateForm = () => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="name" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <DocxTemplateTypeSelectInput source="type" validate={required()} fullWidth />
+ <TypeHelperLink />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <AttachmentInput source="attachment" validate={required()} multiple={false} label={false} accept=".docx" />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default DocxTemplateForm;
--- /dev/null
+import { AttachmentInput, SimpleForm, TextInput, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const EquipmentAttachmentForm = (props) => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm {...props}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="description" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <AttachmentInput source="attachment" multiple={false} label={false} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default EquipmentAttachmentForm;
--- /dev/null
+import { SimpleForm, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { EquipmentTypeAutocompleteInput } from 'components/ra-inputs';
+import { Grid } from '@mui/material';
+
+const EquipmentCreateForm = (props) => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm {...props}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <EquipmentTypeAutocompleteInput source="equipmentTypeId" validate={required()} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default EquipmentCreateForm;
--- /dev/null
+import { SimpleForm, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { EquipmentTypeAutocompleteInput, SupplierAutocompleteInput } from 'components/ra-inputs';
+import { Grid } from '@mui/material';
+
+const EquipmentCreateInDialogForm = (props) => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm {...props}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <SupplierAutocompleteInput source="supplierId" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <EquipmentTypeAutocompleteInput source="equipmentTypeId" validate={required()} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default EquipmentCreateInDialogForm;
--- /dev/null
+import {
+ ActionsMenu,
+ CardForm,
+ DeleteWithConfirmButton,
+ ReadonlyField,
+ Toolbar,
+ usePermissions,
+ useRecordContext,
+ useResourceTitle,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { SupplierBackButton } from 'components/ra-buttons';
+import { EquipmentAttachmentManyInput, SupplyManyInput } from 'components/ra-inputs';
+
+const EquipmentEditForm = () => {
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ const title = useResourceTitle();
+ const { permissions, isLoading } = usePermissions();
+ const canEdit = !isLoading && permissions ? permissions['equipment:edit'] : false;
+ return (
+ <CardForm>
+ <CardForm.Section
+ toolbar={null}
+ title={title}
+ secondary={
+ canEdit ? (
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ ) : (
+ <></>
+ )
+ }
+ >
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <ReadonlyField source="equipmentType.name" />
+ </Grid>
+ </Grid>
+ {!isLoading && permissions.includes('supplier:edit') && (
+ <Toolbar>
+ <SupplierBackButton supplierId={record.supplierId} tab="equipment" />
+ </Toolbar>
+ )}
+ </CardForm.Section>
+ <Grid item xs={12} sm={!isLoading && permissions.includes('supply:new') && 6}>
+ <CardForm.Section title="resources.entities/equipment-attachment.title">
+ <EquipmentAttachmentManyInput />
+ </CardForm.Section>
+ </Grid>
+ {!isLoading && permissions.includes('supply:new') && (
+ <Grid item xs={12} sm={6}>
+ <CardForm.Section title="resources.entities/supply.title">
+ <SupplyManyInput />
+ </CardForm.Section>
+ </Grid>
+ )}
+ </CardForm>
+ );
+};
+
+export default EquipmentEditForm;
--- /dev/null
+import { AttachmentInput, SimpleForm, TextInput, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const EquipmentTypeAttachmentForm = (props) => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm {...props}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="description" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <AttachmentInput source="attachment" multiple={false} label={false} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default EquipmentTypeAttachmentForm;
--- /dev/null
+import { BooleanInput, SimpleForm, TextInput, required, useResourceTitle, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const EquipmentTypeCreateForm = (props) => {
+ const { spacing } = useThemeConfig();
+ const title = useResourceTitle();
+
+ return (
+ <SimpleForm {...props} title={title} defaultValues={{ active: true }}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12}>
+ <TextInput source="name" validate={required()} />
+ </Grid>
+ <Grid item xs={12}>
+ <BooleanInput source="accessory" validate={required()} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default EquipmentTypeCreateForm;
--- /dev/null
+import {
+ ActionsMenu,
+ BooleanInput,
+ CardForm,
+ DeleteWithConfirmButton,
+ MainCard,
+ NumberInput,
+ SaveButton,
+ TextInput,
+ Toolbar,
+ required,
+ useRecordContext,
+ useResourceTitle,
+ useThemeConfig,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+
+import NavigateNextIcon from '@mui/icons-material/NavigateNext';
+import { Link } from 'react-router-dom';
+import { Typography } from '@mui/material';
+import Breadcrumbs from '@mui/material/Breadcrumbs';
+import { Grid } from '@mui/material';
+import { EntityBackButton } from 'components/ra-buttons';
+import { EquipmentTypeAttachmentManyInput, EquipmentTypeManyInput } from 'components/ra-inputs';
+
+const EquipmentTypeBreadcrumbs = ({ record }) => {
+ const parents = record?.parents;
+ if (!Array.isArray(parents)) {
+ return null;
+ }
+
+ const reversedParents = [...parents].reverse();
+
+ return (
+ <Breadcrumbs separator={<NavigateNextIcon fontSize="small" />}>
+ {reversedParents.map(({ id, name }, index) => (
+ <Link to={`/entities/equipment-type/${id}`} key={index} style={{ textDecoration: 'none', color: 'rgba(0, 0, 0, 0.7)' }}>
+ {name}
+ </Link>
+ ))}
+ <Typography style={{ textDecoration: 'none', color: 'rgb(0, 0, 0)' }}>{record?.name}</Typography>
+ </Breadcrumbs>
+ );
+};
+
+const EquipmentTypeEditForm = () => {
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ const title = useResourceTitle();
+ const translate = useTranslate();
+
+ return (
+ <CardForm>
+ <CardForm.Section
+ toolbar={null}
+ title={title}
+ subheader={<EquipmentTypeBreadcrumbs record={record} />}
+ secondary={
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ }
+ >
+ <Grid container spacing={spacing}>
+ <Grid item xs={12}>
+ <TextInput source="name" validate={required()} />
+ </Grid>
+ <Grid item xs={12}>
+ <NumberInput
+ source="breakpointInHours"
+ validate={required()}
+ helperText={translate('resources.entities/equipment-type.fields.breakpointInHours.help')}
+ />
+ </Grid>
+ <Grid item xs={12}>
+ <BooleanInput source="accessory" validate={required()} />
+ </Grid>
+ </Grid>
+
+ <Toolbar>
+ <SaveButton />
+ {record?.parentId && <EntityBackButton type="equipment-type" id={record.parentId} />}
+ </Toolbar>
+ </CardForm.Section>
+ <Grid item xs={12} sm={6}>
+ <MainCard title={translate('resources.entities/equipment-type-attachment.title.list')}>
+ <EquipmentTypeAttachmentManyInput />
+ </MainCard>
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <MainCard title={translate('resources.entities/equipment-type.title.list')}>
+ <EquipmentTypeManyInput />
+ </MainCard>
+ </Grid>
+ </CardForm>
+ );
+};
+
+export default EquipmentTypeEditForm;
--- /dev/null
+import { SimpleForm, SmartTextInput, TextInput, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { LangSelectInput } from '../ra-inputs';
+
+const I18nMessageForm = () => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm defaultValues={{ lang: 'it' }}>
+ <Grid container spacing={spacing}>
+ <Grid item lg={3} md={3} sm={4} xs={12}>
+ <LangSelectInput source="lang" />
+ </Grid>
+ <Grid item lg={9} md={9} sm={8} xs={12}>
+ <TextInput source="code" fullWidth helperText="Code mapped/or to map for the message" />
+ </Grid>
+ <Grid item xs={12}>
+ <SmartTextInput maxLength={200} source="text" fullWidth multiline rows={3} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+export default I18nMessageForm;
--- /dev/null
+import {
+ AttachmentInput,
+ DateInput,
+ SimpleForm,
+ SmartTextInput,
+ TextInput,
+ required,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { ActivitySelectInput, InterventionArrayInput } from 'components/ra-inputs';
+
+const MaintenanceCreateForm = () => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <ActivitySelectInput source="activityId" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="interventionNumber" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="title" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <SmartTextInput source="description" validate={required()} maxLength={500} multiline rows={3} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <DateInput source="date" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <AttachmentInput source="attachment" multiple={false} label={false} />
+ </Grid>
+ <InterventionArrayInput source="interventions" />
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default MaintenanceCreateForm;
--- /dev/null
+import { ActivitySelectInput, InterventionArrayInput } from 'components/ra-inputs';
+import {
+ ActionsMenu,
+ AttachmentInput,
+ CardForm,
+ DateInput,
+ DeleteWithConfirmButton,
+ ReadonlyField,
+ SaveButton,
+ SmartTextInput,
+ TextInput,
+ Toolbar,
+ required,
+ usePermissions,
+ useRecordContext,
+ useResourceTitle,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { EntityBackButton, PrintDocxButton } from 'components/ra-buttons';
+import { DirtyFormPrintAlertField } from '../ra-fields';
+
+const MaintenanceEditForm = () => {
+ const { spacing } = useThemeConfig();
+ const { permissions, isLoading } = usePermissions();
+ const canDelete = !isLoading && permissions.includes('maintenance:delete');
+ const record = useRecordContext();
+ const title = useResourceTitle();
+ return (
+ <CardForm>
+ <CardForm.Section
+ toolbar={null}
+ title={title}
+ secondary={
+ canDelete && (
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ )
+ }
+ >
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="user.name" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ReadonlyField source="protocol.protocolNumber" />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <ActivitySelectInput source="activityId" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="interventionNumber" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="title" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <SmartTextInput source="description" validate={required()} maxLength={500} multiline rows={3} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <DateInput source="date" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <AttachmentInput source="attachment" multiple={false} label={false} />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <InterventionArrayInput source="interventions" />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <DirtyFormPrintAlertField />
+ </Grid>
+ </Grid>
+ <Toolbar>
+ <SaveButton />
+ <PrintDocxButton type="maintenance" />
+ <EntityBackButton type="activity" id={record?.activityId} label="resources.entities/maintenance.actions.backToFather" />
+ </Toolbar>
+ </CardForm.Section>
+ </CardForm>
+ );
+};
+
+export default MaintenanceEditForm;
--- /dev/null
+import {
+ ActionsMenu,
+ CardForm,
+ DateInput,
+ DeleteWithConfirmButton,
+ SaveButton,
+ TextInput,
+ Toolbar,
+ required,
+ useRecordContext,
+ useResourceTitle,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { PriceListBackButton } from 'components/ra-buttons';
+import { PriceListItemManyInput } from 'components/ra-inputs';
+
+const PriceListForm = ({ toolbar }) => {
+ const { spacing } = useThemeConfig();
+ const record = useRecordContext();
+ const title = useResourceTitle();
+
+ return (
+ <CardForm>
+ <CardForm.Section
+ toolbar={toolbar}
+ title={title}
+ secondary={
+ record?.id && (
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ )
+ }
+ >
+ <Grid container spacing={spacing}>
+ <Grid item xs={12}>
+ <TextInput source="name" validate={required()} />
+ </Grid>
+ <Grid item xs={12}>
+ <DateInput source="start" />
+ </Grid>
+ <Grid item xs={12}>
+ <DateInput source="end" />
+ </Grid>
+ </Grid>
+ {!toolbar && (
+ <Toolbar>
+ <SaveButton />
+ {record?.id && <PriceListBackButton supplierId={record?.supplierId} tab="price-list" />}
+ </Toolbar>
+ )}
+ </CardForm.Section>
+
+ {record?.id && (
+ <CardForm.Section title="resources.entities/price-list-item.title.list">
+ <PriceListItemManyInput />
+ </CardForm.Section>
+ )}
+ </CardForm>
+ );
+};
+
+export default PriceListForm;
--- /dev/null
+import { SimpleForm, TextInput, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { EquipmentSelectInput } from 'components/ra-inputs';
+
+const PriceListItemEditForm = () => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <EquipmentSelectInput source="equipmentId" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="price" validate={required()} fullWidth />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default PriceListItemEditForm;
--- /dev/null
+import { EquipmentAutocompleteInput, MoneyInput } from 'components/ra-inputs';
+import { SimpleForm, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const PriceListItemForm = () => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <EquipmentAutocompleteInput source="equipmentId" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <MoneyInput source="price" validate={required()} fullWidth />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default PriceListItemForm;
--- /dev/null
+import { AttachmentInput, SimpleForm, TextInput, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const ProtocolAttachmentForm = (props) => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm {...props}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="description" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <AttachmentInput source="attachment" multiple={false} label={false} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default ProtocolAttachmentForm;
--- /dev/null
+import {
+ DateInput,
+ SimpleForm,
+ SmartTextInput,
+ TextInput,
+ required,
+ usePermissions,
+ useResourceTitle,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+import { CustomerAutocompleteInput } from 'components/ra-inputs';
+import { Grid } from '@mui/material';
+
+const ProtocolDialogForm = () => {
+ const { spacing } = useThemeConfig();
+ const title = useResourceTitle();
+ const { permissions, isLoading } = usePermissions();
+ const isEditable = !isLoading && permissions.includes('protocol:new');
+ return (
+ <SimpleForm title={title}>
+ <Grid container spacing={spacing}>
+ {!isLoading && permissions.includes('customer:list') && (
+ <Grid item xs={12} sm={12}>
+ <CustomerAutocompleteInput source="customerId" validate={required()} fullWidth disabled={!isEditable} />
+ </Grid>
+ )}
+ <Grid item xs={12}>
+ <TextInput source="title" validate={required()} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12}>
+ <SmartTextInput source="description" validate={required()} maxLength={500} multiline rows={3} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12}>
+ <TextInput source="protocolNumber" validate={required()} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12}>
+ <DateInput source="protocolDate" validate={required()} disabled={!isEditable} />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default ProtocolDialogForm;
--- /dev/null
+import {
+ ActionsMenu,
+ DateInput,
+ DeleteWithConfirmButton,
+ LongForm,
+ MainCard,
+ SaveButton,
+ SmartTextInput,
+ TextInput,
+ Toolbar,
+ required,
+ usePermissions,
+ useResourceTitle,
+ useThemeConfig,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import { ProtocolServiceArrayInput, CustomerAutocompleteInput, ProtocolAttachmentManyInput } from 'components/ra-inputs';
+import { InfoCircleOutlined, FileOutlined, AppstoreAddOutlined } from '@ant-design/icons';
+import { Grid } from '@mui/material';
+
+const ProtocolForm = () => {
+ const { spacing } = useThemeConfig();
+ const title = useResourceTitle();
+ const translate = useTranslate();
+ const { permissions, isLoading } = usePermissions();
+ const isEditable = !isLoading && permissions.includes('protocol:new');
+ return (
+ <LongForm>
+ <LongForm.Tab
+ id="details"
+ icon={<InfoCircleOutlined />}
+ title={title}
+ label="resources.entities/protocol.tabs.details"
+ secondary={
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ }
+ >
+ <MainCard
+ title={translate('resources.entities/protocol.tabs.details')}
+ sx={{ mb: spacing }}
+ secondary={
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ }
+ >
+ <Grid container spacing={spacing}>
+ {!isLoading && permissions.includes('customer:list') && (
+ <Grid item xs={12} sm={12}>
+ <CustomerAutocompleteInput source="customerId" validate={required()} fullWidth disabled={!isEditable} />
+ </Grid>
+ )}
+ <Grid item xs={12}>
+ <TextInput source="title" validate={required()} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12}>
+ <SmartTextInput source="description" validate={required()} maxLength={500} multiline rows={3} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12}>
+ <TextInput source="protocolNumber" validate={required()} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12}>
+ <DateInput source="protocolDate" validate={required()} disabled={!isEditable} />
+ </Grid>
+ </Grid>
+ <Toolbar>
+ <SaveButton />
+ </Toolbar>
+ </MainCard>
+ </LongForm.Tab>
+ <LongForm.Tab icon={<FileOutlined />} id="attachment" label="resources.entities/protocol-attachment.title">
+ <ProtocolAttachmentManyInput />
+ </LongForm.Tab>
+ <LongForm.Tab icon={<AppstoreAddOutlined />} id="service" label="resources.entities/protocol-service.title">
+ <MainCard title={translate('resources.entities/protocol-service.title')}>
+ <Grid item xs={12} sm={12}>
+ <ProtocolServiceArrayInput source="services" />
+ </Grid>
+ <Toolbar>
+ <SaveButton />
+ </Toolbar>
+ </MainCard>
+ </LongForm.Tab>
+ </LongForm>
+ );
+};
+
+export default ProtocolForm;
--- /dev/null
+import { SimpleForm, TextInput, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { RFIDDeviceTypeSelectInput } from 'components/ra-inputs';
+
+const RFIDDeviceForm = () => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="name" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="code" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <RFIDDeviceTypeSelectInput source="type" validate={required()} fullWidth />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default RFIDDeviceForm;
--- /dev/null
+import { SimpleForm, TextInput, required, useThemeConfig } from '@applica-software-guru/react-admin';
+import { CityAutocompleteInput } from 'components/ra-inputs';
+
+import { Grid } from '@mui/material';
+
+const SupplierCreateForm = (props) => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm {...props}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="businessName" label="resources.entities/supplier.fields.businessName" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="vatNumber" label="resources.entities/supplier.fields.vatNumber" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="address" label="resources.entities/supplier.fields.address" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <CityAutocompleteInput
+ filterByProvince={false}
+ source="cityId"
+ label="resources.entities/supplier.fields.cityId"
+ validate={required()}
+ />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default SupplierCreateForm;
--- /dev/null
+import {
+ ActionsMenu,
+ DeleteWithConfirmButton,
+ LongForm,
+ MainCard,
+ SaveButton,
+ TextInput,
+ Toolbar,
+ required,
+ useRecordContext,
+ useResourceTitle,
+ useThemeConfig,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import { CityAutocompleteInput, EquipmentManyInput, PriceListManyInput, SupplierReferentManyInput } from 'components/ra-inputs';
+import { ContainerOutlined, InfoCircleOutlined, ToolOutlined } from '@ant-design/icons';
+
+import { FixedMainCard } from '../layout';
+import { Grid } from '@mui/material';
+
+const SupplierForm = () => {
+ const { spacing } = useThemeConfig();
+ const translate = useTranslate();
+ const record = useRecordContext();
+ const title = useResourceTitle();
+ return (
+ <LongForm defaultValues={{ active: true }} syncWithLocation>
+ <LongForm.Tab
+ id="details"
+ icon={<InfoCircleOutlined />}
+ title={title}
+ label="resources.entities/customer.tabs.details"
+ secondary={
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ }
+ >
+ <MainCard
+ title={title}
+ sx={{ mb: spacing }}
+ secondary={
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ }
+ >
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="businessName" label="resources.entities/supplier.fields.businessName" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="vatNumber" label="resources.entities/supplier.fields.vatNumber" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="address" label="resources.entities/supplier.fields.address" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <CityAutocompleteInput
+ filterByProvince={false}
+ source="cityId"
+ label="resources.entities/supplier.fields.cityId"
+ validate={required()}
+ />
+ </Grid>
+ </Grid>
+ <Toolbar>
+ <SaveButton />
+ </Toolbar>
+ </MainCard>
+ {record?.id && (
+ <FixedMainCard title={translate('resources.entities/supplier-referent.title')}>
+ <SupplierReferentManyInput />
+ </FixedMainCard>
+ )}
+ </LongForm.Tab>
+ {record?.id && (
+ <LongForm.Tab id="equipment" icon={<ToolOutlined />} label="resources.entities/supplier.tabs.equipment">
+ <FixedMainCard title={translate('resources.entities/supplier.tabs.equipment')}>
+ <EquipmentManyInput />
+ </FixedMainCard>
+ </LongForm.Tab>
+ )}
+ {record?.id && (
+ <LongForm.Tab id="price-list" icon={<ContainerOutlined />} label="resources.entities/supplier.tabs.price-list">
+ <FixedMainCard title={translate('resources.entities/supplier.tabs.price-list')}>
+ <PriceListManyInput />
+ </FixedMainCard>
+ </LongForm.Tab>
+ )}
+ </LongForm>
+ );
+};
+
+export default SupplierForm;
--- /dev/null
+import { SimpleForm, TextInput, email, required, useThemeConfig } from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+
+const SupplierReferent = (props) => {
+ const { spacing } = useThemeConfig();
+ return (
+ <SimpleForm secondary={null} {...props}>
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="name" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="surname" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="email" validate={[required(), email()]} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="phone" validate={required()} fullWidth />
+ </Grid>
+ <Grid item xs={12} sm={12}>
+ <TextInput source="department" validate={required()} fullWidth />
+ </Grid>
+ </Grid>
+ </SimpleForm>
+ );
+};
+
+export default SupplierReferent;
--- /dev/null
+import {
+ ActionsMenu,
+ CardForm,
+ DateInput,
+ DeleteWithConfirmButton,
+ NumberInput,
+ TextInput,
+ required,
+ useResourceTitle,
+ useThemeConfig,
+ SaveButton,
+ Toolbar
+} from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { CustomerOfficeAutocompleteInput, CustomerReadOnlyInput, ProtocolAutoComplete, RFIDDeviceAutocomplete } from 'components/ra-inputs';
+import CustomerAreaAutocompleteInput from 'components/ra-inputs/CustomerAreaAutocompleteInput';
+
+const SupplyDialogForm = () => {
+ const { spacing } = useThemeConfig();
+ const title = useResourceTitle();
+ return (
+ <CardForm>
+ <CardForm.Section
+ toolbar={null}
+ title={title}
+ secondary={
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ }
+ >
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={6}>
+ <ProtocolAutoComplete source="protocolId" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <CustomerReadOnlyInput source="protocol.customerId" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <NumberInput source="quantity" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <DateInput source="orderDate" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <DateInput source="deliveryDate" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="serialNumber" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="ddtNumber" validate={required()} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <RFIDDeviceAutocomplete source="rfidDeviceId" isAreaPresent={true} type="TAG" />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <CustomerOfficeAutocompleteInput source="area.officeId" isAreaPresent={true} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <CustomerAreaAutocompleteInput source="areaId" isAreaPresent={true} />
+ </Grid>
+ </Grid>
+ <Toolbar>
+ <SaveButton />
+ </Toolbar>
+ </CardForm.Section>
+ </CardForm>
+ );
+};
+
+export default SupplyDialogForm;
--- /dev/null
+import {
+ ActionsMenu,
+ CardForm,
+ DateInput,
+ DeleteWithConfirmButton,
+ NumberInput,
+ TextInput,
+ required,
+ useResourceTitle,
+ useThemeConfig,
+ SaveButton,
+ Toolbar,
+ usePermissions,
+ useRecordContext,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { GenerateQRCodePdfButton } from 'components/ra-buttons';
+import { RFIDDeviceTrackManyField, SupplyStatusField } from 'components/ra-fields';
+import {
+ CustomerOfficeAutocompleteInput,
+ EquipmentAutocompleteInput,
+ ProtocolAutoComplete,
+ RFIDDeviceAutocomplete,
+ SupplierAutocompleteInput
+} from 'components/ra-inputs';
+import { CustomerReadOnlyInput } from 'components/ra-inputs';
+import CustomerAreaAutocompleteInput from 'components/ra-inputs/CustomerAreaAutocompleteInput';
+import { Fragment } from 'react';
+import { SupplyLastTrackAlert } from '../ra-alerts';
+
+const SupplyForm = () => {
+ const { spacing } = useThemeConfig();
+ const title = useResourceTitle();
+ const { permissions, isLoading } = usePermissions();
+ const isEditable = !isLoading && permissions.includes('supply:new');
+ const translate = useTranslate();
+ const record = useRecordContext();
+ return (
+ <CardForm defaultValues={{ usageStatus: 'STOPPED' }}>
+ <CardForm.Section
+ toolbar={null}
+ title={
+ <Fragment>
+ {title} {record?.id && <SupplyStatusField record={record} />}
+ </Fragment>
+ }
+ secondary={
+ isEditable && (
+ <ActionsMenu>
+ <DeleteWithConfirmButton />
+ </ActionsMenu>
+ )
+ }
+ >
+ <Grid container spacing={spacing}>
+ {!isLoading && permissions.includes('supplier:list') && (
+ <Grid item xs={12} sm={6}>
+ <SupplierAutocompleteInput source="supplierId" validate={required()} disabled={!isEditable} />
+ </Grid>
+ )}
+ <Grid item xs={12} sm={6}>
+ <EquipmentAutocompleteInput source="equipmentId" validate={required()} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <ProtocolAutoComplete source="protocolId" validate={required()} disabled={!isEditable} />
+ </Grid>
+ {!isLoading && permissions.includes('customer:list') && (
+ <Grid item xs={12} sm={6}>
+ <CustomerReadOnlyInput source="protocol.customerId" />
+ </Grid>
+ )}
+ <Grid item xs={12} sm={6}>
+ <NumberInput source="quantity" validate={required()} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <DateInput source="orderDate" disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <DateInput source="deliveryDate" disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="serialNumber" validate={required()} disabled={!isEditable} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextInput source="ddtNumber" validate={required()} disabled={!isEditable} />
+ </Grid>
+ {!isLoading && permissions.includes('rfid-device:new') && (
+ <Grid item xs={12} sm={6}>
+ <RFIDDeviceAutocomplete source="rfidDeviceId" isAreaPresent={true} type="TAG" />
+ </Grid>
+ )}
+ {!isLoading && permissions.includes('customer:new') && (
+ <Fragment>
+ <Grid item xs={12} sm={6}>
+ <CustomerOfficeAutocompleteInput source="area.officeId" isAreaPresent={true} />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <CustomerAreaAutocompleteInput source="areaId" isAreaPresent={true} />
+ </Grid>
+ </Fragment>
+ )}
+ <Grid item xs={12} sm={6}>
+ <NumberInput
+ source="breakpointInHours"
+ disabled={!isEditable}
+ helperText={translate('resources.entities/supply.fields.breakpointInHours.help')}
+ />
+ </Grid>
+ </Grid>
+ {!isLoading && permissions.includes('rfid-device:list') && <SupplyLastTrackAlert />}
+ <Toolbar>
+ <GenerateQRCodePdfButton />
+ {isEditable && <SaveButton />}
+ </Toolbar>
+ </CardForm.Section>
+ {record?.id && record?.rfidDeviceId && (
+ <CardForm.Section title="resources.entities/supply.tabs.rfid-device-track">
+ <RFIDDeviceTrackManyField />
+ </CardForm.Section>
+ )}
+ </CardForm>
+ );
+};
+
+export default SupplyForm;
--- /dev/null
+import { ImageInput, SelectArrayInput, SimpleForm, TextInput, required, useThemeConfig } from '@applica-software-guru/react-admin';
+import { useFormContext, useWatch } from 'react-hook-form';
+
+import { CustomerAutocompleteInput } from 'components/ra-inputs';
+import { Grid } from '@mui/material';
+import PropTypes from 'prop-types';
+import { useEffect } from 'react';
+
+const UserFormContext = ({ configuredRoles }) => {
+ const { spacing } = useThemeConfig();
+ const { setValue } = useFormContext();
+ const customerId = useWatch({
+ name: 'customerId'
+ });
+ const roles = useWatch({
+ name: 'roles'
+ });
+
+ useEffect(() => setValue('roles', customerId == null ? [] : ['ROLE_CUSTOMER']), [customerId, setValue]);
+
+ return (
+ <Grid container spacing={spacing}>
+ <Grid item lg={6} xs={12}>
+ <TextInput source="email" fullWidth helperText="E-mail (ex. roob@applica.it)" />
+ </Grid>
+ <Grid item lg={6} xs={12}>
+ <TextInput source="password" fullWidth type="password" />
+ </Grid>
+ <Grid item lg={12} xs={12}>
+ <TextInput source="name" fullWidth />
+ </Grid>
+ <Grid item lg={12} xs={12}>
+ <CustomerAutocompleteInput source="customerId" fullWidth validate={roles?.includes('ROLE_CUSTOMER') ? required() : []} />
+ </Grid>
+ {!customerId && (
+ <Grid item lg={12} xs={12}>
+ <SelectArrayInput source="roles" label="ra.user.roles" choices={configuredRoles} fullWidth />
+ </Grid>
+ )}
+ <Grid item lg={12} xs={12}>
+ <ImageInput source="image" accept="image/*" multiple={false} fullWidth />
+ </Grid>
+ </Grid>
+ );
+};
+
+const UserForm = ({ configuredRoles }) => {
+ return (
+ <SimpleForm defaultValues={{ active: true }}>
+ <UserFormContext configuredRoles={configuredRoles} />
+ </SimpleForm>
+ );
+};
+
+UserForm.propTypes = {
+ configuredRoles: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string, name: PropTypes.string })).isRequired
+};
+
+export default UserForm;
--- /dev/null
+export { default as CustomerAreaForm } from './CustomerAreaForm';
+export { default as CustomerCreateForm } from './CustomerCreateForm';
+export { default as CustomerEditForm } from './CustomerEditForm';
+export { default as CustomerOfficeForm } from './CustomerOfficeForm';
+export { default as CustomerReferentForm } from './CustomerReferentForm';
+export { default as DeviceForm } from './DeviceForm';
+export { default as I18nMessageForm } from './I18nMessageForm';
+export { default as ActivityTypeForm } from './ActivityTypeForm';
+export { default as RFIDDeviceForm } from './RFIDDeviceForm';
+export { default as SupplierForm } from './SupplierForm';
+export { default as SupplierReferentForm } from './SupplierReferentForm';
+export { default as UserForm } from './UserForm';
+export { default as EquipmentTypeEditForm } from './EquipmentTypeEditForm';
+export { default as EquipmentTypeAttachmentForm } from './EquipmentTypeAttachmentForm';
+export { default as EquipmentTypeCreateForm } from './EquipmentTypeCreateForm';
+export { default as PriceListForm } from './PriceListForm';
+export { default as EquipmentCreateForm } from './EquipmentCreateForm';
+export { default as ProtocolForm } from './ProtocolForm';
+export { default as ProtocolAttachmentForm } from './ProtocolAttachmentForm';
+export { default as EquipmentEditForm } from './EquipmentEditForm';
+export { default as SupplyDialogForm } from './SupplyDialogForm';
+export { default as PriceListItemForm } from './PriceListItemForm';
+export { default as SupplyForm } from './SupplyForm';
+export { default as ActivityForm } from './ActivityForm';
+export { default as MaintenanceCreateForm } from './MaintenanceCreateForm';
+export { default as MaintenanceEditForm } from './MaintenanceEditForm';
+export { default as EquipmentAttachmentForm } from './EquipmentAttachmentForm';
+export { default as EquipmentCreateInDialogForm } from './EquipmentCreateInDialogForm';
+export { default as SupplierCreateForm } from './SupplierCreateForm';
+export { default as ProtocolDialogForm } from './ProtocolDialogForm';
+export { default as DocxTemplateForm } from './DocxTemplateForm';
--- /dev/null
+import { Alert, AlertTitle, Typography } from '@mui/material';
+import { ReferenceInput, SelectInput, useDataProvider, useNotify, useTranslate } from '@applica-software-guru/react-admin';
+import { Fragment, useEffect, useState } from 'react';
+
+import { useWatch } from 'react-hook-form';
+
+const AlertBox = () => {
+ const [activityDetails, setActivityDetails] = useState(null);
+ const dataProvider = useDataProvider();
+ const notify = useNotify();
+ const translate = useTranslate();
+ const activityId = useWatch({
+ name: 'activityId'
+ });
+
+ useEffect(() => {
+ if (!activityId) return;
+
+ const fetchDetails = async () => {
+ try {
+ const { data } = await dataProvider.get('maintenance/activity-info/' + activityId);
+ setActivityDetails(data.activityDetailsDTO);
+ } catch (error) {
+ notify('ra.notification.http_error', 'error', {});
+ }
+ };
+
+ fetchDetails();
+ }, [activityId, dataProvider, notify]);
+
+ if (!activityDetails) return null;
+
+ return (
+ <Alert severity="info" sx={{ marginTop: 1 }}>
+ <AlertTitle>{translate('resources.entities/activity.title')}</AlertTitle>
+ <Typography
+ dangerouslySetInnerHTML={{
+ __html: translate('resources.entities/activity.info_message', {
+ supply: activityDetails.equipmentName,
+ supplier: activityDetails.supplierName,
+ location: activityDetails.locationName
+ })
+ }}
+ />
+ </Alert>
+ );
+};
+
+const renderOption = (option) => {
+ return (
+ <Typography variant="body1" noWrap sx={{ maxWidth: 'calc(100vw - 120px)' }}>
+ {option.title}
+ </Typography>
+ );
+};
+
+const ActivitySelectInput = ({ validate, ...props }) => {
+ const supplyId = useWatch({
+ name: 'supplyId'
+ });
+
+ return (
+ <Fragment>
+ <ReferenceInput
+ {...props}
+ reference="entities/activity"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ filter={{ supplyId: supplyId || [] }}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <SelectInput optionText={renderOption} validate={validate} />
+ </ReferenceInput>
+ <AlertBox />
+ </Fragment>
+ );
+};
+
+export default ActivitySelectInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+
+const ActivityTypeAutocompleteInput = ({ validate, ...props }) => (
+ <ReferenceInput
+ {...props}
+ reference="entities/activity-type"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="description" validate={validate} />
+ </ReferenceInput>
+);
+
+export default ActivityTypeAutocompleteInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+
+import { useWatch } from 'react-hook-form';
+
+const CityAutocompleteInput = ({ filterByProvince, validate, ...props }) => {
+ const provinceId = useWatch({
+ name: 'provinceId'
+ });
+ return (
+ <ReferenceInput
+ {...props}
+ reference="entities/city"
+ source="cityId"
+ filter={filterByProvince ? { provinceId: provinceId || 'none' } : {}}
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="name" validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default CityAutocompleteInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+import { useEffect, useState } from 'react';
+import { useWatch, useFormContext } from 'react-hook-form';
+import _ from 'lodash';
+
+const CustomerAreaAutocompleteInput = ({ validate, ...props }) => {
+ const officeId = useWatch({
+ name: 'area.officeId'
+ });
+ const { isAreaPresent, ...rest } = props;
+ const [isDisabled, setIsDisabled] = useState(false);
+ const { setValue } = useFormContext();
+
+ useEffect(() => {
+ if (isAreaPresent) {
+ if (_.isEmpty(officeId)) {
+ setValue('areaId', null);
+ setIsDisabled(true);
+ } else {
+ setIsDisabled(false);
+ }
+ }
+ }, [isAreaPresent, officeId, setValue]);
+
+ return (
+ <ReferenceInput
+ {...rest}
+ reference="entities/customer-area"
+ filter={{ officeId: officeId ? officeId : undefined }}
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="name" disabled={isDisabled} validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default CustomerAreaAutocompleteInput;
--- /dev/null
+import {
+ ActionsMenu,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceField,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext
+} from '@applica-software-guru/react-admin';
+
+import { CustomerAreaForm } from '../ra-forms';
+import CustomerOfficeAutocompleteInput from './CustomerOfficeAutocompleteInput';
+
+const CustomerAreaManyInput = ({ ...props }) => {
+ const record = useRecordContext();
+
+ return (
+ <ReferenceManyInput
+ {...props}
+ reference="entities/customer-area"
+ target="customerId"
+ bulkActionButtons={false}
+ filters={[
+ <SearchInput key="keyword" source="keyword" alwaysOn />,
+ <CustomerOfficeAutocompleteInput key="officeId" source="officeId" alwaysOn display="legend" filter={{ customerId: record?.id }} />
+ ]}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton
+ maxWidth="sm"
+ record={{ customerId: record?.id, active: true }}
+ variant="outlined"
+ color="secondary"
+ size="medium"
+ >
+ <CustomerAreaForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid {...props} bulkActionButtons={false}>
+ <ReferenceField source="office.cityId" reference="entities/city" sortable={false}>
+ <TextField source="name" />
+ </ReferenceField>
+ <TextField source="office.address" sortable={false} />
+ <TextField source="name" />
+ <ReferenceField
+ source="rfidDeviceId"
+ reference="entities/rfid-device"
+ link={(data, reference) => `/entities/rfid-device-track?gateway-id=${data?.id}`}
+ sortable={false}
+ >
+ <TextField source="name" />
+ </ReferenceField>
+ <DateField source="updated" showTime />
+ <ActionsMenu>
+ <EditInDialogButton maxWidth="sm">
+ <CustomerAreaForm />
+ </EditInDialogButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ </Datagrid>
+ </ReferenceManyInput>
+ );
+};
+
+export default CustomerAreaManyInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+
+const CustomerAutocompleteInput = ({ validate, ...props }) => {
+ return (
+ <ReferenceInput
+ {...props}
+ reference="entities/customer"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="name" validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default CustomerAutocompleteInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+import { useEffect, useState } from 'react';
+import { useWatch, useFormContext } from 'react-hook-form';
+import _ from 'lodash';
+
+const renderText = (record) => `${record.city.name} - ${record.address}`;
+const CustomerOfficeAutocompleteInput = ({ validate, ...props }) => {
+ const { isAreaPresent, ...rest } = props;
+ const [isDisabled, setIsDisabled] = useState(false);
+ const rfidDeviceId = useWatch({ name: 'rfidDeviceId' });
+ const customerId = useWatch({ name: 'customerId' });
+ const protocolId = useWatch({ name: 'protocolId' });
+ const { setValue } = useFormContext();
+
+ useEffect(() => {
+ if (isAreaPresent) {
+ if (!_.isEmpty(rfidDeviceId) || _.isEmpty(protocolId) || customerId === '') {
+ setValue('area.officeId', null);
+ setIsDisabled(true);
+ } else {
+ setIsDisabled(false);
+ }
+ }
+ }, [isAreaPresent, rfidDeviceId, customerId, protocolId, setValue]);
+
+ return (
+ <ReferenceInput
+ {...rest}
+ reference="entities/customer-office"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ filter={{ customerId: customerId ? customerId : undefined }}
+ sort={{
+ field: 'address',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText={renderText} disabled={isDisabled} validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default CustomerOfficeAutocompleteInput;
--- /dev/null
+import {
+ ActionsMenu,
+ BooleanField,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext
+} from '@applica-software-guru/react-admin';
+
+import CityAutocompleteInput from './CityAutocompleteInput';
+import { CustomerOfficeForm } from '../ra-forms';
+
+const CustomerOfficeManyInput = ({ ...props }) => {
+ const record = useRecordContext();
+
+ return (
+ <ReferenceManyInput
+ {...props}
+ reference="entities/customer-office"
+ target="customerId"
+ bulkActionButtons={false}
+ filters={[
+ <SearchInput key="keyword" source="keyword" alwaysOn />,
+ <CityAutocompleteInput filterByProvince={false} key="cityId" source="cityId" alwaysOn display="legend" />
+ ]}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton
+ maxWidth="sm"
+ record={{ customerId: record?.id, active: true }}
+ variant="outlined"
+ color="secondary"
+ size="medium"
+ >
+ <CustomerOfficeForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid {...props} bulkActionButtons={false}>
+ <BooleanField source="headquarter" />
+ <TextField source="nation.name" sortable={false} />
+ <TextField source="region.name" sortable={false} />
+ <TextField source="province.name" sortable={false} />
+ <TextField source="city.name" sortable={false} />
+ <TextField source="address" sortable={false} />
+ <DateField source="updated" showTime />
+ <ActionsMenu>
+ <EditInDialogButton maxWidth="sm">
+ <CustomerOfficeForm />
+ </EditInDialogButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ </Datagrid>
+ </ReferenceManyInput>
+ );
+};
+
+export default CustomerOfficeManyInput;
--- /dev/null
+import { TextInput, useDataProvider, useNotify } from '@applica-software-guru/react-admin';
+import { useEffect, useState } from 'react';
+import { useWatch, useFormContext } from 'react-hook-form';
+import _ from 'lodash';
+
+const CustomerReadOnlyInput = (props) => {
+ const dataProvider = useDataProvider();
+ const protocolId = useWatch({ name: 'protocolId' });
+ const { setValue } = useFormContext();
+ const [isInitialized, setIsInitialized] = useState(false);
+ const notify = useNotify();
+
+ useEffect(() => {
+ if (isInitialized || !_.isEmpty(protocolId)) {
+ if (!_.isEmpty(protocolId)) {
+ dataProvider
+ .getOne('entities/protocol', { id: protocolId })
+ .then(({ data }) => {
+ if (data && data.customer) {
+ setValue('customerName', data.customer.name);
+ setValue('customerId', data.customer.id);
+ }
+ })
+ .catch((error) => {
+ notify('ra.error.fetch', error);
+ });
+ } else {
+ setValue('customerName', '');
+ setValue('customerId', '');
+ }
+ }
+ }, [dataProvider, protocolId, setValue, isInitialized, notify]);
+
+ useEffect(() => {
+ setIsInitialized(true);
+ }, [protocolId]);
+
+ return <TextInput {...props} source="customerName" disabled />;
+};
+
+export default CustomerReadOnlyInput;
--- /dev/null
+import {
+ ActionsMenu,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceField,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext
+} from '@applica-software-guru/react-admin';
+
+import { CustomerReferentForm } from '../ra-forms';
+
+const CustomerReferentManyInput = ({ ...props }) => {
+ const record = useRecordContext();
+
+ return (
+ <ReferenceManyInput
+ {...props}
+ reference="entities/customer-referent"
+ target="customerId"
+ bulkActionButtons={false}
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton
+ maxWidth="sm"
+ record={{ customerId: record?.id, active: true }}
+ variant="outlined"
+ color="secondary"
+ size="medium"
+ >
+ <CustomerReferentForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid {...props} bulkActionButtons={false}>
+ <ReferenceField source="office.cityId" reference="entities/city" sortable={false}>
+ <TextField source="name" />
+ </ReferenceField>
+ <ReferenceField source="officeId" reference="entities/customer-office" sortable={false}>
+ <TextField source="address" />
+ </ReferenceField>
+ <TextField source="name" />
+ <TextField source="surname" />
+ <DateField source="updated" showTime />
+ <ActionsMenu>
+ <EditInDialogButton maxWidth="sm">
+ <CustomerReferentForm />
+ </EditInDialogButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ </Datagrid>
+ </ReferenceManyInput>
+ );
+};
+
+export default CustomerReferentManyInput;
--- /dev/null
+import { SelectInput, useDataProvider } from '@applica-software-guru/react-admin';
+import { useEffect, useState } from 'react';
+
+const DocxTemplateTypeSelectInput = (props) => {
+ const dataProvider = useDataProvider();
+ const [choices, setChoices] = useState([]);
+ useEffect(() => {
+ dataProvider
+ .get('values/docx-types')
+ .then(({ data: { value: data } }) =>
+ setChoices(data.map(({ label, value }) => ({ id: value, name: `docx-template.type.${label}` })))
+ );
+ }, [dataProvider]);
+
+ return <SelectInput {...props} choices={choices} />;
+};
+
+export default DocxTemplateTypeSelectInput;
--- /dev/null
+import {
+ ActionsMenu,
+ AttachmentField,
+ BulkDeleteWithConfirmButton,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ usePermissions,
+ useRecordContext,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+
+import { EquipmentAttachmentForm } from 'components/ra-forms';
+import { Grid } from '@mui/material';
+
+const EquipmentAttachmentManyInput = () => {
+ const record = useRecordContext();
+ const { spacing } = useThemeConfig();
+ const { permissions, isLoading } = usePermissions();
+ const canEdit = !isLoading && permissions.includes('equipment-attachment:edit');
+ const canCreate = !isLoading && permissions.includes('equipment-attachment:new');
+
+ return (
+ <Grid container spacing={spacing}>
+ <Grid item lg={12} xs={12}>
+ <ReferenceManyInput
+ reference="entities/equipment-attachment"
+ target="equipmentId"
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ sort={{ field: 'created', order: 'ASC' }}
+ actions={
+ <TopToolbar>
+ {canCreate && (
+ <CreateInDialogButton maxWidth="sm" record={{ equipmentId: record?.id }} variant="outlined" color="secondary" size="medium">
+ <EquipmentAttachmentForm />
+ </CreateInDialogButton>
+ )}
+ </TopToolbar>
+ }
+ >
+ <Datagrid bulkActionButtons={!canEdit ? null : <BulkDeleteWithConfirmButton />}>
+ <TextField source="description" />
+ <AttachmentField source="attachment" title="attachment.name" sortable={false} />
+ <DateField source="updated" showTime />
+ {canEdit && (
+ <ActionsMenu>
+ <EditInDialogButton maxWidth="sm">
+ <EquipmentAttachmentForm />
+ </EditInDialogButton>
+
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ )}
+ </Datagrid>
+ </ReferenceManyInput>
+ </Grid>
+ </Grid>
+ );
+};
+
+export default EquipmentAttachmentManyInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+import { useEffect } from 'react';
+import { useFormContext, useWatch } from 'react-hook-form';
+
+const EquipmentAutocompleteInput = ({ validate, ...props }) => {
+ const { setValue } = useFormContext();
+ const supplierId = useWatch({
+ name: 'supplierId'
+ });
+
+ const priceListId = useWatch({
+ name: 'priceListId'
+ });
+
+ useEffect(() => {
+ setValue('equipmentId', null);
+ }, [supplierId, setValue]);
+
+ const filter = {};
+ if (supplierId) {
+ filter.supplierId = supplierId;
+ }
+ if (priceListId) {
+ filter.priceListId = priceListId;
+ }
+
+ return (
+ <ReferenceInput
+ {...props}
+ reference="entities/equipment"
+ filter={filter}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="equipmentType.name" validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default EquipmentAutocompleteInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+const renderText = (record) => `${record.equipmentType.name} (${record?.supplier?.businessName})`;
+const EquipmentListAutocompleteInput = ({ validate, ...props }) => (
+ <ReferenceInput
+ {...props}
+ reference="entities/equipment"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText={renderText} validate={validate} />
+ </ReferenceInput>
+);
+
+export default EquipmentListAutocompleteInput;
--- /dev/null
+import {
+ ActionsMenu,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext
+} from '@applica-software-guru/react-admin';
+import { EquipmentCreateForm, EquipmentEditForm } from 'components/ra-forms';
+
+const EquipmentManyInput = ({ ...props }) => {
+ const record = useRecordContext();
+
+ return (
+ <ReferenceManyInput
+ {...props}
+ reference="entities/equipment"
+ target="supplierId"
+ bulkActionButtons={false}
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton maxWidth="sm" record={{ supplierId: record?.id }} variant="outlined" color="secondary" size="medium">
+ <EquipmentCreateForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid>
+ <TextField source="equipmentType.name" />
+ <DateField source="updated" showTime />
+ <ActionsMenu>
+ <EditButton>
+ <EquipmentEditForm />
+ </EditButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ </Datagrid>
+ </ReferenceManyInput>
+ );
+};
+
+export default EquipmentManyInput;
--- /dev/null
+import {
+ ActionsMenu,
+ AttachmentField,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+
+import { EquipmentTypeAttachmentForm } from 'components/ra-forms';
+import { Grid } from '@mui/material';
+
+const EquipmentTypeAttachmentManyInput = () => {
+ const record = useRecordContext();
+ const { spacing } = useThemeConfig();
+ return (
+ <Grid container spacing={spacing}>
+ <Grid item lg={12} xs={12}>
+ <ReferenceManyInput
+ reference="entities/equipment-type-attachment"
+ target="typeId"
+ filters={[<SearchInput key="name__like" source="name__like" alwaysOn />]}
+ sort={{ field: 'created', order: 'ASC' }}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton maxWidth="sm" record={{ typeId: record?.id }} variant="outlined" color="secondary" size="medium">
+ <EquipmentTypeAttachmentForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid>
+ <TextField source="description" />
+ <AttachmentField source="attachment" title="attachment.name" />
+ <DateField source="updated" showTime />
+ <ActionsMenu>
+ <EditInDialogButton maxWidth="sm">
+ <EquipmentTypeAttachmentForm />
+ </EditInDialogButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ </Datagrid>
+ </ReferenceManyInput>
+ </Grid>
+ </Grid>
+ );
+};
+
+export default EquipmentTypeAttachmentManyInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+import { useWatch } from 'react-hook-form';
+
+const EquipmentTypeAutocompleteInput = ({ label, validate, ...props }) => {
+ const supplierId = useWatch({ name: 'supplierId' });
+ return (
+ <ReferenceInput
+ {...props}
+ reference="entities/equipment-type"
+ filter={{ supplierId: supplierId }}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput label={label} optionText="name" validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default EquipmentTypeAutocompleteInput;
--- /dev/null
+import {
+ BooleanField,
+ CreateInDialogButton,
+ Datagrid,
+ EditButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+import { Grid } from '@mui/material';
+import { EquipmentTypeEditForm, EquipmentTypeCreateForm } from 'components/ra-forms';
+
+const EquipmentTypeManyInput = () => {
+ const record = useRecordContext();
+ const { spacing } = useThemeConfig();
+ return (
+ <Grid container spacing={spacing}>
+ <Grid item lg={12} xs={12}>
+ <ReferenceManyInput
+ reference="entities/equipment-type"
+ target="parentId"
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ sort={{ field: 'created', order: 'ASC' }}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton maxWidth="sm" record={{ parentId: record?.id }} variant="outlined" color="secondary" size="medium">
+ <EquipmentTypeCreateForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid rowClick="edit">
+ <TextField source="name" />
+ <BooleanField source="accessory" />
+ <EditButton>
+ <EquipmentTypeEditForm />
+ </EditButton>
+ </Datagrid>
+ </ReferenceManyInput>
+ </Grid>
+ </Grid>
+ );
+};
+
+export default EquipmentTypeManyInput;
--- /dev/null
+import {
+ ArrayInput,
+ SimpleFormIterator,
+ TableFormIterator,
+ TextInput,
+ required,
+ useTranslateLabel
+} from '@applica-software-guru/react-admin';
+import { useMediaQuery, Grid } from '@mui/material';
+import EquipmentTypeAutocompleteInput from './EquipmentTypeAutocompleteInput';
+import YesOrNoSelectInput from './YesOrNoSelectInput';
+
+const InterventionArrayInput = (props) => {
+ const translateLabel = useTranslateLabel();
+ const isXsScreen = useMediaQuery((theme) => theme.breakpoints.down('sm'));
+ return (
+ <Grid item xs={12} sm={12}>
+ <ArrayInput {...props} label="" divider={false}>
+ {!isXsScreen ? (
+ <TableFormIterator label={translateLabel(props)}>
+ <EquipmentTypeAutocompleteInput source={'equipmentTypeId'} validate={required()} />
+ <TextInput source="description" validate={required()} />
+ <YesOrNoSelectInput source="substitution" validate={required()} />
+ <YesOrNoSelectInput source="reorder" validate={required()} />
+ </TableFormIterator>
+ ) : (
+ <SimpleFormIterator label={translateLabel(props)} fullWidth disableReordering>
+ <EquipmentTypeAutocompleteInput source={'equipmentTypeId'} validate={required()} />
+ <TextInput source="description" validate={required()} />
+ <YesOrNoSelectInput source="substitution" validate={required()} />
+ <YesOrNoSelectInput source="reorder" validate={required()} />
+ </SimpleFormIterator>
+ )}
+ </ArrayInput>
+ </Grid>
+ );
+};
+export default InterventionArrayInput;
--- /dev/null
+import { SelectInput } from '@applica-software-guru/react-admin';
+const LangSelectInput = (props) => <SelectInput {...props} choices={[{ id: 'it', name: 'Italiano' }]} />;
+
+export default LangSelectInput;
--- /dev/null
+import {
+ BulkDeleteWithConfirmButton,
+ CreateButton,
+ Datagrid,
+ DateField,
+ EditButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ usePermissions,
+ useRecordContext,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+import { MaintenanceCreateForm, MaintenanceEditForm } from 'components/ra-forms';
+
+import { Grid } from '@mui/material';
+
+const MaintenanceManyInput = () => {
+ const record = useRecordContext();
+ const { spacing } = useThemeConfig();
+ const { permissions, isLoading } = usePermissions();
+ const canDelete = !isLoading && permissions.includes('maintenance:delete');
+
+ return (
+ <Grid container spacing={spacing}>
+ <Grid item lg={12} xs={12}>
+ <ReferenceManyInput
+ reference="entities/maintenance"
+ target="activityId"
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ sort={{ field: 'created', order: 'ASC' }}
+ actions={
+ <TopToolbar>
+ <CreateButton state={{ record: { activityId: record?.id } }} variant="outlined" color="secondary" size="medium">
+ <MaintenanceCreateForm />
+ </CreateButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid bulkActionButtons={!canDelete ? null : <BulkDeleteWithConfirmButton />}>
+ <TextField source="activity.title" sortable={false} />
+ <TextField source="title" />
+ <TextField source="interventionNumber" />
+ <DateField source="date" sortable={false} />
+ <DateField source="updated" showTime />
+ <EditButton>
+ <MaintenanceEditForm />
+ </EditButton>
+ </Datagrid>
+ </ReferenceManyInput>
+ </Grid>
+ </Grid>
+ );
+};
+
+export default MaintenanceManyInput;
--- /dev/null
+import { TextInput } from '@applica-software-guru/react-admin';
+
+const parseMoney = (value) => {
+ const convertedValue = value?.replace(',', '.');
+
+ const decimalMatch = /\.(\d{2,})$/.exec(convertedValue);
+
+ if (decimalMatch) {
+ const decimalPart = decimalMatch[1];
+
+ const twoDecimals = decimalPart.slice(0, 2);
+
+ const integerPart = convertedValue.split('.')[0];
+ return `${integerPart}.${twoDecimals}`;
+ }
+ return convertedValue;
+};
+const MoneyInput = (props) => {
+ return (
+ <TextInput
+ {...props}
+ type="number"
+ min="0.01"
+ step="0.01"
+ parse={parseMoney}
+ InputProps={{
+ startAdornment: '€'
+ }}
+ />
+ );
+};
+
+export default MoneyInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+
+const NationAutocompleteInput = ({ validate, ...props }) => (
+ <ReferenceInput
+ {...props}
+ reference="entities/nation"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="name" validate={validate} />
+ </ReferenceInput>
+);
+
+export default NationAutocompleteInput;
--- /dev/null
+import {
+ ActionsMenu,
+ CreateInDialogButton,
+ Datagrid,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext,
+ useThemeConfig,
+ DateField
+} from '@applica-software-guru/react-admin';
+import { Grid } from '@mui/material';
+import { MoneyField } from 'components/ra-fields';
+import { PriceListItemForm } from 'components/ra-forms';
+
+const PriceListItemManyInput = () => {
+ const record = useRecordContext();
+ const { spacing } = useThemeConfig();
+ return (
+ <Grid container spacing={spacing}>
+ <Grid item lg={12} xs={12}>
+ <ReferenceManyInput
+ reference="entities/price-list-item"
+ target="priceListId"
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ sort={{ field: 'created', order: 'ASC' }}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton maxWidth="sm" record={{ priceListId: record?.id }} variant="outlined" color="secondary" size="medium">
+ <PriceListItemForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid>
+ <TextField source="equipmentType.name" />
+ <MoneyField source="price" />
+ <DateField source="updated" showTime />
+ <ActionsMenu>
+ <EditInDialogButton maxWidth="sm">
+ <PriceListItemForm />
+ </EditInDialogButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ </Datagrid>
+ </ReferenceManyInput>
+ </Grid>
+ </Grid>
+ );
+};
+
+export default PriceListItemManyInput;
--- /dev/null
+import {
+ ActionsMenu,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext
+} from '@applica-software-guru/react-admin';
+
+import { PriceListForm } from 'components/ra-forms';
+
+const PriceListManyInput = ({ ...props }) => {
+ const record = useRecordContext();
+
+ return (
+ <ReferenceManyInput
+ {...props}
+ reference="entities/price-list"
+ target="supplierId"
+ bulkActionButtons={false}
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton
+ maxWidth="sm"
+ record={{ supplierId: record?.id, active: true }}
+ variant="outlined"
+ color="secondary"
+ redirect="edit"
+ size="medium"
+ >
+ <PriceListForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid bulkActionButtons={false}>
+ <TextField source="name" />
+ <DateField source="start" />
+ <DateField source="end" />
+ <DateField source="updated" showTime />
+ <ActionsMenu>
+ <EditButton>
+ <PriceListForm />
+ </EditButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ </Datagrid>
+ </ReferenceManyInput>
+ );
+};
+
+export default PriceListManyInput;
--- /dev/null
+import {
+ ActionsMenu,
+ AttachmentField,
+ BulkDeleteWithConfirmButton,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ usePermissions,
+ useRecordContext,
+ useThemeConfig,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import { FixedMainCard } from 'components/layout';
+import { ProtocolAttachmentForm } from 'components/ra-forms';
+
+const ProtocolAttachmentManyInput = () => {
+ const record = useRecordContext();
+ const translate = useTranslate();
+ const { spacing } = useThemeConfig();
+ const { isLoading, permissions } = usePermissions();
+ const canCreate = !isLoading && permissions.includes('protocol-attachment:new');
+ const canEdit = !isLoading && permissions.includes('protocol-attachment:edit');
+ const canDelete = !isLoading && permissions.includes('protocol-attachment:delete');
+ return (
+ <FixedMainCard title={translate('resources.entities/protocol-attachment.title')}>
+ <Grid container spacing={spacing}>
+ <Grid item lg={12} xs={12}>
+ <ReferenceManyInput
+ reference="entities/protocol-attachment"
+ target="protocolId"
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ sort={{ field: 'created', order: 'ASC' }}
+ actions={
+ <TopToolbar>
+ {canCreate && (
+ <CreateInDialogButton
+ maxWidth="sm"
+ record={{ protocolId: record?.id }}
+ variant="outlined"
+ color="secondary"
+ size="medium"
+ >
+ <ProtocolAttachmentForm />
+ </CreateInDialogButton>
+ )}
+ </TopToolbar>
+ }
+ >
+ <Datagrid bulkActionButtons={!canDelete ? null : <BulkDeleteWithConfirmButton />}>
+ <TextField source="description" />
+ <AttachmentField source="attachment" title="attachment.name" />
+ <DateField source="updated" showTime />
+ {canEdit && (
+ <ActionsMenu>
+ <EditInDialogButton maxWidth="sm">
+ <ProtocolAttachmentForm />
+ </EditInDialogButton>
+
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ )}
+ </Datagrid>
+ </ReferenceManyInput>
+ </Grid>
+ </Grid>
+ </FixedMainCard>
+ );
+};
+
+export default ProtocolAttachmentManyInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+
+const ProtocolAutoComplete = ({ validate, ...props }) => (
+ <ReferenceInput
+ {...props}
+ reference="entities/protocol"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="title" validate={validate} />
+ </ReferenceInput>
+);
+
+export default ProtocolAutoComplete;
--- /dev/null
+import {
+ ArrayInput,
+ TableFormIterator,
+ SimpleFormIterator,
+ TextInput,
+ DateInput,
+ required,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import { useMediaQuery } from '@mui/material';
+import { ActivityTypeAutocompleteInput } from '.';
+import { useMemo } from 'react';
+const ProtocolServiceArrayInput = (props) => {
+ const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm'));
+ const translate = useTranslate();
+ const Component = useMemo(() => (isMobile ? SimpleFormIterator : TableFormIterator), [isMobile]);
+
+ return (
+ <ArrayInput {...props} label="" divider={false}>
+ <Component>
+ <ActivityTypeAutocompleteInput
+ label={'resources.entities/protocol.services.fromActivityTypeId'}
+ source="fromActivityTypeId"
+ validate={required()}
+ />
+ <ActivityTypeAutocompleteInput label={'resources.entities/protocol.services.toActivityTypeId'} source="toActivityTypeId" />
+ <TextInput
+ label={'resources.entities/protocol.services.frequency'}
+ source="frequency"
+ type="number"
+ min="0"
+ step="1"
+ InputProps={{
+ endAdornment: translate('ra.time.days')
+ }}
+ />
+ <DateInput label={'resources.entities/protocol.services.end'} source="end" validate={required()} />
+ </Component>
+ </ArrayInput>
+ );
+};
+
+export default ProtocolServiceArrayInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+import { useFormContext, useWatch } from 'react-hook-form';
+
+import { useEffect } from 'react';
+
+const ProvinceAutocompleteInput = ({ validate, ...props }) => {
+ const { setValue } = useFormContext();
+ const regionId = useWatch({
+ name: 'regionId'
+ });
+
+ useEffect(() => {
+ setValue('provinceId', null);
+ }, [regionId, setValue]);
+
+ return (
+ <ReferenceInput
+ {...props}
+ reference="entities/province"
+ filter={{
+ regionId: regionId || 'none'
+ }}
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="name" validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default ProvinceAutocompleteInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+import { useEffect, useState } from 'react';
+import { useWatch, useFormContext } from 'react-hook-form';
+import _ from 'lodash';
+
+const RFIDDeviceAutocomplete = ({ validate, ...props }) => {
+ const { isAreaPresent, type, ...rest } = props;
+ const [isDisabled, setIsDisabled] = useState(false);
+ const officeId = useWatch({ name: 'area.officeId' });
+ const { setValue } = useFormContext();
+
+ useEffect(() => {
+ if (isAreaPresent) {
+ if (!_.isEmpty(officeId)) {
+ setValue('rfidDeviceId', null);
+ setIsDisabled(true);
+ } else {
+ setIsDisabled(false);
+ }
+ }
+ }, [isAreaPresent, officeId, setValue]);
+
+ return (
+ <ReferenceInput
+ {...rest}
+ reference="entities/rfid-device"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ filter={{ type: type }}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ disabled={isDisabled}
+ >
+ <AutocompleteInput optionText="name" validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default RFIDDeviceAutocomplete;
--- /dev/null
+import React, { useState, useEffect } from 'react';
+import { SelectInput, useDataProvider } from '@applica-software-guru/react-admin';
+
+const RFIDDeviceTypeSelect = (props) => {
+ const [options, setOptions] = useState([]);
+ const dataProvider = useDataProvider();
+
+ useEffect(() => {
+ dataProvider
+ .get('values/rfid-device-types', {
+ pagination: { page: 1, perPage: 100 },
+ sort: { field: 'name', order: 'ASC' }
+ })
+ .then((response) => {
+ const { data } = response;
+
+ setOptions(data.value.map((item) => ({ id: item.label, name: item.value })));
+ });
+ }, [dataProvider]);
+
+ return <SelectInput {...props} choices={options} />;
+};
+
+export default RFIDDeviceTypeSelect;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+import { useFormContext, useWatch } from 'react-hook-form';
+
+import { useEffect } from 'react';
+
+const RegionAutocompleteInput = ({ validate, ...props }) => {
+ const { setValue } = useFormContext();
+ const nationId = useWatch({
+ name: 'nationId'
+ });
+
+ useEffect(() => {
+ setValue('regionId', null);
+ }, [nationId, setValue]);
+
+ return (
+ <ReferenceInput
+ {...props}
+ reference="entities/region"
+ filter={{
+ nationId: nationId || 'none'
+ }}
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="name" validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default RegionAutocompleteInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+
+const SupplierAutocompleteInput = ({ validate, ...props }) => {
+ return (
+ <ReferenceInput
+ {...props}
+ reference="entities/supplier"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ key={(searchText) => ({ keyword: searchText })}
+ perPage={100}
+ sort={{
+ field: 'businessName',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText="businessName" validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default SupplierAutocompleteInput;
--- /dev/null
+import {
+ ActionsMenu,
+ CreateInDialogButton,
+ Datagrid,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceManyInput,
+ SearchInput,
+ TextField,
+ TopToolbar,
+ useRecordContext
+} from '@applica-software-guru/react-admin';
+
+import { SupplierReferentForm } from 'components/ra-forms';
+
+const SupplierReferentManyInput = () => {
+ const record = useRecordContext();
+ return (
+ <ReferenceManyInput
+ reference="entities/supplier-referent"
+ target="supplierId"
+ filters={[<SearchInput key="keyword" source="keyword" alwaysOn />]}
+ sort={{ field: 'created', order: 'ASC' }}
+ actions={
+ <TopToolbar>
+ <CreateInDialogButton maxWidth="sm" record={{ supplierId: record?.id }} variant="outlined" color="secondary" size="medium">
+ <SupplierReferentForm />
+ </CreateInDialogButton>
+ </TopToolbar>
+ }
+ >
+ <Datagrid>
+ <TextField source="name" />
+ <TextField source="surname" />
+ <TextField source="email" />
+ <TextField source="department" />
+ <TextField source="updated" />
+ <ActionsMenu>
+ <EditInDialogButton maxWidth="sm">
+ <SupplierReferentForm />
+ </EditInDialogButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ </Datagrid>
+ </ReferenceManyInput>
+ );
+};
+
+export default SupplierReferentManyInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput, useTranslate, useDataProvider, useNotify } from '@applica-software-guru/react-admin';
+import { useEffect, useState } from 'react';
+import { useFormContext, useWatch } from 'react-hook-form';
+import { usePermissions } from 'react-admin';
+import _ from 'lodash';
+import { Alert, AlertTitle, Typography } from '@mui/material';
+import { Box } from '@mui/material';
+
+const AlertBox = () => {
+ const [supplyDetails, setSupplyDetails] = useState(null);
+ const dataProvider = useDataProvider();
+ const notify = useNotify();
+ const translate = useTranslate();
+ const supplyId = useWatch({
+ name: 'supplyId'
+ });
+
+ useEffect(() => {
+ if (!supplyId) return;
+
+ const fetchDetails = async () => {
+ try {
+ const { data } = await dataProvider.get(`maintenance/supply-info/${supplyId}`);
+ setSupplyDetails(data.supplyDetailsDTO);
+ } catch (error) {
+ notify('ra.notification.http_error', 'error', {});
+ }
+ };
+
+ fetchDetails();
+ }, [supplyId, dataProvider, notify]);
+
+ if (!supplyDetails) return null;
+
+ return (
+ <Alert severity="info" sx={{ marginTop: 1 }}>
+ <AlertTitle>{translate('resources.entities/supply.title')}:</AlertTitle>
+ <Typography
+ dangerouslySetInnerHTML={{
+ __html: translate('resources.entities/supply.info_message', {
+ supplier: supplyDetails.supplierName,
+ equipment: supplyDetails.equipmentName,
+ protocol: supplyDetails.protocolName
+ })
+ }}
+ />
+ </Alert>
+ );
+};
+
+const renderText = (record) =>
+ `${record.supplier.businessName} - ${record.equipmentType.name} - ${record.protocol.title} N° ${record.protocol.protocolNumber} - ${record.ddtNumber}`;
+
+const SupplyActivityAutocompleteInput = ({ validate, ...props }) => {
+ const { setValue } = useFormContext();
+ const customerId = useWatch({
+ name: 'customer.id'
+ });
+ const supplierId = useWatch({
+ name: 'supply.supplierId'
+ });
+ const { permissions, isLoading } = usePermissions();
+
+ useEffect(() => {
+ if (_.isEmpty(customerId) || _.isEmpty(supplierId)) {
+ setValue('supplyId', null);
+ }
+ }, [customerId, supplierId, setValue]);
+
+ const filter =
+ !isLoading && permissions.includes('customer:list') ? { customerId: customerId || 'none', supplierId: supplierId || 'none' } : {};
+
+ return (
+ <Box>
+ <ReferenceInput
+ {...props}
+ reference="entities/supply"
+ filterToQuery={(searchText) => ({ keyword: searchText })}
+ filter={filter}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput optionText={renderText} validate={validate} />
+ </ReferenceInput>
+ <AlertBox />
+ </Box>
+ );
+};
+
+export default SupplyActivityAutocompleteInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+import { useWatch } from 'react-hook-form';
+
+const renderText = (record) =>
+ `${record.supplier.businessName} - ${record.equipmentType.name} - ${record.protocol.title} N° ${record.protocol.protocolNumber} - ${record.ddtNumber}`;
+
+const SupplyAutocompleteInput = ({ label, validate, ...props }) => {
+ const supplierId = useWatch({ name: 'supplierId' });
+ return (
+ <ReferenceInput
+ {...props}
+ reference="entities/supply"
+ filter={{ supplierId: supplierId }}
+ perPage={100}
+ sort={{
+ field: 'name',
+ order: 'ASC'
+ }}
+ >
+ <AutocompleteInput label={label} optionText={renderText} validate={validate} />
+ </ReferenceInput>
+ );
+};
+
+export default SupplyAutocompleteInput;
--- /dev/null
+import {
+ ActionsMenu,
+ BulkDeleteWithConfirmButton,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ DeleteWithConfirmButton,
+ EditInDialogButton,
+ ReferenceManyInput,
+ TextField,
+ TopToolbar,
+ usePermissions,
+ useRecordContext,
+ useThemeConfig
+} from '@applica-software-guru/react-admin';
+
+import { Grid } from '@mui/material';
+import ProtocolAutoComplete from './ProtocolAutoComplete';
+import { SupplyDialogForm } from 'components/ra-forms';
+
+const SupplyManyInput = () => {
+ const record = useRecordContext();
+ const { spacing } = useThemeConfig();
+ const { permissions, isLoading } = usePermissions();
+ const canEdit = !isLoading && permissions.includes('equipment-attachment:edit');
+ const canCreate = !isLoading && permissions.includes('equipment-attachment:new');
+
+ return (
+ <Grid container spacing={spacing}>
+ <Grid item lg={12} xs={12}>
+ <ReferenceManyInput
+ reference="entities/supply"
+ target="equipmentId"
+ filters={[<ProtocolAutoComplete source="protocolId" alwaysOn display="legend" fullWidth />]}
+ sort={{ field: 'created', order: 'ASC' }}
+ actions={
+ <TopToolbar>
+ {canCreate && (
+ <CreateInDialogButton
+ record={{ equipmentId: record?.id, supplierId: record?.supplierId }}
+ variant="outlined"
+ color="secondary"
+ size="medium"
+ >
+ <SupplyDialogForm />
+ </CreateInDialogButton>
+ )}
+ </TopToolbar>
+ }
+ >
+ <Datagrid bulkActionButtons={!canEdit ? null : <BulkDeleteWithConfirmButton />}>
+ <TextField source="protocol.title" sortable={false} />
+ <TextField source="quantity" />
+ <DateField source="orderDate" showTime />
+ <DateField source="deliveryDate" showTime />
+ <DateField source="updated" showTime />
+ {canEdit && (
+ <ActionsMenu>
+ <EditInDialogButton>
+ <SupplyDialogForm />
+ </EditInDialogButton>
+ <DeleteWithConfirmButton redirect={false} />
+ </ActionsMenu>
+ )}
+ </Datagrid>
+ </ReferenceManyInput>
+ </Grid>
+ </Grid>
+ );
+};
+
+export default SupplyManyInput;
--- /dev/null
+import { AutocompleteInput, ReferenceInput } from '@applica-software-guru/react-admin';
+
+const UserAutocompleteInput = ({ validate, ...props }) => (
+ <ReferenceInput {...props} reference="entities/user" source="userId" filterToQuery={(searchText) => ({ name: searchText })}>
+ <AutocompleteInput optionText="name" validate={validate} />
+ </ReferenceInput>
+);
+
+export default UserAutocompleteInput;
--- /dev/null
+import { SelectInput } from '@applica-software-guru/react-admin';
+const YesOrNoSelectInput = (props) => (
+ <SelectInput
+ {...props}
+ emptyText={'ra.boolean.null'}
+ choices={[
+ { id: true, name: 'ra.yes' },
+ { id: false, name: 'ra.no' }
+ ]}
+ />
+);
+
+export default YesOrNoSelectInput;
--- /dev/null
+export { default as ActivitySelectInput } from './ActivitySelectInput';
+export { default as ActivityTypeAutocompleteInput } from './ActivityTypeAutocompleteInput';
+export { default as CityAutocompleteInput } from './CityAutocompleteInput';
+export { default as CustomerAreaManyInput } from './CustomerAreaManyInput';
+export { default as CustomerAutocompleteInput } from './CustomerAutocompleteInput';
+export { default as CustomerOfficeAutocompleteInput } from './CustomerOfficeAutocompleteInput';
+export { default as CustomerOfficeManyInput } from './CustomerOfficeManyInput';
+export { default as CustomerReferentManyInput } from './CustomerReferentManyInput';
+export { default as EquipmentAttachmentManyInput } from './EquipmentAttachmentManyInput';
+export { default as EquipmentAutocompleteInput } from './EquipmentAutocompleteInput';
+export { default as EquipmentListAutocompleteInput } from './EquipmentListAutocompleteInput';
+export { default as EquipmentManyInput } from './EquipmentManyInput';
+export { default as EquipmentTypeAttachmentManyInput } from './EquipmentTypeAttachmentManyInput';
+export { default as EquipmentTypeAutocompleteInput } from './EquipmentTypeAutocompleteInput';
+export { default as EquipmentTypeManyInput } from './EquipmentTypeManyInput';
+export { default as InterventionArrayInput } from './InterventionArrayInput';
+export { default as LangSelectInput } from './LangSelectInput';
+export { default as MaintenanceManyInput } from './MaintenanceManyInput';
+export { default as MoneyInput } from './MoneyInput';
+export { default as NationAutocompleteInput } from './NationAutocompleteInput';
+export { default as PriceListItemManyInput } from './PriceListItemManyInput';
+export { default as PriceListManyInput } from './PriceListManyInput';
+export { default as ProtocolAttachmentManyInput } from './ProtocolAttachmentManyInput';
+export { default as ProtocolAutoComplete } from './ProtocolAutoComplete';
+export { default as ProvinceAutocompleteInput } from './ProvinceAutocompleteInput';
+export { default as RFIDDeviceAutocomplete } from './RFIDDeviceAutocomplete';
+export { default as RFIDDeviceTypeSelectInput } from './RFIDDeviceTypeSelectInput';
+export { default as RegionAutocompleteInput } from './RegionAutocompleteInput';
+export { default as SupplierAutocompleteInput } from './SupplierAutocompleteInput';
+export { default as SupplierReferentManyInput } from './SupplierReferentManyInput';
+export { default as SupplyActivityAutocompleteInput } from './SupplyActivityAutocompleteInput';
+export { default as SupplyManyInput } from './SupplyManyInput';
+export { default as UserAutocompleteInput } from './UserAutocompleteInput';
+export { default as YesOrNoSelectInput } from './YesOrNoSelectInput';
+export { default as CustomerReadOnlyInput } from './CustomerReadOnlyInput';
+export { default as CustomerAreaAutocompleteInput } from './CustomerAreaAutocompleteInput';
+export { default as SupplyAutocompleteInput } from './SupplyAutocompleteInput';
+export { default as DocxTemplateTypeSelectInput } from './DocxTemplateTypeSelectInput';
+export { default as ProtocolServiceArrayInput } from './ProtocolServiceArrayInput';
--- /dev/null
+import { ActivityTypeAutocompleteInput, CustomerAutocompleteInput, SupplyActivityAutocompleteInput } from 'components/ra-inputs';
+import {
+ BulkDeleteWithConfirmButton,
+ Datagrid,
+ DateField,
+ List,
+ ReferenceField,
+ SearchInput,
+ SelectInput,
+ TextField,
+ usePermissions
+} from '@applica-software-guru/react-admin';
+
+import { ActivityStatusField } from 'components/ra-fields';
+
+const ActivityList = ({ activityStatus }) => {
+ const { permissions, isLoading } = usePermissions();
+ const canDelete = !isLoading && permissions.includes('activity:delete');
+ const canCreate = !isLoading && permissions.includes('activity:new');
+
+ const filtersList = [
+ <SearchInput source="keyword" alwaysOn />,
+ <ActivityTypeAutocompleteInput source="activityTypeId" display="legend" />,
+ <SupplyActivityAutocompleteInput source="supplyId" display="legend" />,
+ <SelectInput source="status" display="legend" choices={activityStatus} defaultValue={'OPEN'} />
+ ];
+
+ if (!isLoading && permissions.includes('customer:list')) {
+ filtersList.push(<CustomerAutocompleteInput source="customerId" display="legend" />);
+ }
+
+ return (
+ <List perPage={25} filters={filtersList} hasCreate={canCreate}>
+ <Datagrid rowClick="edit" bulkActionButtons={!canDelete ? null : <BulkDeleteWithConfirmButton />}>
+ {!isLoading && permissions.includes('supplier:list') && (
+ <ReferenceField source="supply.supplierId" reference="entities/supplier" link={false} sortable={false}>
+ <TextField source="businessName" />
+ </ReferenceField>
+ )}
+ {!isLoading && permissions.includes('customer:list') && <TextField source="customer.name" sortable={false} />}
+ {!isLoading && permissions.includes('equipment:list') && (
+ <ReferenceField source="supply.equipmentId" reference="entities/equipment" link={false} sortable={false}>
+ <TextField source="equipmentType.name" />
+ </ReferenceField>
+ )}
+ <TextField source="activityType.description" sortable={false} />
+ <TextField source="title" />
+ <TextField source="user.name" sortable={false} />
+ <DateField source="date" />
+ <DateField source="updated" showTime />
+ <ActivityStatusField source="status" />
+ </Datagrid>
+ </List>
+ );
+};
+
+export default ActivityList;
--- /dev/null
+import { AttachmentField, Datagrid, DateField, List, SearchInput, TextField } from '@applica-software-guru/react-admin';
+
+const ActivityTypeList = () => (
+ <List perPage={25} filters={[<SearchInput source="keyword" alwaysOn />]}>
+ <Datagrid rowClick="edit">
+ <TextField source="description" />
+ <AttachmentField source="attachment" title="attachment.name" />
+ <DateField source="updated" showTime />
+ </Datagrid>
+ </List>
+);
+
+export default ActivityTypeList;
--- /dev/null
+import { Datagrid, DateField, DateTimeInput, List, ReferenceField, SearchInput, TextField } from '@applica-software-guru/react-admin';
+
+import { AuditLogMessageField } from '../ra-fields';
+import { UserAutocompleteInput } from 'components/ra-inputs';
+
+const parse = (date) => date;
+
+const AuditLogList = () => {
+ return (
+ <List
+ perPage={25}
+ sort={{ field: 'created', order: 'DESC' }}
+ exporter={false}
+ filters={[
+ <SearchInput key="keyword" source="keyword" alwaysOn fullWidth />,
+ <DateTimeInput
+ type="datetime-local"
+ key="from"
+ source="from"
+ label="resources.entities/audit-log.fields.from"
+ display="legend"
+ parse={parse}
+ alwaysOn
+ fullWidth
+ />,
+ <DateTimeInput
+ key="to"
+ source="to"
+ label="resources.entities/audit-log.fields.to"
+ display="legend"
+ parse={parse}
+ alwaysOn
+ fullWidth
+ />,
+ <UserAutocompleteInput key="userId" source="userId" display="legend" alwaysOn />
+ ]}
+ >
+ <Datagrid rowClick={false} bulkActionButtons={false}>
+ <DateField source="created" showTime />
+ <ReferenceField source="userId" reference="entities/user" emptyText="Anonymous" link={false} sortable={false}>
+ <TextField source="name" />
+ </ReferenceField>
+ <AuditLogMessageField source="message" sortable={false} />
+ </Datagrid>
+ </List>
+ );
+};
+
+export default AuditLogList;
--- /dev/null
+import {
+ BooleanField,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ Empty,
+ List,
+ SearchInput,
+ TextField
+} from '@applica-software-guru/react-admin';
+
+import { CustomerCreateForm } from '../ra-forms';
+
+const Actions = () => (
+ <CreateInDialogButton
+ sx={{
+ minWidth: 130
+ }}
+ redirect="edit"
+ size="medium"
+ variant="outlined"
+ color="secondary"
+ label="resources.entities/customer.actions.create"
+ >
+ <CustomerCreateForm />
+ </CreateInDialogButton>
+);
+
+const CustomerList = () => (
+ <List
+ perPage={25}
+ filters={[<SearchInput source="keyword" key="keyword" alwaysOn />]}
+ empty={<Empty actions={<Actions />} />}
+ actions={<Actions />}
+ >
+ <Datagrid rowClick="edit">
+ <BooleanField source="active" />
+ <TextField source="name" />
+ <TextField source="vatCode" />
+ <DateField source="updated" showTime />
+ </Datagrid>
+ </List>
+);
+
+export default CustomerList;
--- /dev/null
+import { Datagrid, DateField, List, SearchInput, TextField, useThemeConfig, useTranslate } from '@applica-software-guru/react-admin';
+
+import { Alert } from '@mui/material';
+
+const AlertBox = () => {
+ const { spacing } = useThemeConfig();
+ const translate = useTranslate();
+ return (
+ <Alert sx={{ p: spacing, m: spacing }} severity="info">
+ {translate('resources.entities/device.alert')}
+ </Alert>
+ );
+};
+const DeviceList = () => (
+ <List perPage={25} filters={[<SearchInput source="keyword" alwaysOn />]}>
+ <AlertBox />
+ <Datagrid rowClick="edit">
+ <TextField source="id" />
+ <TextField source="code" />
+ <DateField source="registrationDate" showTime />
+ </Datagrid>
+ </List>
+);
+
+export default DeviceList;
--- /dev/null
+import { AttachmentField, Datagrid, DateField, List, SearchInput, TextField } from '@applica-software-guru/react-admin';
+
+const DocxTemplateList = () => (
+ <List perPage={25} filters={[<SearchInput source="keyword" alwaysOn />]}>
+ <Datagrid rowClick="edit">
+ <TextField source="name" />
+ <AttachmentField source="attachment" title="attachment.name" />
+ <DateField source="updated" showTime />
+ </Datagrid>
+ </List>
+);
+
+export default DocxTemplateList;
--- /dev/null
+import {
+ BulkDeleteWithConfirmButton,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ Empty,
+ List,
+ SearchInput,
+ TextField,
+ usePermissions
+} from '@applica-software-guru/react-admin';
+
+import { EquipmentCreateInDialogForm } from 'components/ra-forms';
+
+const Actions = () => {
+ const { permissions, isLoading } = usePermissions();
+ return (
+ !isLoading &&
+ permissions.includes('equipment:new') && (
+ <CreateInDialogButton
+ sx={{
+ minWidth: 130
+ }}
+ redirect="edit"
+ size="medium"
+ variant="outlined"
+ color="secondary"
+ label="ra.action.create"
+ >
+ <EquipmentCreateInDialogForm />
+ </CreateInDialogButton>
+ )
+ );
+};
+
+const EquipmentList = () => {
+ const { permissions, isLoading } = usePermissions();
+ const canDelete = !isLoading && permissions.includes('equipment:delete');
+ return (
+ <List
+ perPage={25}
+ filters={[<SearchInput source="keyword" alwaysOn />]}
+ exporter={false}
+ actions={<Actions />}
+ empty={<Empty actions={<Actions />} />}
+ >
+ <Datagrid rowClick="edit" bulkActionButtons={!canDelete ? null : <BulkDeleteWithConfirmButton />}>
+ {!isLoading && permissions.includes('supplier:list') && <TextField source="supplier.businessName" sortable={false} />}
+ <TextField source="equipmentType.name" />
+ <DateField source="updated" showTime />
+ </Datagrid>
+ </List>
+ );
+};
+
+export default EquipmentList;
--- /dev/null
+import { BooleanField, Datagrid, DateField, List, SearchInput, TextField, CreateInDialogButton } from '@applica-software-guru/react-admin';
+import { EquipmentTypeCreateForm } from 'components/ra-forms';
+
+const Actions = () => (
+ <CreateInDialogButton
+ sx={{
+ minWidth: 130
+ }}
+ redirect="edit"
+ size="medium"
+ variant="outlined"
+ color="secondary"
+ label="ra.action.create"
+ >
+ <EquipmentTypeCreateForm />
+ </CreateInDialogButton>
+);
+
+const EquipmentTypeList = () => (
+ <List perPage={25} filters={[<SearchInput source="keyword" alwaysOn />]} exporter={false} actions={<Actions />}>
+ <Datagrid rowClick="edit">
+ <TextField source="progressive" />
+ <TextField source="name" />
+ <BooleanField source="accessory" />
+ <DateField source="updated" showTime />
+ </Datagrid>
+ </List>
+);
+
+export default EquipmentTypeList;
--- /dev/null
+import { ActionsField, Datagrid, List, RecordInput, SearchInput, TextField } from '@applica-software-guru/react-admin';
+
+const I18nMessageList = () => (
+ <List perPage={25} filters={[<SearchInput key="keyword" source="keyword" alwaysOn fullWidth />]}>
+ <Datagrid>
+ <TextField source="lang" />
+ <RecordInput source="code" />
+ <RecordInput source="text" />
+ <ActionsField />
+ </Datagrid>
+ </List>
+);
+
+export default I18nMessageList;
--- /dev/null
+import { ActivitySelectInput, UserAutocompleteInput } from 'components/ra-inputs';
+import {
+ BulkDeleteWithConfirmButton,
+ Datagrid,
+ DateField,
+ List,
+ SearchInput,
+ TextField,
+ usePermissions
+} from '@applica-software-guru/react-admin';
+
+const MaintenanceList = () => {
+ const { isLoading, permissions } = usePermissions();
+ const canDelete = !isLoading && permissions.includes('maintenance:delete');
+ return (
+ <List
+ perPage={25}
+ filters={[
+ <SearchInput source="keyword" alwaysOn />,
+ <ActivitySelectInput source="activityId" />,
+ <UserAutocompleteInput source="userId" />
+ ]}
+ >
+ <Datagrid rowClick="edit" bulkActionButtons={!canDelete ? null : <BulkDeleteWithConfirmButton />}>
+ <TextField source="activity.title" sortable={false} />
+ <TextField source="title" />
+ <TextField source="interventionNumber" />
+ <DateField source="date" sortable={false} />
+ <DateField source="updated" showTime />
+ </Datagrid>
+ </List>
+ );
+};
+
+export default MaintenanceList;
--- /dev/null
+import {
+ BulkDeleteWithConfirmButton,
+ CreateInDialogButton,
+ Datagrid,
+ DateField,
+ Empty,
+ List,
+ SearchInput,
+ TextField,
+ usePermissions
+} from '@applica-software-guru/react-admin';
+import { ProtocolDialogForm } from 'components/ra-forms';
+
+import { CustomerAutocompleteInput } from 'components/ra-inputs';
+
+const Actions = () => (
+ <CreateInDialogButton
+ sx={{
+ minWidth: 130
+ }}
+ redirect="edit"
+ size="medium"
+ variant="outlined"
+ color="secondary"
+ label="resources.entities/customer.actions.create"
+ >
+ <ProtocolDialogForm />
+ </CreateInDialogButton>
+);
+
+const ProtocolList = () => {
+ const { permissions, isLoading } = usePermissions();
+ const canCreate = !isLoading && permissions.includes('protocol:new');
+ const canDelete = !isLoading && permissions.includes('protocol:delete');
+
+ const filtersList = [<SearchInput source="keyword" alwaysOn />];
+
+ if (!isLoading && permissions.includes('customer:list')) {
+ filtersList.push(<CustomerAutocompleteInput source="customerId" alwaysOn display="legend" />);
+ }
+
+ return (
+ <List perPage={25} filters={filtersList} hasCreate={canCreate} empty={<Empty actions={<Actions />} />} actions={<Actions />}>
+ <Datagrid rowClick="edit" bulkActionButtons={!canDelete ? null : <BulkDeleteWithConfirmButton />}>
+ {!isLoading && permissions.includes('customer:list') && <TextField source="customer.name" sortable={false} />}
+ <TextField source="title" />
+ <TextField source="protocolNumber" />
+ <DateField source="protocolDate" />
+ <DateField source="updated" showTime />
+ </Datagrid>
+ </List>
+ );
+};
+
+export default ProtocolList;
--- /dev/null
+import { Datagrid, DateField, List, SearchInput, TextField } from '@applica-software-guru/react-admin';
+
+const RFIDeviceList = () => (
+ <List perPage={25} filters={[<SearchInput source="keyword" alwaysOn />]}>
+ <Datagrid rowClick="edit">
+ <TextField source="name" />
+ <TextField source="code" />
+ <DateField source="updated" showTime />
+ <TextField source="type" />
+ </Datagrid>
+ </List>
+);
+
+export default RFIDeviceList;
--- /dev/null
+import {
+ Datagrid,
+ DateField,
+ DateTimeInput,
+ List,
+ MainCard,
+ ReferenceField,
+ TextField,
+ useListContext,
+ useThemeConfig,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import { Grid, List as MuiList, ListItem, ListItemText, Typography } from '@mui/material';
+import { RFIDDeviceAutocomplete, SupplyAutocompleteInput, CustomerAreaAutocompleteInput } from 'components/ra-inputs';
+import queryString from 'query-string';
+import { useLocation } from 'react-router-dom';
+import { Fragment, useEffect } from 'react';
+
+const FilterSetter = () => {
+ const location = useLocation();
+ const queryParams = queryString.parse(location.search);
+ const gatewayId = queryParams['gateway-id'];
+ const { setFilters } = useListContext();
+
+ useEffect(() => {
+ if (gatewayId) {
+ setFilters({ gatewayId: gatewayId });
+ }
+ }, [gatewayId, setFilters]);
+
+ return null;
+};
+
+const RFIDDeviceTrackList = () => {
+ const translate = useTranslate();
+ const { spacing } = useThemeConfig();
+ const location = useLocation();
+ const queryParams = queryString.parse(location.search);
+ const gatewayId = queryParams['gateway-id'];
+ const currentUrl = `http://${window.location.hostname}${window.location.port ? `:8080` : ''}/api/track?`;
+
+ return (
+ <Grid container spacing={spacing}>
+ <Grid item xs={12} sm={4}>
+ <MainCard title={translate('resources.entities/rfid-device-track.tutorial.title')} content={false}>
+ <MuiList component="nav" aria-label="Come utilizzare le API di tracciamento" sx={{ m: 0, p: 0, pl: 0.5, pb: 1, pr: 0.5 }}>
+ <ListItem sx={{ paddingTop: 0, paddingBottom: 1 }}>
+ <ListItemText primary="Chiamata HTTP GET base" secondary={<code>{`${currentUrl}`}</code>} />
+ </ListItem>
+ <ListItem sx={{ paddingTop: 0, paddingBottom: 1 }}>
+ <ListItemText
+ primary="Parametri obbligatori"
+ secondary={
+ <Fragment>
+ <Typography textAlign="justify" variant="body2" color="GrayText" component="span">
+ Includere <b>tag</b> (ID del tag RFID), <b>timestamp</b> e <b>gate</b> (ID del gateway RFID). Il campo{' '}
+ <b>timestamp</b>, se non specificato, sarà impostato al momento della chiamata automaticamente.
+ <br />
+ </Typography>
+ <Typography textAlign="justify" variant="body2" color="GrayText" component="span" sx={{ mt: 1 }}>
+ <b>Attenzione:</b> ricorda che tag e gateway devono essere preventivamente registrati nel sistema all'interno della
+ sezione RFID.
+ </Typography>
+ </Fragment>
+ }
+ />
+ </ListItem>
+ <ListItem sx={{ paddingTop: 0, paddingBottom: 1 }}>
+ <ListItemText primary="Esempio" secondary={<code>GET {`${currentUrl}tag=1234&gate=gate01`}</code>} />
+ </ListItem>
+ <ListItem sx={{ paddingTop: 0, paddingBottom: 0 }}>
+ <ListItemText
+ primary="Parametri aggiuntivi"
+ secondary={
+ <Typography textAlign="justify" variant="body2" color="GrayText" component="span">
+ È possibile aggiungere ulteriori parametri che saranno accodati alla chiamata.
+ </Typography>
+ }
+ />
+ </ListItem>
+ </MuiList>
+ </MainCard>
+ </Grid>
+ <Grid item xs={12} sm={8}>
+ <List
+ key={gatewayId}
+ perPage={25}
+ filterDefaultValues={{ gatewayId }}
+ filters={[
+ <RFIDDeviceAutocomplete source="tagId" type="TAG" display="legend" alwaysOn />,
+ <RFIDDeviceAutocomplete source="gatewayId" type="GATEWAY" display="legend" alwaysOn />,
+ <SupplyAutocompleteInput source="supplyId" display="legend" alwaysOn />,
+ <CustomerAreaAutocompleteInput source="areaId" display="legend" alwaysOn />,
+ <DateTimeInput source="from" display="legend" alwaysOn />,
+ <DateTimeInput source="to" display="legend" alwaysOn />
+ ]}
+ sort={{ field: 'ts', order: 'DESC' }}
+ >
+ <FilterSetter />
+ <Datagrid bulkActionButtons={null}>
+ <TextField source="gateway.name" sortable={false} />
+ <TextField source="tag.name" sortable={false} />
+ <ReferenceField source="supplyId" reference="entities/supply" sortable={false}>
+ <TextField source="equipmentType.name" />
+ </ReferenceField>
+ <ReferenceField
+ source="areaId"
+ reference="entities/customer-area"
+ sortable={false}
+ link={(data, reference) => `/entities/customer/${data?.customerId}/areas`}
+ >
+ <TextField source="name" />
+ </ReferenceField>
+ <TextField source="additionalData" />
+ <DateField source="ts" showTime />
+ </Datagrid>
+ </List>
+ </Grid>
+ </Grid>
+ );
+};
+
+export default RFIDDeviceTrackList;
--- /dev/null
+import { CreateInDialogButton, Datagrid, DateField, Empty, List, SearchInput, TextField } from '@applica-software-guru/react-admin';
+import { SupplierCreateForm } from 'components/ra-forms';
+
+const Actions = () => (
+ <CreateInDialogButton
+ sx={{
+ minWidth: 130
+ }}
+ redirect="edit"
+ size="medium"
+ variant="outlined"
+ color="secondary"
+ label="resources.entities/supplier.actions.create"
+ >
+ <SupplierCreateForm />
+ </CreateInDialogButton>
+);
+
+const SupplierList = () => (
+ <List
+ perPage={25}
+ filters={[<SearchInput source="keyword" alwaysOn />]}
+ exporter={false}
+ empty={<Empty actions={<Actions />} />}
+ actions={<Actions />}
+ >
+ <Datagrid rowClick="edit">
+ <TextField source="businessName" />
+ <TextField source="vatNumber" />
+ <TextField source="city.name" sortable={false} />
+ <DateField source="updated" showTime />
+ </Datagrid>
+ </List>
+);
+
+export default SupplierList;
--- /dev/null
+import { BulkDeleteWithConfirmButton, Datagrid, DateField, List, TextField, usePermissions } from '@applica-software-guru/react-admin';
+import { SupplyStatusField } from 'components/ra-fields';
+import { EquipmentListAutocompleteInput, ProtocolAutoComplete, SupplierAutocompleteInput } from 'components/ra-inputs';
+
+const SupplyList = () => {
+ const { isLoading, permissions } = usePermissions();
+ const canCreate = !isLoading && permissions.includes('supply:new');
+ const canDelete = !isLoading && permissions.includes('supply:delete');
+
+ const filtersList = [
+ <EquipmentListAutocompleteInput source="equipmentId" alwaysOn display="legend" />,
+ <ProtocolAutoComplete source="protocolId" alwaysOn display="legend" />
+ ];
+
+ if (!isLoading && permissions.includes('supplier:list')) {
+ filtersList.push(<SupplierAutocompleteInput source="supplierId" alwaysOn display="legend" />);
+ }
+
+ return (
+ <List perPage={25} filters={filtersList} hasCreate={canCreate}>
+ <Datagrid rowClick="edit" bulkActionButtons={!canDelete ? null : <BulkDeleteWithConfirmButton />}>
+ {!isLoading && permissions.includes('supplier:list') && <TextField source="supplier.businessName" sortable={false} />}
+ {!isLoading && permissions.includes('equipment:list') && <TextField source="equipmentType.name" />}
+ <TextField source="protocol.title" sortable={false} />
+ <TextField source="quantity" />
+ <DateField source="orderDate" />
+ <DateField source="deliveryDate" />
+ <DateField source="updated" showTime />
+ <SupplyStatusField source="usageStatus" />
+ </Datagrid>
+ </List>
+ );
+};
+
+export default SupplyList;
--- /dev/null
+import {
+ BooleanField,
+ BooleanInput,
+ CoverField,
+ Datagrid,
+ DateField,
+ EmailField,
+ FunctionField,
+ ImpersonateUserButton,
+ List,
+ ReferenceField,
+ SearchInput,
+ SelectInput,
+ SimpleList,
+ TextField,
+ useTranslate
+} from '@applica-software-guru/react-admin';
+import { Chip, useMediaQuery } from '@mui/material';
+
+import { CustomerAutocompleteInput } from 'components/ra-inputs';
+import PropTypes from 'prop-types';
+
+const sortRoles = (roles, configuredRoles) => {
+ return roles
+ ?.filter((r) => configuredRoles.find((cr) => cr.id === r))
+ .sort((a, b) => {
+ const aIndex = configuredRoles.findIndex((r) => r.id === a);
+ const bIndex = configuredRoles.findIndex((r) => r.id === b);
+
+ return aIndex - bIndex;
+ });
+};
+const UserList = ({ configuredRoles }) => {
+ const isSmall = useMediaQuery((theme) => theme.breakpoints.down('sm'));
+ const translate = useTranslate();
+ return (
+ <List
+ perPage={25}
+ filters={[
+ <SearchInput key="keyword" source="keyword" alwaysOn fullWidth />,
+ <SelectInput
+ key="role"
+ source="role"
+ choices={configuredRoles}
+ alwaysOn
+ emptyText="ra.action.view_all"
+ display="legend"
+ fullWidth
+ />,
+
+ <CustomerAutocompleteInput source="customerId" alwaysOn display="legend" fullWidth />,
+ <BooleanInput key="active" source="active" display="legend" fullWidth />
+ ]}
+ >
+ {isSmall ? (
+ <SimpleList
+ leftAvatar={(record) => <CoverField source="image" circle width={50} height={50} justify="flex-end" record={record} />}
+ primaryText={(record) => record.name}
+ secondaryText={(record) => record.email}
+ />
+ ) : (
+ <Datagrid rowClick="edit">
+ <CoverField source="image" sortable={false} label="" width={50} height={50} circle />
+ <BooleanField source="active" />
+ <TextField source="name" sortBy="username" />
+ <EmailField source="email" />
+ <ReferenceField source="customerId" reference="entities/customer" link="show" sortable={false}>
+ <TextField source="name" />
+ </ReferenceField>
+ <DateField source="registrationDate" showTime />
+ <FunctionField
+ label="ra.user.roles"
+ render={(record) =>
+ sortRoles(record?.roles, configuredRoles)?.map((role) => (
+ <Chip
+ label={translate(configuredRoles.find((r) => r.id === role)?.name)}
+ key={role}
+ sx={{ mr: 1, mt: 1 }}
+ color="secondary"
+ />
+ ))
+ }
+ />
+ <ImpersonateUserButton />
+ </Datagrid>
+ )}
+ </List>
+ );
+};
+
+UserList.propTypes = {
+ configuredRoles: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string, name: PropTypes.string })).isRequired
+};
+
+export default UserList;
--- /dev/null
+export { default as AuditLogList } from './AuditLogList';
+export { default as CustomerList } from './CustomerList';
+export { default as DeviceList } from './DeviceList';
+export { default as I18nMessageList } from './I18nMessageList';
+export { default as RFIDeviceList } from './RFIDDeviceList';
+export { default as SupplierList } from './SupplierList';
+export { default as UserList } from './UserList';
+export { default as EquipmentTypeList } from './EquipmentTypeList';
+export { default as ProtocolList } from './ProtocolList';
+export { default as SupplyList } from './SupplyList';
+export { default as ActivityList } from './ActivityList';
+export { default as ActivityTypeList } from './ActivityTypeList';
+export { default as MaintenanceList } from './MaintenanceList';
+export { default as EquipmentList } from './EquipmentList';
+export { default as RFIDDeviceTrackList } from './RFIDDeviceTrackList';
+export { default as DocxTemplateList } from './DocxTemplateList';
--- /dev/null
+let environment = 'PRODUCTION';
+let appUrl = `//${document.location.host}/`;
+if (appUrl.endsWith(':3000/')) {
+ appUrl = 'http://localhost:8080/';
+ environment = 'DEVELOPER';
+} else if (appUrl.endsWith(':3001/')) {
+ appUrl = 'http://localhost:8001/';
+ environment = 'DEVELOPER';
+}
+export const APP_URL = appUrl;
+export const API_URL = `${APP_URL}api`;
+export const ENVIRONMENT = environment;
+export const APP_NAME = 'Edera';
+export const COPY = '© Dyrecta for ';
+
+export const CONFIGURED_ROLES = [
+ { id: 'ROLE_ADMIN', name: 'ra.roles.admin' },
+ { id: 'ROLE_OPERATOR', name: 'ra.roles.operator' },
+ { id: 'ROLE_USER', name: 'ra.roles.user' },
+ { id: 'ROLE_MAINTAINER', name: 'ra.roles.maintainer' },
+ { id: 'ROLE_CUSTOMER', name: 'ra.roles.customer' }
+];
+
+export const ACTIVITY_STATUS = [
+ { id: 'OPEN', name: 'ra.status.open' },
+ { id: 'CLOSED', name: 'ra.status.closed' }
+];
--- /dev/null
+export {};
--- /dev/null
+import { ActivityTypeForm, ActivityTypeList } from 'components';
+import { Create, Edit } from '@applica-software-guru/react-admin';
+const ActivityTypeCreate = () => (
+ <Create>
+ <ActivityTypeForm />
+ </Create>
+);
+const ActivityTypeEdit = () => (
+ <Edit>
+ <ActivityTypeForm />
+ </Edit>
+);
+
+const config = {
+ list: ActivityTypeList,
+ edit: ActivityTypeEdit,
+ create: ActivityTypeCreate,
+ options: {
+ group: 'maintenance'
+ }
+};
+
+export default config;
--- /dev/null
+import { ActivityForm, ActivityList } from 'components';
+import { Create, Edit } from '@applica-software-guru/react-admin';
+
+import { ACTIVITY_STATUS } from 'config';
+const ActivityCreate = () => (
+ <Create>
+ <ActivityForm activityStatus={ACTIVITY_STATUS} />
+ </Create>
+);
+const ActivityEdit = () => (
+ <Edit>
+ <ActivityForm activityStatus={ACTIVITY_STATUS} />
+ </Edit>
+);
+
+const CustomActivityList = () => <ActivityList activityStatus={ACTIVITY_STATUS} />;
+
+const config = {
+ list: CustomActivityList,
+ edit: ActivityEdit,
+ create: ActivityCreate,
+ options: {
+ group: 'maintenance'
+ }
+};
+
+export default config;
--- /dev/null
+import { AuditLogList } from 'components';
+
+const config = {
+ list: AuditLogList,
+ options: {
+ group: 'security'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { CustomerEditForm, CustomerList } from 'components';
+
+const CustomerCreate = () => (
+ <Create>
+ <CustomerEditForm />
+ </Create>
+);
+
+const CustomerEdit = () => (
+ <Edit>
+ <CustomerEditForm />
+ </Edit>
+);
+
+const config = {
+ list: CustomerList,
+ edit: CustomerEdit,
+ create: CustomerCreate,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { DeviceForm, DeviceList } from 'components';
+const DeviceCreate = () => (
+ <Create>
+ <DeviceForm />
+ </Create>
+);
+const DeviceEdit = () => (
+ <Edit>
+ <DeviceForm />
+ </Edit>
+);
+
+const config = {
+ list: DeviceList,
+ edit: DeviceEdit,
+ create: DeviceCreate,
+ options: {
+ group: 'security'
+ }
+};
+
+export default config;
--- /dev/null
+import { DocxTemplateForm, DocxTemplateList } from 'components';
+import { Create, Edit } from '@applica-software-guru/react-admin';
+const DocxTemplateCreate = () => (
+ <Create>
+ <DocxTemplateForm />
+ </Create>
+);
+const DocxTemplateEdit = () => (
+ <Edit>
+ <DocxTemplateForm />
+ </Edit>
+);
+
+const config = {
+ list: DocxTemplateList,
+ edit: DocxTemplateEdit,
+ create: DocxTemplateCreate,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { EquipmentTypeList, EquipmentTypeEditForm, EquipmentTypeCreateForm } from 'components';
+
+const EquipmentTypeCreate = () => (
+ <Create redirect={false}>
+ <EquipmentTypeCreateForm />
+ </Create>
+);
+const EquipmentTypeEdit = () => (
+ <Edit redirect={false}>
+ <EquipmentTypeEditForm />
+ </Edit>
+);
+
+const config = {
+ list: EquipmentTypeList,
+ edit: EquipmentTypeEdit,
+ create: EquipmentTypeCreate,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { EquipmentCreateForm, EquipmentEditForm, EquipmentList } from 'components';
+
+const EquipmentCreate = () => (
+ <Create>
+ <EquipmentCreateForm />
+ </Create>
+);
+const EquipmentEdit = () => (
+ <Edit>
+ <EquipmentEditForm />
+ </Edit>
+);
+
+const config = {
+ edit: EquipmentEdit,
+ create: EquipmentCreate,
+ list: EquipmentList,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { I18nMessageForm, I18nMessageList } from 'components';
+
+const MessageCreate = () => (
+ <Create>
+ <I18nMessageForm />
+ </Create>
+);
+
+const MessageEdit = () => (
+ <Edit>
+ <I18nMessageForm />
+ </Edit>
+);
+
+const config = {
+ list: I18nMessageList,
+ edit: MessageEdit,
+ create: MessageCreate,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+export { default as user } from './user';
+export { default as notification } from './notification';
+export { default as i18nMessage } from './i18n-message';
+export { default as device } from './device';
+export { default as auditLog } from './audit-log';
+export { default as rfidDevice } from './rfid-device';
+export { default as customer } from './customer';
+export { default as supplier } from './supplier';
+export { default as equipmentType } from './equipment-type';
+export { default as priceList } from './price-list';
+export { default as protocol } from './protocol';
+export { default as equipment } from './equipment';
+export { default as supply } from './supply';
+export { default as activity } from './activity';
+export { default as activityType } from './activity-type';
+export { default as maintenance } from './maintenance';
+export { default as rfidDeviceTrack } from './rfid-device-track';
+export { default as docxTemplate } from './docx-template';
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { MaintenanceCreateForm, MaintenanceEditForm, MaintenanceList } from 'components';
+const MaintenanceCreate = () => (
+ <Create>
+ <MaintenanceCreateForm />
+ </Create>
+);
+const MaintenanceEdit = () => (
+ <Edit mutationMode="pessimistic" redirect={false}>
+ <MaintenanceEditForm />
+ </Edit>
+);
+
+const config = {
+ list: MaintenanceList,
+ edit: MaintenanceEdit,
+ create: MaintenanceCreate,
+ options: {
+ group: 'maintenance'
+ }
+};
+
+export default config;
--- /dev/null
+import { NotificationList as BaseNotificationList, List } from '@applica-software-guru/react-admin';
+
+import React from 'react';
+
+const NotificationList = () => (
+ <List>
+ <BaseNotificationList />
+ </List>
+);
+const config = {
+ list: NotificationList,
+ options: {
+ group: 'dashboard'
+ }
+};
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+
+import { PriceListForm } from 'components';
+
+const PriceListCreate = () => (
+ <Create>
+ <PriceListForm />
+ </Create>
+);
+
+const PriceListEdit = () => (
+ <Edit redirect={false}>
+ <PriceListForm />
+ </Edit>
+);
+
+const config = {
+ edit: PriceListEdit,
+ create: PriceListCreate,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { ProtocolList, ProtocolForm, ProtocolDialogForm } from 'components';
+
+const ProtocolCreate = () => (
+ <Create>
+ <ProtocolDialogForm />
+ </Create>
+);
+const ProtocolEdit = () => (
+ <Edit>
+ <ProtocolForm />
+ </Edit>
+);
+
+const config = {
+ list: ProtocolList,
+ edit: ProtocolEdit,
+ create: ProtocolCreate,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { RFIDDeviceTrackList } from 'components';
+
+const config = {
+ list: RFIDDeviceTrackList,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { RFIDeviceList, RFIDDeviceForm } from 'components';
+const RFIDDeviceCreate = () => (
+ <Create>
+ <RFIDDeviceForm />
+ </Create>
+);
+const RFIDDeviceEdit = () => (
+ <Edit>
+ <RFIDDeviceForm />
+ </Edit>
+);
+
+const config = {
+ list: RFIDeviceList,
+ edit: RFIDDeviceEdit,
+ create: RFIDDeviceCreate,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { SupplierList, SupplierForm } from 'components';
+
+const SupplierCreate = () => (
+ <Create>
+ <SupplierForm />
+ </Create>
+);
+const SupplierEdit = () => (
+ <Edit>
+ <SupplierForm />
+ </Edit>
+);
+
+const config = {
+ list: SupplierList,
+ edit: SupplierEdit,
+ create: SupplierCreate,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { SupplyForm, SupplyList, SupplyShow } from 'components';
+
+const SupplyCreate = () => (
+ <Create>
+ <SupplyForm />
+ </Create>
+);
+const SupplyEdit = () => (
+ <Edit>
+ <SupplyForm />
+ </Edit>
+);
+
+const config = {
+ list: SupplyList,
+ edit: SupplyEdit,
+ create: SupplyCreate,
+ show: SupplyShow,
+ options: {
+ group: 'control-panel'
+ }
+};
+
+export default config;
--- /dev/null
+import { Create, Edit } from '@applica-software-guru/react-admin';
+import { UserForm, UserList } from 'components';
+
+import { CONFIGURED_ROLES } from 'config';
+
+const UserCreate = () => (
+ <Create>
+ <UserForm configuredRoles={CONFIGURED_ROLES} />
+ </Create>
+);
+const UserEdit = () => (
+ <Edit mutationMode="pessimistic">
+ <UserForm configuredRoles={CONFIGURED_ROLES} />
+ </Edit>
+);
+
+const config = {
+ list: <UserList configuredRoles={CONFIGURED_ROLES} />,
+ edit: UserEdit,
+ create: UserCreate,
+ options: {
+ group: 'security'
+ }
+};
+
+export default config;
--- /dev/null
+export { default as useRedirect } from './useRedirect';
--- /dev/null
+const set = (path) => localStorage.setItem('redirect', path);
+const get = (remove = true) => {
+ var redirect = localStorage.getItem('redirect');
+ if (remove) {
+ localStorage.removeItem('redirect');
+ }
+ return redirect;
+};
+const useRedirect = () => ({ set, get });
+
+export default useRedirect;
--- /dev/null
+import App from './App';
+import { createRoot } from 'react-dom/client';
+import reportWebVitals from './reportWebVitals';
+
+const container = document.getElementById('root');
+const root = createRoot(container);
+
+root.render(<App />);
+
+reportWebVitals();
--- /dev/null
+import {
+ DashboardOutlined,
+ EyeOutlined,
+ FlagOutlined,
+ InboxOutlined,
+ NotificationOutlined,
+ ScheduleOutlined,
+ SolutionOutlined,
+ TableOutlined,
+ ToolOutlined,
+ UserOutlined,
+ UsergroupAddOutlined,
+ WifiOutlined,
+ GlobalOutlined
+} from '@ant-design/icons';
+import RadarIcon from '@mui/icons-material/Radar';
+import EngineeringOutlinedIcon from '@mui/icons-material/EngineeringOutlined';
+const config = [
+ {
+ id: 'dashboard',
+ title: 'Dashboard',
+ type: 'group',
+ icon: DashboardOutlined,
+ children: [
+ {
+ id: 'entities/notification',
+ title: 'ra.menu.item.notification',
+ type: 'item',
+ url: '/entities/notification',
+ icon: NotificationOutlined
+ },
+ {
+ id: 'position',
+ title: 'ra.menu.item.position',
+ type: 'item',
+ url: '/position',
+ icon: RadarIcon,
+ roles: ['ROLE_ADMIN', 'ROLE_OPERATOR'],
+ resource: false
+ }
+ ]
+ },
+ {
+ id: 'maintenance',
+ title: 'ra.menu.maintenance',
+ icon: TableOutlined,
+ type: 'group',
+ children: [
+ {
+ id: 'entities/activity-type',
+ title: 'ra.menu.item.entities/activity-type',
+ type: 'item',
+ url: '/entities/activity-type',
+ icon: TableOutlined,
+ resource: true,
+ roles: ['ROLE_ADMIN', 'ROLE_OPERATOR']
+ },
+ {
+ id: 'entities/activity',
+ title: 'ra.menu.item.entities/activity',
+ type: 'item',
+ url: '/entities/activity',
+ icon: ScheduleOutlined,
+ resource: true
+ },
+ {
+ id: 'entities/maintenance',
+ title: 'ra.menu.item.entities/maintenance',
+ type: 'item',
+ url: '/entities/maintenance',
+ icon: EngineeringOutlinedIcon,
+ resource: true
+ }
+ ]
+ },
+ {
+ id: 'control-panel',
+ title: 'ra.menu.control-panel',
+ icon: TableOutlined,
+ type: 'group',
+ children: [
+ {
+ id: 'entities/customer',
+ title: 'ra.menu.item.entities/customer',
+ type: 'item',
+ url: '/entities/customer',
+ icon: UsergroupAddOutlined,
+ roles: ['ROLE_ADMIN', 'ROLE_OPERATOR']
+ },
+ {
+ id: 'entities/protocol',
+ title: 'ra.menu.item.entities/protocol',
+ type: 'item',
+ url: '/entities/protocol',
+ icon: SolutionOutlined,
+ resource: true
+ },
+ {
+ id: 'entities/supplier',
+ title: 'ra.menu.item.supplier',
+ type: 'item',
+ url: '/entities/supplier',
+ icon: UserOutlined,
+ resource: true,
+ roles: ['ROLE_ADMIN', 'ROLE_OPERATOR']
+ },
+ {
+ id: 'entities/supply',
+ title: 'ra.menu.item.entities/supply',
+ type: 'item',
+ url: '/entities/supply',
+ icon: InboxOutlined,
+ resource: true,
+ roles: ['ROLE_ADMIN', 'ROLE_OPERATOR', 'ROLE_CUSTOMER', 'ROLE_MAINTAINER']
+ },
+ {
+ id: 'entities/equipment',
+ title: 'ra.menu.item.entities/equipment',
+ type: 'item',
+ url: '/entities/equipment',
+ icon: ToolOutlined,
+ resource: true
+ },
+ {
+ id: 'entities/equipment-type',
+ title: 'ra.menu.item.entities/equipment-type',
+ type: 'item',
+ url: '/entities/equipment-type',
+ icon: TableOutlined,
+ resource: true,
+ roles: ['ROLE_ADMIN', 'ROLE_OPERATOR']
+ },
+ {
+ id: 'entities/rfid-device',
+ title: 'ra.menu.item.entities/rfid-device',
+ type: 'item',
+ url: '/entities/rfid-device',
+ icon: WifiOutlined,
+ resource: true,
+ roles: ['ROLE_ADMIN']
+ },
+ {
+ id: 'entities/docx-template',
+ title: 'ra.menu.item.entities/docx-template',
+ type: 'item',
+ url: '/entities/docx-template',
+ icon: TableOutlined,
+ resource: true,
+ roles: ['ROLE_ADMIN']
+ },
+ {
+ id: 'entities/rfid-device-track',
+ title: 'ra.menu.item.entities/rfid-device-track',
+ type: 'item',
+ url: '/entities/rfid-device-track',
+ icon: GlobalOutlined,
+ resource: true,
+ roles: ['ROLE_ADMIN']
+ },
+ {
+ id: 'entities/i18n-message',
+ title: 'ra.menu.item.entities/i18n-message',
+ type: 'item',
+ url: '/entities/i18n-message',
+ icon: FlagOutlined,
+ roles: ['ROLE_ADMIN']
+ }
+ ]
+ },
+ {
+ id: 'security',
+ title: 'ra.menu.security',
+ icon: TableOutlined,
+ type: 'group',
+ children: [
+ {
+ id: 'entities/audit-log',
+ title: 'ra.menu.item.entities/audit-log',
+ type: 'item',
+ url: '/entities/audit-log',
+ icon: EyeOutlined,
+ roles: ['ROLE_ADMIN']
+ },
+ {
+ id: 'entities/user',
+ title: 'ra.menu.item.entities/user',
+ type: 'item',
+ url: '/entities/user',
+ icon: UserOutlined,
+ roles: ['ROLE_ADMIN']
+ },
+ {
+ id: 'entities/device',
+ title: 'ra.menu.item.entities/device',
+ type: 'item',
+ url: '/entities/device',
+ icon: TableOutlined,
+ roles: ['ROLE_ADMIN']
+ }
+ ]
+ }
+];
+
+export default config;
--- /dev/null
+/// <reference types="react-scripts" />
--- /dev/null
+const reportWebVitals = (onPerfEntry) => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
--- /dev/null
+const theme = () => ({});
+
+export default theme;