commit 1eff007617801697ebb91f516ce21eac018c351b Author: guilherme Date: Tue Nov 25 11:56:43 2025 -0300 feat(explode): first commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..52d53b9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ + Created by https://www.toptal.com/developers/gitignore/api/maven +# Edit at https://www.toptal.com/developers/gitignore?templates=maven + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# End of https://www.toptal.com/developers/gitignore/api/maven + +.idea +.env* diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5d47c21 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..ba33bd1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "endOfLine": "lf", + "bracketSpacing": true, + "bracketSameLine": true, + "arrowParens": "always", + "tabWidth": 2, + "semi": true, + "trailingComma": "all", + "useTabs": false +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ddc1455 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:20-alpine + +WORKDIR /code + +COPY package.json package.json +COPY package-lock.json package-lock.json + +RUN npm install + +COPY . . + +CMD [ "npm", "run", "dev" ] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..34dad3c --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,72 @@ +@Library('jenkins-library') _ +pipeline { + agent {label 'docker-slave'} + parameters { + string(name: 'PROJETO', defaultValue: 'agprotocolopainelweb', description: 'Digite o nome do projeto:') + choice(name: 'AMBIENTE', choices: ['HML','BETA','PRD'], description: 'Selecione o Ambiente para publicação:') + string(name: 'TAG', defaultValue: '', description: 'Digite a tag da imagem caso deseje republicar uma versão já existente:') + } + environment { + DOCKERIMAGE = " " + MAIL_NOTIFICATION_RECIPIENTS = "rafael.deda@gmail.com" + } + stages{ + stage('Build e Construção da Imagem') { + when { + environment name: 'TAG', value: '' + } + steps{ + script{ + DOCKERIMAGE = buildImages(DOCKERIMAGE) + } + } + } + + stage('Submeter Imagem ao Registry') { + when { + environment name: 'TAG', value: '' + } + steps{ + script{ + DOCKERIMAGE = publishImages(DOCKERIMAGE) + } + } + } + stage('Deploy no Ambiente Selecionado no Cluster') { + when { + anyOf { + environment name: 'AMBIENTE', value: 'BETA' + environment name: 'AMBIENTE', value: 'PRD' + } + } + steps { + deployCluster() + } + } + + stage('Deploy no Ambiente on Promise') { + when { + allOf { + environment name: 'AMBIENTE', value: 'HML' + environment name: 'ON_PROMISE', value: '1' + } + } + steps{ + deployCluster('k8s-inovesolutions-hml') + } + } + + // stage('Limpando Imagens') { + // agent {label 'conteiner'} + // steps{ + // limpandoImagens() + // } + // } + } + post { + always { + //sendNotification() + cleanWorkspace() + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..86b2b11 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information. + +Note: This will impact Vite dev & build performances. + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/components.json b/components.json new file mode 100644 index 0000000..743ac58 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/views/components", + "utils": "@/app/utils/index", + "ui": "@/views/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/index.html b/index.html new file mode 100644 index 0000000..8bf8c47 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + agfrotapainelweb + + +
+ + + diff --git a/k8s/BETA/certificate.yaml b/k8s/BETA/certificate.yaml new file mode 100644 index 0000000..9cbfdca --- /dev/null +++ b/k8s/BETA/certificate.yaml @@ -0,0 +1,13 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: agapesistemas-PROJETOAMBIENTE-tls + namespace: agapesistemas-ns-AMBIENTE +spec: + dnsNames: + - PROJETOAMBIENTE.agapesistemas.com.br + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: letsencrypt-prod + secretName: agapesistemas-PROJETOAMBIENTE-tls \ No newline at end of file diff --git a/k8s/BETA/deployment-withrole.yaml b/k8s/BETA/deployment-withrole.yaml new file mode 100644 index 0000000..bd86a8f --- /dev/null +++ b/k8s/BETA/deployment-withrole.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + name: PROJETO-dp-AMBIENTE + namespace: agapesistemas-ns-AMBIENTE +spec: + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + selector: + matchLabels: + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + template: + metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + namespace: agapesistemas-ns-AMBIENTE + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - PROJETO + topologyKey: kubernetes.io/hostname + containers: + - env: + - name: TZ + value: America/Maceio + envFrom: + - secretRef: + name: agapesistemas-db-credential-sc + - secretRef: + name: agapesistemas-PROJETO-sc + - configMapRef: + name: agapesistemas-PROJETO-cm + - secretRef: + name: agapesistemas-db-role-credential-sc + optional: false + image: IMAGEM:TAG + name: PROJETO + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: + limits: + memory: 1000Mi + requests: + memory: 780Mi + nodeSelector: + doks.digitalocean.com/node-pool: pool-k8s-agapesistemas-app-AMBIENTE + imagePullSecrets: + - name: registry-agapesistemas \ No newline at end of file diff --git a/k8s/BETA/deployment.yaml b/k8s/BETA/deployment.yaml new file mode 100644 index 0000000..8ed355e --- /dev/null +++ b/k8s/BETA/deployment.yaml @@ -0,0 +1,51 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + name: PROJETO-dp-AMBIENTE + namespace: agapesistemas-ns-AMBIENTE +spec: + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + selector: + matchLabels: + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + template: + metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + namespace: agapesistemas-ns-AMBIENTE + spec: + containers: + - env: + - name: TZ + value: America/Maceio + envFrom: + - secretRef: + name: agapesistemas-db-credential-sc + - secretRef: + name: agapesistemas-PROJETO-sc + - configMapRef: + name: agapesistemas-PROJETO-cm + image: IMAGEM:TAG + name: PROJETO + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: + limits: + memory: 1200Mi + requests: + memory: 1000Mi + nodeSelector: + doks.digitalocean.com/node-pool: pool-k8s-agapesistemas-app-AMBIENTE + imagePullSecrets: + - name: registry-agapesistemas \ No newline at end of file diff --git a/k8s/BETA/hpa.yaml b/k8s/BETA/hpa.yaml new file mode 100644 index 0000000..9eeb673 --- /dev/null +++ b/k8s/BETA/hpa.yaml @@ -0,0 +1,19 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: agapesistemas-PROJETO-hpa + namespace: agapesistemas-ns-AMBIENTE +spec: + maxReplicas: 1 + metrics: + - resource: + name: memory + target: + averageValue: 780Mi + type: AverageValue + type: Resource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: PROJETO-dp-AMBIENTE \ No newline at end of file diff --git a/k8s/BETA/ingress.yaml b/k8s/BETA/ingress.yaml new file mode 100644 index 0000000..6361f24 --- /dev/null +++ b/k8s/BETA/ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + traefik.ingress.kubernetes.io/router.middlewares: traefik-http-to-https-redirectscheme@kubernetescrd,traefik-enable-cors-header@kubernetescrd + name: agapesistemas-PROJETO-ing + namespace: agapesistemas-ns-AMBIENTE +spec: + ingressClassName: traefik + rules: + - host: PROJETOAMBIENTE.agapesistemas.com.br + http: + paths: + - backend: + service: + name: agapesistemas-PROJETO-svc + port: + number: 8080 + pathType: ImplementationSpecific + tls: + - hosts: + - PROJETOAMBIENTE.agapesistemas.com.br + secretName: agapesistemas-PROJETOAMBIENTE-tls diff --git a/k8s/BETA/service.yaml b/k8s/BETA/service.yaml new file mode 100644 index 0000000..07cab40 --- /dev/null +++ b/k8s/BETA/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + traefik.ingress.kubernetes.io/service.sticky.cookie: 'true' + #traefik.ingress.kubernetes.io/service.sticky.cookie.name: JSESSIONID + traefik.ingress.kubernetes.io/service.sticky.cookie.secure: 'true' + name: agapesistemas-PROJETO-svc + namespace: agapesistemas-ns-AMBIENTE +spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: PROJETO + type: ClusterIP diff --git a/k8s/BETA/storage/configmap.yaml b/k8s/BETA/storage/configmap.yaml new file mode 100644 index 0000000..7c2d8cf --- /dev/null +++ b/k8s/BETA/storage/configmap.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: agapesistemas-PROJETO-cm + namespace: agapesistemas-ns-AMBIENTE \ No newline at end of file diff --git a/k8s/BETA/storage/secret.yaml b/k8s/BETA/storage/secret.yaml new file mode 100644 index 0000000..b0b32d5 --- /dev/null +++ b/k8s/BETA/storage/secret.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Secret +metadata: + name: agapesistemas-PROJETO-sc + namespace: agapesistemas-ns-AMBIENTE \ No newline at end of file diff --git a/k8s/HML/certificate.yaml b/k8s/HML/certificate.yaml new file mode 100644 index 0000000..9cbfdca --- /dev/null +++ b/k8s/HML/certificate.yaml @@ -0,0 +1,13 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: agapesistemas-PROJETOAMBIENTE-tls + namespace: agapesistemas-ns-AMBIENTE +spec: + dnsNames: + - PROJETOAMBIENTE.agapesistemas.com.br + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: letsencrypt-prod + secretName: agapesistemas-PROJETOAMBIENTE-tls \ No newline at end of file diff --git a/k8s/HML/deployment-withrole.yaml b/k8s/HML/deployment-withrole.yaml new file mode 100644 index 0000000..f0416d4 --- /dev/null +++ b/k8s/HML/deployment-withrole.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + name: PROJETO-dp-AMBIENTE + namespace: agapesistemas-ns-AMBIENTE +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + template: + metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + namespace: agapesistemas-ns-AMBIENTE + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - PROJETO + topologyKey: kubernetes.io/hostname + containers: + - env: + - name: TZ + value: America/Maceio + envFrom: + - secretRef: + name: agapesistemas-db-credential-sc + - secretRef: + name: agapesistemas-PROJETO-sc + - configMapRef: + name: agapesistemas-PROJETO-cm + - secretRef: + name: agapesistemas-db-role-credential-sc + optional: false + image: IMAGEM:TAG + name: PROJETO + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: + limits: + memory: 857Mi + requests: + memory: 720Mi + nodeSelector: + doks.digitalocean.com/node-pool: pool-k8s-agapesistemas-app-AMBIENTE + imagePullSecrets: + - name: registry-agapesistemas \ No newline at end of file diff --git a/k8s/HML/deployment.yaml b/k8s/HML/deployment.yaml new file mode 100644 index 0000000..47a690c --- /dev/null +++ b/k8s/HML/deployment.yaml @@ -0,0 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + name: PROJETO-dp-AMBIENTE + namespace: agapesistemas-ns-AMBIENTE +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + template: + metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + namespace: agapesistemas-ns-AMBIENTE + spec: + containers: + - env: + - name: TZ + value: America/Maceio + envFrom: + - secretRef: + name: agapesistemas-db-credential-sc + - secretRef: + name: agapesistemas-PROJETO-sc + - configMapRef: + name: agapesistemas-PROJETO-cm + image: IMAGEM:TAG + name: PROJETO + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: + limits: + memory: 1800Mi + requests: + memory: 1008Mi + nodeSelector: + doks.digitalocean.com/node-pool: pool-k8s-agapesistemas-app-AMBIENTE + imagePullSecrets: + - name: registry-agapesistemas diff --git a/k8s/HML/hpa.yaml b/k8s/HML/hpa.yaml new file mode 100644 index 0000000..dd0d71f --- /dev/null +++ b/k8s/HML/hpa.yaml @@ -0,0 +1,19 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: agapesistemas-PROJETO-hpa + namespace: agapesistemas-ns-AMBIENTE +spec: + maxReplicas: 1 + metrics: + - resource: + name: memory + target: + averageValue: 720Mi + type: AverageValue + type: Resource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: PROJETO-dp-AMBIENTE \ No newline at end of file diff --git a/k8s/HML/ingress.yaml b/k8s/HML/ingress.yaml new file mode 100644 index 0000000..6361f24 --- /dev/null +++ b/k8s/HML/ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + traefik.ingress.kubernetes.io/router.middlewares: traefik-http-to-https-redirectscheme@kubernetescrd,traefik-enable-cors-header@kubernetescrd + name: agapesistemas-PROJETO-ing + namespace: agapesistemas-ns-AMBIENTE +spec: + ingressClassName: traefik + rules: + - host: PROJETOAMBIENTE.agapesistemas.com.br + http: + paths: + - backend: + service: + name: agapesistemas-PROJETO-svc + port: + number: 8080 + pathType: ImplementationSpecific + tls: + - hosts: + - PROJETOAMBIENTE.agapesistemas.com.br + secretName: agapesistemas-PROJETOAMBIENTE-tls diff --git a/k8s/HML/service.yaml b/k8s/HML/service.yaml new file mode 100644 index 0000000..07cab40 --- /dev/null +++ b/k8s/HML/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + traefik.ingress.kubernetes.io/service.sticky.cookie: 'true' + #traefik.ingress.kubernetes.io/service.sticky.cookie.name: JSESSIONID + traefik.ingress.kubernetes.io/service.sticky.cookie.secure: 'true' + name: agapesistemas-PROJETO-svc + namespace: agapesistemas-ns-AMBIENTE +spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: PROJETO + type: ClusterIP diff --git a/k8s/HML/storage/configmap.yaml b/k8s/HML/storage/configmap.yaml new file mode 100644 index 0000000..9411f3f --- /dev/null +++ b/k8s/HML/storage/configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: agapesistemas-PROJETO-cm + namespace: agapesistemas-ns-AMBIENTE + +data: + VITE_API_URL: 'https://agpainelapi.hml.agapesistemas.com.br' \ No newline at end of file diff --git a/k8s/HML/storage/secret.yaml b/k8s/HML/storage/secret.yaml new file mode 100644 index 0000000..5052dcd --- /dev/null +++ b/k8s/HML/storage/secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: agapesistemas-PROJETO-sc + namespace: agapesistemas-ns-AMBIENTE + + diff --git a/k8s/PRD/certificate.yaml b/k8s/PRD/certificate.yaml new file mode 100644 index 0000000..9cbfdca --- /dev/null +++ b/k8s/PRD/certificate.yaml @@ -0,0 +1,13 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: agapesistemas-PROJETOAMBIENTE-tls + namespace: agapesistemas-ns-AMBIENTE +spec: + dnsNames: + - PROJETOAMBIENTE.agapesistemas.com.br + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: letsencrypt-prod + secretName: agapesistemas-PROJETOAMBIENTE-tls \ No newline at end of file diff --git a/k8s/PRD/deployment-high.yaml b/k8s/PRD/deployment-high.yaml new file mode 100644 index 0000000..75d867b --- /dev/null +++ b/k8s/PRD/deployment-high.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + name: PROJETO-dp-AMBIENTE + namespace: agapesistemas-ns-AMBIENTE +spec: + replicas: 2 + strategy: + type: Recreate + selector: + matchLabels: + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + template: + metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + namespace: agapesistemas-ns-AMBIENTE + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - PROJETO + topologyKey: kubernetes.io/hostname + containers: + - env: + - name: TZ + value: America/Maceio + envFrom: + - secretRef: + name: agapesistemas-db-credential-sc + - secretRef: + name: agapesistemas-PROJETO-sc + - configMapRef: + name: agapesistemas-PROJETO-cm + image: IMAGEM:TAG + name: PROJETO + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: + limits: + memory: 7142Mi + requests: + memory: 6000Mi + nodeSelector: + doks.digitalocean.com/node-pool: pool-k8s-agapesistemas-app-AMBIENTE-high + imagePullSecrets: + - name: registry-agapesistemas \ No newline at end of file diff --git a/k8s/PRD/deployment-withrole.yaml b/k8s/PRD/deployment-withrole.yaml new file mode 100644 index 0000000..885f0da --- /dev/null +++ b/k8s/PRD/deployment-withrole.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + name: PROJETO-dp-AMBIENTE + namespace: agapesistemas-ns-AMBIENTE +spec: + replicas: 2 + strategy: + type: Recreate + selector: + matchLabels: + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + template: + metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + namespace: agapesistemas-ns-AMBIENTE + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - PROJETO + topologyKey: kubernetes.io/hostname + containers: + - env: + - name: TZ + value: America/Maceio + envFrom: + - secretRef: + name: agapesistemas-db-credential-sc + - secretRef: + name: agapesistemas-PROJETO-sc + - configMapRef: + name: agapesistemas-PROJETO-cm + - secretRef: + name: agapesistemas-db-role-credential-sc + optional: false + image: IMAGEM:TAG + name: PROJETO + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: + limits: + memory: 1000Mi + requests: + memory: 780Mi + nodeSelector: + doks.digitalocean.com/node-pool: pool-k8s-agapesistemas-app-AMBIENTE + imagePullSecrets: + - name: registry-agapesistemas \ No newline at end of file diff --git a/k8s/PRD/deployment.yaml b/k8s/PRD/deployment.yaml new file mode 100644 index 0000000..d86d91b --- /dev/null +++ b/k8s/PRD/deployment.yaml @@ -0,0 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + name: PROJETO-dp-AMBIENTE + namespace: agapesistemas-ns-AMBIENTE +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + template: + metadata: + labels: + app: PROJETO + workload.user.cattle.io/workloadselector: apps.deployment-agapesistemas-ns-AMBIENTE-PROJETO + namespace: agapesistemas-ns-AMBIENTE + spec: + containers: + - env: + - name: TZ + value: America/Maceio + envFrom: + - secretRef: + name: agapesistemas-db-credential-sc + - secretRef: + name: agapesistemas-PROJETO-sc + - configMapRef: + name: agapesistemas-PROJETO-cm + image: IMAGEM:TAG + name: PROJETO + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: + limits: + memory: 1200Mi + requests: + memory: 1000Mi + nodeSelector: + doks.digitalocean.com/node-pool: pool-k8s-agapesistemas-app-AMBIENTE + imagePullSecrets: + - name: registry-agapesistemas \ No newline at end of file diff --git a/k8s/PRD/hpa-high.yaml b/k8s/PRD/hpa-high.yaml new file mode 100644 index 0000000..39ae4d5 --- /dev/null +++ b/k8s/PRD/hpa-high.yaml @@ -0,0 +1,19 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: agapesistemas-PROJETO-hpa + namespace: agapesistemas-ns-AMBIENTE +spec: + maxReplicas: 3 + metrics: + - resource: + name: memory + target: + averageValue: 6000Mi + type: AverageValue + type: Resource + minReplicas: 2 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: PROJETO-dp-AMBIENTE \ No newline at end of file diff --git a/k8s/PRD/hpa.yaml b/k8s/PRD/hpa.yaml new file mode 100644 index 0000000..9eeb673 --- /dev/null +++ b/k8s/PRD/hpa.yaml @@ -0,0 +1,19 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: agapesistemas-PROJETO-hpa + namespace: agapesistemas-ns-AMBIENTE +spec: + maxReplicas: 1 + metrics: + - resource: + name: memory + target: + averageValue: 780Mi + type: AverageValue + type: Resource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: PROJETO-dp-AMBIENTE \ No newline at end of file diff --git a/k8s/PRD/ingress.yaml b/k8s/PRD/ingress.yaml new file mode 100644 index 0000000..6361f24 --- /dev/null +++ b/k8s/PRD/ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + traefik.ingress.kubernetes.io/router.middlewares: traefik-http-to-https-redirectscheme@kubernetescrd,traefik-enable-cors-header@kubernetescrd + name: agapesistemas-PROJETO-ing + namespace: agapesistemas-ns-AMBIENTE +spec: + ingressClassName: traefik + rules: + - host: PROJETOAMBIENTE.agapesistemas.com.br + http: + paths: + - backend: + service: + name: agapesistemas-PROJETO-svc + port: + number: 8080 + pathType: ImplementationSpecific + tls: + - hosts: + - PROJETOAMBIENTE.agapesistemas.com.br + secretName: agapesistemas-PROJETOAMBIENTE-tls diff --git a/k8s/PRD/service.yaml b/k8s/PRD/service.yaml new file mode 100644 index 0000000..07cab40 --- /dev/null +++ b/k8s/PRD/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + traefik.ingress.kubernetes.io/service.sticky.cookie: 'true' + #traefik.ingress.kubernetes.io/service.sticky.cookie.name: JSESSIONID + traefik.ingress.kubernetes.io/service.sticky.cookie.secure: 'true' + name: agapesistemas-PROJETO-svc + namespace: agapesistemas-ns-AMBIENTE +spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: PROJETO + type: ClusterIP diff --git a/k8s/PRD/storage/configmap-high.yaml b/k8s/PRD/storage/configmap-high.yaml new file mode 100644 index 0000000..7c2d8cf --- /dev/null +++ b/k8s/PRD/storage/configmap-high.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: agapesistemas-PROJETO-cm + namespace: agapesistemas-ns-AMBIENTE \ No newline at end of file diff --git a/k8s/PRD/storage/configmap.yaml b/k8s/PRD/storage/configmap.yaml new file mode 100644 index 0000000..ea54b7f --- /dev/null +++ b/k8s/PRD/storage/configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: agapesistemas-PROJETO-cm + namespace: agapesistemas-ns-AMBIENTE + +data: + VITE_API_URL: 'https://agpainelapiprd.agapesistemas.com.br' \ No newline at end of file diff --git a/k8s/PRD/storage/secret-high.yaml b/k8s/PRD/storage/secret-high.yaml new file mode 100644 index 0000000..b0b32d5 --- /dev/null +++ b/k8s/PRD/storage/secret-high.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Secret +metadata: + name: agapesistemas-PROJETO-sc + namespace: agapesistemas-ns-AMBIENTE \ No newline at end of file diff --git a/k8s/PRD/storage/secret.yaml b/k8s/PRD/storage/secret.yaml new file mode 100644 index 0000000..b0b32d5 --- /dev/null +++ b/k8s/PRD/storage/secret.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Secret +metadata: + name: agapesistemas-PROJETO-sc + namespace: agapesistemas-ns-AMBIENTE \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..08dfad1 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "agprotocolopainelweb", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@phosphor-icons/react": "^2.1.10", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.1.17", + "@tanstack/react-query": "^5.90.10", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "lucide-react": "^0.554.0", + "react": "^19.2.0", + "react-day-picker": "^9.11.1", + "react-dom": "^19.2.0", + "recharts": "^2.15.4", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^4.1.17", + "vaul": "^1.1.2" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.1.0", + "babel-plugin-react-compiler": "^1.0.0", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "tw-animate-css": "^1.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.3", + "vite": "^7.2.2" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..05a70f3 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,299 @@ +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/views/components/ui/card'; +import { + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from '@/views/components/ui/chart'; +import { Progress } from '@/views/components/ui/progress'; +import { TrendingUp } from 'lucide-react'; +import { + Bar, + BarChart, + CartesianGrid, + Line, + LineChart, + Pie, + PieChart, + XAxis, +} from 'recharts'; +import { Header } from './views/components/header'; + +export const description = 'A stacked bar chart with a legend'; + +export function App() { + const stats = [ + { + title: 'Média de Consumo (R$)', + value: 'R$ 0,00', + description: '', + progress: 44.6, + }, + { + title: 'Total de KM Percorridos', + value: '0 KM', + description: '', + progress: 30.6, + }, + { + title: 'Viagens Realizadas no Mês', + value: '0', + description: '', + progress: 24.8, + }, + ]; + + const evolutionConfig = { + price: { + label: 'Preço', + color: '#0e233d', + }, + } satisfies ChartConfig; + + const evolutionData = [ + { month: 'Shell', price: 186 }, + { month: 'Ipiranga', price: 305 }, + { month: 'BR', price: 237 }, + ]; + + const bestPricesConfig = { + price: { + label: 'Preço', + color: '#0e233d', + }, + } satisfies ChartConfig; + + const bestPricesData = [ + { station: 'Shell', price: 5.42 }, + { station: 'Ipiranga', price: 5.35 }, + { station: 'BR', price: 5.38 }, + { station: 'Ale', price: 5.3 }, + ]; + + const vehicleStatusConfig = { + active: { + label: 'Ativos', + color: '#0e233d', + }, + maintenance: { + label: 'Manutenção', + color: '#173b63', + }, + inactive: { + label: 'Inativos', + color: '#154677', + }, + reserved: { + label: 'Reservados', + color: '#145190', + }, + } satisfies ChartConfig; + + const vehicleStatusData = [ + { status: 'active', count: 45, fill: '#0e233d' }, + { status: 'maintenance', count: 12, fill: '#173b63' }, + { status: 'inactive', count: 8, fill: '#154677' }, + { status: 'reserved', count: 15, fill: '#145190' }, + ]; + + const mileageConfig = { + company: { + label: 'Frota Empresa', + color: '#0e233d', + }, + rented: { + label: 'Frota Terceirizada', + color: '#173b63', + }, + } satisfies ChartConfig; + + const mileageData = [ + { month: 'Jan', company: 1200, rented: 800 }, + { month: 'Fev', company: 1350, rented: 750 }, + { month: 'Mar', company: 1100, rented: 900 }, + { month: 'Abr', company: 1400, rented: 600 }, + { month: 'Mai', company: 1250, rented: 850 }, + { month: 'Jun', company: 1300, rented: 700 }, + ]; + + return ( +
+
+ +
+ {stats.map((stat) => ( + + + + {stat.title} + +
{stat.value}
+
+ +

+ {stat.description} +

+ +
+
+ ))} +
+ +
+ + + Evolução do Consumo de Combustível + + Comparativo de preços entre os principais postos + + + + + + + + } + /> + + + + + + + + + Postos com Melhor Preço + + Comparativo de preços entre os principais postos + + + + + + + + } + /> + + + + + + + + + Quilometragem Percorrida + + Total de quilômetros percorridos nos últimos meses + + + + + + + + } /> + } /> + + + + + + + + + + Status dos Veículos + + Distribuição dos veículos por status de operação + + + + + + } /> + + + + + +
+ Frota operando com 85% de disponibilidade{' '} + +
+
+ Status atual da frota de veículos +
+
+
+
+
+ ); +} diff --git a/src/app/utils/index.ts b/src/app/utils/index.ts new file mode 100644 index 0000000..2819a83 --- /dev/null +++ b/src/app/utils/index.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/assets/fonts/din-next/din-next-black-italic.otf b/src/assets/fonts/din-next/din-next-black-italic.otf new file mode 100644 index 0000000..bcc862d Binary files /dev/null and b/src/assets/fonts/din-next/din-next-black-italic.otf differ diff --git a/src/assets/fonts/din-next/din-next-black.otf b/src/assets/fonts/din-next/din-next-black.otf new file mode 100644 index 0000000..ad4ea8a Binary files /dev/null and b/src/assets/fonts/din-next/din-next-black.otf differ diff --git a/src/assets/fonts/din-next/din-next-bold-italic.otf b/src/assets/fonts/din-next/din-next-bold-italic.otf new file mode 100644 index 0000000..83aee5a Binary files /dev/null and b/src/assets/fonts/din-next/din-next-bold-italic.otf differ diff --git a/src/assets/fonts/din-next/din-next-bold.otf b/src/assets/fonts/din-next/din-next-bold.otf new file mode 100644 index 0000000..8e8bd3f Binary files /dev/null and b/src/assets/fonts/din-next/din-next-bold.otf differ diff --git a/src/assets/fonts/din-next/din-next-heavy-italic.otf b/src/assets/fonts/din-next/din-next-heavy-italic.otf new file mode 100644 index 0000000..6edc8a4 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-heavy-italic.otf differ diff --git a/src/assets/fonts/din-next/din-next-heavy.otf b/src/assets/fonts/din-next/din-next-heavy.otf new file mode 100644 index 0000000..7c2a50e Binary files /dev/null and b/src/assets/fonts/din-next/din-next-heavy.otf differ diff --git a/src/assets/fonts/din-next/din-next-italic.otf b/src/assets/fonts/din-next/din-next-italic.otf new file mode 100644 index 0000000..52f39e9 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-italic.otf differ diff --git a/src/assets/fonts/din-next/din-next-light-italic.otf b/src/assets/fonts/din-next/din-next-light-italic.otf new file mode 100644 index 0000000..e51fb83 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-light-italic.otf differ diff --git a/src/assets/fonts/din-next/din-next-light.otf b/src/assets/fonts/din-next/din-next-light.otf new file mode 100644 index 0000000..ec5dc66 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-light.otf differ diff --git a/src/assets/fonts/din-next/din-next-medium-italic.otf b/src/assets/fonts/din-next/din-next-medium-italic.otf new file mode 100644 index 0000000..8b87aa8 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-medium-italic.otf differ diff --git a/src/assets/fonts/din-next/din-next-medium.otf b/src/assets/fonts/din-next/din-next-medium.otf new file mode 100644 index 0000000..d695015 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-medium.otf differ diff --git a/src/assets/fonts/din-next/din-next-regular.otf b/src/assets/fonts/din-next/din-next-regular.otf new file mode 100644 index 0000000..d9a6f25 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-regular.otf differ diff --git a/src/assets/fonts/din-next/din-next-ultra-light-italic.otf b/src/assets/fonts/din-next/din-next-ultra-light-italic.otf new file mode 100644 index 0000000..7132381 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-ultra-light-italic.otf differ diff --git a/src/assets/fonts/din-next/din-next-ultra-light.otf b/src/assets/fonts/din-next/din-next-ultra-light.otf new file mode 100644 index 0000000..ca7d483 Binary files /dev/null and b/src/assets/fonts/din-next/din-next-ultra-light.otf differ diff --git a/src/assets/images/agape-logo-amarela.png b/src/assets/images/agape-logo-amarela.png new file mode 100644 index 0000000..63a7b39 Binary files /dev/null and b/src/assets/images/agape-logo-amarela.png differ diff --git a/src/assets/images/agape-logo.png b/src/assets/images/agape-logo.png new file mode 100644 index 0000000..ac14053 Binary files /dev/null and b/src/assets/images/agape-logo.png differ diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..c95dcec --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +import './styles/index.css'; + +import { App } from './App.tsx'; + +createRoot(document.getElementById('root')!).render( + + + +); diff --git a/src/styles/index.css b/src/styles/index.css new file mode 100644 index 0000000..e4241d5 --- /dev/null +++ b/src/styles/index.css @@ -0,0 +1,288 @@ +@import 'tailwindcss'; +@import 'tw-animate-css'; + +@custom-variant dark (&:is(.dark *)); + +@font-face { + font-family: 'DIN Next'; + font-weight: 900; + src: url('../assets/fonts/din-next/din-next-black.otf') format('woff'); +} + +@font-face { + font-family: 'DIN Next'; + font-weight: 700; + src: url('../assets/fonts/din-next/din-next-bold.otf') format('woff'); +} + +@font-face { + font-family: 'DIN Next'; + font-weight: 600; + src: url('../assets/fonts/din-next/din-next-medium.otf') format('woff'); +} + +@font-face { + font-family: 'DIN Next'; + font-weight: 400; + src: url('../assets/fonts/din-next/din-next-regular.otf') format('woff'); +} + +@font-face { + font-family: 'DIN Next'; + font-weight: 300; + src: url('../assets/fonts/din-next/din-next-light.otf') format('woff'); +} + +@font-face { + font-family: 'DIN Next'; + font-weight: 200; + src: url('../assets/fonts/din-next/din-next-ultra-light.otf') format('woff'); +} + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --font-sans: 'DIN Next', sans-serif; + + --bigstone-50: #f2f7fd; + --bigstone-100: #e3edfb; + --bigstone-200: #c1dcf6; + --bigstone-300: #8bbfee; + --bigstone-400: #4d9ee3; + --bigstone-500: #2682d1; + --bigstone-600: #1866b1; + --bigstone-700: #145190; + --bigstone-800: #154677; + --bigstone-900: #173b63; + --bigstone-950: #0e233d; + + /* Cores usadas pelos gráficos */ + --color-desktop: var(--bigstone-500); + --color-mobile: var(--bigstone-300); + + --color-chrome: var(--bigstone-500); + --color-safari: var(--bigstone-600); + --color-firefox: var(--bigstone-700); + --color-edge: var(--bigstone-800); + --color-other: var(--bigstone-300); + + --chart-1: var(--bigstone-500); + --chart-2: var(--bigstone-400); + --chart-3: var(--bigstone-600); + --chart-4: var(--bigstone-700); + --chart-5: var(--bigstone-300); + + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: #0e233d; + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +@keyframes slidein { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-slidein { + animation: slidein 1s ease forwards; +} +.animate-slidein300 { + animation: slidein 1s ease 300ms forwards; +} +.animate-slidein500 { + animation: slidein 1s ease 500ms forwards; +} +.animate-slidein700 { + animation: slidein 1s ease 700ms forwards; +} + +/* Greeting pill (avatar header) */ +.greeting-pill { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.125rem 0.5rem; + border-radius: 9999px; + transition: transform 160ms ease, box-shadow 160ms ease; + user-select: none; +} + +.greeting-pill:hover { + transform: translateY(-3px); +} + +.greeting-icon { + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 0; +} + +.greeting-text { + font-size: 0.75rem; + font-weight: 600; + color: inherit; + display: none; /* esconder em telas muito pequenas */ +} + +@media (min-width: 640px) { + .greeting-text { + display: inline-block; + } +} + +/* Animations */ +@keyframes float-y { + 0% { + transform: translateY(0); + } + 50% { + transform: translateY(-3px); + } + 100% { + transform: translateY(0); + } +} + +@keyframes slow-rotate { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.greeting-pill .greeting-icon { + animation: float-y 3.6s ease-in-out infinite; +} + +.greeting-pill.day .greeting-icon svg { + filter: drop-shadow(0 1px 6px rgba(255, 180, 60, 0.12)); +} + +.greeting-pill.night .greeting-icon svg { + filter: drop-shadow(0 2px 8px rgba(96, 165, 250, 0.08)); +} + +.greeting-pill.day .greeting-icon.animate-rotate { + animation: slow-rotate 8s linear infinite, float-y 3.6s ease-in-out infinite; +} + +/* small touch: slightly scale on active */ +.greeting-pill:active { + transform: translateY(-1px) scale(0.995); +} diff --git a/src/views/components/header.tsx b/src/views/components/header.tsx new file mode 100644 index 0000000..5658410 --- /dev/null +++ b/src/views/components/header.tsx @@ -0,0 +1,239 @@ +import { Button } from '@/views/components/ui/button'; +import { Calendar } from '@/views/components/ui/calendar'; +import { + Drawer, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from '@/views/components/ui/drawer'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/views/components/ui/popover'; +import { Funnel, GearSix } from '@phosphor-icons/react'; +import { ptBR } from 'date-fns/locale'; +import { useState } from 'react'; +import type { DateRange as RDateRange } from 'react-day-picker'; + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/views/components/ui/select'; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from '@/views/components/ui/sheet'; +import { SelectPopover } from './ui/select-popover'; + +type DateRange = RDateRange; + +const projects = [ + { id: 1, descricao: 'Projeto Alfa' }, + { id: 2, descricao: 'Projeto Beta' }, + { id: 3, descricao: 'Projeto Gama' }, +]; + +const sectors = [ + { id: 1, descricao: 'Manutenção' }, + { id: 2, descricao: 'Operações' }, + { id: 3, descricao: 'Logística' }, +]; + +const clients = [ + { id: 1, descricao: 'Cliente A' }, + { id: 2, descricao: 'Cliente B' }, + { id: 3, descricao: 'Cliente C' }, +]; + +export function Header() { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [localDateRange, setLocalDateRange] = useState(); + + const [dateValue, setDateValue] = useState(() => ({ + from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7), + to: new Date(), + })); + + const [projectValue, setProjectValue] = useState( + projects[0]?.descricao ?? '' + ); + const [sectorValue, setSectorValue] = useState( + sectors[0]?.descricao ?? '' + ); + const [clientValue, setClientValue] = useState( + clients[0]?.descricao ?? '' + ); + + function handlePopoverOpenChange(open: boolean) { + setIsPopoverOpen(open); + if (open) { + setLocalDateRange(dateValue); + } + } + + function clearDateFilter() { + setDateValue(undefined); + setLocalDateRange(undefined); + setIsPopoverOpen(false); + } + + function applyDateFilter() { + setDateValue(localDateRange); + setIsPopoverOpen(false); + } + + function formatDateRange() { + if (!dateValue?.from) return 'Período'; + const from = dateValue.from.toLocaleDateString('pt-BR'); + if (!dateValue?.to) return `A partir de ${from}`; + const to = dateValue.to.toLocaleDateString('pt-BR'); + return `${from} — ${to}`; + } + + return ( +
+
+

+ Gestão de Frota +

+

+ Dashboard de visualização e controle da frota de veículos +

+
+ +
+ + + + + +
+ + Filtros e Configurações + + Personalize o que deseja visualizar. + + + + + + +
+
+
+
+ +
+
+ + + + + + + + + +
+ +
+ + +
+
+
+
+
+ + + + + + + + Filtros + + +
+ + + + + +
+
+
+
+
+ ); +} diff --git a/src/views/components/ui/button.tsx b/src/views/components/ui/button.tsx new file mode 100644 index 0000000..12fe1c1 --- /dev/null +++ b/src/views/components/ui/button.tsx @@ -0,0 +1,64 @@ +/* eslint-disable react-refresh/only-export-components */ +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from 'react'; + +import { cn } from '@/app/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center whitespace-nowrap rounded-sm text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-gray-300', + { + variants: { + variant: { + default: + 'bg-primary text-gray-50 shadow hover:bg-primary/90 dark:bg-gray-50 dark:!text-primary dark:hover:bg-gray-50/90', + destructive: + 'bg-red-500 text-gray-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/90', + outline: + 'border border-gray-200 bg-gray-50 shadow-sm hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:bg-gray-950 dark:hover:bg-gray-800 dark:hover:text-gray-50', + secondary: + 'bg-gray-50 text-gray-900 shadow-sm hover:bg-gray-300/60 dark:bg-gray-800 dark:text-gray-50 dark:hover:bg-gray-800/60', + ghost: + 'hover:bg-gray-100 hover:text-gray-900 dark:text-gray-50 dark:hover:bg-gray-800/50', + // link: 'text-gray-900 underline-offset-4 hover:underline dark:text-gray-50', + link: 'hover:bg-white/10 underline-offset-4 hover:underline dark:text-gray-50 dark:hover:bg-gray-600/50', + disable: + 'bg-red-900 text-gray-50 shadow-sm hover:bg-red-900/90 dark:bg-red-500 dark:text-gray-50 dark:hover:bg-red-500/90', + search: + 'bg-gray-50 hover:bg-gray-200 text-gray-400 hover:text-gray-600', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-sm px-3 text-xs', + lg: 'h-10 rounded-sm p-6 text-base', + icon: 'h-9 w-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + } +); +Button.displayName = 'Button'; + +export { Button, buttonVariants }; diff --git a/src/views/components/ui/calendar.tsx b/src/views/components/ui/calendar.tsx new file mode 100644 index 0000000..1c202cd --- /dev/null +++ b/src/views/components/ui/calendar.tsx @@ -0,0 +1,216 @@ +"use client" + +import * as React from "react" +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "lucide-react" +import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker" + +import { cn } from "@/app/utils" +import { Button, buttonVariants } from "@/views/components/ui/button" + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = "label", + buttonVariant = "ghost", + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps["variant"] +}) { + const defaultClassNames = getDefaultClassNames() + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString("default", { month: "short" }), + ...formatters, + }} + classNames={{ + root: cn("w-fit", defaultClassNames.root), + months: cn( + "flex gap-4 flex-col md:flex-row relative", + defaultClassNames.months + ), + month: cn("flex flex-col w-full gap-4", defaultClassNames.month), + nav: cn( + "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", + defaultClassNames.nav + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_previous + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_next + ), + month_caption: cn( + "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", + defaultClassNames.month_caption + ), + dropdowns: cn( + "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", + defaultClassNames.dropdowns + ), + dropdown_root: cn( + "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", + defaultClassNames.dropdown_root + ), + dropdown: cn( + "absolute bg-popover inset-0 opacity-0", + defaultClassNames.dropdown + ), + caption_label: cn( + "select-none font-medium", + captionLayout === "label" + ? "text-sm" + : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", + defaultClassNames.caption_label + ), + table: "w-full border-collapse", + weekdays: cn("flex", defaultClassNames.weekdays), + weekday: cn( + "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", + defaultClassNames.weekday + ), + week: cn("flex w-full mt-2", defaultClassNames.week), + week_number_header: cn( + "select-none w-(--cell-size)", + defaultClassNames.week_number_header + ), + week_number: cn( + "text-[0.8rem] select-none text-muted-foreground", + defaultClassNames.week_number + ), + day: cn( + "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", + props.showWeekNumber + ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md" + : "[&:first-child[data-selected=true]_button]:rounded-l-md", + defaultClassNames.day + ), + range_start: cn( + "rounded-l-md bg-accent", + defaultClassNames.range_start + ), + range_middle: cn("rounded-none", defaultClassNames.range_middle), + range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), + today: cn( + "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + defaultClassNames.today + ), + outside: cn( + "text-muted-foreground aria-selected:text-muted-foreground", + defaultClassNames.outside + ), + disabled: cn( + "text-muted-foreground opacity-50", + defaultClassNames.disabled + ), + hidden: cn("invisible", defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ) + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === "left") { + return ( + + ) + } + + if (orientation === "right") { + return ( + + ) + } + + return ( + + ) + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ) + }, + ...components, + }} + {...props} + /> + ) +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames() + + const ref = React.useRef(null) + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus() + }, [modifiers.focused]) + + return ( +