From 501444e1ca05dfe14a9a5cc2855696ed4c9b0ef3 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 3 Apr 2024 15:52:52 -0300 Subject: [PATCH 001/351] Valida os campos de user --- api/users/serializers.py | 17 +++++++++++------ api/users/views.py | 10 ++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/api/users/serializers.py b/api/users/serializers.py index 705c1c4c..ac4f9cce 100644 --- a/api/users/serializers.py +++ b/api/users/serializers.py @@ -3,6 +3,10 @@ from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): + username = serializers.CharField(min_length=6, max_length=23, required=True) + password = serializers.CharField(min_length=6, max_length=200, write_only=True) + first_name = serializers.CharField(required=True) + email = serializers.EmailField(required=True) class Meta: model = User @@ -18,12 +22,13 @@ class Meta: def create(self, validated_data): user = User.objects.create_user( - username=validated_data['username'], - password=validated_data['password'], - email=validated_data.get('email') # use get se o email for opcional + username=validated_data['username'], + password=validated_data['password'], + first_name=validated_data['first_name'], + email=validated_data.get('email') ) return user -class UserSerializerP(serializers.Serializer): - username = serializers.CharField(max_length=200) - password = serializers.CharField(max_length=200) \ No newline at end of file +class UserLoginSerializer(serializers.Serializer): + username = serializers.CharField(min_length=6, max_length=23, required=True) + password = serializers.CharField(min_length=6, max_length=200, required=True) \ No newline at end of file diff --git a/api/users/views.py b/api/users/views.py index c6120c26..4c119d5c 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -8,7 +8,7 @@ from rest_framework import generics from django.views.decorators.csrf import csrf_protect, csrf_exempt from .permissions import IsOwner -from .serializers import UserSerializer, UserSerializerP +from .serializers import UserSerializer, UserLoginSerializer from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout @@ -19,7 +19,6 @@ class UserCreateView(generics.CreateAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [] - authentication_classes = [] @method_decorator(csrf_protect, name='dispatch') class UserDetailView(generics.RetrieveUpdateDestroyAPIView): @@ -31,7 +30,7 @@ class UserDetailView(generics.RetrieveUpdateDestroyAPIView): class LoginView(APIView): permission_classes = [] def post(self, request, format=None): - serializer = UserSerializerP(data=request.data) + serializer = UserLoginSerializer(data=request.data) if(serializer.is_valid()): username = serializer.validated_data["username"] password = serializer.validated_data["password"] @@ -40,12 +39,11 @@ def post(self, request, format=None): login(request, user) return Response({'message': 'Login successful'}, status=status.HTTP_200_OK) else: - print(user) return Response({'message': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) return JsonResponse(serializer.errors) - + class LogoutView(APIView): - permission_classes = [permissions.IsAuthenticated] + permission_classes = [IsAuthenticated] def post(self, request, format=None): logout(request) From 12f48a57518b1438314b3fb802ce5f8354e004eb Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 3 Apr 2024 20:37:08 -0300 Subject: [PATCH 002/351] Cria views e urls para csrf token --- api/users/urls.py | 4 +++- api/users/views.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/api/users/urls.py b/api/users/urls.py index c3e040df..81974fa4 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -1,10 +1,12 @@ # urls.py from django.urls import path from rest_framework.routers import DefaultRouter -from .views import UserCreateView, UserDetailView, LoginView, LogoutView +from .views import GetCSRFToken, CheckAuthenticatedView, UserCreateView, UserDetailView, LoginView, LogoutView from django.urls import include, path urlpatterns = [ + path('csrfcookie/', GetCSRFToken.as_view(), name='csrf_cookie'), + path('checkauth/', CheckAuthenticatedView.as_view(), name='check_auth'), path('users/', UserCreateView.as_view()), path('users//', UserDetailView.as_view()), path('login/', LoginView.as_view(), name='login'), diff --git a/api/users/views.py b/api/users/views.py index 4c119d5c..5a553873 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -3,10 +3,10 @@ from rest_framework import viewsets, permissions, status from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.authentication import BasicAuthentication from rest_framework import generics -from django.views.decorators.csrf import csrf_protect, csrf_exempt +from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect, csrf_exempt from .permissions import IsOwner from .serializers import UserSerializer, UserLoginSerializer @@ -14,6 +14,21 @@ from django.contrib.auth import authenticate, login, logout from django.utils.decorators import method_decorator +@method_decorator(ensure_csrf_cookie, name='dispatch') +class GetCSRFToken(APIView): + permission_classes = [AllowAny] + def get(self, request): + return Response({'success':'CSRF Cookie Set'}) + +@method_decorator(csrf_protect, name='dispatch') +class CheckAuthenticatedView(APIView): + permission_classes=[AllowAny] + def get(self, request): + if request.user.is_authenticated: + return Response({'isAuthenticated': True}) + else: + return Response({'isAuthenticated': False}) + @method_decorator(csrf_protect, name='dispatch') class UserCreateView(generics.CreateAPIView): queryset = User.objects.all() From a4109e29428495bfeb92dba9c4d2a114145a6d22 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 3 Apr 2024 20:38:01 -0300 Subject: [PATCH 003/351] Envia o csrf token do backend para o frontend de login e register --- frontend/sige_ie/lib/screens/login.dart | 18 ++++++++++++++++++ frontend/sige_ie/lib/screens/register.dart | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/frontend/sige_ie/lib/screens/login.dart b/frontend/sige_ie/lib/screens/login.dart index 1b3dc404..e2b42152 100644 --- a/frontend/sige_ie/lib/screens/login.dart +++ b/frontend/sige_ie/lib/screens/login.dart @@ -14,12 +14,30 @@ class _LoginScreenState extends State { final TextEditingController usernameController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); + Future fetchCsrfToken() async { + const String url = 'http://10.0.2.2:8000/api/csrfcookie/'; + final response = await http.get(Uri.parse(url)); + + if (response.statusCode == 200) { + String cookie = response.headers['set-cookie']!; + print(cookie); + String csrfToken = cookie.split(';')[0].substring('csrftoken='.length); + return csrfToken; + } else { + throw Exception('Falha ao obter o token CSRF: ${response.statusCode}'); + } + } + Future login(String username, String password) async { + var csrfToken = await fetchCsrfToken(); + print(csrfToken); var url = Uri.parse('http://10.0.2.2:8000/api/login/'); try { var response = await http.post(url, headers: { 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken, + 'Cookie': 'csrftoken=$csrfToken', }, body: jsonEncode({ 'username': username, diff --git a/frontend/sige_ie/lib/screens/register.dart b/frontend/sige_ie/lib/screens/register.dart index 7d64fb70..8b477360 100644 --- a/frontend/sige_ie/lib/screens/register.dart +++ b/frontend/sige_ie/lib/screens/register.dart @@ -18,13 +18,30 @@ class _RegisterScreenState extends State { final TextEditingController passwordController = TextEditingController(); final TextEditingController emailController = TextEditingController(); + Future fetchCsrfToken() async { + const String url = 'http://10.0.2.2:8000/api/csrfcookie/'; + final response = await http.get(Uri.parse(url)); + + if (response.statusCode == 200) { + String cookie = response.headers['set-cookie']!; + print(cookie); + String csrfToken = cookie.split(';')[0].substring('csrftoken='.length); + return csrfToken; + } else { + throw Exception('Falha ao obter o token CSRF: ${response.statusCode}'); + } + } + Future register( - String username, String firstName, String password, String email) async { + String username, String firstName, String password, String email) async { + var csrfToken = await fetchCsrfToken(); var url = Uri.parse('http://10.0.2.2:8000/api/users/'); try { var response = await http.post(url, headers: { 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken, + 'Cookie': 'csrftoken=$csrfToken', }, body: jsonEncode({ 'username': username, From 1343119e9ad9431116eb9c14151d33431561a5a4 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 4 Apr 2024 00:01:12 -0300 Subject: [PATCH 004/351] =?UTF-8?q?Insere=20Organiza=C3=A7=C3=A3o=20do=20P?= =?UTF-8?q?rojeto=20e=20atualiza=20estrutura=20em=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 50 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 71aae008..560ff540 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,21 @@ SIGE IE -### Fase do projeto +## Fase do projeto Release 1 Ir para milestone da release 1 -### Sobre este projeto +## O produto + +### Sobre Aplicativo web mobile desenvolvido para a Universidade de Brasília com objetivo de gerenciar as instalações elétricas e dar suporte ao retrofitting das instalações. -#### Posição + +### Posição O SIGE IE é um sistema da Universidade de Brasília para o gerenciamento de instalações elétricas com o objetivo de facilitar o cadastro das informações de instalação elétrica para ajudar na reforma da parte elétrica dos prédios e salas. Ele permite a automatização da geração de relatórios das instalações elétricas de cada lugar e a centralização dessas informações para uso dos responsáveis pelas instalações. As pessoas devem usar o SIGE IE porque ele simplifica e agiliza o processo de gerenciamento, principalmente do retrofitting de instalações elétricas, garantindo maior eficiência e segurança. -#### Objetivos +### Objetivos Simplificar o cadastro e gerenciamento de informações de instalações elétricas e automatizar a geração de relatórios. -#### Tecnologias -##### Back-end +### Tecnologias +#### Back-end
@@ -32,7 +35,7 @@ Simplificar o cadastro e gerenciamento de informações de instalações elétri
-###### Observação +##### Observação Atualmente o Django REST Framework suporta as seguintes versões do Python e do Django:
@@ -45,7 +48,7 @@ Atualmente o Django REST Framework suporta as seguintes versões do Python e do Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar o projeto usando Python 3.11. -##### Front-end mobile +#### Front-end mobile
@@ -56,32 +59,52 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar
+## O projeto + +### Orgnanização +
+ +| Papel | Atribuições | Responsável | Participantes | +| --- | --- | --- | --- | +| Cliente | Validar as entregas | Loana | Loana, Alex | +| Desenvolvedor back-end | Codificar o backend, configurar a infraestrutura | Pedro | Pedro, Kauan, Oscar | +| Desenvolvedor frontend | Codificar o frontend, realizar integração com backend | Danilo | Danilo, Ramires, Pedro | +| UX design | Projetar a interface do usuário, criar protótipos e realizar entrevistas com os clientes | Danilo | Danilo | +| Analista de requisitos | Levantar requisitos, gerenciar a documentação, validar com cliente | Oscar | Oscar, Ramires, Pedro | + +
+ ### Contribuidores
+ + + + + @@ -89,6 +112,7 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar
AlefMemTavFullstack Pedro Lucas Contribuidor
EngDannFrontend Danilo Contribuidor
kauan2872Backend Kauan Contribuidor
OscarDeBritoDocumentação Oscar Contribuidor
ramires31Frontend Ramires Contribuidor
+## Configuração do ambiente ### Como subir o projeto Estas etapas são válidas para Linux OS e WSL. #### Como subir o back-end: @@ -183,7 +207,7 @@ Vá para dentro da pasta raiz `api`: ``` Pronto, o servidor já está rodando com o banco de dados configurado. -#### Como Subir o Front-end: +#### Como subir o front-end: Antes de começar, verifique se o Flutter SDK está atualizado e compatível com o projeto. Siga as instruções específicas para sua plataforma (Windows, macOS, Linux) disponíveis na [documentação oficial do Flutter](https://flutter.dev/docs/get-started/install). @@ -230,7 +254,7 @@ Com o ambiente preparado, siga os passos abaixo: Pronto, o Front end já está rodando e você pode utilizá-lo. - +## Contribuição ### Como contribuir 1. Faça um fork do repositório do projeto. 2. Clone o fork na sua máquina: @@ -249,8 +273,10 @@ Pronto, o Front end já está rodando e você pode utilizá-lo. 7. Após enviar suas contribuições para o fork do seu repositório, faça um pull request. 8. Aguarde a revisão. -### Documentação +## Documentação +- [Requisitos de software](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/1) - [Cronograma](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/3) -- [Requisitos](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/1) +- [Backlog do produto](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/9) +- [Releases](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/milestones) - [Arquitetura](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/2) - [Atas de reunião](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/4) From 22e48775660123928d9b95d196fa852240e4bb9c Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 4 Apr 2024 00:10:21 -0300 Subject: [PATCH 005/351] =?UTF-8?q?Corrige=20organiza=C3=A7=C3=A3o=20em=20?= =?UTF-8?q?README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 560ff540..5ee11bae 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ SIGE IE -## Fase do projeto +## Fase Release 1 Ir para milestone da release 1 -## O produto +## Visão geral do produto ### Sobre Aplicativo web mobile desenvolvido para a Universidade de Brasília com objetivo de gerenciar as instalações elétricas e dar suporte ao retrofitting das instalações. @@ -59,52 +59,32 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar
-## O projeto - -### Orgnanização -
- -| Papel | Atribuições | Responsável | Participantes | -| --- | --- | --- | --- | -| Cliente | Validar as entregas | Loana | Loana, Alex | -| Desenvolvedor back-end | Codificar o backend, configurar a infraestrutura | Pedro | Pedro, Kauan, Oscar | -| Desenvolvedor frontend | Codificar o frontend, realizar integração com backend | Danilo | Danilo, Ramires, Pedro | -| UX design | Projetar a interface do usuário, criar protótipos e realizar entrevistas com os clientes | Danilo | Danilo | -| Analista de requisitos | Levantar requisitos, gerenciar a documentação, validar com cliente | Oscar | Oscar, Ramires, Pedro | - -
- ### Contribuidores
- - - - - @@ -112,6 +92,21 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar
AlefMemTavFullstack Pedro Lucas Contribuidor
EngDannFrontend Danilo Contribuidor
kauan2872Backend Kauan Contribuidor
OscarDeBritoDocumentação Oscar Contribuidor
ramires31Frontend Ramires Contribuidor
+## Visão geral do projeto + +### Organização +
+ +| Papel | Atribuições | Responsável | Participantes | +| --- | --- | --- | --- | +| Cliente | Validar as entregas | Loana | Loana, Alex | +| Desenvolvedor back-end | Codificar o backend, configurar a infraestrutura | Pedro | Pedro, Kauan, Oscar | +| Desenvolvedor frontend | Codificar o frontend, realizar integração com backend | Danilo | Danilo, Ramires, Pedro | +| UX design | Projetar a interface do usuário, criar protótipos e realizar entrevistas com os clientes | Danilo | Danilo | +| Analista de requisitos | Levantar requisitos, gerenciar a documentação, validar com cliente | Oscar | Oscar, Ramires, Pedro | + +
+ ## Configuração do ambiente ### Como subir o projeto Estas etapas são válidas para Linux OS e WSL. From 067b330fe90fa65b8330bd651b78de5c2fe39292 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 4 Apr 2024 17:03:25 -0300 Subject: [PATCH 006/351] Telas principais finalizadas --- .vscode/settings.json | 3 + frontend/sige_ie/lib/main.dart | 12 ++- frontend/sige_ie/lib/screens/facilities.dart | 13 +++ frontend/sige_ie/lib/screens/home.dart | 89 +++++++++++++++++++ frontend/sige_ie/lib/screens/login.dart | 2 +- frontend/sige_ie/lib/screens/maps.dart | 13 +++ frontend/sige_ie/lib/screens/profile.dart | 13 +++ .../lib/screens/\341\271\225roflile.dart" | 0 8 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 frontend/sige_ie/lib/screens/profile.dart delete mode 100644 "frontend/sige_ie/lib/screens/\341\271\225roflile.dart" diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..4b8a2e9f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.sourceDirectory": "/home/danilo/Desktop/T2G3-Sistema-Instalacao-Eletrica/frontend/sige_ie/linux" +} diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 60b230d6..f33fe4ce 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -2,6 +2,11 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/screens/first_scren.dart'; import 'package:sige_ie/screens/register.dart'; import 'package:sige_ie/screens/splash_screen.dart'; +import 'package:sige_ie/screens/home.dart'; +import 'package:sige_ie/screens/facilities.dart'; +import 'package:sige_ie/screens/maps.dart'; +import 'package:sige_ie/screens/profile.dart'; + import 'screens/login.dart'; void main() { @@ -17,8 +22,11 @@ class MyApp extends StatelessWidget { '/': (context) => SplashScreen(), // Rota do splash screen '/loginScreen': (context) => LoginScreen(), // Rota da tela de login '/first': (context) => FirstScreen(), - '/registerScreen': (context) => RegisterScreen() - // Defina outras rotas aqui + '/registerScreen': (context) => RegisterScreen(), + '/homeScreen': (context) => HomePage(), + '/facilitiesScreen': (context) => HomePage(), + '/MapsPage': (context) => MapsPage(), + '/profileScreen': (context) => HomePage() }, ); } diff --git a/frontend/sige_ie/lib/screens/facilities.dart b/frontend/sige_ie/lib/screens/facilities.dart index e69de29b..8df8c434 100644 --- a/frontend/sige_ie/lib/screens/facilities.dart +++ b/frontend/sige_ie/lib/screens/facilities.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class FacilitiesPage extends StatefulWidget { + @override + _FacilitiesPageState createState() => _FacilitiesPageState(); +} + +class _FacilitiesPageState extends State { + @override + Widget build(BuildContext context) { + throw UnimplementedError(); + } +} diff --git a/frontend/sige_ie/lib/screens/home.dart b/frontend/sige_ie/lib/screens/home.dart index e69de29b..62241a4d 100644 --- a/frontend/sige_ie/lib/screens/home.dart +++ b/frontend/sige_ie/lib/screens/home.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; + +class HomePage extends StatefulWidget { + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _selectedIndex = 0; + + final List _widgetOptions = [ + Text('Home'), + Text('Instalações'), + Text('Mapa'), + Text('Perfil'), + ]; + + void _onItemTapped(int index) { + setState(() { + _selectedIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: Scaffold( + appBar: AppBar( + title: Text('App de Navegação'), + automaticallyImplyLeading: false, + ), + body: Center( + child: _widgetOptions.elementAt(_selectedIndex), + ), + bottomNavigationBar: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + // Opcional: adicione uma sombra para um efeito elevado + boxShadow: [ + BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + child: BottomNavigationBar( + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home, size: 35), + label: 'Home', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.build, size: 35), + label: 'Instalações', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.map, size: 35), + label: 'Mapa', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.person, size: 35), + label: 'Perfil', + backgroundColor: Color(0xFFF1F60E)), + ], + currentIndex: _selectedIndex, + selectedItemColor: Color(0xFF123C75), + unselectedItemColor: const Color.fromARGB(255, 145, 142, 142), + onTap: _onItemTapped, + selectedLabelStyle: TextStyle( + color: Color(0xFF123C75), + fontSize: 14, + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + fontSize: 12, + ), + ), + ), + ), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/screens/login.dart b/frontend/sige_ie/lib/screens/login.dart index e2b42152..df93c372 100644 --- a/frontend/sige_ie/lib/screens/login.dart +++ b/frontend/sige_ie/lib/screens/login.dart @@ -216,7 +216,7 @@ class _LoginScreenState extends State { if (success) { Navigator.of(context) - .pushReplacementNamed('/?'); + .pushReplacementNamed('/homeScreen'); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( diff --git a/frontend/sige_ie/lib/screens/maps.dart b/frontend/sige_ie/lib/screens/maps.dart index e69de29b..7db9a482 100644 --- a/frontend/sige_ie/lib/screens/maps.dart +++ b/frontend/sige_ie/lib/screens/maps.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class MapsPage extends StatefulWidget { + @override + _MapsPageState createState() => _MapsPageState(); +} + +class _MapsPageState extends State { + @override + Widget build(BuildContext context) { + throw UnimplementedError(); + } +} diff --git a/frontend/sige_ie/lib/screens/profile.dart b/frontend/sige_ie/lib/screens/profile.dart new file mode 100644 index 00000000..418d31b6 --- /dev/null +++ b/frontend/sige_ie/lib/screens/profile.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class ProfilePage extends StatefulWidget { + @override + _ProfilePageState createState() => _ProfilePageState(); +} + +class _ProfilePageState extends State { + @override + Widget build(BuildContext context) { + throw UnimplementedError(); + } +} diff --git "a/frontend/sige_ie/lib/screens/\341\271\225roflile.dart" "b/frontend/sige_ie/lib/screens/\341\271\225roflile.dart" deleted file mode 100644 index e69de29b..00000000 From 205e1e7b80b929e890e0ad7229fd45f889a058c0 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 4 Apr 2024 17:23:29 -0300 Subject: [PATCH 007/351] Cria rota para exibir user autenticado --- api/users/urls.py | 9 +++++---- api/users/views.py | 10 +++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/api/users/urls.py b/api/users/urls.py index 81974fa4..ecd9ecd4 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -1,13 +1,14 @@ # urls.py from django.urls import path from rest_framework.routers import DefaultRouter -from .views import GetCSRFToken, CheckAuthenticatedView, UserCreateView, UserDetailView, LoginView, LogoutView -from django.urls import include, path +from .views import GetCSRFToken, CheckAuthenticatedView, UserCreateView, AuthenticatedUserView, UserDetailView, LoginView, LogoutView +from django.urls import include urlpatterns = [ - path('csrfcookie/', GetCSRFToken.as_view(), name='csrf_cookie'), - path('checkauth/', CheckAuthenticatedView.as_view(), name='check_auth'), + path('csrfcookie/', GetCSRFToken.as_view(), name='csrf-cookie'), + path('checkauth/', CheckAuthenticatedView.as_view(), name='check-auth'), path('users/', UserCreateView.as_view()), + path('userauth/', AuthenticatedUserView.as_view(), name='authenticated-user'), path('users//', UserDetailView.as_view()), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), diff --git a/api/users/views.py b/api/users/views.py index 5a553873..dfcf3fa5 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -30,7 +30,7 @@ def get(self, request): return Response({'isAuthenticated': False}) @method_decorator(csrf_protect, name='dispatch') -class UserCreateView(generics.CreateAPIView): +class UserCreateView(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [] @@ -41,6 +41,14 @@ class UserDetailView(generics.RetrieveUpdateDestroyAPIView): serializer_class = UserSerializer permission_classes = [IsOwner, IsAuthenticated] +class AuthenticatedUserView(generics.RetrieveAPIView): + queryset = User.objects.all() + serializer_class = UserSerializer + permission_classes = [IsAuthenticated] + + def get_object(self): + return self.request.user + @method_decorator(csrf_protect, name='dispatch') class LoginView(APIView): permission_classes = [] From a43d1b72ea28d54b38ab36c850ed9915bf82fba7 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 4 Apr 2024 17:52:16 -0300 Subject: [PATCH 008/351] Editar perfil concluido --- frontend/sige_ie/lib/screens/facilities.dart | 6 +- frontend/sige_ie/lib/screens/home.dart | 132 ++++++++++--------- frontend/sige_ie/lib/screens/maps.dart | 6 +- frontend/sige_ie/lib/screens/profile.dart | 72 +++++++++- 4 files changed, 153 insertions(+), 63 deletions(-) diff --git a/frontend/sige_ie/lib/screens/facilities.dart b/frontend/sige_ie/lib/screens/facilities.dart index 8df8c434..40be26e4 100644 --- a/frontend/sige_ie/lib/screens/facilities.dart +++ b/frontend/sige_ie/lib/screens/facilities.dart @@ -8,6 +8,10 @@ class FacilitiesPage extends StatefulWidget { class _FacilitiesPageState extends State { @override Widget build(BuildContext context) { - throw UnimplementedError(); + return Scaffold( + appBar: AppBar( + title: Text('App de Navegação'), + automaticallyImplyLeading: false, + )); } } diff --git a/frontend/sige_ie/lib/screens/home.dart b/frontend/sige_ie/lib/screens/home.dart index 62241a4d..b157f1b6 100644 --- a/frontend/sige_ie/lib/screens/home.dart +++ b/frontend/sige_ie/lib/screens/home.dart @@ -1,5 +1,10 @@ import 'package:flutter/material.dart'; +// Importe suas páginas personalizadas +import 'profile.dart'; +import 'facilities.dart'; +import 'maps.dart'; + class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); @@ -7,79 +12,86 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { int _selectedIndex = 0; + PageController _pageController = PageController(); - final List _widgetOptions = [ - Text('Home'), - Text('Instalações'), - Text('Mapa'), - Text('Perfil'), - ]; + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); + _pageController.jumpToPage(index); } @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async => false, - child: Scaffold( - appBar: AppBar( - title: Text('App de Navegação'), - automaticallyImplyLeading: false, - ), - body: Center( - child: _widgetOptions.elementAt(_selectedIndex), + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + ), + body: PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _selectedIndex = index; + }); + }, + children: [ + Text(''), // Assumindo que esta é sua HomePage temporária + FacilitiesPage(), + MapsPage(), + ProfilePage(), + ], + ), + bottomNavigationBar: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + boxShadow: [ + BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), + ], ), - bottomNavigationBar: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - // Opcional: adicione uma sombra para um efeito elevado - boxShadow: [ - BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), - ], + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), - child: ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), + child: BottomNavigationBar( + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home, size: 35), + label: 'Home', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.build, size: 35), + label: 'Instalações', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.map, size: 35), + label: 'Mapa', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.person, size: 35), + label: 'Perfil', + backgroundColor: Color(0xFFF1F60E)), + ], + currentIndex: _selectedIndex, + selectedItemColor: Color(0xFF123C75), + unselectedItemColor: const Color.fromARGB(255, 145, 142, 142), + onTap: _onItemTapped, + selectedLabelStyle: TextStyle( + color: Color(0xFF123C75), + fontSize: 14, + fontWeight: FontWeight.bold, ), - child: BottomNavigationBar( - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.home, size: 35), - label: 'Home', - backgroundColor: Color(0xFFF1F60E)), - BottomNavigationBarItem( - icon: Icon(Icons.build, size: 35), - label: 'Instalações', - backgroundColor: Color(0xFFF1F60E)), - BottomNavigationBarItem( - icon: Icon(Icons.map, size: 35), - label: 'Mapa', - backgroundColor: Color(0xFFF1F60E)), - BottomNavigationBarItem( - icon: Icon(Icons.person, size: 35), - label: 'Perfil', - backgroundColor: Color(0xFFF1F60E)), - ], - currentIndex: _selectedIndex, - selectedItemColor: Color(0xFF123C75), - unselectedItemColor: const Color.fromARGB(255, 145, 142, 142), - onTap: _onItemTapped, - selectedLabelStyle: TextStyle( - color: Color(0xFF123C75), - fontSize: 14, - fontWeight: FontWeight.bold, - ), - unselectedLabelStyle: TextStyle( - fontSize: 12, - ), + unselectedLabelStyle: TextStyle( + fontSize: 12, ), ), ), diff --git a/frontend/sige_ie/lib/screens/maps.dart b/frontend/sige_ie/lib/screens/maps.dart index 7db9a482..5b9dcf9a 100644 --- a/frontend/sige_ie/lib/screens/maps.dart +++ b/frontend/sige_ie/lib/screens/maps.dart @@ -8,6 +8,10 @@ class MapsPage extends StatefulWidget { class _MapsPageState extends State { @override Widget build(BuildContext context) { - throw UnimplementedError(); + return Scaffold( + appBar: AppBar( + title: Text('App de Navegação'), + automaticallyImplyLeading: false, + )); } } diff --git a/frontend/sige_ie/lib/screens/profile.dart b/frontend/sige_ie/lib/screens/profile.dart index 418d31b6..046bc23b 100644 --- a/frontend/sige_ie/lib/screens/profile.dart +++ b/frontend/sige_ie/lib/screens/profile.dart @@ -8,6 +8,76 @@ class ProfilePage extends StatefulWidget { class _ProfilePageState extends State { @override Widget build(BuildContext context) { - throw UnimplementedError(); + return Scaffold( + appBar: AppBar( + title: Text( + 'Editar Perfil', + style: TextStyle(fontSize: 24), + ), + centerTitle: true, + automaticallyImplyLeading: false, + ), + body: SingleChildScrollView( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + decoration: InputDecoration(labelText: 'Email'), + ), + SizedBox(height: 10), + TextField( + decoration: InputDecoration(labelText: 'Nome'), + ), + SizedBox(height: 10), + TextField( + decoration: InputDecoration(labelText: 'Username'), + enabled: false, + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () {}, + child: Text( + 'Mudar senha', + style: TextStyle(color: Colors.blue), + ), + ), + InkWell( + onTap: () {}, + child: Text( + 'Mudar username', + style: TextStyle(color: Colors.blue), + ), + ), + ], + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () {}, + child: Text('Salvar', style: TextStyle(color: Colors.black)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey[300], + ), + ), + ElevatedButton( + onPressed: () {}, + child: Text('Sair da Conta', + style: TextStyle(color: Colors.black)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + ), + ), + ], + ), + ], + ), + ), + ); } } From 9dba30639a5c1d7df4aaa323663a0b5f103a542e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Sat, 6 Apr 2024 11:17:44 -0300 Subject: [PATCH 009/351] cria grupos e adiciona owner --- api/places/models.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/api/places/models.py b/api/places/models.py index 901f0268..8de1b5dd 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -1,6 +1,8 @@ from django.db import models from django.core.validators import MinValueValidator from django.contrib.auth.models import User +from django.contrib.auth.models import Permission, Group +from django.contrib.contenttypes.models import ContentType class Place(models.Model): @@ -18,5 +20,48 @@ class Room(models.Model): systems = models.ManyToManyField('systems.System') +def criar_permissoes_place(sender, instance, created, **kwargs): + if created: + + content_type = ContentType.objects.get_for_model(Place) + + permission_view = Permission.objects.create( + codename='view_content_{}'.format(instance.id), + name='Can view content of {}'.format(instance.name), + content_type=content_type + ) + permission_edit = Permission.objects.create( + codename='edit_content_{}'.format(instance.id), + name='Can edit content of {}'.format(instance.name), + content_type=content_type + ) + + permission_delet = Permission.objects.create( + codename='delete_instance_{}'.format(instance.id), + name="Can delete the instance of {}".format(instance.name), + content_type = content_type + + + ) + group_view = Group.objects.create(name='Visualizadores {}'.format(instance.name)) + group_view.permissions.add(permission_view) + + group_edit = Group.objects.create(name='Editores {}'.format(instance.name)) + group_edit.permissions.add(permission_view) + group_edit.permissions.add(permission_edit) + + group_owner = Group.objects.create(name='Donos {}'.format(instance.name)) + group_owner.permissions.add(permission_delet) + group_owner.permissions.add(permission_edit) + group_owner.permissions.add(permission_view) + + usuario = instance.user_id + direitos = Group.objects.get(name='Donos {}'.format(instance.name)) + direitos.user_set.add(usuario) + + + + +models.signals.post_save.connect(criar_permissoes_place, sender=Place) From 6b59d5f88f8235f71cc4cdebb4eecee3fac5afde Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Sun, 7 Apr 2024 13:54:53 -0300 Subject: [PATCH 010/351] Muda pastas para package by feature, adiciona interceptadores e cria visualizar user --- .../lib/core/data/auth_interceptor.dart | 30 +++++ .../sige_ie/lib/core/data/auth_service.dart | 109 ++++++++++++++++++ .../feature/login}/login.dart | 53 ++------- .../feature/register}/register.dart | 50 +------- .../ui/first_screen}/first_scren.dart | 0 .../lib/{screens => home/ui}/home.dart | 6 +- frontend/sige_ie/lib/main.dart | 20 ++-- .../lib/{screens => maps/feature}/maps.dart | 0 .../sige_ie/lib/users/data/user_model.dart | 13 +++ .../sige_ie/lib/users/data/user_service.dart | 57 +++++++++ .../{screens => users/feature}/profile.dart | 23 ++++ frontend/sige_ie/pubspec.lock | 32 ++++- frontend/sige_ie/pubspec.yaml | 2 + 13 files changed, 285 insertions(+), 110 deletions(-) create mode 100644 frontend/sige_ie/lib/core/data/auth_interceptor.dart create mode 100644 frontend/sige_ie/lib/core/data/auth_service.dart rename frontend/sige_ie/lib/{screens => core/feature/login}/login.dart (87%) rename frontend/sige_ie/lib/{screens => core/feature/register}/register.dart (91%) rename frontend/sige_ie/lib/{screens => core/ui/first_screen}/first_scren.dart (100%) rename frontend/sige_ie/lib/{screens => home/ui}/home.dart (95%) rename frontend/sige_ie/lib/{screens => maps/feature}/maps.dart (100%) create mode 100644 frontend/sige_ie/lib/users/data/user_model.dart create mode 100644 frontend/sige_ie/lib/users/data/user_service.dart rename frontend/sige_ie/lib/{screens => users/feature}/profile.dart (75%) diff --git a/frontend/sige_ie/lib/core/data/auth_interceptor.dart b/frontend/sige_ie/lib/core/data/auth_interceptor.dart new file mode 100644 index 00000000..230719bf --- /dev/null +++ b/frontend/sige_ie/lib/core/data/auth_interceptor.dart @@ -0,0 +1,30 @@ +import 'package:cookie_jar/cookie_jar.dart'; +import 'package:http_interceptor/http_interceptor.dart'; + +class AuthInterceptor implements InterceptorContract { + CookieJar cookieJar; + + AuthInterceptor(this.cookieJar); + + @override + Future interceptRequest({required RequestData data}) async { + var cookies = await cookieJar.loadForRequest(Uri.parse('http://10.0.2.2:8000/api/login/')); + var sessionCookie; + for (var cookie in cookies) { + if (cookie.name == 'sessionid') { + sessionCookie = cookie; + break; + } + } + if (sessionCookie != null) { + data.headers.addAll({'Cookie': '${sessionCookie.name}=${sessionCookie.value}'}); + } + return data; + } + + @override + Future interceptResponse({required ResponseData data}) async { + return data; + } +} + diff --git a/frontend/sige_ie/lib/core/data/auth_service.dart b/frontend/sige_ie/lib/core/data/auth_service.dart new file mode 100644 index 00000000..e53bf34b --- /dev/null +++ b/frontend/sige_ie/lib/core/data/auth_service.dart @@ -0,0 +1,109 @@ +import 'dart:convert'; +import 'package:cookie_jar/cookie_jar.dart'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/main.dart'; + +class AuthService { + static Future fetchCsrfToken() async { + const String url = 'http://10.0.2.2:8000/api/csrfcookie/'; + final response = await http.get(Uri.parse(url)); + + if (response.statusCode == 200) { + String cookie = response.headers['set-cookie']!; + String csrfToken = cookie.split(';')[0].substring('csrftoken='.length); + return csrfToken; + } else { + throw Exception('Falha ao obter o token CSRF: ${response.statusCode}'); + } + } + + static Future fetchSessionCookie() async { + const url = 'http://10.0.2.2:8000/api/sessioncookie/'; + final response = await http.get(Uri.parse(url)); + + if (response.statusCode == 200) { + final cookies = response.headers['set-cookie']?.split(','); + + String? sessionid; + if (cookies != null) { + for (var cookie in cookies) { + if (cookie.contains('sessionid')) { + sessionid = cookie.split(';')[0].split('=')[1]; + break; + } + } + } + + print(sessionid); + return sessionid!; + } else { + throw Exception('Failed to fetch session cookie: ${response.statusCode}'); + } + } + + Future checkAuthenticated() async { + var client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + final response = + await client.get(Uri.parse('http://10.0.2.2:8000/api/checkauth/')); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return data['isAuthenticated']; + } else { + throw Exception('Failed to check authentication'); + } + } + + Future login(String username, String password) async { + var csrfToken = await fetchCsrfToken(); + + var url = Uri.parse('http://10.0.2.2:8000/api/login/'); + + try { + var response = await http.post(url, + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken, + 'Cookie': 'csrftoken=$csrfToken', + }, + body: jsonEncode({ + 'username': username, + 'password': password, + })); + final cookies = response.headers['set-cookie']?.split(','); + + String? sessionid; + if (cookies != null) { + for (var cookie in cookies) { + if (cookie.contains('sessionid')) { + sessionid = cookie.split(';')[0].split('=')[1]; + break; + } + } + } + + final cookie = Cookie('sessionid', sessionid!); + cookieJar.saveFromResponse( + Uri.parse('http://10.0.2.2:8000/api/login/'), [cookie]); + + // print('Session ID: $sessionid'); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + print("Login bem-sucedido: $data"); + return true; + } else { + print("Falha no login: ${response.body}"); + return false; + } + } catch (e) { + print("Erro ao tentar fazer login: $e"); + return false; + } + } +} diff --git a/frontend/sige_ie/lib/screens/login.dart b/frontend/sige_ie/lib/core/feature/login/login.dart similarity index 87% rename from frontend/sige_ie/lib/screens/login.dart rename to frontend/sige_ie/lib/core/feature/login/login.dart index df93c372..acc45662 100644 --- a/frontend/sige_ie/lib/screens/login.dart +++ b/frontend/sige_ie/lib/core/feature/login/login.dart @@ -1,6 +1,5 @@ -import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; +import 'package:sige_ie/core/data/auth_service.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({Key? key}) : super(key: key); @@ -9,54 +8,12 @@ class LoginScreen extends StatefulWidget { } class _LoginScreenState extends State { + AuthService authService = AuthService(); bool rememberMe = false; final _loginScreen = GlobalKey(); final TextEditingController usernameController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); - Future fetchCsrfToken() async { - const String url = 'http://10.0.2.2:8000/api/csrfcookie/'; - final response = await http.get(Uri.parse(url)); - - if (response.statusCode == 200) { - String cookie = response.headers['set-cookie']!; - print(cookie); - String csrfToken = cookie.split(';')[0].substring('csrftoken='.length); - return csrfToken; - } else { - throw Exception('Falha ao obter o token CSRF: ${response.statusCode}'); - } - } - - Future login(String username, String password) async { - var csrfToken = await fetchCsrfToken(); - print(csrfToken); - var url = Uri.parse('http://10.0.2.2:8000/api/login/'); - try { - var response = await http.post(url, - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': csrfToken, - 'Cookie': 'csrftoken=$csrfToken', - }, - body: jsonEncode({ - 'username': username, - 'password': password, - })); - if (response.statusCode == 200) { - var data = jsonDecode(response.body); - print("Login bem-sucedido: $data"); - return true; - } else { - print("Falha no login: ${response.body}"); - return false; - } - } catch (e) { - print("Erro ao tentar fazer login: $e"); - return false; - } - } - @override Widget build(BuildContext context) { return Scaffold( @@ -207,10 +164,14 @@ class _LoginScreenState extends State { content: Text('Processando dados')), ); - bool success = await login( + bool success = await authService.login( usernameController.text, passwordController.text); + bool isAuth = + await authService.checkAuthenticated(); + print(isAuth); + ScaffoldMessenger.of(context) .hideCurrentSnackBar(); diff --git a/frontend/sige_ie/lib/screens/register.dart b/frontend/sige_ie/lib/core/feature/register/register.dart similarity index 91% rename from frontend/sige_ie/lib/screens/register.dart rename to frontend/sige_ie/lib/core/feature/register/register.dart index 8b477360..54ec27cb 100644 --- a/frontend/sige_ie/lib/screens/register.dart +++ b/frontend/sige_ie/lib/core/feature/register/register.dart @@ -1,7 +1,6 @@ -import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:http/http.dart' as http; +import 'package:sige_ie/users/data/user_service.dart'; class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); @@ -18,51 +17,6 @@ class _RegisterScreenState extends State { final TextEditingController passwordController = TextEditingController(); final TextEditingController emailController = TextEditingController(); - Future fetchCsrfToken() async { - const String url = 'http://10.0.2.2:8000/api/csrfcookie/'; - final response = await http.get(Uri.parse(url)); - - if (response.statusCode == 200) { - String cookie = response.headers['set-cookie']!; - print(cookie); - String csrfToken = cookie.split(';')[0].substring('csrftoken='.length); - return csrfToken; - } else { - throw Exception('Falha ao obter o token CSRF: ${response.statusCode}'); - } - } - - Future register( - String username, String firstName, String password, String email) async { - var csrfToken = await fetchCsrfToken(); - var url = Uri.parse('http://10.0.2.2:8000/api/users/'); - try { - var response = await http.post(url, - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': csrfToken, - 'Cookie': 'csrftoken=$csrfToken', - }, - body: jsonEncode({ - 'username': username, - 'first_name': firstName, - 'password': password, - 'email': email, - })); - if (response.statusCode == 200 || response.statusCode == 201) { - var data = jsonDecode(response.body); - print("Registro bem-sucedido: $data"); - return true; - } else { - print("Falha no registro: ${response.body}"); - return false; - } - } catch (e) { - print("Erro ao tentar registrar: $e"); - return false; - } - } - @override Widget build(BuildContext context) { return Scaffold( @@ -295,7 +249,7 @@ class _RegisterScreenState extends State { content: Text('Processando Dados'), ), ); - bool success = await register( + bool success = await UserService.register( usernameController.text, nameController.text, passwordController.text, diff --git a/frontend/sige_ie/lib/screens/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_screen/first_scren.dart similarity index 100% rename from frontend/sige_ie/lib/screens/first_scren.dart rename to frontend/sige_ie/lib/core/ui/first_screen/first_scren.dart diff --git a/frontend/sige_ie/lib/screens/home.dart b/frontend/sige_ie/lib/home/ui/home.dart similarity index 95% rename from frontend/sige_ie/lib/screens/home.dart rename to frontend/sige_ie/lib/home/ui/home.dart index b157f1b6..337128c1 100644 --- a/frontend/sige_ie/lib/screens/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; // Importe suas páginas personalizadas -import 'profile.dart'; -import 'facilities.dart'; -import 'maps.dart'; +import '../../users/feature/profile.dart'; +import '../../screens/facilities.dart'; +import '../../maps/feature/maps.dart'; class HomePage extends StatefulWidget { @override diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index f33fe4ce..364ea4f0 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -1,26 +1,28 @@ +import 'package:cookie_jar/cookie_jar.dart'; import 'package:flutter/material.dart'; -import 'package:sige_ie/screens/first_scren.dart'; -import 'package:sige_ie/screens/register.dart'; +import 'package:sige_ie/core/ui/first_screen/first_scren.dart'; +import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/screens/splash_screen.dart'; -import 'package:sige_ie/screens/home.dart'; +import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/screens/facilities.dart'; -import 'package:sige_ie/screens/maps.dart'; -import 'package:sige_ie/screens/profile.dart'; +import 'package:sige_ie/maps/feature/maps.dart'; +import 'package:sige_ie/users/feature/profile.dart'; -import 'screens/login.dart'; +import 'core/feature/login/login.dart'; void main() { runApp(MyApp()); } +final cookieJar = CookieJar(); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - initialRoute: '/', // Defina a rota inicial + initialRoute: '/', routes: { - '/': (context) => SplashScreen(), // Rota do splash screen - '/loginScreen': (context) => LoginScreen(), // Rota da tela de login + '/': (context) => SplashScreen(), + '/loginScreen': (context) => LoginScreen(), '/first': (context) => FirstScreen(), '/registerScreen': (context) => RegisterScreen(), '/homeScreen': (context) => HomePage(), diff --git a/frontend/sige_ie/lib/screens/maps.dart b/frontend/sige_ie/lib/maps/feature/maps.dart similarity index 100% rename from frontend/sige_ie/lib/screens/maps.dart rename to frontend/sige_ie/lib/maps/feature/maps.dart diff --git a/frontend/sige_ie/lib/users/data/user_model.dart b/frontend/sige_ie/lib/users/data/user_model.dart new file mode 100644 index 00000000..cb3634b1 --- /dev/null +++ b/frontend/sige_ie/lib/users/data/user_model.dart @@ -0,0 +1,13 @@ +class UserModel { + String username; + String firstname; + String email; + + UserModel( + {required this.username, required this.firstname, required this.email}); + + UserModel.fromJson(Map json) + : username = json['username'].toString(), + firstname = json['first_name'].toString(), + email = json['email'].toString(); +} diff --git a/frontend/sige_ie/lib/users/data/user_service.dart b/frontend/sige_ie/lib/users/data/user_service.dart new file mode 100644 index 00000000..cf768d07 --- /dev/null +++ b/frontend/sige_ie/lib/users/data/user_service.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/core/data/auth_service.dart'; +import 'package:sige_ie/main.dart'; +import 'package:sige_ie/users/data/user_model.dart'; + +class UserService { + static Future register( + String username, String firstName, String password, String email) async { + var csrfToken = await AuthService.fetchCsrfToken(); + var url = Uri.parse('http://10.0.2.2:8000/api/users/'); + try { + var response = await http.post(url, + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken, + 'Cookie': 'csrftoken=$csrfToken', + }, + body: jsonEncode({ + 'username': username, + 'first_name': firstName, + 'password': password, + 'email': email, + })); + if (response.statusCode == 200 || response.statusCode == 201) { + var data = jsonDecode(response.body); + print("Registro bem-sucedido: $data"); + return true; + } else { + print("Falha no registro: ${response.body}"); + return false; + } + } catch (e) { + print("Erro ao tentar registrar: $e"); + return false; + } + } + + Future fetchProfileData() async { + + var client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + final response = + await client.get(Uri.parse('http://10.0.2.2:8000/api/userauth')); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return UserModel.fromJson(data); + } else { + throw Exception('Falha ao carregar dados do perfil'); + } + } +} diff --git a/frontend/sige_ie/lib/screens/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart similarity index 75% rename from frontend/sige_ie/lib/screens/profile.dart rename to frontend/sige_ie/lib/users/feature/profile.dart index 046bc23b..f58b7c76 100644 --- a/frontend/sige_ie/lib/screens/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/users/data/user_model.dart'; +import 'package:sige_ie/users/data/user_service.dart'; class ProfilePage extends StatefulWidget { @override @@ -6,6 +8,22 @@ class ProfilePage extends StatefulWidget { } class _ProfilePageState extends State { + UserService userService = UserService(); + UserModel userModel = UserModel(email: '', firstname: '', username: ''); + + @override + void initState() { + super.initState(); + userService.fetchProfileData().then((userModel) { + setState(() { + this.userModel = userModel; + }); + }).catchError((error) { + // Handle error + print(error); + }); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -24,14 +42,19 @@ class _ProfilePageState extends State { children: [ TextField( decoration: InputDecoration(labelText: 'Email'), + controller: TextEditingController(text: userModel.email), + onChanged: (value) => userModel.email = value, ), SizedBox(height: 10), TextField( decoration: InputDecoration(labelText: 'Nome'), + controller: TextEditingController(text: userModel.firstname), + onChanged: (value) => userModel.firstname = value, ), SizedBox(height: 10), TextField( decoration: InputDecoration(labelText: 'Username'), + controller: TextEditingController(text: userModel.username), enabled: false, ), SizedBox(height: 20), diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 87f22ca5..ba6e64c5 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cookie_jar: + dependency: "direct main" + description: + name: cookie_jar + sha256: a6ac027d3ed6ed756bfce8f3ff60cb479e266f3b0fdabd6242b804b6765e52de + url: "https://pub.dev" + source: hosted + version: "4.0.8" csslib: dependency: transitive description: @@ -90,10 +98,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" flutter_test: dependency: "direct dev" description: flutter @@ -120,6 +128,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.6" + http_interceptor: + dependency: "direct main" + description: + name: http_interceptor + sha256: "5f3dde028e67789339c250252c09510a74aff21ce16b06d07d9096bda6582bab" + url: "https://pub.dev" + source: hosted + version: "1.0.2" http_parser: dependency: transitive description: @@ -349,6 +365,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" vector_math: dependency: transitive description: @@ -361,10 +385,10 @@ packages: dependency: "direct main" description: name: video_player - sha256: afc65f4b8bcb2c188f64a591f84fb471f4f2e19fc607c65fd8d2f8fedb3dec23 + sha256: efa2e24042166906ddf836dd131258d0371d0009cdf0476f6a83fd992a17f5d0 url: "https://pub.dev" source: hosted - version: "2.8.3" + version: "2.8.5" video_player_android: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index e16666e7..2e87ae05 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -33,6 +33,8 @@ dependencies: shared_preferences: ^2.0.15 video_player: ^2.2.14 http: ^0.13.3 + cookie_jar: ^4.0.8 + http_interceptor: ^1.0.1 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. From b093df0f707c33da78abeecf8ab4670780b01dd9 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Sun, 7 Apr 2024 13:56:39 -0300 Subject: [PATCH 011/351] Cria url de visualizar session id --- api/sigeie/settings.py | 13 +++++++++++-- api/users/urls.py | 3 ++- api/users/views.py | 11 ++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index 30273ec0..0b3f93d9 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -116,8 +116,17 @@ 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': { - 'rest_framework.permissions.IsAuthenticatedOrReadOnly', + 'rest_framework.permissions.IsAuthenticated', } } -CORS_ALLOW_ALL_ORIGINS = True \ No newline at end of file +CORS_ALLOW_CREDENTIALS = True +CORS_ALLOW_ALL_ORIGINS = True +CORS_ALLOW_HEADERS = ( + "accept", + "authorization", + "content-type", + "user-agent", + "x-csrftoken", + "x-requested-with", +) diff --git a/api/users/urls.py b/api/users/urls.py index ecd9ecd4..02ab772a 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -1,11 +1,12 @@ # urls.py from django.urls import path from rest_framework.routers import DefaultRouter -from .views import GetCSRFToken, CheckAuthenticatedView, UserCreateView, AuthenticatedUserView, UserDetailView, LoginView, LogoutView +from .views import GetCSRFToken, GetSessionCookie, CheckAuthenticatedView, UserCreateView, AuthenticatedUserView, UserDetailView, LoginView, LogoutView from django.urls import include urlpatterns = [ path('csrfcookie/', GetCSRFToken.as_view(), name='csrf-cookie'), + path('sessioncookie/', GetSessionCookie.as_view(), name='session-cookie'), path('checkauth/', CheckAuthenticatedView.as_view(), name='check-auth'), path('users/', UserCreateView.as_view()), path('userauth/', AuthenticatedUserView.as_view(), name='authenticated-user'), diff --git a/api/users/views.py b/api/users/views.py index dfcf3fa5..295bd58b 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -1,5 +1,5 @@ # views.py -from django.http import JsonResponse +from django.http import HttpResponse, JsonResponse from rest_framework import viewsets, permissions, status from rest_framework.views import APIView from rest_framework.response import Response @@ -20,6 +20,15 @@ class GetCSRFToken(APIView): def get(self, request): return Response({'success':'CSRF Cookie Set'}) +class GetSessionCookie(APIView): + permission_classes = [AllowAny] + + def get(self, request): + sessionid = request.COOKIES.get('sessionid') + response = HttpResponse() + response.set_cookie('sessionid', sessionid) + return response + @method_decorator(csrf_protect, name='dispatch') class CheckAuthenticatedView(APIView): permission_classes=[AllowAny] From 64151dc7e20b486a9e11f88d6f3e6302a85f427f Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 8 Apr 2024 11:50:32 -0300 Subject: [PATCH 012/351] =?UTF-8?q?Conecta=20front=20com=20logout=20e=20ed?= =?UTF-8?q?itar=20user,=20desabilita=20csrf=20token=20at=C3=A9=20a=20entre?= =?UTF-8?q?ga?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/sigeie/middleware.py | 8 ++++ api/sigeie/settings.py | 2 +- api/users/serializers.py | 13 +++++- api/users/urls.py | 2 +- api/users/views.py | 15 +++---- frontend/.idea/.gitignore | 3 ++ frontend/.idea/frontend.iml | 13 ++++++ frontend/.idea/misc.xml | 6 +++ frontend/.idea/modules.xml | 8 ++++ frontend/.idea/vcs.xml | 6 +++ .../sige_ie/lib/core/data/auth_service.dart | 40 ++++++++++++++++--- .../sige_ie/lib/users/data/user_model.dart | 6 ++- .../sige_ie/lib/users/data/user_service.dart | 39 ++++++++++++++++-- .../sige_ie/lib/users/feature/profile.dart | 15 +++++-- 14 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 api/sigeie/middleware.py create mode 100644 frontend/.idea/.gitignore create mode 100644 frontend/.idea/frontend.iml create mode 100644 frontend/.idea/misc.xml create mode 100644 frontend/.idea/modules.xml create mode 100644 frontend/.idea/vcs.xml diff --git a/api/sigeie/middleware.py b/api/sigeie/middleware.py new file mode 100644 index 00000000..51f9ccec --- /dev/null +++ b/api/sigeie/middleware.py @@ -0,0 +1,8 @@ + +class DisableCSRFMiddleware(object): + def __init__(self, get_response): + self.get_response = get_response + def __call__(self, request): + setattr(request, '_dont_enforce_csrf_checks', True) + response = self.get_response(request) + return response \ No newline at end of file diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index 0b3f93d9..452ff622 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -27,7 +27,7 @@ 'django.contrib.sessions.middleware.SessionMiddleware', "corsheaders.middleware.CorsMiddleware", 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', + 'sigeie.middleware.DisableCSRFMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', diff --git a/api/users/serializers.py b/api/users/serializers.py index ac4f9cce..417d867e 100644 --- a/api/users/serializers.py +++ b/api/users/serializers.py @@ -31,4 +31,15 @@ def create(self, validated_data): class UserLoginSerializer(serializers.Serializer): username = serializers.CharField(min_length=6, max_length=23, required=True) - password = serializers.CharField(min_length=6, max_length=200, required=True) \ No newline at end of file + password = serializers.CharField(min_length=6, max_length=200, required=True) + + +class UserUpdateSerializer(serializers.Serializer): + first_name = serializers.CharField(required=True) + email = serializers.EmailField(required=True) + + def update(self, instance, validated_data): + instance.first_name = validated_data.get('first_name', instance.first_name) + instance.email = validated_data.get('email', instance.email) + instance.save() + return instance diff --git a/api/users/urls.py b/api/users/urls.py index 02ab772a..861f8eb3 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -10,7 +10,7 @@ path('checkauth/', CheckAuthenticatedView.as_view(), name='check-auth'), path('users/', UserCreateView.as_view()), path('userauth/', AuthenticatedUserView.as_view(), name='authenticated-user'), - path('users//', UserDetailView.as_view()), + path('users//', UserDetailView.as_view(), name='user_detail'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), ] diff --git a/api/users/views.py b/api/users/views.py index 295bd58b..c3cb08f9 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -8,13 +8,12 @@ from rest_framework import generics from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect, csrf_exempt from .permissions import IsOwner -from .serializers import UserSerializer, UserLoginSerializer +from .serializers import UserSerializer, UserLoginSerializer, UserUpdateSerializer from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout from django.utils.decorators import method_decorator -@method_decorator(ensure_csrf_cookie, name='dispatch') class GetCSRFToken(APIView): permission_classes = [AllowAny] def get(self, request): @@ -29,7 +28,6 @@ def get(self, request): response.set_cookie('sessionid', sessionid) return response -@method_decorator(csrf_protect, name='dispatch') class CheckAuthenticatedView(APIView): permission_classes=[AllowAny] def get(self, request): @@ -38,17 +36,15 @@ def get(self, request): else: return Response({'isAuthenticated': False}) -@method_decorator(csrf_protect, name='dispatch') class UserCreateView(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [] -@method_decorator(csrf_protect, name='dispatch') class UserDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = User.objects.all() - serializer_class = UserSerializer - permission_classes = [IsOwner, IsAuthenticated] + serializer_class = UserUpdateSerializer + permission_classes = [IsOwner, AllowAny] class AuthenticatedUserView(generics.RetrieveAPIView): queryset = User.objects.all() @@ -58,9 +54,8 @@ class AuthenticatedUserView(generics.RetrieveAPIView): def get_object(self): return self.request.user -@method_decorator(csrf_protect, name='dispatch') class LoginView(APIView): - permission_classes = [] + permission_classes = [AllowAny] def post(self, request, format=None): serializer = UserLoginSerializer(data=request.data) if(serializer.is_valid()): @@ -75,7 +70,7 @@ def post(self, request, format=None): return JsonResponse(serializer.errors) class LogoutView(APIView): - permission_classes = [IsAuthenticated] + permission_classes = [AllowAny] def post(self, request, format=None): logout(request) diff --git a/frontend/.idea/.gitignore b/frontend/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/frontend/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/frontend/.idea/frontend.iml b/frontend/.idea/frontend.iml new file mode 100644 index 00000000..92e2a39e --- /dev/null +++ b/frontend/.idea/frontend.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/.idea/misc.xml b/frontend/.idea/misc.xml new file mode 100644 index 00000000..639900d1 --- /dev/null +++ b/frontend/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/.idea/modules.xml b/frontend/.idea/modules.xml new file mode 100644 index 00000000..f3d93d75 --- /dev/null +++ b/frontend/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/frontend/.idea/vcs.xml b/frontend/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/frontend/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/sige_ie/lib/core/data/auth_service.dart b/frontend/sige_ie/lib/core/data/auth_service.dart index e53bf34b..1005d6c3 100644 --- a/frontend/sige_ie/lib/core/data/auth_service.dart +++ b/frontend/sige_ie/lib/core/data/auth_service.dart @@ -6,6 +6,7 @@ import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; class AuthService { + static Future fetchCsrfToken() async { const String url = 'http://10.0.2.2:8000/api/csrfcookie/'; final response = await http.get(Uri.parse(url)); @@ -13,6 +14,11 @@ class AuthService { if (response.statusCode == 200) { String cookie = response.headers['set-cookie']!; String csrfToken = cookie.split(';')[0].substring('csrftoken='.length); + + final csrfCookie = Cookie('csrftoken', csrfToken); + cookieJar.saveFromResponse( + Uri.parse('http://10.0.2.2:8000/api/csrftoken/'), [csrfCookie]); + return csrfToken; } else { throw Exception('Falha ao obter o token CSRF: ${response.statusCode}'); @@ -60,16 +66,15 @@ class AuthService { } Future login(String username, String password) async { - var csrfToken = await fetchCsrfToken(); + //var csrfToken = await fetchCsrfToken(); + //print(csrfToken); var url = Uri.parse('http://10.0.2.2:8000/api/login/'); try { var response = await http.post(url, headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': csrfToken, - 'Cookie': 'csrftoken=$csrfToken', + 'Content-Type': 'application/json' }, body: jsonEncode({ 'username': username, @@ -91,8 +96,6 @@ class AuthService { cookieJar.saveFromResponse( Uri.parse('http://10.0.2.2:8000/api/login/'), [cookie]); - // print('Session ID: $sessionid'); - if (response.statusCode == 200) { var data = jsonDecode(response.body); print("Login bem-sucedido: $data"); @@ -106,4 +109,29 @@ class AuthService { return false; } } + + Future logout() async { + //final cookies = await cookieJar.loadForRequest(Uri.parse('http://10.0.2.2:8000/api/csrftoken/')); + + var url = Uri.parse('http://10.0.2.2:8000/api/logout/'); + + try { + var client = + InterceptedClient.build(interceptors: [AuthInterceptor(cookieJar)]); + var response = await client.post(url, headers: { + 'Content-Type': 'application/json' + }); + cookieJar.deleteAll(); + if (response.statusCode == 200) { + print("Logout bem-sucedido"); + } else { + print("Falha no logout: ${response.body}"); + + bool isAuth = await checkAuthenticated(); + print(isAuth); + } + } catch (e) { + print("Erro ao tentar fazer logout: $e"); + } + } } diff --git a/frontend/sige_ie/lib/users/data/user_model.dart b/frontend/sige_ie/lib/users/data/user_model.dart index cb3634b1..414ea7d7 100644 --- a/frontend/sige_ie/lib/users/data/user_model.dart +++ b/frontend/sige_ie/lib/users/data/user_model.dart @@ -1,13 +1,15 @@ class UserModel { + String id; String username; String firstname; String email; UserModel( - {required this.username, required this.firstname, required this.email}); + {required this.id, required this.username, required this.firstname, required this.email}); UserModel.fromJson(Map json) - : username = json['username'].toString(), + : id = json['id'].toString(), + username = json['username'].toString(), firstname = json['first_name'].toString(), email = json['email'].toString(); } diff --git a/frontend/sige_ie/lib/users/data/user_service.dart b/frontend/sige_ie/lib/users/data/user_service.dart index cf768d07..10ab2964 100644 --- a/frontend/sige_ie/lib/users/data/user_service.dart +++ b/frontend/sige_ie/lib/users/data/user_service.dart @@ -7,16 +7,15 @@ import 'package:sige_ie/main.dart'; import 'package:sige_ie/users/data/user_model.dart'; class UserService { + static Future register( String username, String firstName, String password, String email) async { - var csrfToken = await AuthService.fetchCsrfToken(); + //var csrfToken = await AuthService.fetchCsrfToken(); var url = Uri.parse('http://10.0.2.2:8000/api/users/'); try { var response = await http.post(url, headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': csrfToken, - 'Cookie': 'csrftoken=$csrfToken', + 'Content-Type': 'application/json' }, body: jsonEncode({ 'username': username, @@ -38,6 +37,38 @@ class UserService { } } + Future update(String id, String firstName, String email) async { + + var client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + //var csrfToken = await AuthService.fetchCsrfToken(); + + var url = Uri.parse('http://10.0.2.2:8000/api/users/$id/'); + try { + var response = await client.put(url, + headers: { + 'Content-Type': 'application/json' + }, + body: jsonEncode({ + 'first_name': firstName, + 'email': email, + })); + if (response.statusCode == 200 || response.statusCode == 201) { + var data = jsonDecode(response.body); + print("Atualizado com sucesso: $data"); + return true; + } else { + print("Falha: ${response.body}"); + return false; + } + } catch (e) { + print("Erro ao tentar registrar: $e"); + return false; + } + } + Future fetchProfileData() async { var client = InterceptedClient.build( diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index f58b7c76..f064976d 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/core/data/auth_service.dart'; import 'package:sige_ie/users/data/user_model.dart'; import 'package:sige_ie/users/data/user_service.dart'; @@ -9,7 +10,9 @@ class ProfilePage extends StatefulWidget { class _ProfilePageState extends State { UserService userService = UserService(); - UserModel userModel = UserModel(email: '', firstname: '', username: ''); + AuthService authService = AuthService(); + UserModel userModel = + UserModel(id: '', email: '', firstname: '', username: ''); @override void initState() { @@ -82,14 +85,20 @@ class _ProfilePageState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( - onPressed: () {}, + onPressed: () async { + await userService.update( + userModel.id, userModel.firstname, userModel.email); + }, child: Text('Salvar', style: TextStyle(color: Colors.black)), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[300], ), ), ElevatedButton( - onPressed: () {}, + onPressed: () async { + await authService.logout(); + Navigator.pushReplacementNamed(context, '/loginScreen'); + }, child: Text('Sair da Conta', style: TextStyle(color: Colors.black)), style: ElevatedButton.styleFrom( From ba22583f4f05e3b64cf932e42dd99fdfc904b153 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 8 Apr 2024 12:21:42 -0300 Subject: [PATCH 013/351] =?UTF-8?q?Cria=20bot=C3=A3o=20de=20excluir=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/users/urls.py | 2 +- api/users/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/users/urls.py b/api/users/urls.py index 861f8eb3..3663dd7c 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -8,7 +8,7 @@ path('csrfcookie/', GetCSRFToken.as_view(), name='csrf-cookie'), path('sessioncookie/', GetSessionCookie.as_view(), name='session-cookie'), path('checkauth/', CheckAuthenticatedView.as_view(), name='check-auth'), - path('users/', UserCreateView.as_view()), + path('users/', UserCreateView.as_view(), name='create-user'), path('userauth/', AuthenticatedUserView.as_view(), name='authenticated-user'), path('users//', UserDetailView.as_view(), name='user_detail'), path('login/', LoginView.as_view(), name='login'), diff --git a/api/users/views.py b/api/users/views.py index c3cb08f9..cc86eeac 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -36,7 +36,7 @@ def get(self, request): else: return Response({'isAuthenticated': False}) -class UserCreateView(generics.RetrieveAPIView): +class UserCreateView(generics.CreateAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [] From 5cf18d7efb947d72ed244a645ca2d8481dc548a0 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 8 Apr 2024 16:26:40 -0300 Subject: [PATCH 014/351] =?UTF-8?q?Muda=20bot=C3=B5es=20do=20frontend=20de?= =?UTF-8?q?=20perfil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sige_ie/lib/core/data/auth_service.dart | 1 + .../sige_ie/lib/users/data/user_service.dart | 41 +++++++--- .../sige_ie/lib/users/feature/profile.dart | 81 ++++++++++++++----- 3 files changed, 93 insertions(+), 30 deletions(-) diff --git a/frontend/sige_ie/lib/core/data/auth_service.dart b/frontend/sige_ie/lib/core/data/auth_service.dart index 1005d6c3..c93119b6 100644 --- a/frontend/sige_ie/lib/core/data/auth_service.dart +++ b/frontend/sige_ie/lib/core/data/auth_service.dart @@ -134,4 +134,5 @@ class AuthService { print("Erro ao tentar fazer logout: $e"); } } + } diff --git a/frontend/sige_ie/lib/users/data/user_service.dart b/frontend/sige_ie/lib/users/data/user_service.dart index 10ab2964..c029ef79 100644 --- a/frontend/sige_ie/lib/users/data/user_service.dart +++ b/frontend/sige_ie/lib/users/data/user_service.dart @@ -2,21 +2,17 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/core/data/auth_service.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/users/data/user_model.dart'; class UserService { - static Future register( String username, String firstName, String password, String email) async { //var csrfToken = await AuthService.fetchCsrfToken(); var url = Uri.parse('http://10.0.2.2:8000/api/users/'); try { var response = await http.post(url, - headers: { - 'Content-Type': 'application/json' - }, + headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'username': username, 'first_name': firstName, @@ -38,19 +34,16 @@ class UserService { } Future update(String id, String firstName, String email) async { - - var client = InterceptedClient.build( + var client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); //var csrfToken = await AuthService.fetchCsrfToken(); - + var url = Uri.parse('http://10.0.2.2:8000/api/users/$id/'); try { var response = await client.put(url, - headers: { - 'Content-Type': 'application/json' - }, + headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'first_name': firstName, 'email': email, @@ -69,8 +62,32 @@ class UserService { } } - Future fetchProfileData() async { + Future delete(String id) async { + var client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + //var csrfToken = await AuthService.fetchCsrfToken(); + var url = Uri.parse('http://10.0.2.2:8000/api/users/$id/'); + try { + var response = + await client.delete(url, headers: {'Content-Type': 'application/json'}); + if (response.statusCode == 204) { + var data = jsonDecode(response.body); + print("Excluido com sucesso: $data"); + return true; + } else { + print("Falha: ${response.body}"); + return false; + } + } catch (e) { + print("Erro ao tentar excluir: $e"); + return false; + } + } + + Future fetchProfileData() async { var client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index f064976d..ef6f8d4b 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -80,33 +80,78 @@ class _ProfilePageState extends State { ), ], ), - SizedBox(height: 20), + SizedBox(height: 80), + Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + ElevatedButton( + onPressed: () async { + await userService.update( + userModel.id, userModel.firstname, userModel.email); + }, + child: Text('Salvar', style: TextStyle(color: Colors.black)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey[300], + ), + ), + ElevatedButton( + onPressed: () async { + await authService.logout(); + Navigator.pushReplacementNamed(context, '/loginScreen'); + }, + child: Text('Sair da Conta', + style: TextStyle(color: Colors.black)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueGrey[200], + ), + ), + ]), + SizedBox(height: 80), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () async { - await userService.update( - userModel.id, userModel.firstname, userModel.email); - }, - child: Text('Salvar', style: TextStyle(color: Colors.black)), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey[300], - ), - ), - ElevatedButton( - onPressed: () async { - await authService.logout(); - Navigator.pushReplacementNamed(context, '/loginScreen'); + // Mostrar o diálogo de confirmação + bool deleteConfirmed = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Excluir Conta'), + content: + Text('Tem certeza que deseja excluir sua conta?'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop( + false); // Retorna falso para indicar que a exclusão não foi confirmada + }, + child: Text('Cancelar'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop( + true); // Retorna verdadeiro para indicar que a exclusão foi confirmada + }, + child: Text('Confirmar'), + ), + ], + ); + }, + ); + + // Se a exclusão for confirmada, exclua a conta + if (deleteConfirmed) { + await userService.delete(userModel.id); + Navigator.pushReplacementNamed(context, '/loginScreen'); + } }, - child: Text('Sair da Conta', - style: TextStyle(color: Colors.black)), + child: Text('Excluir Conta', + style: TextStyle(color: Colors.white)), style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, + backgroundColor: Colors.red[800], ), ), ], - ), + ) ], ), ), From e6db1d5c70542bc298206793faf723f6f1e2ca6a Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 8 Apr 2024 17:49:02 -0300 Subject: [PATCH 015/351] Front: arquivo de estilizacao --- frontend/sige_ie/lib/config/app_styles.dart | 21 +++++++++++++++++++ .../lib/core/ui/first_screen/first_scren.dart | 10 ++++----- .../sige_ie/lib/users/feature/profile.dart | 13 +++++++----- 3 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 frontend/sige_ie/lib/config/app_styles.dart diff --git a/frontend/sige_ie/lib/config/app_styles.dart b/frontend/sige_ie/lib/config/app_styles.dart new file mode 100644 index 00000000..010372b0 --- /dev/null +++ b/frontend/sige_ie/lib/config/app_styles.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class AppColors { + static const Color sigeIeYellow = Color(0xFFF1F60E); + static const Color sigeIeBlue = Color(0xff123c75); + static const Color dartText = Color.fromRGBO(22, 22, 22, 1); + static const Color lightText = Color.fromARGB(255, 233, 226, 226); + static const Color warn = Color.fromARGB(255, 231, 27, 27); + static const Color accent = Color.fromARGB(255, 231, 85, 27); +} + +class AppButtonStyles { + static ButtonStyle warnButton = ElevatedButton.styleFrom( + backgroundColor: Color.fromARGB(255, 231, 27, 27), + minimumSize: Size(150, 50), + ); + static ButtonStyle accentButton = ElevatedButton.styleFrom( + backgroundColor: Color.fromARGB(255, 231, 85, 27), + minimumSize: Size(150, 50), + ); +} diff --git a/frontend/sige_ie/lib/core/ui/first_screen/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_screen/first_scren.dart index a8f40d2c..3f6d3b94 100644 --- a/frontend/sige_ie/lib/core/ui/first_screen/first_scren.dart +++ b/frontend/sige_ie/lib/core/ui/first_screen/first_scren.dart @@ -7,13 +7,12 @@ class FirstScreen extends StatelessWidget { backgroundColor: Color(0xff123c75), body: Center( child: Column( - mainAxisSize: MainAxisSize.min, // Centraliza no meio + mainAxisSize: MainAxisSize.min, children: [ - Image.asset('assets/1000x1000.png'), // Sua imagem + Image.asset('assets/1000x1000.png'), ElevatedButton( onPressed: () { - Navigator.pushNamed( - context, '/loginScreen'); // Navega para Login + Navigator.pushNamed(context, '/loginScreen'); }, child: Text( "Login", @@ -33,8 +32,7 @@ class FirstScreen extends StatelessWidget { ), ElevatedButton( onPressed: () { - Navigator.pushNamed( - context, '/registerScreen'); // Navega para Registro + Navigator.pushNamed(context, '/registerScreen'); }, child: Text( "Registro", diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index ef6f8d4b..7d7cad12 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/data/auth_service.dart'; import 'package:sige_ie/users/data/user_model.dart'; import 'package:sige_ie/users/data/user_service.dart'; +import 'package:sige_ie/config/app_styles.dart'; class ProfilePage extends StatefulWidget { @override @@ -75,7 +76,8 @@ class _ProfilePageState extends State { onTap: () {}, child: Text( 'Mudar username', - style: TextStyle(color: Colors.blue), + style: TextStyle( + color: const Color.fromARGB(255, 33, 150, 243)), ), ), ], @@ -87,7 +89,8 @@ class _ProfilePageState extends State { await userService.update( userModel.id, userModel.firstname, userModel.email); }, - child: Text('Salvar', style: TextStyle(color: Colors.black)), + child: + Text('Salvar', style: TextStyle(color: AppColors.dartText)), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[300], ), @@ -98,7 +101,7 @@ class _ProfilePageState extends State { Navigator.pushReplacementNamed(context, '/loginScreen'); }, child: Text('Sair da Conta', - style: TextStyle(color: Colors.black)), + style: TextStyle(color: AppColors.dartText)), style: ElevatedButton.styleFrom( backgroundColor: Colors.blueGrey[200], ), @@ -145,9 +148,9 @@ class _ProfilePageState extends State { } }, child: Text('Excluir Conta', - style: TextStyle(color: Colors.white)), + style: TextStyle(color: AppColors.lightText)), style: ElevatedButton.styleFrom( - backgroundColor: Colors.red[800], + backgroundColor: AppColors.warn, ), ), ], From 0c8e046cdb41ec7b4b17c3508aaaa82428b1a250 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 10 Apr 2024 09:19:19 -0300 Subject: [PATCH 016/351] =?UTF-8?q?backend:=20fix=20permiss=C3=B5es=20da?= =?UTF-8?q?=20view=20de=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/users/permissions.py | 2 +- api/users/views.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/users/permissions.py b/api/users/permissions.py index 8db7ad9e..e2d4f9e7 100644 --- a/api/users/permissions.py +++ b/api/users/permissions.py @@ -8,4 +8,4 @@ class IsOwnerOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in permissions.SAFE_METHODS: return True - return obj.owner == request.user + return obj.owner == request.user \ No newline at end of file diff --git a/api/users/views.py b/api/users/views.py index cc86eeac..f3ec12ab 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -20,7 +20,7 @@ def get(self, request): return Response({'success':'CSRF Cookie Set'}) class GetSessionCookie(APIView): - permission_classes = [AllowAny] + permission_classes = [IsAuthenticated] def get(self, request): sessionid = request.COOKIES.get('sessionid') @@ -39,12 +39,12 @@ def get(self, request): class UserCreateView(generics.CreateAPIView): queryset = User.objects.all() serializer_class = UserSerializer - permission_classes = [] + permission_classes = [AllowAny] class UserDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = User.objects.all() serializer_class = UserUpdateSerializer - permission_classes = [IsOwner, AllowAny] + permission_classes = [IsOwner, IsAuthenticated] class AuthenticatedUserView(generics.RetrieveAPIView): queryset = User.objects.all() @@ -70,7 +70,7 @@ def post(self, request, format=None): return JsonResponse(serializer.errors) class LogoutView(APIView): - permission_classes = [AllowAny] + permission_classes = [IsAuthenticated] def post(self, request, format=None): logout(request) From 14a95364e6a9c7c9f58fbdcf7f587901f4acccb1 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 10 Apr 2024 16:45:10 -0300 Subject: [PATCH 017/351] =?UTF-8?q?backend:=20Cria=20PlaceOwner=20e=20rela?= =?UTF-8?q?=C3=A7=C3=A3o=20one-to-many=20entre=20PlaceOwner=20e=20Places?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...007_remove_place_user_place_place_owner.py | 24 +++++++++ .../0008_alter_place_place_owner.py | 20 +++++++ .../migrations/0009_alter_room_place.py | 19 +++++++ ...lter_place_place_owner_alter_room_place.py | 25 +++++++++ api/places/models.py | 53 ++----------------- api/places/serializers.py | 6 ++- api/places/views.py | 24 +++++++-- api/users/migrations/0001_initial.py | 24 +++++++++ api/users/models.py | 7 +-- api/users/views.py | 1 + 10 files changed, 146 insertions(+), 57 deletions(-) create mode 100644 api/places/migrations/0007_remove_place_user_place_place_owner.py create mode 100644 api/places/migrations/0008_alter_place_place_owner.py create mode 100644 api/places/migrations/0009_alter_room_place.py create mode 100644 api/places/migrations/0010_alter_place_place_owner_alter_room_place.py create mode 100644 api/users/migrations/0001_initial.py diff --git a/api/places/migrations/0007_remove_place_user_place_place_owner.py b/api/places/migrations/0007_remove_place_user_place_place_owner.py new file mode 100644 index 00000000..a87f5d96 --- /dev/null +++ b/api/places/migrations/0007_remove_place_user_place_place_owner.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2 on 2024-04-10 18:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ('places', '0006_room_systems'), + ] + + operations = [ + migrations.RemoveField( + model_name='place', + name='user', + ), + migrations.AddField( + model_name='place', + name='place_owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='users.placeowner'), + ), + ] diff --git a/api/places/migrations/0008_alter_place_place_owner.py b/api/places/migrations/0008_alter_place_place_owner.py new file mode 100644 index 00000000..de40950f --- /dev/null +++ b/api/places/migrations/0008_alter_place_place_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-10 18:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ('places', '0007_remove_place_user_place_place_owner'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='place_owner', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='users.placeowner'), + ), + ] diff --git a/api/places/migrations/0009_alter_room_place.py b/api/places/migrations/0009_alter_room_place.py new file mode 100644 index 00000000..6558ffb1 --- /dev/null +++ b/api/places/migrations/0009_alter_room_place.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2 on 2024-04-10 18:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0008_alter_place_place_owner'), + ] + + operations = [ + migrations.AlterField( + model_name='room', + name='place', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to='places.place'), + ), + ] diff --git a/api/places/migrations/0010_alter_place_place_owner_alter_room_place.py b/api/places/migrations/0010_alter_place_place_owner_alter_room_place.py new file mode 100644 index 00000000..761e92be --- /dev/null +++ b/api/places/migrations/0010_alter_place_place_owner_alter_room_place.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2 on 2024-04-10 19:19 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ('places', '0009_alter_room_place'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='place_owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.placeowner'), + ), + migrations.AlterField( + model_name='room', + name='place', + field=models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, related_name='rooms', to='places.place'), + ), + ] diff --git a/api/places/models.py b/api/places/models.py index 8de1b5dd..177bb1eb 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -1,13 +1,13 @@ from django.db import models from django.core.validators import MinValueValidator -from django.contrib.auth.models import User +from users.models import PlaceOwner from django.contrib.auth.models import Permission, Group from django.contrib.contenttypes.models import ContentType class Place(models.Model): name = models.CharField(max_length=50) - user = models.ForeignKey(User, verbose_name=("creator"), on_delete=models.CASCADE) + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) def __str__(self): return self.name @@ -16,52 +16,5 @@ class Room(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) - place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE) + place = models.OneToOneField(Place, related_name='rooms', on_delete=models.DO_NOTHING) systems = models.ManyToManyField('systems.System') - - -def criar_permissoes_place(sender, instance, created, **kwargs): - - if created: - - content_type = ContentType.objects.get_for_model(Place) - - permission_view = Permission.objects.create( - codename='view_content_{}'.format(instance.id), - name='Can view content of {}'.format(instance.name), - content_type=content_type - ) - permission_edit = Permission.objects.create( - codename='edit_content_{}'.format(instance.id), - name='Can edit content of {}'.format(instance.name), - content_type=content_type - ) - - permission_delet = Permission.objects.create( - codename='delete_instance_{}'.format(instance.id), - name="Can delete the instance of {}".format(instance.name), - content_type = content_type - - - ) - group_view = Group.objects.create(name='Visualizadores {}'.format(instance.name)) - group_view.permissions.add(permission_view) - - group_edit = Group.objects.create(name='Editores {}'.format(instance.name)) - group_edit.permissions.add(permission_view) - group_edit.permissions.add(permission_edit) - - group_owner = Group.objects.create(name='Donos {}'.format(instance.name)) - group_owner.permissions.add(permission_delet) - group_owner.permissions.add(permission_edit) - group_owner.permissions.add(permission_view) - - usuario = instance.user_id - direitos = Group.objects.get(name='Donos {}'.format(instance.name)) - direitos.user_set.add(usuario) - - - - -models.signals.post_save.connect(criar_permissoes_place, sender=Place) - diff --git a/api/places/serializers.py b/api/places/serializers.py index 39ef39d0..9eac1b6e 100644 --- a/api/places/serializers.py +++ b/api/places/serializers.py @@ -4,7 +4,11 @@ class PlaceSerializer(serializers.ModelSerializer): class Meta: model = Place - fields = ['name', 'user'] + fields = ['name', 'place_owner'] + extra_kwargs = { + 'name': {'required': True}, + 'place_owner': {'read_only': True} + } class RoomSerializer(serializers.ModelSerializer): class Meta: diff --git a/api/places/views.py b/api/places/views.py index c4dbd123..dc5200c3 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -1,9 +1,10 @@ from django.shortcuts import render +from users.models import PlaceOwner from rest_framework import generics from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated -from rest_framework import viewsets, mixins +from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response @@ -14,7 +15,24 @@ class PlaceViewSet(viewsets.ModelViewSet): queryset = Place.objects.all() serializer_class = PlaceSerializer - permission_classes = [] + permission_classes = [IsAuthenticated] + + def create(self, request, *args, **kwargs): + user = request.user + + try: + place_owner = user.placeowner + except PlaceOwner.DoesNotExist: + place_owner = PlaceOwner.objects.create(user=user) + + place_data = request.data.copy() + place_data['place_owner'] = place_owner.id + place_serializer = self.get_serializer(data=place_data) + place_serializer.is_valid(raise_exception=True) + self.perform_create(place_serializer) + + headers = self.get_success_headers(place_serializer.data) + return Response(place_serializer.data, status=status.HTTP_201_CREATED, headers=headers) @action(detail=True, methods=['get']) def rooms(self, request, pk=None): @@ -32,4 +50,4 @@ def room(self, request, pk=None, room_pk=None): class RoomViewSet(viewsets.ModelViewSet): queryset = Room.objects.all() serializer_class = RoomSerializer - permission_classes = [] \ No newline at end of file + permission_classes = [IsAuthenticated] \ No newline at end of file diff --git a/api/users/migrations/0001_initial.py b/api/users/migrations/0001_initial.py new file mode 100644 index 00000000..650ab025 --- /dev/null +++ b/api/users/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2 on 2024-04-10 18:53 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='PlaceOwner', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL, verbose_name='user')), + ], + ), + ] diff --git a/api/users/models.py b/api/users/models.py index 41229132..29bae012 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -1,6 +1,7 @@ from django.db import models +from django.contrib.auth.models import User -# Create your models here - - +class PlaceOwner(models.Model): + user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.DO_NOTHING) + \ No newline at end of file diff --git a/api/users/views.py b/api/users/views.py index f3ec12ab..a78591c9 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -14,6 +14,7 @@ from django.contrib.auth import authenticate, login, logout from django.utils.decorators import method_decorator +@method_decorator(ensure_csrf_cookie, name='dispatch') class GetCSRFToken(APIView): permission_classes = [AllowAny] def get(self, request): From 567d6306bb6a00bd21722145bc2c2f848cb9cf86 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 11 Apr 2024 17:31:49 -0300 Subject: [PATCH 018/351] =?UTF-8?q?backend:=20fix=20serializa=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20places?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0011_alter_place_place_owner.py | 20 ++++++++++++++++++ .../0012_alter_place_place_owner.py | 20 ++++++++++++++++++ .../0013_alter_place_place_owner.py | 20 ++++++++++++++++++ api/places/models.py | 2 +- api/places/serializers.py | 7 ++++--- .../migrations/0002_alter_placeowner_user.py | 21 +++++++++++++++++++ api/users/models.py | 2 +- api/users/serializers.py | 6 ++++++ 8 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 api/places/migrations/0011_alter_place_place_owner.py create mode 100644 api/places/migrations/0012_alter_place_place_owner.py create mode 100644 api/places/migrations/0013_alter_place_place_owner.py create mode 100644 api/users/migrations/0002_alter_placeowner_user.py diff --git a/api/places/migrations/0011_alter_place_place_owner.py b/api/places/migrations/0011_alter_place_place_owner.py new file mode 100644 index 00000000..13bfae87 --- /dev/null +++ b/api/places/migrations/0011_alter_place_place_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-11 19:38 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_alter_placeowner_user'), + ('places', '0010_alter_place_place_owner_alter_room_place'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='place_owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.placeowner'), + ), + ] diff --git a/api/places/migrations/0012_alter_place_place_owner.py b/api/places/migrations/0012_alter_place_place_owner.py new file mode 100644 index 00000000..ad6d9136 --- /dev/null +++ b/api/places/migrations/0012_alter_place_place_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-11 19:43 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_alter_placeowner_user'), + ('places', '0011_alter_place_place_owner'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='place_owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.placeowner', unique=True), + ), + ] diff --git a/api/places/migrations/0013_alter_place_place_owner.py b/api/places/migrations/0013_alter_place_place_owner.py new file mode 100644 index 00000000..357ff692 --- /dev/null +++ b/api/places/migrations/0013_alter_place_place_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-11 19:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_alter_placeowner_user'), + ('places', '0012_alter_place_place_owner'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='place_owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.placeowner'), + ), + ] diff --git a/api/places/models.py b/api/places/models.py index 177bb1eb..28717cb7 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -7,7 +7,7 @@ class Place(models.Model): name = models.CharField(max_length=50) - place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE) def __str__(self): return self.name diff --git a/api/places/serializers.py b/api/places/serializers.py index 9eac1b6e..ba33ab0c 100644 --- a/api/places/serializers.py +++ b/api/places/serializers.py @@ -1,13 +1,14 @@ from rest_framework import serializers from .models import Place, Room +from users.serializers import PlaceOwnerSerializer class PlaceSerializer(serializers.ModelSerializer): + class Meta: model = Place - fields = ['name', 'place_owner'] + fields = ['id', 'name', 'place_owner'] extra_kwargs = { - 'name': {'required': True}, - 'place_owner': {'read_only': True} + 'name': {'required': True} } class RoomSerializer(serializers.ModelSerializer): diff --git a/api/users/migrations/0002_alter_placeowner_user.py b/api/users/migrations/0002_alter_placeowner_user.py new file mode 100644 index 00000000..0131ed9d --- /dev/null +++ b/api/users/migrations/0002_alter_placeowner_user.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2 on 2024-04-11 19:34 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('users', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='placeowner', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user'), + ), + ] diff --git a/api/users/models.py b/api/users/models.py index 29bae012..f0c8b90d 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -3,5 +3,5 @@ class PlaceOwner(models.Model): - user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.DO_NOTHING) + user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE) \ No newline at end of file diff --git a/api/users/serializers.py b/api/users/serializers.py index 417d867e..6ab44b23 100644 --- a/api/users/serializers.py +++ b/api/users/serializers.py @@ -1,6 +1,7 @@ # serializers.py from rest_framework import serializers, response from django.contrib.auth.models import User +from .models import PlaceOwner class UserSerializer(serializers.ModelSerializer): username = serializers.CharField(min_length=6, max_length=23, required=True) @@ -43,3 +44,8 @@ def update(self, instance, validated_data): instance.email = validated_data.get('email', instance.email) instance.save() return instance + +class PlaceOwnerSerializer(serializers.ModelSerializer): + class Meta: + model = PlaceOwner + fields = ['id'] From 460be618591a523db4ac8a19c9bcf9913cca63a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Fri, 12 Apr 2024 10:46:12 -0300 Subject: [PATCH 019/351] Backend: list places por placesowner --- api/places/models.py | 4 +++- api/places/views.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/api/places/models.py b/api/places/models.py index 28717cb7..541e2a7c 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -16,5 +16,7 @@ class Room(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) - place = models.OneToOneField(Place, related_name='rooms', on_delete=models.DO_NOTHING) + place = models.ForeignKey(Place, related_name='rooms', on_delete=models.DO_NOTHING) systems = models.ManyToManyField('systems.System') + + \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index dc5200c3..850b417b 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -34,6 +34,17 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(place_serializer.data) return Response(place_serializer.data, status=status.HTTP_201_CREATED, headers=headers) + def list(self, request, *args, **kwargs): + user = request.user + place_owner = user.placeowner + + place_id = request.data.get('place') + places = Place.objects.filter(place_owner=place_owner) + + place_serializer = PlaceSerializer(places, many=True) + return Response(place_serializer.data) + + @action(detail=True, methods=['get']) def rooms(self, request, pk=None): place = self.get_object() @@ -50,4 +61,5 @@ def room(self, request, pk=None, room_pk=None): class RoomViewSet(viewsets.ModelViewSet): queryset = Room.objects.all() serializer_class = RoomSerializer - permission_classes = [IsAuthenticated] \ No newline at end of file + permission_classes = [IsAuthenticated] + From 77c1dfea24639f4f6892bdad4fb56440c631c7a2 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 12 Apr 2024 11:11:40 -0300 Subject: [PATCH 020/351] backend: fix list de places e create de rooms por place owners --- api/places/views.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/api/places/views.py b/api/places/views.py index 850b417b..16990887 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -33,18 +33,16 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(place_serializer.data) return Response(place_serializer.data, status=status.HTTP_201_CREATED, headers=headers) - + def list(self, request, *args, **kwargs): user = request.user place_owner = user.placeowner - place_id = request.data.get('place') places = Place.objects.filter(place_owner=place_owner) place_serializer = PlaceSerializer(places, many=True) return Response(place_serializer.data) - - + @action(detail=True, methods=['get']) def rooms(self, request, pk=None): place = self.get_object() @@ -63,3 +61,17 @@ class RoomViewSet(viewsets.ModelViewSet): serializer_class = RoomSerializer permission_classes = [IsAuthenticated] + def create(self, request, *args, **kwargs): + user = request.user + place_owner = user.placeowner + place_id = request.data.get('place') + + place = get_object_or_404(Place, id=place_id, place_owner=place_owner) + + if place.place_owner == place_owner: + room_serializer = self.get_serializer(data=request.data) + room_serializer.is_valid(raise_exception=True) + room_serializer.save() + return Response(room_serializer.data, status=status.HTTP_201_CREATED) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) From 1687ccb7733ab4b51f0166972be7c6111c7b001d Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 12 Apr 2024 11:15:44 -0300 Subject: [PATCH 021/351] backend: migration de place em room --- .../migrations/0014_alter_room_place.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 api/places/migrations/0014_alter_room_place.py diff --git a/api/places/migrations/0014_alter_room_place.py b/api/places/migrations/0014_alter_room_place.py new file mode 100644 index 00000000..3a37a425 --- /dev/null +++ b/api/places/migrations/0014_alter_room_place.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2 on 2024-04-12 14:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0013_alter_place_place_owner'), + ] + + operations = [ + migrations.AlterField( + model_name='room', + name='place', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='rooms', to='places.place'), + ), + ] From 943d1856e1315ed6155882db653e2e4b4ca8efcf Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 12 Apr 2024 11:47:17 -0300 Subject: [PATCH 022/351] backend: fix retrieve, update e destroy de place por place owner Co-authored-by: Kauan Jose --- api/places/models.py | 2 +- api/places/views.py | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/api/places/models.py b/api/places/models.py index 541e2a7c..e8df9d84 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -16,7 +16,7 @@ class Room(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) - place = models.ForeignKey(Place, related_name='rooms', on_delete=models.DO_NOTHING) + place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE) systems = models.ManyToManyField('systems.System') \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index 16990887..2e291321 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -33,7 +33,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(place_serializer.data) return Response(place_serializer.data, status=status.HTTP_201_CREATED, headers=headers) - + def list(self, request, *args, **kwargs): user = request.user place_owner = user.placeowner @@ -42,7 +42,39 @@ def list(self, request, *args, **kwargs): place_serializer = PlaceSerializer(places, many=True) return Response(place_serializer.data) - + + def retrieve(self, request, pk=None): + place_owner_id = request.user.placeowner.id + + place = get_object_or_404(Place, pk=pk) + if place.place_owner.id == place_owner_id: + serializer = PlaceSerializer(place) + return Response(serializer.data) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + + def update(self, request, pk=None): + place_owner_id = request.user.placeowner.id + + place = get_object_or_404(Place, pk=pk) + if place.place_owner.id == place_owner_id: + serializer = PlaceSerializer(place, data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + + def destroy(self, request, pk=None): + place_owner_id = request.user.placeowner.id + + place = get_object_or_404(Place, pk=pk) + if place.place_owner.id == place_owner_id: + place.delete() + return Response({"message": "Place deleted successfully"}, status=status.HTTP_204_NO_CONTENT) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + @action(detail=True, methods=['get']) def rooms(self, request, pk=None): place = self.get_object() From 5f29f1e8a1e684986fb0b9e674c3cb86c6a1c8f7 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 12 Apr 2024 13:17:00 -0300 Subject: [PATCH 023/351] =?UTF-8?q?backend:=20cria=20permiss=C3=B5es=20de?= =?UTF-8?q?=20place?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kauan Jose --- .../migrations/0015_alter_room_place.py | 19 +++++++++++++++++++ api/places/permissions.py | 12 ++++++++++-- api/places/views.py | 4 ++-- 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 api/places/migrations/0015_alter_room_place.py diff --git a/api/places/migrations/0015_alter_room_place.py b/api/places/migrations/0015_alter_room_place.py new file mode 100644 index 00000000..91bbb25c --- /dev/null +++ b/api/places/migrations/0015_alter_room_place.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2 on 2024-04-12 15:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0014_alter_room_place'), + ] + + operations = [ + migrations.AlterField( + model_name='room', + name='place', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to='places.place'), + ), + ] diff --git a/api/places/permissions.py b/api/places/permissions.py index 8f743582..85144c6b 100644 --- a/api/places/permissions.py +++ b/api/places/permissions.py @@ -1,7 +1,15 @@ from rest_framework import permissions -class IsOwnerOrReadOnly(permissions.BasePermission): +from places.models import Place + +class IsPlaceOwner(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + return obj.place.place_owner.user == request.user + +class IsPlaceOwnerOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in permissions.SAFE_METHODS: return True - return obj.owner == request.user + place_owner = obj.place_owner + return place_owner.user == request.user + diff --git a/api/places/views.py b/api/places/views.py index 2e291321..220402a1 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -4,13 +4,13 @@ from rest_framework import generics from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated +from places.permissions import IsPlaceOwner from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from .models import Place, Room from .serializers import PlaceSerializer, RoomSerializer -from .permissions import IsOwnerOrReadOnly class PlaceViewSet(viewsets.ModelViewSet): queryset = Place.objects.all() @@ -106,4 +106,4 @@ def create(self, request, *args, **kwargs): room_serializer.save() return Response(room_serializer.data, status=status.HTTP_201_CREATED) else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) \ No newline at end of file From 023545a0acb8c77887609cb1ec81c20fe1df592d Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 12 Apr 2024 14:37:25 -0300 Subject: [PATCH 024/351] =?UTF-8?q?Backend:=20Fix=20Cria=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20place=20owner=20em=20list=20de=20places?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0016_alter_place_place_owner.py | 20 ++++++++++++++++++ api/places/views.py | 10 +++++++-- api/sigeie/docker-compose.yml | 1 + .../migrations/0003_alter_placeowner_user.py | 21 +++++++++++++++++++ api/users/models.py | 2 +- 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 api/places/migrations/0016_alter_place_place_owner.py create mode 100644 api/users/migrations/0003_alter_placeowner_user.py diff --git a/api/places/migrations/0016_alter_place_place_owner.py b/api/places/migrations/0016_alter_place_place_owner.py new file mode 100644 index 00000000..69d70a4a --- /dev/null +++ b/api/places/migrations/0016_alter_place_place_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-12 17:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_alter_placeowner_user'), + ('places', '0015_alter_room_place'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='place_owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.placeowner'), + ), + ] diff --git a/api/places/views.py b/api/places/views.py index 220402a1..8a2b38db 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -17,6 +17,12 @@ class PlaceViewSet(viewsets.ModelViewSet): serializer_class = PlaceSerializer permission_classes = [IsAuthenticated] + def get_place_owner(self, user): + try: + return user.placeowner + except PlaceOwner.DoesNotExist: + return PlaceOwner.objects.create(user=user) + def create(self, request, *args, **kwargs): user = request.user @@ -36,7 +42,7 @@ def create(self, request, *args, **kwargs): def list(self, request, *args, **kwargs): user = request.user - place_owner = user.placeowner + place_owner = self.get_place_owner(user) places = Place.objects.filter(place_owner=place_owner) @@ -101,7 +107,7 @@ def create(self, request, *args, **kwargs): place = get_object_or_404(Place, id=place_id, place_owner=place_owner) if place.place_owner == place_owner: - room_serializer = self.get_serializer(data=request.data) + room_serializer = RoomSerializer(data=request.data) room_serializer.is_valid(raise_exception=True) room_serializer.save() return Response(room_serializer.data, status=status.HTTP_201_CREATED) diff --git a/api/sigeie/docker-compose.yml b/api/sigeie/docker-compose.yml index 63bf3cdc..2645fb73 100644 --- a/api/sigeie/docker-compose.yml +++ b/api/sigeie/docker-compose.yml @@ -13,6 +13,7 @@ services: - ./data/mysql/db:/var/lib/mysql redis: image: redis:7.2 + restart: always container_name: sigeie_redis_db ports: - "6379:6379" diff --git a/api/users/migrations/0003_alter_placeowner_user.py b/api/users/migrations/0003_alter_placeowner_user.py new file mode 100644 index 00000000..6a60a746 --- /dev/null +++ b/api/users/migrations/0003_alter_placeowner_user.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2 on 2024-04-12 17:23 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('users', '0002_alter_placeowner_user'), + ] + + operations = [ + migrations.AlterField( + model_name='placeowner', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='placeowner', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/api/users/models.py b/api/users/models.py index f0c8b90d..b5829866 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -3,5 +3,5 @@ class PlaceOwner(models.Model): - user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE) + user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE, related_name='placeowner') \ No newline at end of file From b09e0f5844f62bdf1ce602bb24a25c14fab82117 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 12 Apr 2024 15:13:04 -0300 Subject: [PATCH 025/351] Backend: cria list de rooms por place que pertence ao placeowner --- api/places/views.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/api/places/views.py b/api/places/views.py index 8a2b38db..f327873c 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -8,6 +8,8 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework.exceptions import NotFound + from .models import Place, Room from .serializers import PlaceSerializer, RoomSerializer @@ -112,4 +114,19 @@ def create(self, request, *args, **kwargs): room_serializer.save() return Response(room_serializer.data, status=status.HTTP_201_CREATED) else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) \ No newline at end of file + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + + def list(self,request,*args, **kwargs): + user = request.user + place_owner = user.placeowner + place_id = request.query_params.get('place') + + if not place_id: + raise NotFound("Place ID must be provided.") + + place = get_object_or_404(Place, id=place_id, place_owner=place_owner) + + rooms = Room.objects.filter(place=place) + + room_serializer = RoomSerializer(rooms, many=True) + return Response(room_serializer.data) \ No newline at end of file From 2b0785157f618a4e19049718ad3c92cac0ca74a9 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Tue, 16 Apr 2024 15:16:26 -0300 Subject: [PATCH 026/351] =?UTF-8?q?backend:=20permite=20que=20dono=20de=20?= =?UTF-8?q?lugar=20seja=20null=20at=C3=A9=20que=20o=20lugar=20seja=20criad?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/places/models.py | 2 +- .../migrations/0004_alter_placeowner_user.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 api/users/migrations/0004_alter_placeowner_user.py diff --git a/api/places/models.py b/api/places/models.py index e8df9d84..b19f7329 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -7,7 +7,7 @@ class Place(models.Model): name = models.CharField(max_length=50) - place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE) + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) def __str__(self): return self.name diff --git a/api/users/migrations/0004_alter_placeowner_user.py b/api/users/migrations/0004_alter_placeowner_user.py new file mode 100644 index 00000000..4c48c10c --- /dev/null +++ b/api/users/migrations/0004_alter_placeowner_user.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2 on 2024-04-16 18:14 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('users', '0003_alter_placeowner_user'), + ] + + operations = [ + migrations.AlterField( + model_name='placeowner', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='placeowner', to=settings.AUTH_USER_MODEL, verbose_name='user'), + ), + ] From 805c48da288b32d8b473ee27245cca2acc911c73 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 16 Apr 2024 15:30:21 -0300 Subject: [PATCH 027/351] fix - remove linha --- api/places/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/places/views.py b/api/places/views.py index f327873c..d55961ce 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -10,7 +10,6 @@ from rest_framework.response import Response from rest_framework.exceptions import NotFound - from .models import Place, Room from .serializers import PlaceSerializer, RoomSerializer @@ -129,4 +128,4 @@ def list(self,request,*args, **kwargs): rooms = Room.objects.filter(place=place) room_serializer = RoomSerializer(rooms, many=True) - return Response(room_serializer.data) \ No newline at end of file + return Response(room_serializer.data) \ No newline at end of file From 3f1fe1eaa0bbf29f69542e3f79e965f76fe3cb59 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 16 Apr 2024 15:58:05 -0300 Subject: [PATCH 028/351] Backend: Cria deletar sala pelo dono do lugar --- api/places/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/api/places/views.py b/api/places/views.py index d55961ce..f7ff8cbd 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -128,4 +128,15 @@ def list(self,request,*args, **kwargs): rooms = Room.objects.filter(place=place) room_serializer = RoomSerializer(rooms, many=True) - return Response(room_serializer.data) \ No newline at end of file + return Response(room_serializer.data) + + # Só o dono do lugar pode excluir uma sala espécífca + def destroy(self, request, pk=None): + place_owner_id = request.user.placeowner.id + room = get_object_or_404(Room, pk=pk) + + if room.place.place_owner.id == place_owner_id: + room.delete() + return Response({"message": "Room deleted successfully"}, status=status.HTTP_204_NO_CONTENT) + else: + return Response({"message": "You are not the owner of this room"}, status=status.HTTP_403_FORBIDDEN) From 76a03650ffbf4979aab64161ace0e4d6e398404e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Tue, 16 Apr 2024 16:17:37 -0300 Subject: [PATCH 029/351] Backend: fixing retrieve room --- api/places/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/api/places/views.py b/api/places/views.py index f7ff8cbd..dabd9633 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -130,6 +130,17 @@ def list(self,request,*args, **kwargs): room_serializer = RoomSerializer(rooms, many=True) return Response(room_serializer.data) + def retrieve(self, request, pk=None): + place_owner = request.user.placeowner.id + + room = get_object_or_404(Room,pk=pk) + + if(room.place.place_owner.id == place_owner): + serializer = RoomSerializer(room) + return Response(serializer.data) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + # Só o dono do lugar pode excluir uma sala espécífca def destroy(self, request, pk=None): place_owner_id = request.user.placeowner.id @@ -139,4 +150,4 @@ def destroy(self, request, pk=None): room.delete() return Response({"message": "Room deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: - return Response({"message": "You are not the owner of this room"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner of this room"}, status=status.HTTP_403_FORBIDDEN) \ No newline at end of file From 0d584ad29d4a62f902ffdfdf6477f710caf4993f Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 16 Apr 2024 16:58:06 -0300 Subject: [PATCH 030/351] =?UTF-8?q?Front:=20Mudan=C3=A7as=20nos=20bot?= =?UTF-8?q?=C3=B5es=20do=20perfil=20e=20altera=C3=A7=C3=B5es=20nas=20pr?= =?UTF-8?q?=C3=A9-defini=C3=A7=C3=B5es=20do=20front?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/config/app_styles.dart | 10 +- .../sige_ie/lib/users/feature/profile.dart | 141 ++++++++++-------- 2 files changed, 87 insertions(+), 64 deletions(-) diff --git a/frontend/sige_ie/lib/config/app_styles.dart b/frontend/sige_ie/lib/config/app_styles.dart index 010372b0..817868ba 100644 --- a/frontend/sige_ie/lib/config/app_styles.dart +++ b/frontend/sige_ie/lib/config/app_styles.dart @@ -12,10 +12,14 @@ class AppColors { class AppButtonStyles { static ButtonStyle warnButton = ElevatedButton.styleFrom( backgroundColor: Color.fromARGB(255, 231, 27, 27), - minimumSize: Size(150, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), ); static ButtonStyle accentButton = ElevatedButton.styleFrom( - backgroundColor: Color.fromARGB(255, 231, 85, 27), - minimumSize: Size(150, 50), + backgroundColor: Color.fromARGB(255, 231, 160, 27), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), ); } diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index 7d7cad12..5ffc8d13 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -83,75 +83,94 @@ class _ProfilePageState extends State { ], ), SizedBox(height: 80), - Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - ElevatedButton( - onPressed: () async { - await userService.update( - userModel.id, userModel.firstname, userModel.email); - }, - child: - Text('Salvar', style: TextStyle(color: AppColors.dartText)), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey[300], + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox( + width: 150, // Define a largura uniforme + height: 50, // Define a altura uniforme + child: ElevatedButton( + onPressed: () async { + await userService.update( + userModel.id, userModel.firstname, userModel.email); + }, + child: Text('Salvar', + style: TextStyle(color: AppColors.dartText)), + style: ElevatedButton.styleFrom( + backgroundColor: const Color.fromARGB(255, 224, 221, 221), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), ), - ), - ElevatedButton( - onPressed: () async { - await authService.logout(); - Navigator.pushReplacementNamed(context, '/loginScreen'); - }, - child: Text('Sair da Conta', - style: TextStyle(color: AppColors.dartText)), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blueGrey[200], + SizedBox( + width: 150, // Mesma largura para manter uniformidade + height: 50, // Mesma altura + child: ElevatedButton( + onPressed: () async { + await authService.logout(); + Navigator.pushReplacementNamed(context, '/loginScreen'); + }, + child: Text('Sair da Conta', + style: TextStyle(color: AppColors.dartText)), + style: ElevatedButton.styleFrom( + backgroundColor: Color.fromARGB(255, 153, 163, 168), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), ), - ), - ]), + ], + ), SizedBox(height: 80), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - ElevatedButton( - onPressed: () async { - // Mostrar o diálogo de confirmação - bool deleteConfirmed = await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('Excluir Conta'), - content: - Text('Tem certeza que deseja excluir sua conta?'), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop( - false); // Retorna falso para indicar que a exclusão não foi confirmada - }, - child: Text('Cancelar'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop( - true); // Retorna verdadeiro para indicar que a exclusão foi confirmada - }, - child: Text('Confirmar'), - ), - ], + SizedBox( + width: 150, // Mantendo a consistência no tamanho + height: 50, // Altura uniforme para todos os botões + child: ElevatedButton( + onPressed: () async { + // Mostrar o diálogo de confirmação + bool deleteConfirmed = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Excluir Conta'), + content: Text( + 'Tem certeza que deseja excluir sua conta?'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop( + false); // Retorna falso para indicar que a exclusão não foi confirmada + }, + child: Text('Cancelar'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop( + true); // Retorna verdadeiro para indicar que a exclusão foi confirmada + }, + child: Text('Confirmar'), + ), + ], + ); + }, ); - }, - ); - // Se a exclusão for confirmada, exclua a conta - if (deleteConfirmed) { - await userService.delete(userModel.id); - Navigator.pushReplacementNamed(context, '/loginScreen'); - } - }, - child: Text('Excluir Conta', - style: TextStyle(color: AppColors.lightText)), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.warn, - ), + // Se a exclusão for confirmada, exclua a conta + if (deleteConfirmed) { + await userService.delete(userModel.id); + Navigator.pushReplacementNamed( + context, '/loginScreen'); + } + }, + child: Text('Excluir Conta', + style: TextStyle(color: AppColors.lightText)), + style: AppButtonStyles.warnButton), ), ], ) From da4e723ac311f7f46f1dcac46c61fd4427def95e Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 17 Apr 2024 15:00:50 -0300 Subject: [PATCH 031/351] =?UTF-8?q?backend:=20cria=20model=20de=20descarga?= =?UTF-8?q?s=20atmosf=C3=A9ricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0017_atmosfericdischargesystem.py | 24 +++++++++++++ ...tmosfericdischargesystem_place_and_more.py | 34 +++++++++++++++++++ .../0019_delete_atmosfericdischargesystem.py | 16 +++++++++ api/places/models.py | 1 - .../0002_atmosfericdischargesystem.py | 27 +++++++++++++++ ...003_atmosphericdischargesystem_and_more.py | 30 ++++++++++++++++ api/systems/models.py | 9 +++++ 7 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 api/places/migrations/0017_atmosfericdischargesystem.py create mode 100644 api/places/migrations/0018_alter_atmosfericdischargesystem_place_and_more.py create mode 100644 api/places/migrations/0019_delete_atmosfericdischargesystem.py create mode 100644 api/systems/migrations/0002_atmosfericdischargesystem.py create mode 100644 api/systems/migrations/0003_atmosphericdischargesystem_and_more.py diff --git a/api/places/migrations/0017_atmosfericdischargesystem.py b/api/places/migrations/0017_atmosfericdischargesystem.py new file mode 100644 index 00000000..ec3b46c8 --- /dev/null +++ b/api/places/migrations/0017_atmosfericdischargesystem.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2 on 2024-04-17 17:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0001_initial'), + ('places', '0016_alter_place_place_owner'), + ] + + operations = [ + migrations.CreateModel( + name='AtmosfericDischargeSystem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharg_systems', to='places.place')), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharg_systems', to='places.room')), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharg_systems', to='systems.system')), + ], + ), + ] diff --git a/api/places/migrations/0018_alter_atmosfericdischargesystem_place_and_more.py b/api/places/migrations/0018_alter_atmosfericdischargesystem_place_and_more.py new file mode 100644 index 00000000..f1ed9298 --- /dev/null +++ b/api/places/migrations/0018_alter_atmosfericdischargesystem_place_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2 on 2024-04-17 17:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0001_initial'), + ('places', '0017_atmosfericdischargesystem'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosfericdischargesystem', + name='place', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharge_systems', to='places.place'), + ), + migrations.AlterField( + model_name='atmosfericdischargesystem', + name='room', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharge_systems', to='places.room'), + ), + migrations.AlterField( + model_name='atmosfericdischargesystem', + name='system', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharge_systems', to='systems.system'), + ), + migrations.AlterModelTable( + name='atmosfericdischargesystem', + table='atmosferic_discharge_system', + ), + ] diff --git a/api/places/migrations/0019_delete_atmosfericdischargesystem.py b/api/places/migrations/0019_delete_atmosfericdischargesystem.py new file mode 100644 index 00000000..9b467921 --- /dev/null +++ b/api/places/migrations/0019_delete_atmosfericdischargesystem.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2 on 2024-04-17 17:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0018_alter_atmosfericdischargesystem_place_and_more'), + ] + + operations = [ + migrations.DeleteModel( + name='AtmosfericDischargeSystem', + ), + ] diff --git a/api/places/models.py b/api/places/models.py index b19f7329..fc671245 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -19,4 +19,3 @@ class Room(models.Model): place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE) systems = models.ManyToManyField('systems.System') - \ No newline at end of file diff --git a/api/systems/migrations/0002_atmosfericdischargesystem.py b/api/systems/migrations/0002_atmosfericdischargesystem.py new file mode 100644 index 00000000..4f81e285 --- /dev/null +++ b/api/systems/migrations/0002_atmosfericdischargesystem.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2 on 2024-04-17 17:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0019_delete_atmosfericdischargesystem'), + ('systems', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='AtmosfericDischargeSystem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharge_systems', to='places.place')), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharge_systems', to='places.room')), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmosferic_discharge_systems', to='systems.system')), + ], + options={ + 'db_table': 'atmosferic_discharge_system', + }, + ), + ] diff --git a/api/systems/migrations/0003_atmosphericdischargesystem_and_more.py b/api/systems/migrations/0003_atmosphericdischargesystem_and_more.py new file mode 100644 index 00000000..b7cad046 --- /dev/null +++ b/api/systems/migrations/0003_atmosphericdischargesystem_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2 on 2024-04-17 18:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0019_delete_atmosfericdischargesystem'), + ('systems', '0002_atmosfericdischargesystem'), + ] + + operations = [ + migrations.CreateModel( + name='AtmosphericDischargeSystem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='places.place')), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='places.room')), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='systems.system')), + ], + options={ + 'db_table': 'atmospheric_discharge_systems', + }, + ), + migrations.DeleteModel( + name='AtmosfericDischargeSystem', + ), + ] diff --git a/api/systems/models.py b/api/systems/models.py index ed104f43..f4503147 100644 --- a/api/systems/models.py +++ b/api/systems/models.py @@ -1,4 +1,5 @@ from django.db import models +from places.models import Place, Room class System(models.Model): @@ -6,3 +7,11 @@ class System(models.Model): def __str__(self): return self.name + +class AtmosphericDischargeSystem(models.Model): + place = models.ForeignKey(Place, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) + room = models.ForeignKey(Room, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) + system = models.ForeignKey('systems.System', related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) + + class Meta: + db_table = 'atmospheric_discharge_systems' From fc0769891854ab6fe11880f74986c4a05787cfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Wed, 17 Apr 2024 15:31:57 -0300 Subject: [PATCH 032/351] Backend: Cria model EquipmentPhoto, EquipmentType, Equipment --- api/requirements.txt | 1 + api/systems/models.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/api/requirements.txt b/api/requirements.txt index 9268dd4e..23875533 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -7,6 +7,7 @@ django-redis-session-store==0.1.1 django-redis-sessions==0.6.2 djangorestframework==3.14.0 mysqlclient==2.2.4 +pillow==10.3.0 pytz==2024.1 redis==5.0.3 sqlparse==0.4.4 diff --git a/api/systems/models.py b/api/systems/models.py index f4503147..ea8b0198 100644 --- a/api/systems/models.py +++ b/api/systems/models.py @@ -7,6 +7,21 @@ class System(models.Model): def __str__(self): return self.name +class EquipmentType(models.Model): + type = models.CharField(max_length=50) + system = models.ForeignKey(System, on_delete=models.CASCADE) + + + +class EquipmentPhoto(models.Model): + photo = models.ImageField(null=True, upload_to='equipment_photos/') + description = models.CharField(max_length=50) + +class Equipment(models.Model): + equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) + equipmentPhoto = models.ForeignKey(EquipmentPhoto, on_delete=models.CASCADE) + + class AtmosphericDischargeSystem(models.Model): place = models.ForeignKey(Place, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) From 6c8cf01c07a9b0c2ef39d8e48ee2543f3b7cf54c Mon Sep 17 00:00:00 2001 From: ramires Date: Wed, 17 Apr 2024 17:11:51 -0300 Subject: [PATCH 033/351] =?UTF-8?q?arquivo=20para=20c=C3=B3digo=20salas=20?= =?UTF-8?q?e=20locais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/places/register / gerenciar local.dart | 0 frontend/sige_ie/lib/places/register novo local.dart | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 frontend/sige_ie/lib/places/register / gerenciar local.dart create mode 100644 frontend/sige_ie/lib/places/register novo local.dart diff --git a/frontend/sige_ie/lib/places/register / gerenciar local.dart b/frontend/sige_ie/lib/places/register / gerenciar local.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/places/register novo local.dart b/frontend/sige_ie/lib/places/register novo local.dart new file mode 100644 index 00000000..e69de29b From ae9bfcefc2a8ef0e1cdab7e461620e2a1beb1d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 18 Apr 2024 14:02:16 -0300 Subject: [PATCH 034/351] Backend: Views Equipment Type --- api/systems/models.py | 2 +- api/systems/serializers.py | 9 ++++++++- api/systems/urls.py | 7 +++++-- api/systems/views.py | 18 +++++++++++++++--- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/api/systems/models.py b/api/systems/models.py index ea8b0198..3b4f58ae 100644 --- a/api/systems/models.py +++ b/api/systems/models.py @@ -19,7 +19,7 @@ class EquipmentPhoto(models.Model): class Equipment(models.Model): equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) - equipmentPhoto = models.ForeignKey(EquipmentPhoto, on_delete=models.CASCADE) + equipmentPhoto = models.ForeignKey(EquipmentPhoto, on_delete=models.CASCADE, null=True) diff --git a/api/systems/serializers.py b/api/systems/serializers.py index a7e01212..a4d2ef9b 100644 --- a/api/systems/serializers.py +++ b/api/systems/serializers.py @@ -1,7 +1,14 @@ from rest_framework import serializers -from .models import System +from .models import System, EquipmentType class SystemSerializer(serializers.ModelSerializer): class Meta: model = System fields = ['id', 'name'] + +class EquipmentTypeSerializer(serializers.ModelSerializer): + + class Meta: + model = EquipmentType + fields = '__all__' + diff --git a/api/systems/urls.py b/api/systems/urls.py index 0f12d8fa..a58ce036 100644 --- a/api/systems/urls.py +++ b/api/systems/urls.py @@ -1,8 +1,11 @@ from django.urls import path, include -from .views import SystemViewDetail, SystemViewList +from .views import SystemViewDetail, SystemViewList, EquipmentTypeList, EquipmentTypeDetail urlpatterns = [ path('systems/', SystemViewList.as_view()), - path('systems//', SystemViewDetail.as_view()) + path('systems//', SystemViewDetail.as_view()), + path('equipment-type/', EquipmentTypeList.as_view()), + path('equipment-type//', EquipmentTypeDetail.as_view()) + ] diff --git a/api/systems/views.py b/api/systems/views.py index 9d14da17..b5f4a01b 100644 --- a/api/systems/views.py +++ b/api/systems/views.py @@ -1,6 +1,6 @@ from rest_framework import viewsets, generics -from .models import System -from .serializers import SystemSerializer +from .models import System, EquipmentType +from .serializers import SystemSerializer, EquipmentTypeSerializer class SystemViewList(generics.ListAPIView): queryset = System.objects.all() @@ -10,4 +10,16 @@ class SystemViewList(generics.ListAPIView): class SystemViewDetail(generics.RetrieveAPIView): queryset = System.objects.all() serializer_class = SystemSerializer - permission_classes = [] \ No newline at end of file + permission_classes = [] + +class EquipmentTypeList(generics.ListAPIView): + queryset = EquipmentType.objects.all() + serializer_class = EquipmentTypeSerializer + permission_classes = [] + +class EquipmentTypeDetail(generics.RetrieveAPIView): + queryset = EquipmentType.objects.all() + serializer_class = EquipmentTypeSerializer + permission_classes = [] + + From c5c2685ba4522eb7d8c9e36336973942f07c482b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 18 Apr 2024 14:27:12 -0300 Subject: [PATCH 035/351] refatora system para equipments Co-authored-by: OscarDeBrito --- api/sigeie/settings.py | 3 ++- api/sigeie/urls.py | 3 ++- api/systems/models.py | 25 +------------------------ api/systems/serializers.py | 9 +-------- api/systems/urls.py | 6 ++---- api/systems/views.py | 15 +++------------ 6 files changed, 11 insertions(+), 50 deletions(-) diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index 452ff622..4cd19ec4 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -19,7 +19,8 @@ 'corsheaders', 'users', 'places', - 'systems' + 'systems', + 'equipments' ] MIDDLEWARE = [ diff --git a/api/sigeie/urls.py b/api/sigeie/urls.py index c44d28d7..1ad0cbab 100644 --- a/api/sigeie/urls.py +++ b/api/sigeie/urls.py @@ -23,5 +23,6 @@ path('api/', include('users.urls')), path('api/', include(router.urls)), path('api/', include('systems.urls')), - path('auth/', include('rest_framework.urls')) + path('auth/', include('rest_framework.urls')), + path('api/', include('equipments.urls')) ] diff --git a/api/systems/models.py b/api/systems/models.py index 3b4f58ae..7cdf2b2f 100644 --- a/api/systems/models.py +++ b/api/systems/models.py @@ -6,27 +6,4 @@ class System(models.Model): name = models.CharField(max_length=50) def __str__(self): - return self.name -class EquipmentType(models.Model): - type = models.CharField(max_length=50) - system = models.ForeignKey(System, on_delete=models.CASCADE) - - - -class EquipmentPhoto(models.Model): - photo = models.ImageField(null=True, upload_to='equipment_photos/') - description = models.CharField(max_length=50) - -class Equipment(models.Model): - equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) - equipmentPhoto = models.ForeignKey(EquipmentPhoto, on_delete=models.CASCADE, null=True) - - - -class AtmosphericDischargeSystem(models.Model): - place = models.ForeignKey(Place, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) - room = models.ForeignKey(Room, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) - system = models.ForeignKey('systems.System', related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) - - class Meta: - db_table = 'atmospheric_discharge_systems' + return self.name \ No newline at end of file diff --git a/api/systems/serializers.py b/api/systems/serializers.py index a4d2ef9b..a7e01212 100644 --- a/api/systems/serializers.py +++ b/api/systems/serializers.py @@ -1,14 +1,7 @@ from rest_framework import serializers -from .models import System, EquipmentType +from .models import System class SystemSerializer(serializers.ModelSerializer): class Meta: model = System fields = ['id', 'name'] - -class EquipmentTypeSerializer(serializers.ModelSerializer): - - class Meta: - model = EquipmentType - fields = '__all__' - diff --git a/api/systems/urls.py b/api/systems/urls.py index a58ce036..d186b7e5 100644 --- a/api/systems/urls.py +++ b/api/systems/urls.py @@ -1,11 +1,9 @@ from django.urls import path, include -from .views import SystemViewDetail, SystemViewList, EquipmentTypeList, EquipmentTypeDetail +from .views import SystemViewDetail, SystemViewList urlpatterns = [ path('systems/', SystemViewList.as_view()), - path('systems//', SystemViewDetail.as_view()), - path('equipment-type/', EquipmentTypeList.as_view()), - path('equipment-type//', EquipmentTypeDetail.as_view()) + path('systems//', SystemViewDetail.as_view()) ] diff --git a/api/systems/views.py b/api/systems/views.py index b5f4a01b..f0677b42 100644 --- a/api/systems/views.py +++ b/api/systems/views.py @@ -1,6 +1,6 @@ -from rest_framework import viewsets, generics -from .models import System, EquipmentType -from .serializers import SystemSerializer, EquipmentTypeSerializer +from rest_framework import generics +from .models import System +from .serializers import SystemSerializer class SystemViewList(generics.ListAPIView): queryset = System.objects.all() @@ -12,14 +12,5 @@ class SystemViewDetail(generics.RetrieveAPIView): serializer_class = SystemSerializer permission_classes = [] -class EquipmentTypeList(generics.ListAPIView): - queryset = EquipmentType.objects.all() - serializer_class = EquipmentTypeSerializer - permission_classes = [] - -class EquipmentTypeDetail(generics.RetrieveAPIView): - queryset = EquipmentType.objects.all() - serializer_class = EquipmentTypeSerializer - permission_classes = [] From 797d006d2358d9d573f59a4fbb5231aedf63f9c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 18 Apr 2024 15:43:25 -0300 Subject: [PATCH 036/351] Adiciona euquipments create Co-authored-by: OscarDeBrito --- api/systems/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api/systems/models.py b/api/systems/models.py index 7cdf2b2f..1d2de4eb 100644 --- a/api/systems/models.py +++ b/api/systems/models.py @@ -6,4 +6,12 @@ class System(models.Model): name = models.CharField(max_length=50) def __str__(self): - return self.name \ No newline at end of file + return self.name + +class AtmosphericDischargeSystem(models.Model): + place = models.ForeignKey(Place, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) + room = models.ForeignKey(Room, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) + system = models.ForeignKey('systems.System', related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) + + class Meta: + db_table = 'atmospheric_discharge_systems' \ No newline at end of file From 2f66fe692e6845772394f7f80cc78e0f9bca5fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 18 Apr 2024 15:48:31 -0300 Subject: [PATCH 037/351] Backend: views equipments Co-authored-by: OscarDeBrito --- api/equipments/__init__.py | 0 api/equipments/admin.py | 3 ++ api/equipments/apps.py | 6 +++ api/equipments/migrations/0001_initial.py | 46 +++++++++++++++++++ .../0002_equipment_placeowner_and_more.py | 23 ++++++++++ api/equipments/migrations/__init__.py | 0 api/equipments/models.py | 18 ++++++++ api/equipments/permissions.py | 8 ++++ api/equipments/serializers.py | 15 ++++++ api/equipments/tests.py | 3 ++ api/equipments/urls.py | 11 +++++ api/equipments/views.py | 37 +++++++++++++++ ..._equipmentphoto_equipmenttype_equipment.py | 38 +++++++++++++++ .../0005_alter_equipment_equipmentphoto.py | 19 ++++++++ ...emove_equipment_equipmentphoto_and_more.py | 37 +++++++++++++++ .../0007_atmosphericdischargesystem.py | 27 +++++++++++ 16 files changed, 291 insertions(+) create mode 100644 api/equipments/__init__.py create mode 100644 api/equipments/admin.py create mode 100644 api/equipments/apps.py create mode 100644 api/equipments/migrations/0001_initial.py create mode 100644 api/equipments/migrations/0002_equipment_placeowner_and_more.py create mode 100644 api/equipments/migrations/__init__.py create mode 100644 api/equipments/models.py create mode 100644 api/equipments/permissions.py create mode 100644 api/equipments/serializers.py create mode 100644 api/equipments/tests.py create mode 100644 api/equipments/urls.py create mode 100644 api/equipments/views.py create mode 100644 api/systems/migrations/0004_equipmentphoto_equipmenttype_equipment.py create mode 100644 api/systems/migrations/0005_alter_equipment_equipmentphoto.py create mode 100644 api/systems/migrations/0006_remove_equipment_equipmentphoto_and_more.py create mode 100644 api/systems/migrations/0007_atmosphericdischargesystem.py diff --git a/api/equipments/__init__.py b/api/equipments/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/equipments/admin.py b/api/equipments/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/api/equipments/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/equipments/apps.py b/api/equipments/apps.py new file mode 100644 index 00000000..0b795db5 --- /dev/null +++ b/api/equipments/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EquipmentsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'equipments' diff --git a/api/equipments/migrations/0001_initial.py b/api/equipments/migrations/0001_initial.py new file mode 100644 index 00000000..70dd3fa6 --- /dev/null +++ b/api/equipments/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2 on 2024-04-18 17:23 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('places', '0019_delete_atmosfericdischargesystem'), + ('systems', '0006_remove_equipment_equipmentphoto_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='EquipmentType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(max_length=50)), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='systems.system')), + ], + ), + migrations.CreateModel( + name='Equipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('photo', models.ImageField(null=True, upload_to='equipment_photos/')), + ('description', models.CharField(max_length=50)), + ('equipmentType', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmenttype')), + ], + ), + migrations.CreateModel( + name='AtmosphericDischargeSystem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='places.place')), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='places.room')), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='systems.system')), + ], + options={ + 'db_table': 'atmospheric_discharge_systems', + }, + ), + ] diff --git a/api/equipments/migrations/0002_equipment_placeowner_and_more.py b/api/equipments/migrations/0002_equipment_placeowner_and_more.py new file mode 100644 index 00000000..34ec64e4 --- /dev/null +++ b/api/equipments/migrations/0002_equipment_placeowner_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2 on 2024-04-18 18:24 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0004_alter_placeowner_user'), + ('equipments', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='equipment', + name='placeOwner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.placeowner'), + ), + migrations.DeleteModel( + name='AtmosphericDischargeSystem', + ), + ] diff --git a/api/equipments/migrations/__init__.py b/api/equipments/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/equipments/models.py b/api/equipments/models.py new file mode 100644 index 00000000..0fbac870 --- /dev/null +++ b/api/equipments/models.py @@ -0,0 +1,18 @@ +from django.db import models +from places.models import Place, Room +from systems.models import System +from users.models import PlaceOwner + +class EquipmentType(models.Model): + type = models.CharField(max_length=50) + system = models.ForeignKey(System, on_delete=models.CASCADE) + def __str__(self): + return self.type + +class Equipment(models.Model): + placeOwner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) + equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) + photo = models.ImageField(null=True, upload_to='equipment_photos/') + description = models.CharField(max_length=50) + + diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py new file mode 100644 index 00000000..324d265e --- /dev/null +++ b/api/equipments/permissions.py @@ -0,0 +1,8 @@ +from rest_framework.permissions import BasePermission + +class OwnerEquip(BasePermission): + def has_object_permission(self, request, view, obj): + if(request.user.placeowner == obj.placeOwner): + return True + else: + return False \ No newline at end of file diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py new file mode 100644 index 00000000..af25e086 --- /dev/null +++ b/api/equipments/serializers.py @@ -0,0 +1,15 @@ +from rest_framework import serializers +from .models import EquipmentType, Equipment + +class EquipmentTypeSerializer(serializers.ModelSerializer): + + class Meta: + model = EquipmentType + fields = '__all__' + +class EquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = Equipment + fields = '__all__' + diff --git a/api/equipments/tests.py b/api/equipments/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/api/equipments/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/equipments/urls.py b/api/equipments/urls.py new file mode 100644 index 00000000..d6efa3e9 --- /dev/null +++ b/api/equipments/urls.py @@ -0,0 +1,11 @@ +from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentList, EquipmentDetail +from django.urls import path + +urlpatterns = [ + + path('equipment-type/', EquipmentTypeList.as_view()), + path('equipment-type//', EquipmentTypeDetail.as_view()), + path('equipments/', EquipmentList.as_view()), + path('equipments//', EquipmentDetail.as_view()) + +] diff --git a/api/equipments/views.py b/api/equipments/views.py new file mode 100644 index 00000000..fff40368 --- /dev/null +++ b/api/equipments/views.py @@ -0,0 +1,37 @@ +from django.shortcuts import render +from rest_framework import viewsets, generics +from rest_framework.response import Response +from .models import EquipmentType, Equipment +from .serializers import EquipmentTypeSerializer, EquipmentSerializer +from .permissions import OwnerEquip +from rest_framework import viewsets, status + +class EquipmentTypeList(generics.ListAPIView): + queryset = EquipmentType.objects.all() + serializer_class = EquipmentTypeSerializer + permission_classes = [] + +class EquipmentTypeDetail(generics.RetrieveAPIView): + queryset = EquipmentType.objects.all() + serializer_class = EquipmentTypeSerializer + permission_classes = [] + +class EquipmentList(generics.ListCreateAPIView): + queryset = Equipment.objects.all() + serializer_class = EquipmentSerializer + permission_classes = [OwnerEquip] + + def create(self, request, *args, **kwargs): + + if(OwnerEquip): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(placeOwner=request.user.placeowner) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + + +class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Equipment.objects.all() + serializer_class = EquipmentSerializer + permission_classes = [OwnerEquip] \ No newline at end of file diff --git a/api/systems/migrations/0004_equipmentphoto_equipmenttype_equipment.py b/api/systems/migrations/0004_equipmentphoto_equipmenttype_equipment.py new file mode 100644 index 00000000..6e60b0fa --- /dev/null +++ b/api/systems/migrations/0004_equipmentphoto_equipmenttype_equipment.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2 on 2024-04-17 18:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0003_atmosphericdischargesystem_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='EquipmentPhoto', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('photo', models.ImageField(null=True, upload_to='equipment_photos/')), + ('description', models.CharField(max_length=50)), + ], + ), + migrations.CreateModel( + name='EquipmentType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(max_length=50)), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='systems.system')), + ], + ), + migrations.CreateModel( + name='Equipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('equipmentPhoto', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='systems.equipmentphoto')), + ('equipmentType', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='systems.equipmenttype')), + ], + ), + ] diff --git a/api/systems/migrations/0005_alter_equipment_equipmentphoto.py b/api/systems/migrations/0005_alter_equipment_equipmentphoto.py new file mode 100644 index 00000000..b0d4bac5 --- /dev/null +++ b/api/systems/migrations/0005_alter_equipment_equipmentphoto.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2 on 2024-04-18 12:50 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0004_equipmentphoto_equipmenttype_equipment'), + ] + + operations = [ + migrations.AlterField( + model_name='equipment', + name='equipmentPhoto', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.equipmentphoto'), + ), + ] diff --git a/api/systems/migrations/0006_remove_equipment_equipmentphoto_and_more.py b/api/systems/migrations/0006_remove_equipment_equipmentphoto_and_more.py new file mode 100644 index 00000000..7ab2c6fb --- /dev/null +++ b/api/systems/migrations/0006_remove_equipment_equipmentphoto_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 4.2 on 2024-04-18 17:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0005_alter_equipment_equipmentphoto'), + ] + + operations = [ + migrations.RemoveField( + model_name='equipment', + name='equipmentPhoto', + ), + migrations.RemoveField( + model_name='equipment', + name='equipmentType', + ), + migrations.RemoveField( + model_name='equipmenttype', + name='system', + ), + migrations.DeleteModel( + name='AtmosphericDischargeSystem', + ), + migrations.DeleteModel( + name='Equipment', + ), + migrations.DeleteModel( + name='EquipmentPhoto', + ), + migrations.DeleteModel( + name='EquipmentType', + ), + ] diff --git a/api/systems/migrations/0007_atmosphericdischargesystem.py b/api/systems/migrations/0007_atmosphericdischargesystem.py new file mode 100644 index 00000000..fa37505a --- /dev/null +++ b/api/systems/migrations/0007_atmosphericdischargesystem.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2 on 2024-04-18 18:24 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0019_delete_atmosfericdischargesystem'), + ('systems', '0006_remove_equipment_equipmentphoto_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='AtmosphericDischargeSystem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='places.place')), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='places.room')), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_systems', to='systems.system')), + ], + options={ + 'db_table': 'atmospheric_discharge_systems', + }, + ), + ] From 3f9ca026f62268678cac4878b5576cb0e061da63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 18 Apr 2024 15:51:41 -0300 Subject: [PATCH 038/351] Backend: views equipments Co-authored-by: OscarDeBrito --- api/places/models.py | 7 +++++++ api/places/views.py | 14 +++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/api/places/models.py b/api/places/models.py index fc671245..ff39e7e7 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -16,6 +16,13 @@ class Room(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) +<<<<<<< HEAD place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE) systems = models.ManyToManyField('systems.System') +======= + place = models.ForeignKey(Place, related_name='rooms', on_delete=models.DO_NOTHING) + systems = models.ManyToManyField('systems.System') + + +>>>>>>> 460be61 (Backend: list places por placesowner) diff --git a/api/places/views.py b/api/places/views.py index dabd9633..c4d70182 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -43,13 +43,20 @@ def create(self, request, *args, **kwargs): def list(self, request, *args, **kwargs): user = request.user +<<<<<<< HEAD place_owner = self.get_place_owner(user) +======= + place_owner = user.placeowner + + place_id = request.data.get('place') +>>>>>>> 460be61 (Backend: list places por placesowner) places = Place.objects.filter(place_owner=place_owner) place_serializer = PlaceSerializer(places, many=True) return Response(place_serializer.data) +<<<<<<< HEAD def retrieve(self, request, pk=None): place_owner_id = request.user.placeowner.id @@ -81,6 +88,8 @@ def destroy(self, request, pk=None): return Response({"message": "Place deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) +======= +>>>>>>> 460be61 (Backend: list places por placesowner) @action(detail=True, methods=['get']) def rooms(self, request, pk=None): @@ -100,6 +109,7 @@ class RoomViewSet(viewsets.ModelViewSet): serializer_class = RoomSerializer permission_classes = [IsAuthenticated] +<<<<<<< HEAD def create(self, request, *args, **kwargs): user = request.user place_owner = user.placeowner @@ -150,4 +160,6 @@ def destroy(self, request, pk=None): room.delete() return Response({"message": "Room deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: - return Response({"message": "You are not the owner of this room"}, status=status.HTTP_403_FORBIDDEN) \ No newline at end of file + return Response({"message": "You are not the owner of this room"}, status=status.HTTP_403_FORBIDDEN) +======= +>>>>>>> 460be61 (Backend: list places por placesowner) From 6c154feb62b62f12e6c072e96233be701d7954c4 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 18 Apr 2024 16:42:43 -0300 Subject: [PATCH 039/351] backend: corrige conflitos --- api/places/models.py | 7 ------- api/places/views.py | 12 ------------ 2 files changed, 19 deletions(-) diff --git a/api/places/models.py b/api/places/models.py index ff39e7e7..fc671245 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -16,13 +16,6 @@ class Room(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) -<<<<<<< HEAD place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE) systems = models.ManyToManyField('systems.System') -======= - place = models.ForeignKey(Place, related_name='rooms', on_delete=models.DO_NOTHING) - systems = models.ManyToManyField('systems.System') - - ->>>>>>> 460be61 (Backend: list places por placesowner) diff --git a/api/places/views.py b/api/places/views.py index c4d70182..5a5aaf5a 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -43,20 +43,13 @@ def create(self, request, *args, **kwargs): def list(self, request, *args, **kwargs): user = request.user -<<<<<<< HEAD place_owner = self.get_place_owner(user) -======= - place_owner = user.placeowner - - place_id = request.data.get('place') ->>>>>>> 460be61 (Backend: list places por placesowner) places = Place.objects.filter(place_owner=place_owner) place_serializer = PlaceSerializer(places, many=True) return Response(place_serializer.data) -<<<<<<< HEAD def retrieve(self, request, pk=None): place_owner_id = request.user.placeowner.id @@ -88,8 +81,6 @@ def destroy(self, request, pk=None): return Response({"message": "Place deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) -======= ->>>>>>> 460be61 (Backend: list places por placesowner) @action(detail=True, methods=['get']) def rooms(self, request, pk=None): @@ -109,7 +100,6 @@ class RoomViewSet(viewsets.ModelViewSet): serializer_class = RoomSerializer permission_classes = [IsAuthenticated] -<<<<<<< HEAD def create(self, request, *args, **kwargs): user = request.user place_owner = user.placeowner @@ -161,5 +151,3 @@ def destroy(self, request, pk=None): return Response({"message": "Room deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: return Response({"message": "You are not the owner of this room"}, status=status.HTTP_403_FORBIDDEN) -======= ->>>>>>> 460be61 (Backend: list places por placesowner) From 9f78ef2a8a168dd949bfa85e7962d46a18981afa Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 18 Apr 2024 17:33:09 -0300 Subject: [PATCH 040/351] FrontEnd: Base da home --- ...0_remove_room_place_remove_room_systems.py | 21 +++ api/places/models.py | 5 +- frontend/sige_ie/lib/home/ui/home.dart | 130 +++++++++++------- ...enciar local.dart => gerenciarLocal.dart} | 0 ...novo local.dart => registerNovoLocal.dart} | 0 5 files changed, 102 insertions(+), 54 deletions(-) create mode 100644 api/places/migrations/0020_remove_room_place_remove_room_systems.py rename frontend/sige_ie/lib/places/register /{ gerenciar local.dart => gerenciarLocal.dart} (100%) rename frontend/sige_ie/lib/places/{register novo local.dart => registerNovoLocal.dart} (100%) diff --git a/api/places/migrations/0020_remove_room_place_remove_room_systems.py b/api/places/migrations/0020_remove_room_place_remove_room_systems.py new file mode 100644 index 00000000..7a2532fc --- /dev/null +++ b/api/places/migrations/0020_remove_room_place_remove_room_systems.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2 on 2024-04-18 19:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0019_delete_atmosfericdischargesystem'), + ] + + operations = [ + migrations.RemoveField( + model_name='room', + name='place', + ), + migrations.RemoveField( + model_name='room', + name='systems', + ), + ] diff --git a/api/places/models.py b/api/places/models.py index fc671245..c4a84948 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -16,6 +16,7 @@ class Room(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) - place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE) - systems = models.ManyToManyField('systems.System') +place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE) +systems = models.ManyToManyField('systems.System') + diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 337128c1..85ecb5b7 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; - -// Importe suas páginas personalizadas import '../../users/feature/profile.dart'; import '../../screens/facilities.dart'; import '../../maps/feature/maps.dart'; @@ -30,9 +28,6 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - ), body: PageView( controller: _pageController, onPageChanged: (index) { @@ -41,59 +36,90 @@ class _HomePageState extends State { }); }, children: [ - Text(''), // Assumindo que esta é sua HomePage temporária + buildHomePage(context), FacilitiesPage(), MapsPage(), ProfilePage(), ], ), - bottomNavigationBar: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - boxShadow: [ - BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), - ], + bottomNavigationBar: buildBottomNavigationBar(), + ); + } + + Widget buildHomePage(BuildContext context) { + return Column( + children: [ + AppBar( + automaticallyImplyLeading: false, + backgroundColor: Color(0xff123c75), + elevation: 0, + ), + Container( + color: Color(0xff123c75), + height: MediaQuery.of(context).size.height * 0.30, + ), + Spacer(), + buildSmallRectangle(context), + buildSmallRectangle(context), + Spacer(), + ], + ); + } + + Widget buildSmallRectangle(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Color(0xff123c75), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), + ], + ), + height: 100, + width: MediaQuery.of(context).size.width * 0.8, + margin: EdgeInsets.symmetric(vertical: 10), + ); + } + + Widget buildBottomNavigationBar() { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), - child: ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - child: BottomNavigationBar( - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.home, size: 35), - label: 'Home', - backgroundColor: Color(0xFFF1F60E)), - BottomNavigationBarItem( - icon: Icon(Icons.build, size: 35), - label: 'Instalações', - backgroundColor: Color(0xFFF1F60E)), - BottomNavigationBarItem( - icon: Icon(Icons.map, size: 35), - label: 'Mapa', - backgroundColor: Color(0xFFF1F60E)), - BottomNavigationBarItem( - icon: Icon(Icons.person, size: 35), - label: 'Perfil', - backgroundColor: Color(0xFFF1F60E)), - ], - currentIndex: _selectedIndex, - selectedItemColor: Color(0xFF123C75), - unselectedItemColor: const Color.fromARGB(255, 145, 142, 142), - onTap: _onItemTapped, - selectedLabelStyle: TextStyle( - color: Color(0xFF123C75), - fontSize: 14, - fontWeight: FontWeight.bold, - ), - unselectedLabelStyle: TextStyle( - fontSize: 12, - ), - ), + boxShadow: [ + BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + child: BottomNavigationBar( + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home, size: 35), + label: 'Home', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.build, size: 35), + label: 'Instalações', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.map, size: 35), + label: 'Mapa', + backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.person, size: 35), + label: 'Perfil', + backgroundColor: Color(0xFFF1F60E)), + ], + currentIndex: _selectedIndex, + selectedItemColor: Color(0xFF123C75), + unselectedItemColor: const Color.fromARGB(255, 145, 142, 142), + onTap: _onItemTapped, ), ), ); diff --git a/frontend/sige_ie/lib/places/register / gerenciar local.dart b/frontend/sige_ie/lib/places/register / gerenciarLocal.dart similarity index 100% rename from frontend/sige_ie/lib/places/register / gerenciar local.dart rename to frontend/sige_ie/lib/places/register / gerenciarLocal.dart diff --git a/frontend/sige_ie/lib/places/register novo local.dart b/frontend/sige_ie/lib/places/registerNovoLocal.dart similarity index 100% rename from frontend/sige_ie/lib/places/register novo local.dart rename to frontend/sige_ie/lib/places/registerNovoLocal.dart From 7b023282aad558c8a2a90589135564f4befeb187 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia Date: Thu, 18 Apr 2024 19:12:30 -0300 Subject: [PATCH 041/351] Atualiza como subir backend em README.md --- README.md | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5ee11bae..c5943af7 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,8 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar Estas etapas são válidas para Linux OS e WSL. #### Como subir o back-end: +##### Pela primeira vez + Primeiramente, interrompa qualquer processo que use o porto 8080, 3306 e 6379. Então atualize o seu sistema: ``` sudo apt-get update @@ -165,27 +167,21 @@ Vá para dentro da pasta raiz `api`: Ativar ambiente: ``` source venv/bin/activate - ``` + ``` -3. Com o ambiente virtual ativado, instale as dependências: +2. Com o ambiente virtual ativado, instale as dependências: ``` pip install -r requirements.txt ``` -4. Com o docker iniciado, crie a imagem do banco de dados pela primeira vez: - - ``` - docker-compose build - ``` - -6. Suba a imagem: +3. Vá para a pasta `api/sigeie` e, com o docker iniciado, crie a imagem do banco de dados pela primeira vez e suba a imagem: ``` - docker-compose up + docker-compose up -d ``` -8. Ainda no diretório raiz `api`, aplique as migrações: +4. Retorne para o diretório raiz `api` e aplique as migrações: ``` python manage.py makemigrations @@ -195,13 +191,33 @@ Vá para dentro da pasta raiz `api`: python3 manage.py migrate ``` -10. Inicie o servidor: +5. Inicie o servidor: ``` python3 manage.py runserver ``` Pronto, o servidor já está rodando com o banco de dados configurado. + +##### Pela segunda vez +Agora que todas as dependências e o banco estão configurados, sempre que for subir o backend: + +1. Inicie o Docker e o container `sigeie`; + +2. Baixa as atualizações (caso haja): + + ``` + git pull + ``` + +3. Atualize as dependências e migrações e inicie o servidor: + +``` + source venv/bin/activate && pip install -r requirements.txt && python3 manage.py makemigrations && python3 manage.py migrate && python3 manage.py runserver +``` + +Isso é tudo, pessoal. + #### Como subir o front-end: Antes de começar, verifique se o Flutter SDK está atualizado e compatível com o projeto. Siga as instruções específicas para sua plataforma (Windows, macOS, Linux) disponíveis na [documentação oficial do Flutter](https://flutter.dev/docs/get-started/install). From 97cf99e2b570656150f057271f78cf8aa70eabb9 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia Date: Thu, 18 Apr 2024 19:49:11 -0300 Subject: [PATCH 042/351] =?UTF-8?q?Remove=20etapa=20n=C3=A3o=20necess?= =?UTF-8?q?=C3=A1ria=20e=20melhora=20como=20subir=20backend=20em=20README.?= =?UTF-8?q?md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c5943af7..7c0c1a94 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Simplificar o cadastro e gerenciamento de informações de instalações elétri | Docker | 25.0.4 | Conteiner e imagem | [Site oficial do Docker](https://docs.docker.com/desktop/install/ubuntu/) | | Redis | 7.2 | Banco de dados cache para sessão | Automática via Docker | | MySQL | 8.1 | Banco de dados | Automática via Docker | -| mysqlclient | 2.2.4 | Cliente para se conectar com MySQL | [Site do Pypi com as configurações](https://pypi.org/project/mysqlclient/) Ou veja na seção "Como subir o back-end" +| Cabeçalhos do Python3 e do MySQL | - | Cabeçalhos de desenvolvimento e bibliotecas | [Site do Pypi com as configurações](https://pypi.org/project/mysqlclient/) Ou veja na seção "Como subir o back-end" @@ -142,12 +142,6 @@ Em seguida, caso já não tenha instalado: sudo apt-get install python3.11-dev default-libmysqlclient-dev build-essential pkg-config ``` - mysqlclient: - - ``` - pip install mysqlclient - ``` - - Instale o virtualenv para criar um ambiente virtual do projeto: Virtualenv: @@ -175,45 +169,45 @@ Vá para dentro da pasta raiz `api`: pip install -r requirements.txt ``` -3. Vá para a pasta `api/sigeie` e, com o docker iniciado, crie a imagem do banco de dados pela primeira vez e suba a imagem: +3. Inicie o Docker, depois vá para o diretório `api/sigeie` e crie a imagem do banco de dados pela primeira vez: ``` docker-compose up -d ``` -4. Retorne para o diretório raiz `api` e aplique as migrações: +4. Ainda no mesmo terminal, retorne para o diretório raiz `api` e aplique as migrações: ``` python manage.py makemigrations ``` ``` - python3 manage.py migrate + python manage.py migrate ``` 5. Inicie o servidor: ``` - python3 manage.py runserver + python manage.py runserver ``` Pronto, o servidor já está rodando com o banco de dados configurado. ##### Pela segunda vez -Agora que todas as dependências e o banco estão configurados, sempre que for subir o backend: -1. Inicie o Docker e o container `sigeie`; - -2. Baixa as atualizações (caso haja): +Garanta que não haja nenhum processo que use o porto 8080, 3306 e 6379. Por fim, com todas as dependências configuradas, basta: + +- Inicar o Docker e o container `sigeie`; +- Baixar as atualizações (caso haja): ``` git pull ``` - -3. Atualize as dependências e migrações e inicie o servidor: + +- Atualizar as dependências, fazer as migrações e iniciar o servidor: ``` - source venv/bin/activate && pip install -r requirements.txt && python3 manage.py makemigrations && python3 manage.py migrate && python3 manage.py runserver + source venv/bin/activate && pip install -r requirements.txt && python manage.py makemigrations && python manage.py migrate && python manage.py runserver ``` Isso é tudo, pessoal. From 8dff4cd892cf83aa9fdd4fa1e5edbe39f77e076a Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 22 Apr 2024 09:12:58 -0300 Subject: [PATCH 043/351] Adiciona model spda --- api/atmosphericdischargesystems/__init__.py | 0 api/atmosphericdischargesystems/admin.py | 3 +++ api/atmosphericdischargesystems/apps.py | 6 +++++ .../migrations/__init__.py | 0 api/atmosphericdischargesystems/models.py | 14 +++++++++++ .../serializers.py | 8 ++++++ api/atmosphericdischargesystems/tests.py | 3 +++ api/atmosphericdischargesystems/views.py | 6 +++++ .../0021_room_place_room_systems.py | 25 +++++++++++++++++++ api/places/models.py | 4 +-- .../0008_delete_atmosphericdischargesystem.py | 16 ++++++++++++ api/systems/models.py | 13 +++------- api/systems/serializers.py | 2 +- api/systems/views.py | 5 +--- 14 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 api/atmosphericdischargesystems/__init__.py create mode 100644 api/atmosphericdischargesystems/admin.py create mode 100644 api/atmosphericdischargesystems/apps.py create mode 100644 api/atmosphericdischargesystems/migrations/__init__.py create mode 100644 api/atmosphericdischargesystems/models.py create mode 100644 api/atmosphericdischargesystems/serializers.py create mode 100644 api/atmosphericdischargesystems/tests.py create mode 100644 api/atmosphericdischargesystems/views.py create mode 100644 api/places/migrations/0021_room_place_room_systems.py create mode 100644 api/systems/migrations/0008_delete_atmosphericdischargesystem.py diff --git a/api/atmosphericdischargesystems/__init__.py b/api/atmosphericdischargesystems/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/atmosphericdischargesystems/admin.py b/api/atmosphericdischargesystems/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/api/atmosphericdischargesystems/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/atmosphericdischargesystems/apps.py b/api/atmosphericdischargesystems/apps.py new file mode 100644 index 00000000..d3aa67f9 --- /dev/null +++ b/api/atmosphericdischargesystems/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AtmosphericdischargesystemsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'atmosphericdischargesystems' diff --git a/api/atmosphericdischargesystems/migrations/__init__.py b/api/atmosphericdischargesystems/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/atmosphericdischargesystems/models.py b/api/atmosphericdischargesystems/models.py new file mode 100644 index 00000000..dbcf41de --- /dev/null +++ b/api/atmosphericdischargesystems/models.py @@ -0,0 +1,14 @@ +from django.db import models +from equipments.models import Equipment +from places.models import Place + + +class AtmosphericDischargeSystem(models.Model): + place = models.ForeignKey(Place, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE,null=True) + equipment = models.ForeignKey(Equipment, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) + + class Meta: + db_table = 'atmospheric_discharge_systems' + + + diff --git a/api/atmosphericdischargesystems/serializers.py b/api/atmosphericdischargesystems/serializers.py new file mode 100644 index 00000000..7b1bcff2 --- /dev/null +++ b/api/atmosphericdischargesystems/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +from .models import AtmosphericDischargeSystem + + +class AtmosphericDischargeSystemSerializer(serializers.ModelSerializer): + class meta: + model = AtmosphericDischargeSystem + fields = ['id','place','equipment'] \ No newline at end of file diff --git a/api/atmosphericdischargesystems/tests.py b/api/atmosphericdischargesystems/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/api/atmosphericdischargesystems/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/atmosphericdischargesystems/views.py b/api/atmosphericdischargesystems/views.py new file mode 100644 index 00000000..2f8ab04f --- /dev/null +++ b/api/atmosphericdischargesystems/views.py @@ -0,0 +1,6 @@ +from django.shortcuts import render + +class AtmosphericDischargeSystem(generics.ListCreateAPIView): + queryset = System.objects.all() + serializer_class = SystemSerializer + permission_classes = [] diff --git a/api/places/migrations/0021_room_place_room_systems.py b/api/places/migrations/0021_room_place_room_systems.py new file mode 100644 index 00000000..4659a37a --- /dev/null +++ b/api/places/migrations/0021_room_place_room_systems.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2 on 2024-04-19 16:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('places', '0020_remove_room_place_remove_room_systems'), + ] + + operations = [ + migrations.AddField( + model_name='room', + name='place', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to='places.place'), + ), + migrations.AddField( + model_name='room', + name='systems', + field=models.ManyToManyField(to='systems.system'), + ), + ] diff --git a/api/places/models.py b/api/places/models.py index c4a84948..09220bbe 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -16,7 +16,7 @@ class Room(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) -place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE) -systems = models.ManyToManyField('systems.System') + place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE, null=True) + systems = models.ManyToManyField('systems.System') diff --git a/api/systems/migrations/0008_delete_atmosphericdischargesystem.py b/api/systems/migrations/0008_delete_atmosphericdischargesystem.py new file mode 100644 index 00000000..d3aed114 --- /dev/null +++ b/api/systems/migrations/0008_delete_atmosphericdischargesystem.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2 on 2024-04-19 16:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0007_atmosphericdischargesystem'), + ] + + operations = [ + migrations.DeleteModel( + name='AtmosphericDischargeSystem', + ), + ] diff --git a/api/systems/models.py b/api/systems/models.py index 1d2de4eb..868caff4 100644 --- a/api/systems/models.py +++ b/api/systems/models.py @@ -1,17 +1,10 @@ from django.db import models -from places.models import Place, Room +from places.models import Place, Room, models + class System(models.Model): name = models.CharField(max_length=50) def __str__(self): - return self.name - -class AtmosphericDischargeSystem(models.Model): - place = models.ForeignKey(Place, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) - room = models.ForeignKey(Room, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) - system = models.ForeignKey('systems.System', related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) - - class Meta: - db_table = 'atmospheric_discharge_systems' \ No newline at end of file + return self.name \ No newline at end of file diff --git a/api/systems/serializers.py b/api/systems/serializers.py index a7e01212..210a7fa2 100644 --- a/api/systems/serializers.py +++ b/api/systems/serializers.py @@ -4,4 +4,4 @@ class SystemSerializer(serializers.ModelSerializer): class Meta: model = System - fields = ['id', 'name'] + fields = ['id', 'name'] \ No newline at end of file diff --git a/api/systems/views.py b/api/systems/views.py index f0677b42..98e8b448 100644 --- a/api/systems/views.py +++ b/api/systems/views.py @@ -10,7 +10,4 @@ class SystemViewList(generics.ListAPIView): class SystemViewDetail(generics.RetrieveAPIView): queryset = System.objects.all() serializer_class = SystemSerializer - permission_classes = [] - - - + permission_classes = [] \ No newline at end of file From 8392488faacb516e03101ddeac343c97fb7fae30 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 22 Apr 2024 09:27:12 -0300 Subject: [PATCH 044/351] Front: Tela home 99% finalizada --- frontend/sige_ie/lib/config/app_styles.dart | 7 ++ frontend/sige_ie/lib/home/ui/home.dart | 101 +++++++++++++++++--- 2 files changed, 97 insertions(+), 11 deletions(-) diff --git a/frontend/sige_ie/lib/config/app_styles.dart b/frontend/sige_ie/lib/config/app_styles.dart index 817868ba..43c9a6e7 100644 --- a/frontend/sige_ie/lib/config/app_styles.dart +++ b/frontend/sige_ie/lib/config/app_styles.dart @@ -22,4 +22,11 @@ class AppButtonStyles { borderRadius: BorderRadius.circular(8), ), ); + static ButtonStyle standardButton = ElevatedButton.styleFrom( + backgroundColor: AppColors.sigeIeYellow, + foregroundColor: AppColors.sigeIeBlue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ); } diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 85ecb5b7..718b446b 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; import '../../users/feature/profile.dart'; import '../../screens/facilities.dart'; import '../../maps/feature/maps.dart'; @@ -39,7 +40,7 @@ class _HomePageState extends State { buildHomePage(context), FacilitiesPage(), MapsPage(), - ProfilePage(), + ProfilePage() ], ), bottomNavigationBar: buildBottomNavigationBar(), @@ -54,19 +55,68 @@ class _HomePageState extends State { backgroundColor: Color(0xff123c75), elevation: 0, ), - Container( - color: Color(0xff123c75), - height: MediaQuery.of(context).size.height * 0.30, + Expanded( + flex: 3, // Proporção ajustada para a imagem e o texto + child: Container( + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), // Bordas arredondadas embaixo + bottomRight: Radius.circular(20), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/1000x1000.png'), + fit: BoxFit.cover, // Imagem cobrindo todo o espaço + ), + ), + ), + ), + Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only( + left: 20), // Padding para alinhamento à esquerda + child: Text( + 'Olá, ', + style: TextStyle( + color: AppColors.sigeIeYellow, + fontSize: 20, + ), + ), + ), + SizedBox(height: 10), + ], + ), + ), + ), + Expanded( + flex: 6, // Restante do conteúdo + child: Column( + children: [ + Spacer(), + buildSmallRectangle(context, 'Registrar novo local', 'Registrar', + () { + print("Registrar novo local clicado"); + }), + buildSmallRectangle(context, 'Gerenciar locais', 'Gerenciar', () { + print("Gerenciar locais clicado"); + }), + Spacer(), + ], + ), ), - Spacer(), - buildSmallRectangle(context), - buildSmallRectangle(context), - Spacer(), ], ); } - Widget buildSmallRectangle(BuildContext context) { + Widget buildSmallRectangle(BuildContext context, String text, + String buttonText, VoidCallback onPress) { return Container( decoration: BoxDecoration( color: Color(0xff123c75), @@ -75,9 +125,38 @@ class _HomePageState extends State { BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), ], ), - height: 100, + height: 135, // Increased height to better accommodate vertical layout width: MediaQuery.of(context).size.width * 0.8, - margin: EdgeInsets.symmetric(vertical: 10), + margin: EdgeInsets.symmetric(vertical: 15), + padding: EdgeInsets.all(20), // Adjusted padding for better spacing + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, // Align children to center horizontally + children: [ + Text( + text, + textAlign: TextAlign.center, // Center-align the text + style: TextStyle( + color: Colors.white, + fontSize: 18, // Increased font size + fontWeight: FontWeight.bold, // Bold font weight + ), + ), + SizedBox(height: 10), // Space between text and button + ElevatedButton( + style: AppButtonStyles.standardButton, + onPressed: onPress, + child: Text( + buttonText, + style: TextStyle( + fontSize: 16, // Font size for button text + fontWeight: FontWeight.bold, // Bold font weight + ), + ), + ) + ], + ), ); } From 87af95fe777e7fe876b37d61cdb50500e46db746 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 22 Apr 2024 12:20:09 -0300 Subject: [PATCH 045/351] Front: Tela de registro visualmente finalizada --- .../register /register_new_location.dart | 1 + .../register/register_new_location.dart | 1 + frontend/sige_ie/lib/home/ui/home.dart | 32 ++-- frontend/sige_ie/lib/main.dart | 8 +- .../manage_locations.dart} | 0 .../sige_ie/lib/places/register/position.dart | 44 +++++ .../register/register_new_location.dart | 151 ++++++++++++++++++ .../sige_ie/lib/places/registerNovoLocal.dart | 0 .../Flutter/GeneratedPluginRegistrant.swift | 2 + frontend/sige_ie/pubspec.lock | 80 ++++++++++ frontend/sige_ie/pubspec.yaml | 3 +- .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 13 files changed, 305 insertions(+), 21 deletions(-) create mode 100644 frontend/sige_ie/lib/frontend/sige_ie/lib/places/register /register_new_location.dart create mode 100644 frontend/sige_ie/lib/frontend/sige_ie/lib/places/register/register_new_location.dart rename frontend/sige_ie/lib/places/{register / gerenciarLocal.dart => manage/manage_locations.dart} (100%) create mode 100644 frontend/sige_ie/lib/places/register/position.dart create mode 100644 frontend/sige_ie/lib/places/register/register_new_location.dart delete mode 100644 frontend/sige_ie/lib/places/registerNovoLocal.dart diff --git a/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register /register_new_location.dart b/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register /register_new_location.dart new file mode 100644 index 00000000..b9f78bb0 --- /dev/null +++ b/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register /register_new_location.dart @@ -0,0 +1 @@ +// TODO Implement this library. \ No newline at end of file diff --git a/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register/register_new_location.dart b/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register/register_new_location.dart new file mode 100644 index 00000000..b9f78bb0 --- /dev/null +++ b/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register/register_new_location.dart @@ -0,0 +1 @@ +// TODO Implement this library. \ No newline at end of file diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 718b446b..be97cb0a 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -56,12 +56,12 @@ class _HomePageState extends State { elevation: 0, ), Expanded( - flex: 3, // Proporção ajustada para a imagem e o texto + flex: 3, child: Container( decoration: BoxDecoration( color: AppColors.sigeIeBlue, borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(20), // Bordas arredondadas embaixo + bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), ), @@ -73,15 +73,14 @@ class _HomePageState extends State { decoration: BoxDecoration( image: DecorationImage( image: AssetImage('assets/1000x1000.png'), - fit: BoxFit.cover, // Imagem cobrindo todo o espaço + fit: BoxFit.cover, ), ), ), ), Container( alignment: Alignment.centerLeft, - padding: EdgeInsets.only( - left: 20), // Padding para alinhamento à esquerda + padding: EdgeInsets.only(left: 20), child: Text( 'Olá, ', style: TextStyle( @@ -96,12 +95,14 @@ class _HomePageState extends State { ), ), Expanded( - flex: 6, // Restante do conteúdo + flex: 6, child: Column( children: [ Spacer(), buildSmallRectangle(context, 'Registrar novo local', 'Registrar', () { + Navigator.of(context).pushNamed( + '/newLocation'); // Usando pushNamed ao invés de pushReplacementNamed print("Registrar novo local clicado"); }), buildSmallRectangle(context, 'Gerenciar locais', 'Gerenciar', () { @@ -125,33 +126,32 @@ class _HomePageState extends State { BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), ], ), - height: 135, // Increased height to better accommodate vertical layout + height: 135, width: MediaQuery.of(context).size.width * 0.8, margin: EdgeInsets.symmetric(vertical: 15), - padding: EdgeInsets.all(20), // Adjusted padding for better spacing + padding: EdgeInsets.all(20), child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.center, // Align children to center horizontally + crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( text, - textAlign: TextAlign.center, // Center-align the text + textAlign: TextAlign.center, style: TextStyle( color: Colors.white, - fontSize: 18, // Increased font size - fontWeight: FontWeight.bold, // Bold font weight + fontSize: 18, + fontWeight: FontWeight.bold, ), ), - SizedBox(height: 10), // Space between text and button + SizedBox(height: 10), ElevatedButton( style: AppButtonStyles.standardButton, onPressed: onPress, child: Text( buttonText, style: TextStyle( - fontSize: 16, // Font size for button text - fontWeight: FontWeight.bold, // Bold font weight + fontSize: 16, + fontWeight: FontWeight.bold, ), ), ) diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 364ea4f0..67db82d7 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -2,17 +2,16 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_screen/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; +import 'package:sige_ie/places/register/register_new_location.dart'; import 'package:sige_ie/screens/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; -import 'package:sige_ie/screens/facilities.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/users/feature/profile.dart'; - import 'core/feature/login/login.dart'; void main() { runApp(MyApp()); } + final cookieJar = CookieJar(); class MyApp extends StatelessWidget { @@ -28,7 +27,8 @@ class MyApp extends StatelessWidget { '/homeScreen': (context) => HomePage(), '/facilitiesScreen': (context) => HomePage(), '/MapsPage': (context) => MapsPage(), - '/profileScreen': (context) => HomePage() + '/profileScreen': (context) => HomePage(), + '/newLocation': (context) => newLocation(), }, ); } diff --git a/frontend/sige_ie/lib/places/register / gerenciarLocal.dart b/frontend/sige_ie/lib/places/manage/manage_locations.dart similarity index 100% rename from frontend/sige_ie/lib/places/register / gerenciarLocal.dart rename to frontend/sige_ie/lib/places/manage/manage_locations.dart diff --git a/frontend/sige_ie/lib/places/register/position.dart b/frontend/sige_ie/lib/places/register/position.dart new file mode 100644 index 00000000..3b31ef78 --- /dev/null +++ b/frontend/sige_ie/lib/places/register/position.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; + +class PositionController extends ChangeNotifier { + double lat = 0.0; + double long = 0.0; + String error = ''; + + PositionController() { + getPosition(); + } + + getPosition() async { + try { + Position position = await _currentPostion(); + lat = position.latitude; + long = position.longitude; + } catch (e) { + error = e.toString(); + } + notifyListeners(); + } +} + +Future _currentPostion() async { + LocationPermission permission; + bool active = await Geolocator.isLocationServiceEnabled(); + if (!active) { + return Future.error("Por favor, autorize o acesso a localização"); + } + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + return Future.error("Por favor, autorize o acesso a localização"); + } + } + + if (permission == LocationPermission.deniedForever) { + return Future.error("Autorize o acesso a localização"); + } + + return await Geolocator.getCurrentPosition(); +} diff --git a/frontend/sige_ie/lib/places/register/register_new_location.dart b/frontend/sige_ie/lib/places/register/register_new_location.dart new file mode 100644 index 00000000..ba446283 --- /dev/null +++ b/frontend/sige_ie/lib/places/register/register_new_location.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class newLocation extends StatefulWidget { + @override + _newLocationState createState() => _newLocationState(); +} + +class _newLocationState extends State { + String coordinates = ''; + final TextEditingController _nameController = TextEditingController(); + + void _getCoordinates() { + setState(() { + coordinates = "Latitude: 40.7128, Longitude: -74.0060"; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('Registrar Novo Local', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + SizedBox(height: 60), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Coordenadas', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black)), + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.grey[200], // Cinza claro + borderRadius: + BorderRadius.circular(10), // Bordas arredondadas + ), + child: Row( + children: [ + Expanded( + child: TextField( + decoration: InputDecoration( + hintText: + 'Clique na lupa para obter as coordenadas', + border: InputBorder.none, // Sem bordas internas + contentPadding: EdgeInsets.symmetric( + horizontal: 10), // Padding interno + ), + controller: + TextEditingController(text: coordinates), + enabled: false, + ), + ), + IconButton( + icon: Icon(Icons.search), + onPressed: _getCoordinates, + ), + ], + ), + ), + SizedBox(height: 40), + Text('Nome do Local', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black)), + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.grey[200], // Cinza claro + borderRadius: + BorderRadius.circular(10), // Bordas arredondadas + ), + child: TextField( + controller: _nameController, + decoration: InputDecoration( + hintText: 'Digite o nome do local', + border: InputBorder.none, // Sem bordas internas + contentPadding: EdgeInsets.symmetric( + horizontal: 10), // Padding interno + ), + ), + ), + SizedBox(height: 60), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: MaterialStateProperty.all( + Size(200, 50)), // Tamanho maior para o botão + textStyle: MaterialStateProperty.all( + TextStyle( + fontSize: 18, // Aumentar o tamanho do texto + fontWeight: + FontWeight.bold, // Deixar o texto em negrito + ), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 8), // Bordas arredondadas com raio de 25 + ), + ), + ), + onPressed: () { + // Lógica de ação + print('Local Registrado: ${_nameController.text}'); + }, + child: Text('Registrar'), + )), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/places/registerNovoLocal.dart b/frontend/sige_ie/lib/places/registerNovoLocal.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift index 15cedd17..e5a3e464 100644 --- a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,12 @@ import FlutterMacOS import Foundation +import geolocator_apple import shared_preferences_foundation import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index ba6e64c5..8e0ec0fd 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.8" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" csslib: dependency: transitive description: @@ -89,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -112,6 +128,54 @@ packages: description: flutter source: sdk version: "0.0.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: "5c23f3613f50586c0bbb2b8f970240ae66b3bd992088cf60dd5ee2e6f7dde3a8" + url: "https://pub.dev" + source: hosted + version: "9.0.2" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: f15d1536cd01b1399578f1da1eb5d566e7a718db6a3648f2c24d2e2f859f0692 + url: "https://pub.dev" + source: hosted + version: "4.5.4" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd + url: "https://pub.dev" + source: hosted + version: "2.3.7" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "009a21c4bc2761e58dccf07c24f219adaebe0ff707abdfd40b0a763d4003fab9" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: "102e7da05b48ca6bf0a5bda0010f886b171d1a08059f01bfe02addd0175ebece" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "4f4218f122a6978d0ad655fa3541eea74c67417440b09f0657238810d5af6bdc" + url: "https://pub.dev" + source: hosted + version: "0.1.3" html: dependency: transitive description: @@ -317,6 +381,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -373,6 +445,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" vector_math: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 2e87ae05..cae53499 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -30,12 +30,13 @@ environment: dependencies: flutter: sdk: flutter + geolocator: ^9.0.2 shared_preferences: ^2.0.15 video_player: ^2.2.14 http: ^0.13.3 cookie_jar: ^4.0.8 http_interceptor: ^1.0.1 - + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 diff --git a/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc b/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc index 8b6d4680..1ece8f25 100644 --- a/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc +++ b/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + GeolocatorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("GeolocatorWindows")); } diff --git a/frontend/sige_ie/windows/flutter/generated_plugins.cmake b/frontend/sige_ie/windows/flutter/generated_plugins.cmake index b93c4c30..7f101a77 100644 --- a/frontend/sige_ie/windows/flutter/generated_plugins.cmake +++ b/frontend/sige_ie/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + geolocator_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From db84a318e08517291ead29db42b8baa40b551282 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 22 Apr 2024 16:00:42 -0300 Subject: [PATCH 046/351] =?UTF-8?q?Resolu=C3=A7=C3=A3o=20de=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/android/app/build.gradle | 9 +- .../android/app/src/main/AndroidManifest.xml | 2 +- frontend/sige_ie/lib/home/ui/home.dart | 1 + frontend/sige_ie/lib/main.dart | 6 +- .../lib/places/register / gerenciarLocal.dart | 0 .../sige_ie/lib/places/register /positon.dart | 44 +++++ .../register /register_new_location.dart | 151 ++++++++++++++++++ .../Flutter/GeneratedPluginRegistrant.swift | 2 + frontend/sige_ie/pubspec.lock | 80 ++++++++++ frontend/sige_ie/pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 12 files changed, 294 insertions(+), 6 deletions(-) delete mode 100644 frontend/sige_ie/lib/places/register / gerenciarLocal.dart create mode 100644 frontend/sige_ie/lib/places/register /positon.dart create mode 100644 frontend/sige_ie/lib/places/register /register_new_location.dart diff --git a/frontend/sige_ie/android/app/build.gradle b/frontend/sige_ie/android/app/build.gradle index 56e85494..6462f3c4 100644 --- a/frontend/sige_ie/android/app/build.gradle +++ b/frontend/sige_ie/android/app/build.gradle @@ -45,8 +45,9 @@ android { applicationId "com.example.sige_ie" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion + minSdkVersion flutter.minSdkVersion // Multidex requer no mínimo Android 4.1 (API level 16) + targetSdkVersion 31 + multiDexEnabled true versionCode flutterVersionCode.toInteger() versionName flutterVersionName } @@ -64,4 +65,6 @@ flutter { source '../..' } -dependencies {} +dependencies { + implementation 'androidx.multidex:multidex:2.0.1' +} diff --git a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml index 05705d3f..3bf349e8 100644 --- a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml +++ b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ { Spacer(), buildSmallRectangle(context, 'Registrar novo local', 'Registrar', () { + Navigator.of(context).pushNamed('/newLocation'); print("Registrar novo local clicado"); }), buildSmallRectangle(context, 'Gerenciar locais', 'Gerenciar', () { diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 364ea4f0..d009069f 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -2,17 +2,18 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_screen/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; +import 'package:sige_ie/places/register%20/register_new_location.dart'; import 'package:sige_ie/screens/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/screens/facilities.dart'; import 'package:sige_ie/maps/feature/maps.dart'; import 'package:sige_ie/users/feature/profile.dart'; - import 'core/feature/login/login.dart'; void main() { runApp(MyApp()); } + final cookieJar = CookieJar(); class MyApp extends StatelessWidget { @@ -28,7 +29,8 @@ class MyApp extends StatelessWidget { '/homeScreen': (context) => HomePage(), '/facilitiesScreen': (context) => HomePage(), '/MapsPage': (context) => MapsPage(), - '/profileScreen': (context) => HomePage() + '/profileScreen': (context) => HomePage(), + '/newLocation': (context) => newLocation(), }, ); } diff --git a/frontend/sige_ie/lib/places/register / gerenciarLocal.dart b/frontend/sige_ie/lib/places/register / gerenciarLocal.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/sige_ie/lib/places/register /positon.dart b/frontend/sige_ie/lib/places/register /positon.dart new file mode 100644 index 00000000..3b31ef78 --- /dev/null +++ b/frontend/sige_ie/lib/places/register /positon.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; + +class PositionController extends ChangeNotifier { + double lat = 0.0; + double long = 0.0; + String error = ''; + + PositionController() { + getPosition(); + } + + getPosition() async { + try { + Position position = await _currentPostion(); + lat = position.latitude; + long = position.longitude; + } catch (e) { + error = e.toString(); + } + notifyListeners(); + } +} + +Future _currentPostion() async { + LocationPermission permission; + bool active = await Geolocator.isLocationServiceEnabled(); + if (!active) { + return Future.error("Por favor, autorize o acesso a localização"); + } + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + return Future.error("Por favor, autorize o acesso a localização"); + } + } + + if (permission == LocationPermission.deniedForever) { + return Future.error("Autorize o acesso a localização"); + } + + return await Geolocator.getCurrentPosition(); +} diff --git a/frontend/sige_ie/lib/places/register /register_new_location.dart b/frontend/sige_ie/lib/places/register /register_new_location.dart new file mode 100644 index 00000000..ba446283 --- /dev/null +++ b/frontend/sige_ie/lib/places/register /register_new_location.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class newLocation extends StatefulWidget { + @override + _newLocationState createState() => _newLocationState(); +} + +class _newLocationState extends State { + String coordinates = ''; + final TextEditingController _nameController = TextEditingController(); + + void _getCoordinates() { + setState(() { + coordinates = "Latitude: 40.7128, Longitude: -74.0060"; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('Registrar Novo Local', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + SizedBox(height: 60), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Coordenadas', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black)), + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.grey[200], // Cinza claro + borderRadius: + BorderRadius.circular(10), // Bordas arredondadas + ), + child: Row( + children: [ + Expanded( + child: TextField( + decoration: InputDecoration( + hintText: + 'Clique na lupa para obter as coordenadas', + border: InputBorder.none, // Sem bordas internas + contentPadding: EdgeInsets.symmetric( + horizontal: 10), // Padding interno + ), + controller: + TextEditingController(text: coordinates), + enabled: false, + ), + ), + IconButton( + icon: Icon(Icons.search), + onPressed: _getCoordinates, + ), + ], + ), + ), + SizedBox(height: 40), + Text('Nome do Local', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black)), + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.grey[200], // Cinza claro + borderRadius: + BorderRadius.circular(10), // Bordas arredondadas + ), + child: TextField( + controller: _nameController, + decoration: InputDecoration( + hintText: 'Digite o nome do local', + border: InputBorder.none, // Sem bordas internas + contentPadding: EdgeInsets.symmetric( + horizontal: 10), // Padding interno + ), + ), + ), + SizedBox(height: 60), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: MaterialStateProperty.all( + Size(200, 50)), // Tamanho maior para o botão + textStyle: MaterialStateProperty.all( + TextStyle( + fontSize: 18, // Aumentar o tamanho do texto + fontWeight: + FontWeight.bold, // Deixar o texto em negrito + ), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 8), // Bordas arredondadas com raio de 25 + ), + ), + ), + onPressed: () { + // Lógica de ação + print('Local Registrado: ${_nameController.text}'); + }, + child: Text('Registrar'), + )), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift index 15cedd17..e5a3e464 100644 --- a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,12 @@ import FlutterMacOS import Foundation +import geolocator_apple import shared_preferences_foundation import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index ba6e64c5..8e0ec0fd 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.8" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" csslib: dependency: transitive description: @@ -89,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -112,6 +128,54 @@ packages: description: flutter source: sdk version: "0.0.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: "5c23f3613f50586c0bbb2b8f970240ae66b3bd992088cf60dd5ee2e6f7dde3a8" + url: "https://pub.dev" + source: hosted + version: "9.0.2" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: f15d1536cd01b1399578f1da1eb5d566e7a718db6a3648f2c24d2e2f859f0692 + url: "https://pub.dev" + source: hosted + version: "4.5.4" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd + url: "https://pub.dev" + source: hosted + version: "2.3.7" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "009a21c4bc2761e58dccf07c24f219adaebe0ff707abdfd40b0a763d4003fab9" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: "102e7da05b48ca6bf0a5bda0010f886b171d1a08059f01bfe02addd0175ebece" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "4f4218f122a6978d0ad655fa3541eea74c67417440b09f0657238810d5af6bdc" + url: "https://pub.dev" + source: hosted + version: "0.1.3" html: dependency: transitive description: @@ -317,6 +381,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -373,6 +445,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" vector_math: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 2e87ae05..87464d7b 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -30,6 +30,7 @@ environment: dependencies: flutter: sdk: flutter + geolocator: ^9.0.2 shared_preferences: ^2.0.15 video_player: ^2.2.14 http: ^0.13.3 diff --git a/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc b/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc index 8b6d4680..1ece8f25 100644 --- a/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc +++ b/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + GeolocatorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("GeolocatorWindows")); } diff --git a/frontend/sige_ie/windows/flutter/generated_plugins.cmake b/frontend/sige_ie/windows/flutter/generated_plugins.cmake index b93c4c30..7f101a77 100644 --- a/frontend/sige_ie/windows/flutter/generated_plugins.cmake +++ b/frontend/sige_ie/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + geolocator_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From 5948c9e4b0e190ae3e383d9e305f850b309f3ed6 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 22 Apr 2024 17:05:22 -0300 Subject: [PATCH 047/351] Telas finalizadas --- .../android/app/src/main/AndroidManifest.xml | 2 + frontend/sige_ie/ios/Runner/Info.plist | 4 + .../register /register_new_location.dart | 1 - .../register/register_new_location.dart | 1 - frontend/sige_ie/lib/main.dart | 4 +- .../sige_ie/lib/places/register /positon.dart | 44 ----- .../register /register_new_location.dart | 151 ------------------ .../register/register_new_location.dart | 54 ++++++- 8 files changed, 57 insertions(+), 204 deletions(-) delete mode 100644 frontend/sige_ie/lib/frontend/sige_ie/lib/places/register /register_new_location.dart delete mode 100644 frontend/sige_ie/lib/frontend/sige_ie/lib/places/register/register_new_location.dart delete mode 100644 frontend/sige_ie/lib/places/register /positon.dart delete mode 100644 frontend/sige_ie/lib/places/register /register_new_location.dart diff --git a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml index 3bf349e8..8f00cf37 100644 --- a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml +++ b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + UIApplicationSupportsIndirectInputEvents + NSLocationWhenInUseUsageDescription + Este aplicativo precisa de acesso à sua localização para funcionar corretamente. + NSLocationAlwaysUsageDescription + Este aplicativo precisa de acesso constante à sua localização para oferecer os melhores serviços. diff --git a/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register /register_new_location.dart b/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register /register_new_location.dart deleted file mode 100644 index b9f78bb0..00000000 --- a/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register /register_new_location.dart +++ /dev/null @@ -1 +0,0 @@ -// TODO Implement this library. \ No newline at end of file diff --git a/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register/register_new_location.dart b/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register/register_new_location.dart deleted file mode 100644 index b9f78bb0..00000000 --- a/frontend/sige_ie/lib/frontend/sige_ie/lib/places/register/register_new_location.dart +++ /dev/null @@ -1 +0,0 @@ -// TODO Implement this library. \ No newline at end of file diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index d009069f..67db82d7 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -2,12 +2,10 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_screen/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; -import 'package:sige_ie/places/register%20/register_new_location.dart'; +import 'package:sige_ie/places/register/register_new_location.dart'; import 'package:sige_ie/screens/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; -import 'package:sige_ie/screens/facilities.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/users/feature/profile.dart'; import 'core/feature/login/login.dart'; void main() { diff --git a/frontend/sige_ie/lib/places/register /positon.dart b/frontend/sige_ie/lib/places/register /positon.dart deleted file mode 100644 index 3b31ef78..00000000 --- a/frontend/sige_ie/lib/places/register /positon.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:geolocator/geolocator.dart'; - -class PositionController extends ChangeNotifier { - double lat = 0.0; - double long = 0.0; - String error = ''; - - PositionController() { - getPosition(); - } - - getPosition() async { - try { - Position position = await _currentPostion(); - lat = position.latitude; - long = position.longitude; - } catch (e) { - error = e.toString(); - } - notifyListeners(); - } -} - -Future _currentPostion() async { - LocationPermission permission; - bool active = await Geolocator.isLocationServiceEnabled(); - if (!active) { - return Future.error("Por favor, autorize o acesso a localização"); - } - permission = await Geolocator.checkPermission(); - if (permission == LocationPermission.denied) { - permission = await Geolocator.requestPermission(); - if (permission == LocationPermission.denied) { - return Future.error("Por favor, autorize o acesso a localização"); - } - } - - if (permission == LocationPermission.deniedForever) { - return Future.error("Autorize o acesso a localização"); - } - - return await Geolocator.getCurrentPosition(); -} diff --git a/frontend/sige_ie/lib/places/register /register_new_location.dart b/frontend/sige_ie/lib/places/register /register_new_location.dart deleted file mode 100644 index ba446283..00000000 --- a/frontend/sige_ie/lib/places/register /register_new_location.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; - -class newLocation extends StatefulWidget { - @override - _newLocationState createState() => _newLocationState(); -} - -class _newLocationState extends State { - String coordinates = ''; - final TextEditingController _nameController = TextEditingController(); - - void _getCoordinates() { - setState(() { - coordinates = "Latitude: 40.7128, Longitude: -74.0060"; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), - ), - body: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - width: double.infinity, - padding: EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text('Registrar Novo Local', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - SizedBox(height: 60), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Coordenadas', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black)), - SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.grey[200], // Cinza claro - borderRadius: - BorderRadius.circular(10), // Bordas arredondadas - ), - child: Row( - children: [ - Expanded( - child: TextField( - decoration: InputDecoration( - hintText: - 'Clique na lupa para obter as coordenadas', - border: InputBorder.none, // Sem bordas internas - contentPadding: EdgeInsets.symmetric( - horizontal: 10), // Padding interno - ), - controller: - TextEditingController(text: coordinates), - enabled: false, - ), - ), - IconButton( - icon: Icon(Icons.search), - onPressed: _getCoordinates, - ), - ], - ), - ), - SizedBox(height: 40), - Text('Nome do Local', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black)), - SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.grey[200], // Cinza claro - borderRadius: - BorderRadius.circular(10), // Bordas arredondadas - ), - child: TextField( - controller: _nameController, - decoration: InputDecoration( - hintText: 'Digite o nome do local', - border: InputBorder.none, // Sem bordas internas - contentPadding: EdgeInsets.symmetric( - horizontal: 10), // Padding interno - ), - ), - ), - SizedBox(height: 60), - Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: MaterialStateProperty.all( - Size(200, 50)), // Tamanho maior para o botão - textStyle: MaterialStateProperty.all( - TextStyle( - fontSize: 18, // Aumentar o tamanho do texto - fontWeight: - FontWeight.bold, // Deixar o texto em negrito - ), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 8), // Bordas arredondadas com raio de 25 - ), - ), - ), - onPressed: () { - // Lógica de ação - print('Local Registrado: ${_nameController.text}'); - }, - child: Text('Registrar'), - )), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/places/register/register_new_location.dart b/frontend/sige_ie/lib/places/register/register_new_location.dart index ba446283..d67c5f71 100644 --- a/frontend/sige_ie/lib/places/register/register_new_location.dart +++ b/frontend/sige_ie/lib/places/register/register_new_location.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import '../register/position.dart'; class newLocation extends StatefulWidget { @override @@ -8,11 +9,33 @@ class newLocation extends StatefulWidget { class _newLocationState extends State { String coordinates = ''; + bool Coord = false; final TextEditingController _nameController = TextEditingController(); + late PositionController positionController; + + @override + void initState() { + super.initState(); + positionController = PositionController(); + } void _getCoordinates() { - setState(() { - coordinates = "Latitude: 40.7128, Longitude: -74.0060"; + positionController.getPosition().then((_) { + setState(() { + if (positionController.error.isEmpty) { + coordinates = + "Latitude: ${positionController.lat}, Longitude: ${positionController.long}"; + Coord = true; + } else { + coordinates = "Erro: ${positionController.error}"; + Coord = false; + } + }); + }).catchError((e) { + setState(() { + coordinates = "Erro ao obter localização: $e"; + Coord = false; + }); }); } @@ -135,8 +158,31 @@ class _newLocationState extends State { ), ), onPressed: () { - // Lógica de ação - print('Local Registrado: ${_nameController.text}'); + if (Coord && _nameController.text.trim().isNotEmpty) { + // Código para registrar o local + print('Local Registrado: ${_nameController.text}'); + Navigator.of(context).pushNamed('?'); + } else if (_nameController.text.trim().isEmpty) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("Erro"), + content: Text( + "Por favor, insira um nome para o local"), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context) + .pop(); // Fecha o dialogo + }, + ), + ], + ); + }, + ); + } }, child: Text('Registrar'), )), From 100a2be8de9cb5fdc6fa0039d57530b74eaeb8d9 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Tue, 23 Apr 2024 16:57:41 -0300 Subject: [PATCH 048/351] =?UTF-8?q?backend:=20refatora=20descargas=20atmos?= =?UTF-8?q?f=C3=A9ricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__init__.py | 0 .../admin.py | 0 .../apps.py | 2 +- .../migrations/0001_initial.py | 28 +++++++++++++++++++ .../migrations/__init__.py | 0 .../models.py | 9 +++--- api/atmosphericdischarges/serializers.py | 2 ++ .../tests.py | 0 api/atmosphericdischarges/views.py | 1 + .../serializers.py | 8 ------ api/atmosphericdischargesystems/views.py | 6 ---- api/sigeie/settings.py | 3 +- 12 files changed, 38 insertions(+), 21 deletions(-) rename api/{atmosphericdischargesystems => atmosphericdischarges}/__init__.py (100%) rename api/{atmosphericdischargesystems => atmosphericdischarges}/admin.py (100%) rename api/{atmosphericdischargesystems => atmosphericdischarges}/apps.py (77%) create mode 100644 api/atmosphericdischarges/migrations/0001_initial.py rename api/{atmosphericdischargesystems => atmosphericdischarges}/migrations/__init__.py (100%) rename api/{atmosphericdischargesystems => atmosphericdischarges}/models.py (54%) create mode 100644 api/atmosphericdischarges/serializers.py rename api/{atmosphericdischargesystems => atmosphericdischarges}/tests.py (100%) create mode 100644 api/atmosphericdischarges/views.py delete mode 100644 api/atmosphericdischargesystems/serializers.py delete mode 100644 api/atmosphericdischargesystems/views.py diff --git a/api/atmosphericdischargesystems/__init__.py b/api/atmosphericdischarges/__init__.py similarity index 100% rename from api/atmosphericdischargesystems/__init__.py rename to api/atmosphericdischarges/__init__.py diff --git a/api/atmosphericdischargesystems/admin.py b/api/atmosphericdischarges/admin.py similarity index 100% rename from api/atmosphericdischargesystems/admin.py rename to api/atmosphericdischarges/admin.py diff --git a/api/atmosphericdischargesystems/apps.py b/api/atmosphericdischarges/apps.py similarity index 77% rename from api/atmosphericdischargesystems/apps.py rename to api/atmosphericdischarges/apps.py index d3aa67f9..c8c21ff8 100644 --- a/api/atmosphericdischargesystems/apps.py +++ b/api/atmosphericdischarges/apps.py @@ -3,4 +3,4 @@ class AtmosphericdischargesystemsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'atmosphericdischargesystems' + name = 'atmosphericdischarges' diff --git a/api/atmosphericdischarges/migrations/0001_initial.py b/api/atmosphericdischarges/migrations/0001_initial.py new file mode 100644 index 00000000..e568d909 --- /dev/null +++ b/api/atmosphericdischarges/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2 on 2024-04-23 19:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('places', '0021_room_place_room_systems'), + ('equipments', '0002_equipment_placeowner_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='AtmosphericDischarge', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharges', to='equipments.equipment')), + ('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharges', to='places.place')), + ], + options={ + 'db_table': 'atmospheric_discharges', + }, + ), + ] diff --git a/api/atmosphericdischargesystems/migrations/__init__.py b/api/atmosphericdischarges/migrations/__init__.py similarity index 100% rename from api/atmosphericdischargesystems/migrations/__init__.py rename to api/atmosphericdischarges/migrations/__init__.py diff --git a/api/atmosphericdischargesystems/models.py b/api/atmosphericdischarges/models.py similarity index 54% rename from api/atmosphericdischargesystems/models.py rename to api/atmosphericdischarges/models.py index dbcf41de..aaf55598 100644 --- a/api/atmosphericdischargesystems/models.py +++ b/api/atmosphericdischarges/models.py @@ -2,13 +2,12 @@ from equipments.models import Equipment from places.models import Place - -class AtmosphericDischargeSystem(models.Model): - place = models.ForeignKey(Place, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE,null=True) - equipment = models.ForeignKey(Equipment, related_name='atmospheric_discharge_systems', on_delete=models.CASCADE) +class AtmosphericDischarge(models.Model): + place = models.ForeignKey(Place, related_name='atmospheric_discharges', on_delete=models.CASCADE,null=True) + equipment = models.ForeignKey(Equipment, related_name='atmospheric_discharges', on_delete=models.CASCADE) class Meta: - db_table = 'atmospheric_discharge_systems' + db_table = 'atmospheric_discharges' diff --git a/api/atmosphericdischarges/serializers.py b/api/atmosphericdischarges/serializers.py new file mode 100644 index 00000000..ebda1a86 --- /dev/null +++ b/api/atmosphericdischarges/serializers.py @@ -0,0 +1,2 @@ +from rest_framework import serializers +from .models import AtmosphericDischargeSystem \ No newline at end of file diff --git a/api/atmosphericdischargesystems/tests.py b/api/atmosphericdischarges/tests.py similarity index 100% rename from api/atmosphericdischargesystems/tests.py rename to api/atmosphericdischarges/tests.py diff --git a/api/atmosphericdischarges/views.py b/api/atmosphericdischarges/views.py new file mode 100644 index 00000000..2536b376 --- /dev/null +++ b/api/atmosphericdischarges/views.py @@ -0,0 +1 @@ +from django.shortcuts import render diff --git a/api/atmosphericdischargesystems/serializers.py b/api/atmosphericdischargesystems/serializers.py deleted file mode 100644 index 7b1bcff2..00000000 --- a/api/atmosphericdischargesystems/serializers.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import serializers -from .models import AtmosphericDischargeSystem - - -class AtmosphericDischargeSystemSerializer(serializers.ModelSerializer): - class meta: - model = AtmosphericDischargeSystem - fields = ['id','place','equipment'] \ No newline at end of file diff --git a/api/atmosphericdischargesystems/views.py b/api/atmosphericdischargesystems/views.py deleted file mode 100644 index 2f8ab04f..00000000 --- a/api/atmosphericdischargesystems/views.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.shortcuts import render - -class AtmosphericDischargeSystem(generics.ListCreateAPIView): - queryset = System.objects.all() - serializer_class = SystemSerializer - permission_classes = [] diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index 4cd19ec4..283baea0 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -20,7 +20,8 @@ 'users', 'places', 'systems', - 'equipments' + 'equipments', + 'atmosphericdischarges' ] MIDDLEWARE = [ From b948a0a2346c1d0975653fef029c6571def392dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Tue, 23 Apr 2024 17:12:49 -0300 Subject: [PATCH 049/351] =?UTF-8?q?backend:=20testes=20descargas=20el?= =?UTF-8?q?=C3=A9tricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0002_alter_atmosphericdischarge_place.py | 20 +++++++++++++++++++ ...3_remove_atmosphericdischarge_equipment.py | 17 ++++++++++++++++ api/atmosphericdischarges/models.py | 5 ++--- .../0003_equipment_atmosphericdischarge.py | 20 +++++++++++++++++++ api/equipments/models.py | 2 ++ 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 api/atmosphericdischarges/migrations/0002_alter_atmosphericdischarge_place.py create mode 100644 api/atmosphericdischarges/migrations/0003_remove_atmosphericdischarge_equipment.py create mode 100644 api/equipments/migrations/0003_equipment_atmosphericdischarge.py diff --git a/api/atmosphericdischarges/migrations/0002_alter_atmosphericdischarge_place.py b/api/atmosphericdischarges/migrations/0002_alter_atmosphericdischarge_place.py new file mode 100644 index 00000000..fb196547 --- /dev/null +++ b/api/atmosphericdischarges/migrations/0002_alter_atmosphericdischarge_place.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-23 20:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0021_room_place_room_systems'), + ('atmosphericdischarges', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosphericdischarge', + name='place', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharges', to='places.place'), + ), + ] diff --git a/api/atmosphericdischarges/migrations/0003_remove_atmosphericdischarge_equipment.py b/api/atmosphericdischarges/migrations/0003_remove_atmosphericdischarge_equipment.py new file mode 100644 index 00000000..3a2be3aa --- /dev/null +++ b/api/atmosphericdischarges/migrations/0003_remove_atmosphericdischarge_equipment.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2 on 2024-04-23 20:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('atmosphericdischarges', '0002_alter_atmosphericdischarge_place'), + ] + + operations = [ + migrations.RemoveField( + model_name='atmosphericdischarge', + name='equipment', + ), + ] diff --git a/api/atmosphericdischarges/models.py b/api/atmosphericdischarges/models.py index aaf55598..559711f8 100644 --- a/api/atmosphericdischarges/models.py +++ b/api/atmosphericdischarges/models.py @@ -1,10 +1,9 @@ from django.db import models -from equipments.models import Equipment from places.models import Place class AtmosphericDischarge(models.Model): - place = models.ForeignKey(Place, related_name='atmospheric_discharges', on_delete=models.CASCADE,null=True) - equipment = models.ForeignKey(Equipment, related_name='atmospheric_discharges', on_delete=models.CASCADE) + place = models.OneToOneField(Place, related_name='atmospheric_discharges', on_delete=models.CASCADE,null=True) + class Meta: db_table = 'atmospheric_discharges' diff --git a/api/equipments/migrations/0003_equipment_atmosphericdischarge.py b/api/equipments/migrations/0003_equipment_atmosphericdischarge.py new file mode 100644 index 00000000..bf597d6f --- /dev/null +++ b/api/equipments/migrations/0003_equipment_atmosphericdischarge.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-23 20:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('atmosphericdischarges', '0003_remove_atmosphericdischarge_equipment'), + ('equipments', '0002_equipment_placeowner_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='equipment', + name='atmosphericDischarge', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='atmosphericdischarges.atmosphericdischarge'), + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index 0fbac870..b28c090d 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -2,6 +2,7 @@ from places.models import Place, Room from systems.models import System from users.models import PlaceOwner +from atmosphericdischarges.models import AtmosphericDischarge class EquipmentType(models.Model): type = models.CharField(max_length=50) @@ -14,5 +15,6 @@ class Equipment(models.Model): equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) + atmosphericDischarge = models.ForeignKey(AtmosphericDischarge, on_delete=models.CASCADE, null=True) From d22e3263184641b47a7bd609a0aaafc35d19e3a5 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 24 Apr 2024 15:32:53 -0300 Subject: [PATCH 050/351] =?UTF-8?q?backend:=20corrige=20model=20de=20equip?= =?UTF-8?q?amentos=20de=20descargas=20atmosf=C3=A9ricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/atmosphericdischarges/__init__.py | 0 api/atmosphericdischarges/admin.py | 3 -- api/atmosphericdischarges/apps.py | 6 ---- .../migrations/0001_initial.py | 28 ------------------- .../0002_alter_atmosphericdischarge_place.py | 20 ------------- ...3_remove_atmosphericdischarge_equipment.py | 17 ----------- .../migrations/__init__.py | 0 api/atmosphericdischarges/models.py | 12 -------- api/atmosphericdischarges/serializers.py | 2 -- api/atmosphericdischarges/tests.py | 3 -- api/atmosphericdischarges/views.py | 1 - .../0003_atmosphericdischargeequipment.py | 23 +++++++++++++++ .../0003_equipment_atmosphericdischarge.py | 20 ------------- api/equipments/models.py | 9 +++--- api/places/models.py | 2 -- api/sigeie/settings.py | 3 +- api/systems/models.py | 2 +- 17 files changed, 30 insertions(+), 121 deletions(-) delete mode 100644 api/atmosphericdischarges/__init__.py delete mode 100644 api/atmosphericdischarges/admin.py delete mode 100644 api/atmosphericdischarges/apps.py delete mode 100644 api/atmosphericdischarges/migrations/0001_initial.py delete mode 100644 api/atmosphericdischarges/migrations/0002_alter_atmosphericdischarge_place.py delete mode 100644 api/atmosphericdischarges/migrations/0003_remove_atmosphericdischarge_equipment.py delete mode 100644 api/atmosphericdischarges/migrations/__init__.py delete mode 100644 api/atmosphericdischarges/models.py delete mode 100644 api/atmosphericdischarges/serializers.py delete mode 100644 api/atmosphericdischarges/tests.py delete mode 100644 api/atmosphericdischarges/views.py create mode 100644 api/equipments/migrations/0003_atmosphericdischargeequipment.py delete mode 100644 api/equipments/migrations/0003_equipment_atmosphericdischarge.py diff --git a/api/atmosphericdischarges/__init__.py b/api/atmosphericdischarges/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/api/atmosphericdischarges/admin.py b/api/atmosphericdischarges/admin.py deleted file mode 100644 index 8c38f3f3..00000000 --- a/api/atmosphericdischarges/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/api/atmosphericdischarges/apps.py b/api/atmosphericdischarges/apps.py deleted file mode 100644 index c8c21ff8..00000000 --- a/api/atmosphericdischarges/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class AtmosphericdischargesystemsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'atmosphericdischarges' diff --git a/api/atmosphericdischarges/migrations/0001_initial.py b/api/atmosphericdischarges/migrations/0001_initial.py deleted file mode 100644 index e568d909..00000000 --- a/api/atmosphericdischarges/migrations/0001_initial.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 4.2 on 2024-04-23 19:56 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('places', '0021_room_place_room_systems'), - ('equipments', '0002_equipment_placeowner_and_more'), - ] - - operations = [ - migrations.CreateModel( - name='AtmosphericDischarge', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharges', to='equipments.equipment')), - ('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharges', to='places.place')), - ], - options={ - 'db_table': 'atmospheric_discharges', - }, - ), - ] diff --git a/api/atmosphericdischarges/migrations/0002_alter_atmosphericdischarge_place.py b/api/atmosphericdischarges/migrations/0002_alter_atmosphericdischarge_place.py deleted file mode 100644 index fb196547..00000000 --- a/api/atmosphericdischarges/migrations/0002_alter_atmosphericdischarge_place.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.2 on 2024-04-23 20:02 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('places', '0021_room_place_room_systems'), - ('atmosphericdischarges', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='atmosphericdischarge', - name='place', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharges', to='places.place'), - ), - ] diff --git a/api/atmosphericdischarges/migrations/0003_remove_atmosphericdischarge_equipment.py b/api/atmosphericdischarges/migrations/0003_remove_atmosphericdischarge_equipment.py deleted file mode 100644 index 3a2be3aa..00000000 --- a/api/atmosphericdischarges/migrations/0003_remove_atmosphericdischarge_equipment.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2 on 2024-04-23 20:10 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('atmosphericdischarges', '0002_alter_atmosphericdischarge_place'), - ] - - operations = [ - migrations.RemoveField( - model_name='atmosphericdischarge', - name='equipment', - ), - ] diff --git a/api/atmosphericdischarges/migrations/__init__.py b/api/atmosphericdischarges/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/api/atmosphericdischarges/models.py b/api/atmosphericdischarges/models.py deleted file mode 100644 index 559711f8..00000000 --- a/api/atmosphericdischarges/models.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.db import models -from places.models import Place - -class AtmosphericDischarge(models.Model): - place = models.OneToOneField(Place, related_name='atmospheric_discharges', on_delete=models.CASCADE,null=True) - - - class Meta: - db_table = 'atmospheric_discharges' - - - diff --git a/api/atmosphericdischarges/serializers.py b/api/atmosphericdischarges/serializers.py deleted file mode 100644 index ebda1a86..00000000 --- a/api/atmosphericdischarges/serializers.py +++ /dev/null @@ -1,2 +0,0 @@ -from rest_framework import serializers -from .models import AtmosphericDischargeSystem \ No newline at end of file diff --git a/api/atmosphericdischarges/tests.py b/api/atmosphericdischarges/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/api/atmosphericdischarges/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/api/atmosphericdischarges/views.py b/api/atmosphericdischarges/views.py deleted file mode 100644 index 2536b376..00000000 --- a/api/atmosphericdischarges/views.py +++ /dev/null @@ -1 +0,0 @@ -from django.shortcuts import render diff --git a/api/equipments/migrations/0003_atmosphericdischargeequipment.py b/api/equipments/migrations/0003_atmosphericdischargeequipment.py new file mode 100644 index 00000000..434456c4 --- /dev/null +++ b/api/equipments/migrations/0003_atmosphericdischargeequipment.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2 on 2024-04-24 18:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0021_room_place_room_systems'), + ('equipments', '0002_equipment_placeowner_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='AtmosphericDischargeEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('equipment', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipment')), + ('room', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.room')), + ], + ), + ] diff --git a/api/equipments/migrations/0003_equipment_atmosphericdischarge.py b/api/equipments/migrations/0003_equipment_atmosphericdischarge.py deleted file mode 100644 index bf597d6f..00000000 --- a/api/equipments/migrations/0003_equipment_atmosphericdischarge.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.2 on 2024-04-23 20:10 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('atmosphericdischarges', '0003_remove_atmosphericdischarge_equipment'), - ('equipments', '0002_equipment_placeowner_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='equipment', - name='atmosphericDischarge', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='atmosphericdischarges.atmosphericdischarge'), - ), - ] diff --git a/api/equipments/models.py b/api/equipments/models.py index b28c090d..7e3f4f40 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -1,8 +1,7 @@ from django.db import models -from places.models import Place, Room +from places.models import Room from systems.models import System from users.models import PlaceOwner -from atmosphericdischarges.models import AtmosphericDischarge class EquipmentType(models.Model): type = models.CharField(max_length=50) @@ -15,6 +14,8 @@ class Equipment(models.Model): equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) - atmosphericDischarge = models.ForeignKey(AtmosphericDischarge, on_delete=models.CASCADE, null=True) - +#Corrige este model +class AtmosphericDischargeEquipment(models.Model): + equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) + room = models.ForeignKey(Room, on_delete=models.CASCADE, null=True) diff --git a/api/places/models.py b/api/places/models.py index 09220bbe..04b790c8 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -1,8 +1,6 @@ from django.db import models from django.core.validators import MinValueValidator from users.models import PlaceOwner -from django.contrib.auth.models import Permission, Group -from django.contrib.contenttypes.models import ContentType class Place(models.Model): diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index 283baea0..4cd19ec4 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -20,8 +20,7 @@ 'users', 'places', 'systems', - 'equipments', - 'atmosphericdischarges' + 'equipments' ] MIDDLEWARE = [ diff --git a/api/systems/models.py b/api/systems/models.py index 868caff4..878d054f 100644 --- a/api/systems/models.py +++ b/api/systems/models.py @@ -1,5 +1,5 @@ from django.db import models -from places.models import Place, Room, models +from places.models import models class System(models.Model): From 03449c498b7f6409dc60759c5b2804a4d03f571e Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 24 Apr 2024 15:56:57 -0300 Subject: [PATCH 051/351] backend: refatora nome Equipment para EquipmentDetail Co-authored-by: OscarDeBrito --- .../0004_rename_equipment_equipmentdetail.py | 18 +++++++++++++++ api/equipments/models.py | 5 ++--- api/equipments/serializers.py | 6 ++--- api/equipments/urls.py | 4 ++-- api/equipments/views.py | 22 +++++++++---------- 5 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 api/equipments/migrations/0004_rename_equipment_equipmentdetail.py diff --git a/api/equipments/migrations/0004_rename_equipment_equipmentdetail.py b/api/equipments/migrations/0004_rename_equipment_equipmentdetail.py new file mode 100644 index 00000000..b7cb03fe --- /dev/null +++ b/api/equipments/migrations/0004_rename_equipment_equipmentdetail.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2024-04-24 18:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0004_alter_placeowner_user'), + ('equipments', '0003_atmosphericdischargeequipment'), + ] + + operations = [ + migrations.RenameModel( + old_name='Equipment', + new_name='EquipmentDetail', + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index 7e3f4f40..a8938ae5 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -9,13 +9,12 @@ class EquipmentType(models.Model): def __str__(self): return self.type -class Equipment(models.Model): +class EquipmentDetail(models.Model): placeOwner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) -#Corrige este model class AtmosphericDischargeEquipment(models.Model): - equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) + equipment = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) room = models.ForeignKey(Room, on_delete=models.CASCADE, null=True) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index af25e086..16bb0d2b 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import EquipmentType, Equipment +from .models import EquipmentType, EquipmentDetail class EquipmentTypeSerializer(serializers.ModelSerializer): @@ -7,9 +7,9 @@ class Meta: model = EquipmentType fields = '__all__' -class EquipmentSerializer(serializers.ModelSerializer): +class EquipmentDetailSerializer(serializers.ModelSerializer): class Meta: - model = Equipment + model = EquipmentDetail fields = '__all__' diff --git a/api/equipments/urls.py b/api/equipments/urls.py index d6efa3e9..2f750cf3 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -1,11 +1,11 @@ -from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentList, EquipmentDetail +from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail from django.urls import path urlpatterns = [ path('equipment-type/', EquipmentTypeList.as_view()), path('equipment-type//', EquipmentTypeDetail.as_view()), - path('equipments/', EquipmentList.as_view()), + path('equipments/', EquipmentDetailList.as_view()), path('equipments//', EquipmentDetail.as_view()) ] diff --git a/api/equipments/views.py b/api/equipments/views.py index fff40368..68906dde 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -1,10 +1,9 @@ -from django.shortcuts import render -from rest_framework import viewsets, generics +from rest_framework import generics from rest_framework.response import Response -from .models import EquipmentType, Equipment -from .serializers import EquipmentTypeSerializer, EquipmentSerializer +from .models import EquipmentType, EquipmentDetail +from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer from .permissions import OwnerEquip -from rest_framework import viewsets, status +from rest_framework import status class EquipmentTypeList(generics.ListAPIView): queryset = EquipmentType.objects.all() @@ -16,22 +15,21 @@ class EquipmentTypeDetail(generics.RetrieveAPIView): serializer_class = EquipmentTypeSerializer permission_classes = [] -class EquipmentList(generics.ListCreateAPIView): - queryset = Equipment.objects.all() - serializer_class = EquipmentSerializer +class EquipmentDetailList(generics.ListCreateAPIView): + queryset = EquipmentDetail.objects.all() + serializer_class = EquipmentDetailSerializer permission_classes = [OwnerEquip] def create(self, request, *args, **kwargs): - + if(OwnerEquip): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(placeOwner=request.user.placeowner) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) - class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = Equipment.objects.all() - serializer_class = EquipmentSerializer + queryset = EquipmentDetail.objects.all() + serializer_class = EquipmentDetailSerializer permission_classes = [OwnerEquip] \ No newline at end of file From eb3a8261c97309c4276642b796f42a7a60b3f921 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 24 Apr 2024 16:30:36 -0300 Subject: [PATCH 052/351] backend: refatore nome de Room para Area --- ...move_atmosphericdischargeequipment_room.py | 17 ++++++ ...0006_atmosphericdischargeequipment_area.py | 20 +++++++ ...phericdischargeequipment_table_and_more.py | 25 +++++++++ ...phericdischargeequipment_table_and_more.py | 25 +++++++++ api/equipments/models.py | 13 ++++- .../migrations/0022_area_delete_room.py | 30 ++++++++++ api/places/models.py | 4 +- api/places/permissions.py | 10 +--- api/places/serializers.py | 6 +- api/places/urls.py | 4 +- api/places/views.py | 55 +++++++++---------- 11 files changed, 163 insertions(+), 46 deletions(-) create mode 100644 api/equipments/migrations/0005_remove_atmosphericdischargeequipment_room.py create mode 100644 api/equipments/migrations/0006_atmosphericdischargeequipment_area.py create mode 100644 api/equipments/migrations/0007_alter_atmosphericdischargeequipment_table_and_more.py create mode 100644 api/equipments/migrations/0008_alter_atmosphericdischargeequipment_table_and_more.py create mode 100644 api/places/migrations/0022_area_delete_room.py diff --git a/api/equipments/migrations/0005_remove_atmosphericdischargeequipment_room.py b/api/equipments/migrations/0005_remove_atmosphericdischargeequipment_room.py new file mode 100644 index 00000000..f1047511 --- /dev/null +++ b/api/equipments/migrations/0005_remove_atmosphericdischargeequipment_room.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2 on 2024-04-24 19:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0004_rename_equipment_equipmentdetail'), + ] + + operations = [ + migrations.RemoveField( + model_name='atmosphericdischargeequipment', + name='room', + ), + ] diff --git a/api/equipments/migrations/0006_atmosphericdischargeequipment_area.py b/api/equipments/migrations/0006_atmosphericdischargeequipment_area.py new file mode 100644 index 00000000..bf889635 --- /dev/null +++ b/api/equipments/migrations/0006_atmosphericdischargeequipment_area.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-24 19:22 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0022_area_delete_room'), + ('equipments', '0005_remove_atmosphericdischargeequipment_room'), + ] + + operations = [ + migrations.AddField( + model_name='atmosphericdischargeequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + ] diff --git a/api/equipments/migrations/0007_alter_atmosphericdischargeequipment_table_and_more.py b/api/equipments/migrations/0007_alter_atmosphericdischargeequipment_table_and_more.py new file mode 100644 index 00000000..8ab0948b --- /dev/null +++ b/api/equipments/migrations/0007_alter_atmosphericdischargeequipment_table_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2 on 2024-04-24 19:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0006_atmosphericdischargeequipment_area'), + ] + + operations = [ + migrations.AlterModelTable( + name='atmosphericdischargeequipment', + table='atmospheric_discharge_equipments', + ), + migrations.AlterModelTable( + name='equipmentdetail', + table='equipment_details', + ), + migrations.AlterModelTable( + name='equipmenttype', + table='equipment_types', + ), + ] diff --git a/api/equipments/migrations/0008_alter_atmosphericdischargeequipment_table_and_more.py b/api/equipments/migrations/0008_alter_atmosphericdischargeequipment_table_and_more.py new file mode 100644 index 00000000..d45e74ad --- /dev/null +++ b/api/equipments/migrations/0008_alter_atmosphericdischargeequipment_table_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2 on 2024-04-24 19:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0007_alter_atmosphericdischargeequipment_table_and_more'), + ] + + operations = [ + migrations.AlterModelTable( + name='atmosphericdischargeequipment', + table='equipments_atmospheric_discharge_equipments', + ), + migrations.AlterModelTable( + name='equipmentdetail', + table='equipments_equipment_details', + ), + migrations.AlterModelTable( + name='equipmenttype', + table='equipments_equipment_types', + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index a8938ae5..c853fd14 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -1,5 +1,5 @@ from django.db import models -from places.models import Room +from places.models import Area from systems.models import System from users.models import PlaceOwner @@ -9,12 +9,21 @@ class EquipmentType(models.Model): def __str__(self): return self.type + class Meta: + db_table = 'equipments_equipment_types' + class EquipmentDetail(models.Model): placeOwner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) + class Meta: + db_table = 'equipments_equipment_details' + class AtmosphericDischargeEquipment(models.Model): equipment = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - room = models.ForeignKey(Room, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + + class Meta: + db_table = 'equipments_atmospheric_discharge_equipments' diff --git a/api/places/migrations/0022_area_delete_room.py b/api/places/migrations/0022_area_delete_room.py new file mode 100644 index 00000000..f55fe63a --- /dev/null +++ b/api/places/migrations/0022_area_delete_room.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2 on 2024-04-24 19:22 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('equipments', '0005_remove_atmosphericdischargeequipment_room'), + ('places', '0021_room_place_room_systems'), + ] + + operations = [ + migrations.CreateModel( + name='Area', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('floor', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])), + ('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='areas', to='places.place')), + ('systems', models.ManyToManyField(to='systems.system')), + ], + ), + migrations.DeleteModel( + name='Room', + ), + ] diff --git a/api/places/models.py b/api/places/models.py index 04b790c8..686a93c4 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -10,11 +10,11 @@ class Place(models.Model): def __str__(self): return self.name -class Room(models.Model): +class Area(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) - place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE, null=True) + place = models.ForeignKey(Place, related_name='areas', on_delete=models.CASCADE, null=True) systems = models.ManyToManyField('systems.System') diff --git a/api/places/permissions.py b/api/places/permissions.py index 85144c6b..46fd51bc 100644 --- a/api/places/permissions.py +++ b/api/places/permissions.py @@ -4,12 +4,4 @@ class IsPlaceOwner(permissions.BasePermission): def has_object_permission(self, request, view, obj): - return obj.place.place_owner.user == request.user - -class IsPlaceOwnerOrReadOnly(permissions.BasePermission): - def has_object_permission(self, request, view, obj): - if request.method in permissions.SAFE_METHODS: - return True - place_owner = obj.place_owner - return place_owner.user == request.user - + return obj.place_owner.user == request.user diff --git a/api/places/serializers.py b/api/places/serializers.py index ba33ab0c..d059e46c 100644 --- a/api/places/serializers.py +++ b/api/places/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import Place, Room +from .models import Place, Area from users.serializers import PlaceOwnerSerializer class PlaceSerializer(serializers.ModelSerializer): @@ -11,9 +11,9 @@ class Meta: 'name': {'required': True} } -class RoomSerializer(serializers.ModelSerializer): +class AreaSerializer(serializers.ModelSerializer): class Meta: - model = Room + model = Area fields = ['id', 'name', 'floor', 'systems', 'place'] extra_kwargs = { 'name': {'required': True}, diff --git a/api/places/urls.py b/api/places/urls.py index cfbcb330..40f60081 100644 --- a/api/places/urls.py +++ b/api/places/urls.py @@ -1,7 +1,7 @@ from django.urls import path, include -from .views import PlaceViewSet, RoomViewSet +from .views import PlaceViewSet, AreaViewSet from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register(r'places',PlaceViewSet) -router.register(r'rooms', RoomViewSet) \ No newline at end of file +router.register(r'areas', AreaViewSet) \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index 5a5aaf5a..48ea92f7 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -10,8 +10,8 @@ from rest_framework.response import Response from rest_framework.exceptions import NotFound -from .models import Place, Room -from .serializers import PlaceSerializer, RoomSerializer +from .models import Place, Area +from .serializers import PlaceSerializer, AreaSerializer class PlaceViewSet(viewsets.ModelViewSet): queryset = Place.objects.all() @@ -83,21 +83,21 @@ def destroy(self, request, pk=None): return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) @action(detail=True, methods=['get']) - def rooms(self, request, pk=None): + def areas(self, request, pk=None): place = self.get_object() - serializer = RoomSerializer(place.rooms.all(), many=True) + serializer = AreaSerializer(place.areas.all(), many=True) return Response(serializer.data) - @action(detail=True, methods=['get'], url_path='rooms/(?P\d+)') - def room(self, request, pk=None, room_pk=None): + @action(detail=True, methods=['get'], url_path='areas/(?P\d+)') + def area(self, request, pk=None, area_pk=None): place = self.get_object() - room = get_object_or_404(place.rooms.all(), pk=room_pk) - serializer = RoomSerializer(room) + area = get_object_or_404(place.areas.all(), pk=area_pk) + serializer = AreaSerializer(area) return Response(serializer.data) -class RoomViewSet(viewsets.ModelViewSet): - queryset = Room.objects.all() - serializer_class = RoomSerializer +class AreaViewSet(viewsets.ModelViewSet): + queryset = Area.objects.all() + serializer_class = AreaSerializer permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): @@ -108,10 +108,10 @@ def create(self, request, *args, **kwargs): place = get_object_or_404(Place, id=place_id, place_owner=place_owner) if place.place_owner == place_owner: - room_serializer = RoomSerializer(data=request.data) - room_serializer.is_valid(raise_exception=True) - room_serializer.save() - return Response(room_serializer.data, status=status.HTTP_201_CREATED) + area_serializer = AreaSerializer(data=request.data) + area_serializer.is_valid(raise_exception=True) + area_serializer.save() + return Response(area_serializer.data, status=status.HTTP_201_CREATED) else: return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) @@ -125,29 +125,28 @@ def list(self,request,*args, **kwargs): place = get_object_or_404(Place, id=place_id, place_owner=place_owner) - rooms = Room.objects.filter(place=place) + areas = Area.objects.filter(place=place) - room_serializer = RoomSerializer(rooms, many=True) - return Response(room_serializer.data) + area_serializer = AreaSerializer(areas, many=True) + return Response(area_serializer.data) def retrieve(self, request, pk=None): place_owner = request.user.placeowner.id - room = get_object_or_404(Room,pk=pk) + area = get_object_or_404(Area,pk=pk) - if(room.place.place_owner.id == place_owner): - serializer = RoomSerializer(room) + if(area.place.place_owner.id == place_owner): + serializer = AreaSerializer(area) return Response(serializer.data) else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) - # Só o dono do lugar pode excluir uma sala espécífca def destroy(self, request, pk=None): place_owner_id = request.user.placeowner.id - room = get_object_or_404(Room, pk=pk) + area = get_object_or_404(Area, pk=pk) - if room.place.place_owner.id == place_owner_id: - room.delete() - return Response({"message": "Room deleted successfully"}, status=status.HTTP_204_NO_CONTENT) + if area.place.place_owner.id == place_owner_id: + area.delete() + return Response({"message": "Area deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: - return Response({"message": "You are not the owner of this room"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) From 806a5752b4d0b200a1fcd49fd5225dc9725e008b Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 24 Apr 2024 16:30:36 -0300 Subject: [PATCH 053/351] backend: refatore nome de Room para Area Co-authored-by: OscarDeBrito --- ...move_atmosphericdischargeequipment_room.py | 17 ++++++ ...0006_atmosphericdischargeequipment_area.py | 20 +++++++ ...phericdischargeequipment_table_and_more.py | 25 +++++++++ ...phericdischargeequipment_table_and_more.py | 25 +++++++++ api/equipments/models.py | 13 ++++- .../migrations/0022_area_delete_room.py | 30 ++++++++++ api/places/models.py | 4 +- api/places/permissions.py | 10 +--- api/places/serializers.py | 6 +- api/places/urls.py | 4 +- api/places/views.py | 55 +++++++++---------- 11 files changed, 163 insertions(+), 46 deletions(-) create mode 100644 api/equipments/migrations/0005_remove_atmosphericdischargeequipment_room.py create mode 100644 api/equipments/migrations/0006_atmosphericdischargeequipment_area.py create mode 100644 api/equipments/migrations/0007_alter_atmosphericdischargeequipment_table_and_more.py create mode 100644 api/equipments/migrations/0008_alter_atmosphericdischargeequipment_table_and_more.py create mode 100644 api/places/migrations/0022_area_delete_room.py diff --git a/api/equipments/migrations/0005_remove_atmosphericdischargeequipment_room.py b/api/equipments/migrations/0005_remove_atmosphericdischargeequipment_room.py new file mode 100644 index 00000000..f1047511 --- /dev/null +++ b/api/equipments/migrations/0005_remove_atmosphericdischargeequipment_room.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2 on 2024-04-24 19:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0004_rename_equipment_equipmentdetail'), + ] + + operations = [ + migrations.RemoveField( + model_name='atmosphericdischargeequipment', + name='room', + ), + ] diff --git a/api/equipments/migrations/0006_atmosphericdischargeequipment_area.py b/api/equipments/migrations/0006_atmosphericdischargeequipment_area.py new file mode 100644 index 00000000..bf889635 --- /dev/null +++ b/api/equipments/migrations/0006_atmosphericdischargeequipment_area.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-04-24 19:22 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0022_area_delete_room'), + ('equipments', '0005_remove_atmosphericdischargeequipment_room'), + ] + + operations = [ + migrations.AddField( + model_name='atmosphericdischargeequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + ] diff --git a/api/equipments/migrations/0007_alter_atmosphericdischargeequipment_table_and_more.py b/api/equipments/migrations/0007_alter_atmosphericdischargeequipment_table_and_more.py new file mode 100644 index 00000000..8ab0948b --- /dev/null +++ b/api/equipments/migrations/0007_alter_atmosphericdischargeequipment_table_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2 on 2024-04-24 19:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0006_atmosphericdischargeequipment_area'), + ] + + operations = [ + migrations.AlterModelTable( + name='atmosphericdischargeequipment', + table='atmospheric_discharge_equipments', + ), + migrations.AlterModelTable( + name='equipmentdetail', + table='equipment_details', + ), + migrations.AlterModelTable( + name='equipmenttype', + table='equipment_types', + ), + ] diff --git a/api/equipments/migrations/0008_alter_atmosphericdischargeequipment_table_and_more.py b/api/equipments/migrations/0008_alter_atmosphericdischargeequipment_table_and_more.py new file mode 100644 index 00000000..d45e74ad --- /dev/null +++ b/api/equipments/migrations/0008_alter_atmosphericdischargeequipment_table_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2 on 2024-04-24 19:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0007_alter_atmosphericdischargeequipment_table_and_more'), + ] + + operations = [ + migrations.AlterModelTable( + name='atmosphericdischargeequipment', + table='equipments_atmospheric_discharge_equipments', + ), + migrations.AlterModelTable( + name='equipmentdetail', + table='equipments_equipment_details', + ), + migrations.AlterModelTable( + name='equipmenttype', + table='equipments_equipment_types', + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index a8938ae5..c853fd14 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -1,5 +1,5 @@ from django.db import models -from places.models import Room +from places.models import Area from systems.models import System from users.models import PlaceOwner @@ -9,12 +9,21 @@ class EquipmentType(models.Model): def __str__(self): return self.type + class Meta: + db_table = 'equipments_equipment_types' + class EquipmentDetail(models.Model): placeOwner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) + class Meta: + db_table = 'equipments_equipment_details' + class AtmosphericDischargeEquipment(models.Model): equipment = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - room = models.ForeignKey(Room, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + + class Meta: + db_table = 'equipments_atmospheric_discharge_equipments' diff --git a/api/places/migrations/0022_area_delete_room.py b/api/places/migrations/0022_area_delete_room.py new file mode 100644 index 00000000..f55fe63a --- /dev/null +++ b/api/places/migrations/0022_area_delete_room.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2 on 2024-04-24 19:22 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('equipments', '0005_remove_atmosphericdischargeequipment_room'), + ('places', '0021_room_place_room_systems'), + ] + + operations = [ + migrations.CreateModel( + name='Area', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('floor', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])), + ('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='areas', to='places.place')), + ('systems', models.ManyToManyField(to='systems.system')), + ], + ), + migrations.DeleteModel( + name='Room', + ), + ] diff --git a/api/places/models.py b/api/places/models.py index 04b790c8..686a93c4 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -10,11 +10,11 @@ class Place(models.Model): def __str__(self): return self.name -class Room(models.Model): +class Area(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) - place = models.ForeignKey(Place, related_name='rooms', on_delete=models.CASCADE, null=True) + place = models.ForeignKey(Place, related_name='areas', on_delete=models.CASCADE, null=True) systems = models.ManyToManyField('systems.System') diff --git a/api/places/permissions.py b/api/places/permissions.py index 85144c6b..46fd51bc 100644 --- a/api/places/permissions.py +++ b/api/places/permissions.py @@ -4,12 +4,4 @@ class IsPlaceOwner(permissions.BasePermission): def has_object_permission(self, request, view, obj): - return obj.place.place_owner.user == request.user - -class IsPlaceOwnerOrReadOnly(permissions.BasePermission): - def has_object_permission(self, request, view, obj): - if request.method in permissions.SAFE_METHODS: - return True - place_owner = obj.place_owner - return place_owner.user == request.user - + return obj.place_owner.user == request.user diff --git a/api/places/serializers.py b/api/places/serializers.py index ba33ab0c..d059e46c 100644 --- a/api/places/serializers.py +++ b/api/places/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import Place, Room +from .models import Place, Area from users.serializers import PlaceOwnerSerializer class PlaceSerializer(serializers.ModelSerializer): @@ -11,9 +11,9 @@ class Meta: 'name': {'required': True} } -class RoomSerializer(serializers.ModelSerializer): +class AreaSerializer(serializers.ModelSerializer): class Meta: - model = Room + model = Area fields = ['id', 'name', 'floor', 'systems', 'place'] extra_kwargs = { 'name': {'required': True}, diff --git a/api/places/urls.py b/api/places/urls.py index cfbcb330..40f60081 100644 --- a/api/places/urls.py +++ b/api/places/urls.py @@ -1,7 +1,7 @@ from django.urls import path, include -from .views import PlaceViewSet, RoomViewSet +from .views import PlaceViewSet, AreaViewSet from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register(r'places',PlaceViewSet) -router.register(r'rooms', RoomViewSet) \ No newline at end of file +router.register(r'areas', AreaViewSet) \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index 5a5aaf5a..48ea92f7 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -10,8 +10,8 @@ from rest_framework.response import Response from rest_framework.exceptions import NotFound -from .models import Place, Room -from .serializers import PlaceSerializer, RoomSerializer +from .models import Place, Area +from .serializers import PlaceSerializer, AreaSerializer class PlaceViewSet(viewsets.ModelViewSet): queryset = Place.objects.all() @@ -83,21 +83,21 @@ def destroy(self, request, pk=None): return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) @action(detail=True, methods=['get']) - def rooms(self, request, pk=None): + def areas(self, request, pk=None): place = self.get_object() - serializer = RoomSerializer(place.rooms.all(), many=True) + serializer = AreaSerializer(place.areas.all(), many=True) return Response(serializer.data) - @action(detail=True, methods=['get'], url_path='rooms/(?P\d+)') - def room(self, request, pk=None, room_pk=None): + @action(detail=True, methods=['get'], url_path='areas/(?P\d+)') + def area(self, request, pk=None, area_pk=None): place = self.get_object() - room = get_object_or_404(place.rooms.all(), pk=room_pk) - serializer = RoomSerializer(room) + area = get_object_or_404(place.areas.all(), pk=area_pk) + serializer = AreaSerializer(area) return Response(serializer.data) -class RoomViewSet(viewsets.ModelViewSet): - queryset = Room.objects.all() - serializer_class = RoomSerializer +class AreaViewSet(viewsets.ModelViewSet): + queryset = Area.objects.all() + serializer_class = AreaSerializer permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): @@ -108,10 +108,10 @@ def create(self, request, *args, **kwargs): place = get_object_or_404(Place, id=place_id, place_owner=place_owner) if place.place_owner == place_owner: - room_serializer = RoomSerializer(data=request.data) - room_serializer.is_valid(raise_exception=True) - room_serializer.save() - return Response(room_serializer.data, status=status.HTTP_201_CREATED) + area_serializer = AreaSerializer(data=request.data) + area_serializer.is_valid(raise_exception=True) + area_serializer.save() + return Response(area_serializer.data, status=status.HTTP_201_CREATED) else: return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) @@ -125,29 +125,28 @@ def list(self,request,*args, **kwargs): place = get_object_or_404(Place, id=place_id, place_owner=place_owner) - rooms = Room.objects.filter(place=place) + areas = Area.objects.filter(place=place) - room_serializer = RoomSerializer(rooms, many=True) - return Response(room_serializer.data) + area_serializer = AreaSerializer(areas, many=True) + return Response(area_serializer.data) def retrieve(self, request, pk=None): place_owner = request.user.placeowner.id - room = get_object_or_404(Room,pk=pk) + area = get_object_or_404(Area,pk=pk) - if(room.place.place_owner.id == place_owner): - serializer = RoomSerializer(room) + if(area.place.place_owner.id == place_owner): + serializer = AreaSerializer(area) return Response(serializer.data) else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) - # Só o dono do lugar pode excluir uma sala espécífca def destroy(self, request, pk=None): place_owner_id = request.user.placeowner.id - room = get_object_or_404(Room, pk=pk) + area = get_object_or_404(Area, pk=pk) - if room.place.place_owner.id == place_owner_id: - room.delete() - return Response({"message": "Room deleted successfully"}, status=status.HTTP_204_NO_CONTENT) + if area.place.place_owner.id == place_owner_id: + area.delete() + return Response({"message": "Area deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: - return Response({"message": "You are not the owner of this room"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) From b286954bd1a320e5eac685dae04a01b3ca13c376 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 24 Apr 2024 17:14:33 -0300 Subject: [PATCH 054/351] =?UTF-8?q?backend:=20cria=20permiss=C3=A3o=20para?= =?UTF-8?q?=20descargas=20atmosf=C3=A9ricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: OscarDeBrito --- api/equipments/models.py | 4 ++-- api/equipments/permissions.py | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/api/equipments/models.py b/api/equipments/models.py index c853fd14..f304d9b8 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -22,8 +22,8 @@ class Meta: db_table = 'equipments_equipment_details' class AtmosphericDischargeEquipment(models.Model): - equipment = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) - + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + class Meta: db_table = 'equipments_atmospheric_discharge_equipments' diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 324d265e..a43161f8 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -2,7 +2,14 @@ class OwnerEquip(BasePermission): def has_object_permission(self, request, view, obj): - if(request.user.placeowner == obj.placeOwner): + if request.user.placeowner == obj.placeOwner: return True else: - return False \ No newline at end of file + return False + +class AtmosphericDischargeEquipmentOwner(BasePermission): + def has_object_permission(self, request, view, obj): + if request.user.placeowner == obj.area.place.place_owner: + return True + else: + return False From 276325bd115f3102f87eac4fd806a1262117af78 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Wed, 24 Apr 2024 17:37:41 -0300 Subject: [PATCH 055/351] Backend: Adiciona views e create do AtmosphericDischargeEquipment Co-authored-by: Pedro Lucas --- ...hericdischargeequipment_equipment_detail.py | 18 ++++++++++++++++++ api/equipments/serializers.py | 8 +++++++- api/equipments/urls.py | 6 +++--- api/equipments/views.py | 14 ++++++++++---- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 api/equipments/migrations/0009_rename_equipment_atmosphericdischargeequipment_equipment_detail.py diff --git a/api/equipments/migrations/0009_rename_equipment_atmosphericdischargeequipment_equipment_detail.py b/api/equipments/migrations/0009_rename_equipment_atmosphericdischargeequipment_equipment_detail.py new file mode 100644 index 00000000..e522ad03 --- /dev/null +++ b/api/equipments/migrations/0009_rename_equipment_atmosphericdischargeequipment_equipment_detail.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2024-04-24 20:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0008_alter_atmosphericdischargeequipment_table_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='atmosphericdischargeequipment', + old_name='equipment', + new_name='equipment_detail', + ), + ] diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 16bb0d2b..6bbf948c 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import EquipmentType, EquipmentDetail +from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment class EquipmentTypeSerializer(serializers.ModelSerializer): @@ -13,3 +13,9 @@ class Meta: model = EquipmentDetail fields = '__all__' +class AtmosphericDischargeEquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = AtmosphericDischargeEquipment + fields = '__all__' + diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 2f750cf3..31457128 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -1,4 +1,4 @@ -from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail +from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail, AtmosphericDischargeEquipmentList from django.urls import path urlpatterns = [ @@ -6,6 +6,6 @@ path('equipment-type/', EquipmentTypeList.as_view()), path('equipment-type//', EquipmentTypeDetail.as_view()), path('equipments/', EquipmentDetailList.as_view()), - path('equipments//', EquipmentDetail.as_view()) - + path('equipments//', EquipmentDetail.as_view()), + path('atmospheric_discharges/', AtmosphericDischargeEquipmentList.as_view()), ] diff --git a/api/equipments/views.py b/api/equipments/views.py index 68906dde..5af39996 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -1,8 +1,9 @@ from rest_framework import generics from rest_framework.response import Response -from .models import EquipmentType, EquipmentDetail -from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer -from .permissions import OwnerEquip +from rest_framework.permissions import IsAuthenticated +from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment +from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer +from .permissions import OwnerEquip, AtmosphericDischargeEquipmentOwner from rest_framework import status class EquipmentTypeList(generics.ListAPIView): @@ -32,4 +33,9 @@ def create(self, request, *args, **kwargs): class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentDetail.objects.all() serializer_class = EquipmentDetailSerializer - permission_classes = [OwnerEquip] \ No newline at end of file + permission_classes = [OwnerEquip] + +class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): + queryset = AtmosphericDischargeEquipment.objects.all() + serializer_class = AtmosphericDischargeEquipmentSerializer + permission_classes = [AtmosphericDischargeEquipmentOwner, IsAuthenticated] \ No newline at end of file From 95ed32ba417367a9cc368b5e72bf7a06078fa414 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 25 Apr 2024 09:48:39 -0300 Subject: [PATCH 056/351] backend: fix permission de dono de equipamentos por sala --- api/equipments/models.py | 4 ++++ api/equipments/permissions.py | 10 ++++++---- api/equipments/urls.py | 6 +++--- api/equipments/views.py | 9 +++++++-- .../migrations/0023_alter_area_place.py | 19 +++++++++++++++++++ api/places/models.py | 2 +- api/users/models.py | 4 +++- 7 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 api/places/migrations/0023_alter_area_place.py diff --git a/api/equipments/models.py b/api/equipments/models.py index f304d9b8..6194ae48 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -6,6 +6,7 @@ class EquipmentType(models.Model): type = models.CharField(max_length=50) system = models.ForeignKey(System, on_delete=models.CASCADE) + def __str__(self): return self.type @@ -18,6 +19,9 @@ class EquipmentDetail(models.Model): photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) + def __str__(self): + return self.description + class Meta: db_table = 'equipments_equipment_details' diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index a43161f8..998fd216 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -1,5 +1,7 @@ from rest_framework.permissions import BasePermission +from places.models import Area + class OwnerEquip(BasePermission): def has_object_permission(self, request, view, obj): if request.user.placeowner == obj.placeOwner: @@ -7,9 +9,9 @@ def has_object_permission(self, request, view, obj): else: return False -class AtmosphericDischargeEquipmentOwner(BasePermission): +class IsPlaceOwner(BasePermission): def has_object_permission(self, request, view, obj): - if request.user.placeowner == obj.area.place.place_owner: + if obj.area.place.place_owner == request.user.placeowner: return True - else: - return False + return False + diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 31457128..5d15dd16 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -1,11 +1,11 @@ -from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail, AtmosphericDischargeEquipmentList +from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail, AtmosphericDischargeEquipmentList, AtmosphericDischargeEquipmentDetail from django.urls import path urlpatterns = [ - path('equipment-type/', EquipmentTypeList.as_view()), path('equipment-type//', EquipmentTypeDetail.as_view()), path('equipments/', EquipmentDetailList.as_view()), path('equipments//', EquipmentDetail.as_view()), - path('atmospheric_discharges/', AtmosphericDischargeEquipmentList.as_view()), + path('atmospheric-discharges/', AtmosphericDischargeEquipmentList.as_view()), + path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()) ] diff --git a/api/equipments/views.py b/api/equipments/views.py index 5af39996..9b51e1a3 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -3,7 +3,7 @@ from rest_framework.permissions import IsAuthenticated from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer -from .permissions import OwnerEquip, AtmosphericDischargeEquipmentOwner +from .permissions import OwnerEquip, IsPlaceOwner from rest_framework import status class EquipmentTypeList(generics.ListAPIView): @@ -38,4 +38,9 @@ class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [AtmosphericDischargeEquipmentOwner, IsAuthenticated] \ No newline at end of file + permission_classes = [IsPlaceOwner, IsAuthenticated] + +class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = AtmosphericDischargeEquipment.objects.all() + serializer_class = AtmosphericDischargeEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] \ No newline at end of file diff --git a/api/places/migrations/0023_alter_area_place.py b/api/places/migrations/0023_alter_area_place.py new file mode 100644 index 00000000..3f3e1056 --- /dev/null +++ b/api/places/migrations/0023_alter_area_place.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2 on 2024-04-24 20:50 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0022_area_delete_room'), + ] + + operations = [ + migrations.AlterField( + model_name='area', + name='place', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.place'), + ), + ] diff --git a/api/places/models.py b/api/places/models.py index 686a93c4..72b3dd1b 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -14,7 +14,7 @@ class Area(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) - place = models.ForeignKey(Place, related_name='areas', on_delete=models.CASCADE, null=True) + place = models.ForeignKey(Place, on_delete=models.CASCADE, null=True) systems = models.ManyToManyField('systems.System') diff --git a/api/users/models.py b/api/users/models.py index b5829866..c395489b 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -4,4 +4,6 @@ class PlaceOwner(models.Model): user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE, related_name='placeowner') - \ No newline at end of file + + def __str__(self): + return self.user.first_name \ No newline at end of file From cf35f79707811b16def011854a5163cb13618afe Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 25 Apr 2024 09:52:52 -0300 Subject: [PATCH 057/351] backend: refatora atributo de equipment type Co-authored-by: Kauan Jose --- .../0010_rename_type_equipmenttype_name.py | 18 ++++++++++++++++++ api/equipments/models.py | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 api/equipments/migrations/0010_rename_type_equipmenttype_name.py diff --git a/api/equipments/migrations/0010_rename_type_equipmenttype_name.py b/api/equipments/migrations/0010_rename_type_equipmenttype_name.py new file mode 100644 index 00000000..7d8e4a0f --- /dev/null +++ b/api/equipments/migrations/0010_rename_type_equipmenttype_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2024-04-25 12:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0009_rename_equipment_atmosphericdischargeequipment_equipment_detail'), + ] + + operations = [ + migrations.RenameField( + model_name='equipmenttype', + old_name='type', + new_name='name', + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index 6194ae48..d5016059 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -4,11 +4,11 @@ from users.models import PlaceOwner class EquipmentType(models.Model): - type = models.CharField(max_length=50) + name = models.CharField(max_length=50) system = models.ForeignKey(System, on_delete=models.CASCADE) def __str__(self): - return self.type + return self.name class Meta: db_table = 'equipments_equipment_types' From 31d63261b89dc478905aa319265003c7c703e480 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 25 Apr 2024 10:01:21 -0300 Subject: [PATCH 058/351] backend: adiciona __str__ em area Co-authored-by: Kauan Jose --- api/places/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/places/models.py b/api/places/models.py index 72b3dd1b..2497c3c4 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -17,4 +17,5 @@ class Area(models.Model): place = models.ForeignKey(Place, on_delete=models.CASCADE, null=True) systems = models.ManyToManyField('systems.System') - + def __str__(self): + return self.name From 5b5345f437fe50fc9fd01463ef8837e37b82b15e Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 25 Apr 2024 10:10:00 -0300 Subject: [PATCH 059/351] backend: fix views de atmospheric discharge para permitir que somente o dono tenha acesso Co-authored-by: Kauan Jose --- api/equipments/views.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/api/equipments/views.py b/api/equipments/views.py index 9b51e1a3..07a2c4f6 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -1,6 +1,7 @@ from rest_framework import generics from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated +from places.models import Area from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer from .permissions import OwnerEquip, IsPlaceOwner @@ -40,6 +41,22 @@ class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): serializer_class = AtmosphericDischargeEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] + def get_queryset(self): + user = self.request.user + return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + area_id = request.data.get('area') + area = Area.objects.filter(id=area_id).first() + if area and area.place.place_owner == request.user.placeowner: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer From 8394b2e8953f408fc0db25eb1299bfc8f9392676 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 25 Apr 2024 10:32:38 -0300 Subject: [PATCH 060/351] backend: fix list de equipment detail para somente o dono poder visualizar Co-authored-by: Kauan Jose --- ..._placeowner_equipmentdetail_place_owner.py | 18 +++++++++++++ api/equipments/models.py | 2 +- api/equipments/permissions.py | 4 +-- api/equipments/views.py | 27 +++++++++++-------- 4 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 api/equipments/migrations/0011_rename_placeowner_equipmentdetail_place_owner.py diff --git a/api/equipments/migrations/0011_rename_placeowner_equipmentdetail_place_owner.py b/api/equipments/migrations/0011_rename_placeowner_equipmentdetail_place_owner.py new file mode 100644 index 00000000..9ac02fb3 --- /dev/null +++ b/api/equipments/migrations/0011_rename_placeowner_equipmentdetail_place_owner.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2024-04-25 13:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0010_rename_type_equipmenttype_name'), + ] + + operations = [ + migrations.RenameField( + model_name='equipmentdetail', + old_name='placeOwner', + new_name='place_owner', + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index d5016059..517220fd 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -14,7 +14,7 @@ class Meta: db_table = 'equipments_equipment_types' class EquipmentDetail(models.Model): - placeOwner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 998fd216..82b75a4e 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -2,9 +2,9 @@ from places.models import Area -class OwnerEquip(BasePermission): +class IsEquipmentDetailOwner(BasePermission): def has_object_permission(self, request, view, obj): - if request.user.placeowner == obj.placeOwner: + if obj.place_owner == request.user.placeowner: return True else: return False diff --git a/api/equipments/views.py b/api/equipments/views.py index 07a2c4f6..68a4b565 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -4,37 +4,42 @@ from places.models import Area from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer -from .permissions import OwnerEquip, IsPlaceOwner +from .permissions import IsEquipmentDetailOwner, IsPlaceOwner from rest_framework import status class EquipmentTypeList(generics.ListAPIView): queryset = EquipmentType.objects.all() serializer_class = EquipmentTypeSerializer - permission_classes = [] + permission_classes = [IsAuthenticated] class EquipmentTypeDetail(generics.RetrieveAPIView): queryset = EquipmentType.objects.all() serializer_class = EquipmentTypeSerializer - permission_classes = [] + permission_classes = [IsAuthenticated] class EquipmentDetailList(generics.ListCreateAPIView): queryset = EquipmentDetail.objects.all() serializer_class = EquipmentDetailSerializer - permission_classes = [OwnerEquip] + permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + queryset = super().get_queryset() + return queryset.filter(place_owner__user=user) def create(self, request, *args, **kwargs): - if(OwnerEquip): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save(placeOwner=request.user.placeowner) - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + if(IsEquipmentDetailOwner): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(place_owner=request.user.placeowner) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentDetail.objects.all() serializer_class = EquipmentDetailSerializer - permission_classes = [OwnerEquip] + permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): queryset = AtmosphericDischargeEquipment.objects.all() From 0b00622560f3f3cddd3827e9c1a161ef58ef9001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 25 Apr 2024 10:48:59 -0300 Subject: [PATCH 061/351] Backend: cria models base dos equipamentos Co-authored-by: Pedro Lucas Co-authored-by: OscarDeBrito --- api/equipments/models.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/api/equipments/models.py b/api/equipments/models.py index 517220fd..fbba1192 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -31,3 +31,31 @@ class AtmosphericDischargeEquipment(models.Model): class Meta: db_table = 'equipments_atmospheric_discharge_equipments' + +class FireAlarmEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + +class SructeredCablingEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + +class DistributionBoardEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + +class ElectricalCircuitEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + +class ElectricalLineEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + +class ElectricalLoadEquipment(models.MOdel): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + +class IluminationEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) From cc15060f5f3107d0c4d21580fff14e2483d2b65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 25 Apr 2024 10:52:52 -0300 Subject: [PATCH 062/351] Backend: fix erro --- ...equipment_iluminationequipment_and_more.py | 71 +++++++++++++++++++ api/equipments/models.py | 2 +- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 api/equipments/migrations/0012_sructeredcablingequipment_iluminationequipment_and_more.py diff --git a/api/equipments/migrations/0012_sructeredcablingequipment_iluminationequipment_and_more.py b/api/equipments/migrations/0012_sructeredcablingequipment_iluminationequipment_and_more.py new file mode 100644 index 00000000..807a3d82 --- /dev/null +++ b/api/equipments/migrations/0012_sructeredcablingequipment_iluminationequipment_and_more.py @@ -0,0 +1,71 @@ +# Generated by Django 4.2 on 2024-04-25 13:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0023_alter_area_place'), + ('equipments', '0011_rename_placeowner_equipmentdetail_place_owner'), + ] + + operations = [ + migrations.CreateModel( + name='SructeredCablingEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('area', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area')), + ('equipment_detail', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ], + ), + migrations.CreateModel( + name='IluminationEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('area', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area')), + ('equipment_detail', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ], + ), + migrations.CreateModel( + name='FireAlarmEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('area', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area')), + ('equipment_detail', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ], + ), + migrations.CreateModel( + name='ElectricalLoadEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('area', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area')), + ('equipment_detail', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ], + ), + migrations.CreateModel( + name='ElectricalLineEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('area', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area')), + ('equipment_detail', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ], + ), + migrations.CreateModel( + name='ElectricalCircuitEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('area', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area')), + ('equipment_detail', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ], + ), + migrations.CreateModel( + name='DistributionBoardEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('area', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area')), + ('equipment_detail', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ], + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index fbba1192..5b9839c8 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -52,7 +52,7 @@ class ElectricalLineEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) -class ElectricalLoadEquipment(models.MOdel): +class ElectricalLoadEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) From bac7c3745e718407188598eb03eea604aea902fc Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 25 Apr 2024 12:16:45 -0300 Subject: [PATCH 063/351] backend: adiciona coordenadas em places --- .../migrations/0024_place_lat_place_lon.py | 23 +++++++++++++++++++ api/places/models.py | 2 ++ api/places/serializers.py | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 api/places/migrations/0024_place_lat_place_lon.py diff --git a/api/places/migrations/0024_place_lat_place_lon.py b/api/places/migrations/0024_place_lat_place_lon.py new file mode 100644 index 00000000..409a2182 --- /dev/null +++ b/api/places/migrations/0024_place_lat_place_lon.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2 on 2024-04-25 15:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0023_alter_area_place'), + ] + + operations = [ + migrations.AddField( + model_name='place', + name='lat', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='place', + name='lon', + field=models.FloatField(null=True), + ), + ] diff --git a/api/places/models.py b/api/places/models.py index 2497c3c4..19cdb863 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -6,6 +6,8 @@ class Place(models.Model): name = models.CharField(max_length=50) place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) + lon = models.FloatField(null=True) + lat = models.FloatField(null=True) def __str__(self): return self.name diff --git a/api/places/serializers.py b/api/places/serializers.py index d059e46c..53b2ce8d 100644 --- a/api/places/serializers.py +++ b/api/places/serializers.py @@ -6,7 +6,7 @@ class PlaceSerializer(serializers.ModelSerializer): class Meta: model = Place - fields = ['id', 'name', 'place_owner'] + fields = ['id', 'name', 'place_owner', 'lon', 'lat'] extra_kwargs = { 'name': {'required': True} } From 4605e66b00192cf3fce87b4390cdb397df1b9132 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 26 Apr 2024 10:10:18 -0300 Subject: [PATCH 064/351] frontend: refatora feature de places --- frontend/sige_ie/lib/core/data/auth_service.dart | 5 +---- frontend/sige_ie/lib/{screens => core/ui}/facilities.dart | 0 .../sige_ie/lib/core/ui/{first_screen => }/first_scren.dart | 0 .../sige_ie/lib/{screens => core/ui}/splash_screen.dart | 0 frontend/sige_ie/lib/home/ui/home.dart | 2 +- frontend/sige_ie/lib/main.dart | 6 +++--- .../manage_locations.dart => data/place_service.dart} | 0 .../sige_ie/lib/places/feature/manage/manage_locations.dart | 0 .../sige_ie/lib/places/{ => feature}/register/position.dart | 0 .../{ => feature}/register/register_new_location.dart | 2 +- 10 files changed, 6 insertions(+), 9 deletions(-) rename frontend/sige_ie/lib/{screens => core/ui}/facilities.dart (100%) rename frontend/sige_ie/lib/core/ui/{first_screen => }/first_scren.dart (100%) rename frontend/sige_ie/lib/{screens => core/ui}/splash_screen.dart (100%) rename frontend/sige_ie/lib/places/{manage/manage_locations.dart => data/place_service.dart} (100%) create mode 100644 frontend/sige_ie/lib/places/feature/manage/manage_locations.dart rename frontend/sige_ie/lib/places/{ => feature}/register/position.dart (100%) rename frontend/sige_ie/lib/places/{ => feature}/register/register_new_location.dart (99%) diff --git a/frontend/sige_ie/lib/core/data/auth_service.dart b/frontend/sige_ie/lib/core/data/auth_service.dart index c93119b6..bd369cb8 100644 --- a/frontend/sige_ie/lib/core/data/auth_service.dart +++ b/frontend/sige_ie/lib/core/data/auth_service.dart @@ -41,8 +41,6 @@ class AuthService { } } } - - print(sessionid); return sessionid!; } else { throw Exception('Failed to fetch session cookie: ${response.statusCode}'); @@ -54,8 +52,7 @@ class AuthService { interceptors: [AuthInterceptor(cookieJar)], ); - final response = - await client.get(Uri.parse('http://10.0.2.2:8000/api/checkauth/')); + final response = await client.get(Uri.parse('http://10.0.2.2:8000/api/checkauth/')); if (response.statusCode == 200) { var data = jsonDecode(response.body); diff --git a/frontend/sige_ie/lib/screens/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart similarity index 100% rename from frontend/sige_ie/lib/screens/facilities.dart rename to frontend/sige_ie/lib/core/ui/facilities.dart diff --git a/frontend/sige_ie/lib/core/ui/first_screen/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_scren.dart similarity index 100% rename from frontend/sige_ie/lib/core/ui/first_screen/first_scren.dart rename to frontend/sige_ie/lib/core/ui/first_scren.dart diff --git a/frontend/sige_ie/lib/screens/splash_screen.dart b/frontend/sige_ie/lib/core/ui/splash_screen.dart similarity index 100% rename from frontend/sige_ie/lib/screens/splash_screen.dart rename to frontend/sige_ie/lib/core/ui/splash_screen.dart diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 5ec4ff1d..314359b2 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import '../../users/feature/profile.dart'; -import '../../screens/facilities.dart'; +import '../../core/ui/facilities.dart'; import '../../maps/feature/maps.dart'; class HomePage extends StatefulWidget { diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 67db82d7..fbb82be1 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -1,9 +1,9 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:flutter/material.dart'; -import 'package:sige_ie/core/ui/first_screen/first_scren.dart'; +import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; -import 'package:sige_ie/places/register/register_new_location.dart'; -import 'package:sige_ie/screens/splash_screen.dart'; +import 'package:sige_ie/places/feature/register/register_new_location.dart'; +import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; import 'core/feature/login/login.dart'; diff --git a/frontend/sige_ie/lib/places/manage/manage_locations.dart b/frontend/sige_ie/lib/places/data/place_service.dart similarity index 100% rename from frontend/sige_ie/lib/places/manage/manage_locations.dart rename to frontend/sige_ie/lib/places/data/place_service.dart diff --git a/frontend/sige_ie/lib/places/feature/manage/manage_locations.dart b/frontend/sige_ie/lib/places/feature/manage/manage_locations.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/places/register/position.dart b/frontend/sige_ie/lib/places/feature/register/position.dart similarity index 100% rename from frontend/sige_ie/lib/places/register/position.dart rename to frontend/sige_ie/lib/places/feature/register/position.dart diff --git a/frontend/sige_ie/lib/places/register/register_new_location.dart b/frontend/sige_ie/lib/places/feature/register/register_new_location.dart similarity index 99% rename from frontend/sige_ie/lib/places/register/register_new_location.dart rename to frontend/sige_ie/lib/places/feature/register/register_new_location.dart index d67c5f71..8da9b6ab 100644 --- a/frontend/sige_ie/lib/places/register/register_new_location.dart +++ b/frontend/sige_ie/lib/places/feature/register/register_new_location.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import '../register/position.dart'; +import 'position.dart'; class newLocation extends StatefulWidget { @override From 110dd464ba46c996c685390e24218fb91165e9be Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia Date: Fri, 26 Apr 2024 10:16:15 -0300 Subject: [PATCH 065/351] Atualiza README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c0c1a94..25c02038 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ SIGE IE ## Fase -Release 1 Ir para milestone da release 1 +Release 1 ✔️ Ir para milestone da release 1 + +Release 2 (atual) Ir para milestone da release 2 ## Visão geral do produto From ec3b5ed9f644e2ebeddadc4c2e3a1534d9e66b4d Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 26 Apr 2024 11:05:21 -0300 Subject: [PATCH 066/351] frontend: cria service criar novo place --- frontend/sige_ie/lib/main.dart | 2 +- .../lib/places/data/place_request_model.dart | 20 ++++++++++++++ .../lib/places/data/place_service.dart | 27 +++++++++++++++++++ .../lib/places/feature/register/position.dart | 4 +-- ..._location.dart => register_new_place.dart} | 23 +++++++++------- 5 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 frontend/sige_ie/lib/places/data/place_request_model.dart rename frontend/sige_ie/lib/places/feature/register/{register_new_location.dart => register_new_place.dart} (92%) diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index fbb82be1..78a22c17 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -2,7 +2,7 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; -import 'package:sige_ie/places/feature/register/register_new_location.dart'; +import 'package:sige_ie/places/feature/register/register_new_place.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; diff --git a/frontend/sige_ie/lib/places/data/place_request_model.dart b/frontend/sige_ie/lib/places/data/place_request_model.dart new file mode 100644 index 00000000..6aecbec2 --- /dev/null +++ b/frontend/sige_ie/lib/places/data/place_request_model.dart @@ -0,0 +1,20 @@ +class PlaceRequestModel { + String name; + double lon; + double lat; + + PlaceRequestModel({required this.name, required this.lon, required this.lat}); + + Map toJson() { + return { + 'name': name, + 'lon': lon, + 'lat': lat, + }; + } + + PlaceRequestModel.fromJson(Map json) + : name = json['name'].toString(), + lon = json['lon'].toDouble(), + lat = json['lat'].toDouble(); +} diff --git a/frontend/sige_ie/lib/places/data/place_service.dart b/frontend/sige_ie/lib/places/data/place_service.dart index e69de29b..f7ffd4dd 100644 --- a/frontend/sige_ie/lib/places/data/place_service.dart +++ b/frontend/sige_ie/lib/places/data/place_service.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:sige_ie/places/data/place_request_model.dart'; + +class PlaceService { + static Future register(PlaceRequestModel placeRequestModel) async { + var url = Uri.parse('http://10.0.2.2:8000/api/places/'); + try { + var response = await http.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(placeRequestModel.toJson()), + ); + if (response.statusCode == 200 || response.statusCode == 201) { + var data = jsonDecode(response.body); + print("Registro bem-sucedido: $data"); + return true; + } else { + print("Falha no registro: ${response.body}"); + return false; + } + } catch (e) { + print("Erro ao tentar registrar: $e"); + return false; + } + } +} diff --git a/frontend/sige_ie/lib/places/feature/register/position.dart b/frontend/sige_ie/lib/places/feature/register/position.dart index 3b31ef78..929a5176 100644 --- a/frontend/sige_ie/lib/places/feature/register/position.dart +++ b/frontend/sige_ie/lib/places/feature/register/position.dart @@ -3,7 +3,7 @@ import 'package:geolocator/geolocator.dart'; class PositionController extends ChangeNotifier { double lat = 0.0; - double long = 0.0; + double lon = 0.0; String error = ''; PositionController() { @@ -14,7 +14,7 @@ class PositionController extends ChangeNotifier { try { Position position = await _currentPostion(); lat = position.latitude; - long = position.longitude; + lon = position.longitude; } catch (e) { error = e.toString(); } diff --git a/frontend/sige_ie/lib/places/feature/register/register_new_location.dart b/frontend/sige_ie/lib/places/feature/register/register_new_place.dart similarity index 92% rename from frontend/sige_ie/lib/places/feature/register/register_new_location.dart rename to frontend/sige_ie/lib/places/feature/register/register_new_place.dart index 8da9b6ab..4f55f12d 100644 --- a/frontend/sige_ie/lib/places/feature/register/register_new_location.dart +++ b/frontend/sige_ie/lib/places/feature/register/register_new_place.dart @@ -1,15 +1,20 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/places/data/place_request_model.dart'; +import 'package:sige_ie/places/data/place_service.dart'; import 'position.dart'; -class newLocation extends StatefulWidget { +class NewPlace extends StatefulWidget { @override - _newLocationState createState() => _newLocationState(); + _NewPlaceState createState() => _NewPlaceState(); } -class _newLocationState extends State { +class _NewPlaceState extends State { + PlaceService placeService = PlaceService(); + PlaceRequestModel placeRequestModel = + PlaceRequestModel(name: '', lon: 0, lat: 0); String coordinates = ''; - bool Coord = false; + bool coord = false; final TextEditingController _nameController = TextEditingController(); late PositionController positionController; @@ -24,17 +29,17 @@ class _newLocationState extends State { setState(() { if (positionController.error.isEmpty) { coordinates = - "Latitude: ${positionController.lat}, Longitude: ${positionController.long}"; - Coord = true; + "Latitude: ${positionController.lat}, Longitude: ${positionController.lon}"; + coord = true; } else { coordinates = "Erro: ${positionController.error}"; - Coord = false; + coord = false; } }); }).catchError((e) { setState(() { coordinates = "Erro ao obter localização: $e"; - Coord = false; + coord = false; }); }); } @@ -158,7 +163,7 @@ class _newLocationState extends State { ), ), onPressed: () { - if (Coord && _nameController.text.trim().isNotEmpty) { + if (coord && _nameController.text.trim().isNotEmpty) { // Código para registrar o local print('Local Registrado: ${_nameController.text}'); Navigator.of(context).pushNamed('?'); From 051809550fdc221f604bd8691e1606d958d80431 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 26 Apr 2024 13:31:47 -0300 Subject: [PATCH 067/351] Backend: Adiciona as views, urls e serializers do FireAlarmEquipment Co-authored-by: Pedro Lucas --- api/equipments/models.py | 4 ++-- api/equipments/serializers.py | 10 ++++++++-- api/equipments/urls.py | 6 ++++-- api/equipments/views.py | 31 +++++++++++++++++++++++++++++-- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/api/equipments/models.py b/api/equipments/models.py index 5b9839c8..c1e2c34d 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -14,13 +14,13 @@ class Meta: db_table = 'equipments_equipment_types' class EquipmentDetail(models.Model): + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) - def __str__(self): - return self.description + def __str__(self):return self.description class Meta: db_table = 'equipments_equipment_details' diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 6bbf948c..ee274e91 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment +from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment, FireAlarmEquipment class EquipmentTypeSerializer(serializers.ModelSerializer): @@ -17,5 +17,11 @@ class AtmosphericDischargeEquipmentSerializer(serializers.ModelSerializer): class Meta: model = AtmosphericDischargeEquipment - fields = '__all__' + fields = '__all__' + +class FireAlarmEquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = FireAlarmEquipment + fields = '__all__' diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 5d15dd16..ed78de15 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -1,4 +1,4 @@ -from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail, AtmosphericDischargeEquipmentList, AtmosphericDischargeEquipmentDetail +from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail, AtmosphericDischargeEquipmentList, AtmosphericDischargeEquipmentDetail, FireAlarmEquipmentList, FireAlarmEquipmentDetail from django.urls import path urlpatterns = [ @@ -7,5 +7,7 @@ path('equipments/', EquipmentDetailList.as_view()), path('equipments//', EquipmentDetail.as_view()), path('atmospheric-discharges/', AtmosphericDischargeEquipmentList.as_view()), - path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()) + path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()), + path('fire-alarms/', FireAlarmEquipmentList.as_view()), + path('fire-alarms//', FireAlarmEquipmentDetail.as_view()), ] diff --git a/api/equipments/views.py b/api/equipments/views.py index 68a4b565..f88a5533 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -2,8 +2,8 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from places.models import Area -from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment -from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer +from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment, FireAlarmEquipment +from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer, FireAlarmEquipmentSerializer from .permissions import IsEquipmentDetailOwner, IsPlaceOwner from rest_framework import status @@ -65,4 +65,31 @@ def create(self, request, *args, **kwargs): class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + +class FireAlarmEquipmentList(generics.ListCreateAPIView): + queryset = FireAlarmEquipment.objects.all() + serializer_class = FireAlarmEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return FireAlarmEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + area_id = request.data.get('area') + area = Area.objects.filter(id=area_id).first() + + if area and area.place.place_owner == request.user.placeowner: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + +class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = FireAlarmEquipment.objects.all() + serializer_class = FireAlarmEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] \ No newline at end of file From 2d78d49ea58e9bee1b584f1a0a9c71fee5b97121 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 26 Apr 2024 15:15:34 -0300 Subject: [PATCH 068/351] Backend: Adiciona Serializers, urls e views do SructeredCablingEquipment Co-authored-by: Pedro Lucas --- api/equipments/serializers.py | 8 ++++++- api/equipments/urls.py | 5 +++- api/equipments/views.py | 43 ++++++++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index ee274e91..e923eacc 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment, FireAlarmEquipment +from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment, FireAlarmEquipment, SructeredCablingEquipment class EquipmentTypeSerializer(serializers.ModelSerializer): @@ -25,3 +25,9 @@ class Meta: model = FireAlarmEquipment fields = '__all__' +class SructeredCablingEquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = SructeredCablingEquipment + fields = '__all__' + diff --git a/api/equipments/urls.py b/api/equipments/urls.py index ed78de15..2209c4f9 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -1,4 +1,4 @@ -from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail, AtmosphericDischargeEquipmentList, AtmosphericDischargeEquipmentDetail, FireAlarmEquipmentList, FireAlarmEquipmentDetail +from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail, AtmosphericDischargeEquipmentList, AtmosphericDischargeEquipmentDetail, FireAlarmEquipmentList, FireAlarmEquipmentDetail, SructeredCablingEquipmentList, SructeredCablingEquipmentDetail from django.urls import path urlpatterns = [ @@ -10,4 +10,7 @@ path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()), path('fire-alarms/', FireAlarmEquipmentList.as_view()), path('fire-alarms//', FireAlarmEquipmentDetail.as_view()), + path('sructered-cabling/', SructeredCablingEquipmentList.as_view()), + path('sructered-cabling//', SructeredCablingEquipmentDetail.as_view()), + ] diff --git a/api/equipments/views.py b/api/equipments/views.py index f88a5533..89ebd0a1 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -2,8 +2,8 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from places.models import Area -from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment, FireAlarmEquipment -from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer, FireAlarmEquipmentSerializer +from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment, FireAlarmEquipment, SructeredCablingEquipment +from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer, FireAlarmEquipmentSerializer, SructeredCablingEquipmentSerializer from .permissions import IsEquipmentDetailOwner, IsPlaceOwner from rest_framework import status @@ -53,7 +53,11 @@ def get_queryset(self): def create(self, request, *args, **kwargs): area_id = request.data.get('area') area = Area.objects.filter(id=area_id).first() - if area and area.place.place_owner == request.user.placeowner: + + equipment_detail_id = request.data.get('equipment_detail') + equipment_detail = EquipmentDetail.objects.filter(id=equipment_detail_id).first() + + if area and area.place.place_owner == request.user.placeowner and equipment_detail.equipment_type.system == 8: serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() @@ -92,4 +96,37 @@ def create(self, request, *args, **kwargs): class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + + + + + + +class SructeredCablingEquipmentList(generics.ListCreateAPIView): + queryset = SructeredCablingEquipment.objects.all() + serializer_class = SructeredCablingEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return SructeredCablingEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + area_id = request.data.get('area') + area = Area.objects.filter(id=area_id).first() + + if area and area.place.place_owner == request.user.placeowner: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + +class SructeredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = SructeredCablingEquipment.objects.all() + serializer_class = SructeredCablingEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] \ No newline at end of file From ec01f58725aabf784e32119a736e73f05a74845d Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 26 Apr 2024 15:24:26 -0300 Subject: [PATCH 069/351] backend: filtra equipment_detail pelo system_id --- api/equipments/views.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/api/equipments/views.py b/api/equipments/views.py index 89ebd0a1..2022604f 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -53,18 +53,20 @@ def get_queryset(self): def create(self, request, *args, **kwargs): area_id = request.data.get('area') area = Area.objects.filter(id=area_id).first() - - equipment_detail_id = request.data.get('equipment_detail') - equipment_detail = EquipmentDetail.objects.filter(id=equipment_detail_id).first() - if area and area.place.place_owner == request.user.placeowner and equipment_detail.equipment_type.system == 8: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + if area and area.place.place_owner == request.user.placeowner: + system_id = 8 + equipment_detail_id = request.data.get('equipment_detail') + equipment_detail = EquipmentDetail.objects.filter(id=equipment_detail_id, equipmentType__system_id=system_id).first() + + if equipment_detail: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() From 7729a7ed13faee6227082c2ebb2f457455df8bf1 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 26 Apr 2024 15:59:26 -0300 Subject: [PATCH 070/351] backend: fix bug no nome da view de equipment detail --- api/equipments/urls.py | 4 ++-- api/equipments/views.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 2209c4f9..1ed78f11 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -1,11 +1,11 @@ -from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetail, AtmosphericDischargeEquipmentList, AtmosphericDischargeEquipmentDetail, FireAlarmEquipmentList, FireAlarmEquipmentDetail, SructeredCablingEquipmentList, SructeredCablingEquipmentDetail +from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetailDetail, AtmosphericDischargeEquipmentList, AtmosphericDischargeEquipmentDetail, FireAlarmEquipmentList, FireAlarmEquipmentDetail, SructeredCablingEquipmentList, SructeredCablingEquipmentDetail from django.urls import path urlpatterns = [ path('equipment-type/', EquipmentTypeList.as_view()), path('equipment-type//', EquipmentTypeDetail.as_view()), path('equipments/', EquipmentDetailList.as_view()), - path('equipments//', EquipmentDetail.as_view()), + path('equipments//', EquipmentDetailDetail.as_view()), path('atmospheric-discharges/', AtmosphericDischargeEquipmentList.as_view()), path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()), path('fire-alarms/', FireAlarmEquipmentList.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index 2022604f..24f54a54 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -36,7 +36,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) -class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): +class EquipmentDetailDetail(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentDetail.objects.all() serializer_class = EquipmentDetailSerializer permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] @@ -66,7 +66,9 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "The equipment detail does not belong to the specified system"}, status=status.HTTP_400_BAD_REQUEST) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() From 59d8745bbba8e3ce78525c422bf78790c8907a55 Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 26 Apr 2024 16:29:53 -0300 Subject: [PATCH 071/351] =?UTF-8?q?fix:=20Arruma=20importa=C3=A7=C3=B5es?= =?UTF-8?q?=20e=20arquivos=20front=20end?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/main.dart | 4 +++- .../lib/places/feature/register/register_new_place.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 78a22c17..3464d2bb 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -6,6 +6,7 @@ import 'package:sige_ie/places/feature/register/register_new_place.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; +import 'package:sige_ie/places/register/room_state.dart'; import 'core/feature/login/login.dart'; void main() { @@ -28,7 +29,8 @@ class MyApp extends StatelessWidget { '/facilitiesScreen': (context) => HomePage(), '/MapsPage': (context) => MapsPage(), '/profileScreen': (context) => HomePage(), - '/newLocation': (context) => newLocation(), + '/newLocation': (context) => NewPlace(), + '/roomlocation':(context) => Roomlocation() }, ); } diff --git a/frontend/sige_ie/lib/places/feature/register/register_new_place.dart b/frontend/sige_ie/lib/places/feature/register/register_new_place.dart index 4f55f12d..0987239a 100644 --- a/frontend/sige_ie/lib/places/feature/register/register_new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/register_new_place.dart @@ -166,7 +166,7 @@ class _NewPlaceState extends State { if (coord && _nameController.text.trim().isNotEmpty) { // Código para registrar o local print('Local Registrado: ${_nameController.text}'); - Navigator.of(context).pushNamed('?'); + Navigator.of(context).pushNamed('/roomlocation'); } else if (_nameController.text.trim().isEmpty) { showDialog( context: context, From a97940b6fbe716ccb3729a84caa92f060ad3f0d3 Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 26 Apr 2024 16:30:25 -0300 Subject: [PATCH 072/351] Frontend: Adiciona local sala --- .../lib/places/register/room_state.dart | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 frontend/sige_ie/lib/places/register/room_state.dart diff --git a/frontend/sige_ie/lib/places/register/room_state.dart b/frontend/sige_ie/lib/places/register/room_state.dart new file mode 100644 index 00000000..8f54c058 --- /dev/null +++ b/frontend/sige_ie/lib/places/register/room_state.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + + +class Roomlocation extends StatefulWidget { + @override + _RoomlocationState createState() => _RoomlocationState(); +} + +class _RoomlocationState extends State{ + + @override + Widget build(BuildContext context){ + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: Icon(Icons.arrow_back,color:Colors.white), + onPressed: ()=> Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child:Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child:Text('Local-Sala', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color:Colors.white )), + ), + ), + ], + ), + ), + ); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7297049d9507b50371dc4295eabe363cfc819bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Mon, 29 Apr 2024 14:58:36 -0300 Subject: [PATCH 073/351] Backend: cria serializers de todos sistemas --- api/equipments/serializers.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index e923eacc..9d7a6979 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment, FireAlarmEquipment, SructeredCablingEquipment +from .models import * class EquipmentTypeSerializer(serializers.ModelSerializer): @@ -31,3 +31,34 @@ class Meta: model = SructeredCablingEquipment fields = '__all__' +class DistributionBoardEquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = DistributionBoardEquipment + fields = '__all__' + +class ElectricalCircuitEquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = ElectricalCircuitEquipment + fields = '__all__' + + +class ElectricalLineEquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = ElectricalLineEquipment + fields = '__all__' + +class ElectricalLoadEquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = ElectricalLoadEquipment + fields = '__all__' + +class IluminationEquipmentSerializer(serializers.ModelSerializer): + + class Meta: + model = IluminationEquipment + fields = '__all__' + \ No newline at end of file From 6beac563bc462a02b732cfad4eda5eb19f1e8d06 Mon Sep 17 00:00:00 2001 From: ramires Date: Mon, 29 Apr 2024 13:45:57 -0300 Subject: [PATCH 074/351] adicionar andar da sala e nome do sala --- .../lib/places/register/room_state.dart | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/frontend/sige_ie/lib/places/register/room_state.dart b/frontend/sige_ie/lib/places/register/room_state.dart index 8f54c058..ddc6433c 100644 --- a/frontend/sige_ie/lib/places/register/room_state.dart +++ b/frontend/sige_ie/lib/places/register/room_state.dart @@ -9,6 +9,9 @@ class Roomlocation extends StatefulWidget { class _RoomlocationState extends State{ + String? selectedFloor; + final List floors = ['Andar 1','Andar 2','Andar 3','Andar 4','Andar 5']; + @override Widget build(BuildContext context){ return Scaffold( @@ -39,6 +42,72 @@ class _RoomlocationState extends State{ color:Colors.white )), ), ), + SizedBox(height:60), + Padding( + padding:const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children:[ + Text('Andar', + style: TextStyle( + fontSize:16, + fontWeight: FontWeight.bold, + color: Colors.black )), + SizedBox(height:10), + Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: + BorderRadius.circular(10), + ), + child: DropdownButtonHideUnderline( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + value: selectedFloor, + hint: Text('Selecione o Andar'), + isExpanded: true, + items: floors.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (newValue) { + setState(() { + selectedFloor = newValue; + }); + }, + ), + ), + ), + ), + SizedBox(height:40), + Text('Sala', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color:Colors.black)), + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: + BorderRadius.circular(10), + ), + child: TextField( + decoration: InputDecoration( + hintText: 'Digite o nome da Sala', + border:InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10), + ), + ), + ), + SizedBox(height: 60), + + ], + ), ) ], ), ), From afb2a0302429e902655b51fdfe94135eb6a7156c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Tue, 30 Apr 2024 12:46:42 -0300 Subject: [PATCH 075/351] Backend: adiciona views faltantes --- api/equipments/urls.py | 12 +++- api/equipments/views.py | 155 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 155 insertions(+), 12 deletions(-) diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 1ed78f11..74d58ca7 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -1,4 +1,4 @@ -from .views import EquipmentTypeList, EquipmentTypeDetail, EquipmentDetailList, EquipmentDetailDetail, AtmosphericDischargeEquipmentList, AtmosphericDischargeEquipmentDetail, FireAlarmEquipmentList, FireAlarmEquipmentDetail, SructeredCablingEquipmentList, SructeredCablingEquipmentDetail +from .views import * from django.urls import path urlpatterns = [ @@ -12,5 +12,15 @@ path('fire-alarms//', FireAlarmEquipmentDetail.as_view()), path('sructered-cabling/', SructeredCablingEquipmentList.as_view()), path('sructered-cabling//', SructeredCablingEquipmentDetail.as_view()), + path('distribution-boards/', DistributionBoardEquipmentList.as_view()), + path('distribution-boards//', DistributionBoardEquipmentDetail.as_view()), + path('electrical-circuits/', ElectricalCircuitEquipmentList.as_view()), + path('electrical-circuits//', ElectricalCircuitEquipmentDetail.as_view()), + path('electrical-lines/', ElectricalLineEquipmentList.as_view()), + path('electrical-lines//', ElectricalLineEquipmentDetail.as_view()), + path('electrical-loads/', ElectricalLoadEquipmentList.as_view()), + path('electrical-loads//', ElectricalLoadEquipmentDetail.as_view()), + path('iluminations/', IluminationEquipmentList.as_view()), + path('iluminations//', IluminationEquipmentDetail.as_view()) ] diff --git a/api/equipments/views.py b/api/equipments/views.py index 24f54a54..35425b77 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -2,9 +2,9 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from places.models import Area -from .models import EquipmentType, EquipmentDetail, AtmosphericDischargeEquipment, FireAlarmEquipment, SructeredCablingEquipment -from .serializers import EquipmentTypeSerializer, EquipmentDetailSerializer, AtmosphericDischargeEquipmentSerializer, FireAlarmEquipmentSerializer, SructeredCablingEquipmentSerializer -from .permissions import IsEquipmentDetailOwner, IsPlaceOwner +from .models import * +from .serializers import * +from .permissions import * from rest_framework import status class EquipmentTypeList(generics.ListAPIView): @@ -102,12 +102,6 @@ class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): serializer_class = FireAlarmEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] - - - - - - class SructeredCablingEquipmentList(generics.ListCreateAPIView): queryset = SructeredCablingEquipment.objects.all() serializer_class = SructeredCablingEquipmentSerializer @@ -121,7 +115,7 @@ def create(self, request, *args, **kwargs): area_id = request.data.get('area') area = Area.objects.filter(id=area_id).first() - if area and area.place.place_owner == request.user.placeowner: + if area is not None and area.place.place_owner == request.user.placeowner: serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() @@ -133,4 +127,143 @@ def create(self, request, *args, **kwargs): class SructeredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = SructeredCablingEquipment.objects.all() serializer_class = SructeredCablingEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] \ No newline at end of file + permission_classes = [IsPlaceOwner, IsAuthenticated] + +class DistributionBoardEquipmentList(generics.ListCreateAPIView): + queryset = DistributionBoardEquipment.objects.all() + serializer_class = DistributionBoardEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return DistributionBoardEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + area_id = request.data.get('area') + area = Area.objects.filter(id=area_id).first() + + if area is not None and area.place.place_owner == request.user.placeowner: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + +class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = DistributionBoardEquipment.objects.all() + serializer_class = DistributionBoardEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + +class ElectricalCircuitEquipmentList(generics.ListCreateAPIView): + queryset = ElectricalCircuitEquipment.objects.all() + serializer_class = ElectricalCircuitEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return ElectricalCircuitEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + area_id = request.data.get('area') + area = Area.objects.filter(id=area_id).first() + + if area is not None and area.place.place_owner == request.user.placeowner: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + +class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = ElectricalCircuitEquipment.objects.all() + serializer_class = ElectricalCircuitEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + +class ElectricalLineEquipmentList(generics.ListCreateAPIView): + queryset = ElectricalLineEquipment.objects.all() + serializer_class = ElectricalLineEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return ElectricalLineEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + area_id = request.data.get('area') + area = Area.objects.filter(id=area_id).first() + + if area is not None and area.place.place_owner == request.user.placeowner: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + +class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = ElectricalLineEquipment.objects.all() + serializer_class = ElectricalLineEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + +class ElectricalLoadEquipmentList(generics.ListCreateAPIView): + queryset = ElectricalLoadEquipment.objects.all() + serializer_class = ElectricalLoadEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return ElectricalLoadEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + area_id = request.data.get('area') + area = Area.objects.filter(id=area_id).first() + + if area is not None and area.place.place_owner == request.user.placeowner: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + +class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = ElectricalLoadEquipment.objects.all() + serializer_class = ElectricalLoadEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + +class IluminationEquipmentList(generics.ListCreateAPIView): + queryset = IluminationEquipment.objects.all() + serializer_class = IluminationEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return IluminationEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + area_id = request.data.get('area') + area = Area.objects.filter(id=area_id).first() + + if area is not None and area.place.place_owner == request.user.placeowner: + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + else: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + +class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = IluminationEquipment.objects.all() + serializer_class = IluminationEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + + + From 08df1fdd0c27e9bc53cdcc4bb99871c5930dcb0f Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 1 May 2024 15:28:43 -0300 Subject: [PATCH 076/351] frontend: refatora core, place e users e conecta place com backend --- frontend/sige_ie/lib/config/app_styles.dart | 4 +- .../lib/core/data/auth_interceptor.dart | 7 +- .../sige_ie/lib/core/data/auth_service.dart | 32 +++--- .../sige_ie/lib/core/feature/login/login.dart | 23 +++-- .../lib/core/feature/register/register.dart | 51 +++++----- frontend/sige_ie/lib/core/ui/facilities.dart | 2 +- frontend/sige_ie/lib/core/ui/first_scren.dart | 23 +++-- .../sige_ie/lib/core/ui/splash_screen.dart | 2 +- frontend/sige_ie/lib/home/ui/home.dart | 40 ++++---- frontend/sige_ie/lib/main.dart | 6 +- frontend/sige_ie/lib/maps/feature/maps.dart | 2 +- .../lib/places/data/place_request_model.dart | 5 - .../lib/places/data/place_service.dart | 34 +++---- ...register_new_place.dart => new_place.dart} | 99 +++++++++---------- .../{ => feature}/register/room_state.dart | 0 .../lib/users/data/user_request_model.dart | 21 ++++ ...er_model.dart => user_response_model.dart} | 11 ++- .../sige_ie/lib/users/data/user_service.dart | 62 ++++-------- .../sige_ie/lib/users/feature/profile.dart | 70 ++++++------- 19 files changed, 240 insertions(+), 254 deletions(-) rename frontend/sige_ie/lib/places/feature/register/{register_new_place.dart => new_place.dart} (64%) rename frontend/sige_ie/lib/places/{ => feature}/register/room_state.dart (100%) create mode 100644 frontend/sige_ie/lib/users/data/user_request_model.dart rename frontend/sige_ie/lib/users/data/{user_model.dart => user_response_model.dart} (53%) diff --git a/frontend/sige_ie/lib/config/app_styles.dart b/frontend/sige_ie/lib/config/app_styles.dart index 43c9a6e7..aefd2003 100644 --- a/frontend/sige_ie/lib/config/app_styles.dart +++ b/frontend/sige_ie/lib/config/app_styles.dart @@ -11,13 +11,13 @@ class AppColors { class AppButtonStyles { static ButtonStyle warnButton = ElevatedButton.styleFrom( - backgroundColor: Color.fromARGB(255, 231, 27, 27), + backgroundColor: const Color.fromARGB(255, 231, 27, 27), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ); static ButtonStyle accentButton = ElevatedButton.styleFrom( - backgroundColor: Color.fromARGB(255, 231, 160, 27), + backgroundColor: const Color.fromARGB(255, 231, 160, 27), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), diff --git a/frontend/sige_ie/lib/core/data/auth_interceptor.dart b/frontend/sige_ie/lib/core/data/auth_interceptor.dart index 230719bf..3b0da707 100644 --- a/frontend/sige_ie/lib/core/data/auth_interceptor.dart +++ b/frontend/sige_ie/lib/core/data/auth_interceptor.dart @@ -8,7 +8,8 @@ class AuthInterceptor implements InterceptorContract { @override Future interceptRequest({required RequestData data}) async { - var cookies = await cookieJar.loadForRequest(Uri.parse('http://10.0.2.2:8000/api/login/')); + var cookies = await cookieJar + .loadForRequest(Uri.parse('http://10.0.2.2:8000/api/login/')); var sessionCookie; for (var cookie in cookies) { if (cookie.name == 'sessionid') { @@ -17,7 +18,8 @@ class AuthInterceptor implements InterceptorContract { } } if (sessionCookie != null) { - data.headers.addAll({'Cookie': '${sessionCookie.name}=${sessionCookie.value}'}); + data.headers + .addAll({'Cookie': '${sessionCookie.name}=${sessionCookie.value}'}); } return data; } @@ -27,4 +29,3 @@ class AuthInterceptor implements InterceptorContract { return data; } } - diff --git a/frontend/sige_ie/lib/core/data/auth_service.dart b/frontend/sige_ie/lib/core/data/auth_service.dart index bd369cb8..b645f393 100644 --- a/frontend/sige_ie/lib/core/data/auth_service.dart +++ b/frontend/sige_ie/lib/core/data/auth_service.dart @@ -6,7 +6,6 @@ import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; class AuthService { - static Future fetchCsrfToken() async { const String url = 'http://10.0.2.2:8000/api/csrfcookie/'; final response = await http.get(Uri.parse(url)); @@ -52,7 +51,8 @@ class AuthService { interceptors: [AuthInterceptor(cookieJar)], ); - final response = await client.get(Uri.parse('http://10.0.2.2:8000/api/checkauth/')); + final response = + await client.get(Uri.parse('http://10.0.2.2:8000/api/checkauth/')); if (response.statusCode == 200) { var data = jsonDecode(response.body); @@ -70,9 +70,7 @@ class AuthService { try { var response = await http.post(url, - headers: { - 'Content-Type': 'application/json' - }, + headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'username': username, 'password': password, @@ -94,15 +92,14 @@ class AuthService { Uri.parse('http://10.0.2.2:8000/api/login/'), [cookie]); if (response.statusCode == 200) { - var data = jsonDecode(response.body); - print("Login bem-sucedido: $data"); + //print("Login bem-sucedido: $data"); return true; } else { - print("Falha no login: ${response.body}"); + //print("Falha no login: ${response.body}"); return false; } } catch (e) { - print("Erro ao tentar fazer login: $e"); + //print("Erro ao tentar fazer login: $e"); return false; } } @@ -115,21 +112,18 @@ class AuthService { try { var client = InterceptedClient.build(interceptors: [AuthInterceptor(cookieJar)]); - var response = await client.post(url, headers: { - 'Content-Type': 'application/json' - }); + var response = + await client.post(url, headers: {'Content-Type': 'application/json'}); cookieJar.deleteAll(); if (response.statusCode == 200) { - print("Logout bem-sucedido"); + //print("Logout bem-sucedido"); } else { - print("Falha no logout: ${response.body}"); - - bool isAuth = await checkAuthenticated(); - print(isAuth); + //print("Falha no logout: ${response.body}"); + //bool isAuth = await checkAuthenticated(); + //print(isAuth); } } catch (e) { - print("Erro ao tentar fazer logout: $e"); + //print("Erro ao tentar fazer logout: $e"); } } - } diff --git a/frontend/sige_ie/lib/core/feature/login/login.dart b/frontend/sige_ie/lib/core/feature/login/login.dart index acc45662..e41cfd47 100644 --- a/frontend/sige_ie/lib/core/feature/login/login.dart +++ b/frontend/sige_ie/lib/core/feature/login/login.dart @@ -19,7 +19,7 @@ class _LoginScreenState extends State { return Scaffold( backgroundColor: const Color(0xff123c75), appBar: AppBar( - iconTheme: IconThemeData(color: Colors.white), + iconTheme: const IconThemeData(color: Colors.white), backgroundColor: const Color(0xff123c75), ), body: Center( @@ -28,7 +28,7 @@ class _LoginScreenState extends State { Expanded( flex: 2, child: Container( - decoration: BoxDecoration( + decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/1000x1000.png'), fit: BoxFit.cover, @@ -50,7 +50,7 @@ class _LoginScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( + const Text( 'Login', style: TextStyle( fontSize: 30.0, @@ -132,7 +132,7 @@ class _LoginScreenState extends State { }); }, activeColor: - Color.fromARGB(255, 12, 78, 170)), + const Color.fromARGB(255, 12, 78, 170)), const Text( 'Manter conectado', style: TextStyle( @@ -142,7 +142,7 @@ class _LoginScreenState extends State { ], ), GestureDetector( - child: Text( + child: const Text( 'Esqueceu a senha?', style: TextStyle( fontWeight: FontWeight.bold, @@ -168,9 +168,8 @@ class _LoginScreenState extends State { usernameController.text, passwordController.text); - bool isAuth = - await authService.checkAuthenticated(); - print(isAuth); + //bool isAuth = await authService.checkAuthenticated(); + //print(isAuth); ScaffoldMessenger.of(context) .hideCurrentSnackBar(); @@ -201,8 +200,8 @@ class _LoginScreenState extends State { style: ElevatedButton.styleFrom( elevation: 6, backgroundColor: - Color.fromARGB(255, 244, 248, 0), - foregroundColor: Color(0xff123c75), + const Color.fromARGB(255, 244, 248, 0), + foregroundColor: const Color(0xff123c75), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), )), @@ -222,11 +221,11 @@ class _LoginScreenState extends State { Navigator.pushNamed( context, '/registerScreen'); }, - child: Text( + child: const Text( 'Registre-se', style: TextStyle( fontWeight: FontWeight.bold, - color: const Color(0xff123c75), + color: Color(0xff123c75), ), ), ), diff --git a/frontend/sige_ie/lib/core/feature/register/register.dart b/frontend/sige_ie/lib/core/feature/register/register.dart index 54ec27cb..a21783be 100644 --- a/frontend/sige_ie/lib/core/feature/register/register.dart +++ b/frontend/sige_ie/lib/core/feature/register/register.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; +import 'package:sige_ie/users/data/user_request_model.dart'; import 'package:sige_ie/users/data/user_service.dart'; class RegisterScreen extends StatefulWidget { @@ -8,31 +8,30 @@ class RegisterScreen extends StatefulWidget { State createState() => _RegisterScreenState(); } -// Definição da classe da tela de Registro class _RegisterScreenState extends State { + UserService userService = UserService(); bool terms = true; final _registerScreen = GlobalKey(); - final TextEditingController usernameController = TextEditingController(); - final TextEditingController nameController = TextEditingController(); - final TextEditingController passwordController = TextEditingController(); - final TextEditingController emailController = TextEditingController(); + final usernameController = TextEditingController(); + final nameController = TextEditingController(); + final passwordController = TextEditingController(); + final emailController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xff123c75), appBar: AppBar( - iconTheme: IconThemeData(color: Colors.white), + iconTheme: const IconThemeData(color: Colors.white), backgroundColor: const Color(0xff123c75), ), body: Center( - // Logo da Página e Seu Formato child: Column( children: [ Expanded( flex: 2, child: Container( - decoration: BoxDecoration( + decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/1000x1000.png'), fit: BoxFit.cover, @@ -41,7 +40,7 @@ class _RegisterScreenState extends State { ), ), Expanded( - flex: 6, // Configuração da página + flex: 6, child: Container( padding: const EdgeInsets.fromLTRB(25.0, 50.0, 25.0, 20.0), decoration: const BoxDecoration( @@ -54,7 +53,7 @@ class _RegisterScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text('Registro', // Nome da Página de Registro + const Text('Registro', style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.w900, @@ -81,7 +80,6 @@ class _RegisterScreenState extends State { ), borderRadius: BorderRadius.circular(10), ), - // Outras propriedades... ), validator: (value) { if (value == null || value.isEmpty) { @@ -224,12 +222,12 @@ class _RegisterScreenState extends State { terms = value!; }); }, - activeColor: - Color.fromARGB(255, 12, 78, 170)), + activeColor: const Color.fromARGB( + 255, 12, 78, 170)), const Text( 'Aceite os Termos', style: TextStyle( - color: const Color(0xff123c75), + color: Color(0xff123c75), ), ), ], @@ -249,11 +247,16 @@ class _RegisterScreenState extends State { content: Text('Processando Dados'), ), ); - bool success = await UserService.register( - usernameController.text, - nameController.text, - passwordController.text, - emailController.text); + + final user = UserRequestModel( + username: usernameController.text, + name: nameController.text, + password: passwordController.text, + email: emailController.text, + ); + + bool success = + await userService.register(user); ScaffoldMessenger.of(context) .hideCurrentSnackBar(); @@ -288,8 +291,8 @@ class _RegisterScreenState extends State { style: ElevatedButton.styleFrom( elevation: 6, backgroundColor: - Color.fromARGB(255, 244, 248, 0), - foregroundColor: Color(0xff123c75), + const Color.fromARGB(255, 244, 248, 0), + foregroundColor: const Color(0xff123c75), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), )), @@ -308,11 +311,11 @@ class _RegisterScreenState extends State { onTap: () { Navigator.pushNamed(context, '/loginScreen'); }, - child: Text( + child: const Text( 'Fazer login', style: TextStyle( fontWeight: FontWeight.bold, - color: const Color(0xff123c75), + color: Color(0xff123c75), ), ), ), diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index 40be26e4..7fdf777c 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -10,7 +10,7 @@ class _FacilitiesPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('App de Navegação'), + title: const Text('App de Navegação'), automaticallyImplyLeading: false, )); } diff --git a/frontend/sige_ie/lib/core/ui/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_scren.dart index 3f6d3b94..8affbef2 100644 --- a/frontend/sige_ie/lib/core/ui/first_scren.dart +++ b/frontend/sige_ie/lib/core/ui/first_scren.dart @@ -4,7 +4,7 @@ class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Color(0xff123c75), + backgroundColor: const Color(0xff123c75), body: Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -14,38 +14,37 @@ class FirstScreen extends StatelessWidget { onPressed: () { Navigator.pushNamed(context, '/loginScreen'); }, - child: Text( + child: const Text( "Login", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), style: ElevatedButton.styleFrom( elevation: 6, - minimumSize: Size(200, 50), - backgroundColor: Color(0xfff1f60e), - foregroundColor: Color(0xff123c75), + minimumSize: const Size(200, 50), + backgroundColor: const Color(0xfff1f60e), + foregroundColor: const Color(0xff123c75), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 12), // Arredondamento dos cantos do botão ))), - SizedBox( + const SizedBox( height: 15, // Espaço entre os botões ), ElevatedButton( onPressed: () { Navigator.pushNamed(context, '/registerScreen'); }, - child: Text( + child: const Text( "Registro", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), style: ElevatedButton.styleFrom( elevation: 6, - minimumSize: Size(200, 50), - backgroundColor: Color(0xfff1f60e), - foregroundColor: Color(0xff123c75), + minimumSize: const Size(200, 50), + backgroundColor: const Color(0xfff1f60e), + foregroundColor: const Color(0xff123c75), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 12), // Arredondamento dos cantos do botão + borderRadius: BorderRadius.circular(12), )), ), ], diff --git a/frontend/sige_ie/lib/core/ui/splash_screen.dart b/frontend/sige_ie/lib/core/ui/splash_screen.dart index 9cc10ca3..199db34f 100644 --- a/frontend/sige_ie/lib/core/ui/splash_screen.dart +++ b/frontend/sige_ie/lib/core/ui/splash_screen.dart @@ -49,7 +49,7 @@ class _SplashScreenState extends State { ), ), ) - : CircularProgressIndicator(), // Mostra o indicador de carregamento enquanto o vídeo está carregando + : const CircularProgressIndicator(), // Mostra o indicador de carregamento enquanto o vídeo está carregando ), ); } diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 314359b2..13f318c9 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -52,13 +52,13 @@ class _HomePageState extends State { children: [ AppBar( automaticallyImplyLeading: false, - backgroundColor: Color(0xff123c75), + backgroundColor: const Color(0xff123c75), elevation: 0, ), Expanded( flex: 3, child: Container( - decoration: BoxDecoration( + decoration: const BoxDecoration( color: AppColors.sigeIeBlue, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), @@ -70,7 +70,7 @@ class _HomePageState extends State { children: [ Expanded( child: Container( - decoration: BoxDecoration( + decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/1000x1000.png'), fit: BoxFit.cover, @@ -80,8 +80,8 @@ class _HomePageState extends State { ), Container( alignment: Alignment.centerLeft, - padding: EdgeInsets.only(left: 20), - child: Text( + padding: const EdgeInsets.only(left: 20), + child: const Text( 'Olá, ', style: TextStyle( color: AppColors.sigeIeYellow, @@ -89,7 +89,7 @@ class _HomePageState extends State { ), ), ), - SizedBox(height: 10), + const SizedBox(height: 10), ], ), ), @@ -98,16 +98,16 @@ class _HomePageState extends State { flex: 6, child: Column( children: [ - Spacer(), + const Spacer(), buildSmallRectangle(context, 'Registrar novo local', 'Registrar', () { Navigator.of(context).pushNamed('/newLocation'); - print("Registrar novo local clicado"); + //print("Registrar novo local clicado"); }), buildSmallRectangle(context, 'Gerenciar locais', 'Gerenciar', () { - print("Gerenciar locais clicado"); + //print("Gerenciar locais clicado"); }), - Spacer(), + const Spacer(), ], ), ), @@ -119,16 +119,16 @@ class _HomePageState extends State { String buttonText, VoidCallback onPress) { return Container( decoration: BoxDecoration( - color: Color(0xff123c75), + color: const Color(0xff123c75), borderRadius: BorderRadius.circular(20), boxShadow: [ - BoxShadow(color: Colors.black38, spreadRadius: 0, blurRadius: 10), + const BoxShadow(color: Colors.black, spreadRadius: 0, blurRadius: 10), ], ), height: 135, width: MediaQuery.of(context).size.width * 0.8, - margin: EdgeInsets.symmetric(vertical: 15), - padding: EdgeInsets.all(20), + margin: const EdgeInsets.symmetric(vertical: 15), + padding: const EdgeInsets.all(20), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -136,19 +136,19 @@ class _HomePageState extends State { Text( text, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), - SizedBox(height: 10), + const SizedBox(height: 10), ElevatedButton( style: AppButtonStyles.standardButton, onPressed: onPress, child: Text( buttonText, - style: TextStyle( + style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), @@ -161,7 +161,7 @@ class _HomePageState extends State { Widget buildBottomNavigationBar() { return Container( - decoration: BoxDecoration( + decoration: const BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), @@ -171,7 +171,7 @@ class _HomePageState extends State { ], ), child: ClipRRect( - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), @@ -195,7 +195,7 @@ class _HomePageState extends State { backgroundColor: Color(0xFFF1F60E)), ], currentIndex: _selectedIndex, - selectedItemColor: Color(0xFF123C75), + selectedItemColor: const Color(0xFF123C75), unselectedItemColor: const Color.fromARGB(255, 145, 142, 142), onTap: _onItemTapped, ), diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 3464d2bb..088c7a73 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -2,11 +2,11 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; -import 'package:sige_ie/places/feature/register/register_new_place.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/places/register/room_state.dart'; +import 'package:sige_ie/places/feature/register/new_place.dart'; +import 'package:sige_ie/places/feature/register/room_state.dart'; import 'core/feature/login/login.dart'; void main() { @@ -30,7 +30,7 @@ class MyApp extends StatelessWidget { '/MapsPage': (context) => MapsPage(), '/profileScreen': (context) => HomePage(), '/newLocation': (context) => NewPlace(), - '/roomlocation':(context) => Roomlocation() + '/roomlocation': (context) => Roomlocation() }, ); } diff --git a/frontend/sige_ie/lib/maps/feature/maps.dart b/frontend/sige_ie/lib/maps/feature/maps.dart index 5b9dcf9a..efba6b49 100644 --- a/frontend/sige_ie/lib/maps/feature/maps.dart +++ b/frontend/sige_ie/lib/maps/feature/maps.dart @@ -10,7 +10,7 @@ class _MapsPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('App de Navegação'), + title: const Text('App de Navegação'), automaticallyImplyLeading: false, )); } diff --git a/frontend/sige_ie/lib/places/data/place_request_model.dart b/frontend/sige_ie/lib/places/data/place_request_model.dart index 6aecbec2..1834ccee 100644 --- a/frontend/sige_ie/lib/places/data/place_request_model.dart +++ b/frontend/sige_ie/lib/places/data/place_request_model.dart @@ -12,9 +12,4 @@ class PlaceRequestModel { 'lat': lat, }; } - - PlaceRequestModel.fromJson(Map json) - : name = json['name'].toString(), - lon = json['lon'].toDouble(), - lat = json['lat'].toDouble(); } diff --git a/frontend/sige_ie/lib/places/data/place_service.dart b/frontend/sige_ie/lib/places/data/place_service.dart index f7ffd4dd..a02e95f7 100644 --- a/frontend/sige_ie/lib/places/data/place_service.dart +++ b/frontend/sige_ie/lib/places/data/place_service.dart @@ -1,27 +1,21 @@ import 'dart:convert'; -import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/main.dart'; import 'package:sige_ie/places/data/place_request_model.dart'; class PlaceService { - static Future register(PlaceRequestModel placeRequestModel) async { + Future register(PlaceRequestModel placeRequestModel) async { + var client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); var url = Uri.parse('http://10.0.2.2:8000/api/places/'); - try { - var response = await http.post( - url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(placeRequestModel.toJson()), - ); - if (response.statusCode == 200 || response.statusCode == 201) { - var data = jsonDecode(response.body); - print("Registro bem-sucedido: $data"); - return true; - } else { - print("Falha no registro: ${response.body}"); - return false; - } - } catch (e) { - print("Erro ao tentar registrar: $e"); - return false; - } + + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(placeRequestModel.toJson()), + ); + return response.statusCode == 201; } } diff --git a/frontend/sige_ie/lib/places/feature/register/register_new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart similarity index 64% rename from frontend/sige_ie/lib/places/feature/register/register_new_place.dart rename to frontend/sige_ie/lib/places/feature/register/new_place.dart index 0987239a..e9af1a01 100644 --- a/frontend/sige_ie/lib/places/feature/register/register_new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -6,16 +6,14 @@ import 'position.dart'; class NewPlace extends StatefulWidget { @override - _NewPlaceState createState() => _NewPlaceState(); + NewPlaceState createState() => NewPlaceState(); } -class _NewPlaceState extends State { +class NewPlaceState extends State { PlaceService placeService = PlaceService(); - PlaceRequestModel placeRequestModel = - PlaceRequestModel(name: '', lon: 0, lat: 0); String coordinates = ''; bool coord = false; - final TextEditingController _nameController = TextEditingController(); + final nameController = TextEditingController(); late PositionController positionController; @override @@ -50,7 +48,7 @@ class _NewPlaceState extends State { appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, leading: IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.of(context).pop(), ), ), @@ -60,13 +58,13 @@ class _NewPlaceState extends State { children: [ Container( width: double.infinity, - padding: EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( color: AppColors.sigeIeBlue, borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), - child: Center( + child: const Center( child: Text('Registrar Novo Local', style: TextStyle( fontSize: 26, @@ -74,34 +72,33 @@ class _NewPlaceState extends State { color: Colors.white)), ), ), - SizedBox(height: 60), + const SizedBox(height: 60), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Coordenadas', + const Text('Coordenadas', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), - SizedBox(height: 10), + const SizedBox(height: 10), Container( decoration: BoxDecoration( - color: Colors.grey[200], // Cinza claro - borderRadius: - BorderRadius.circular(10), // Bordas arredondadas + color: Colors.grey[200], + borderRadius: BorderRadius.circular(10), ), child: Row( children: [ Expanded( child: TextField( - decoration: InputDecoration( + decoration: const InputDecoration( hintText: 'Clique na lupa para obter as coordenadas', - border: InputBorder.none, // Sem bordas internas - contentPadding: EdgeInsets.symmetric( - horizontal: 10), // Padding interno + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10), ), controller: TextEditingController(text: coordinates), @@ -109,36 +106,34 @@ class _NewPlaceState extends State { ), ), IconButton( - icon: Icon(Icons.search), + icon: const Icon(Icons.search), onPressed: _getCoordinates, ), ], ), ), - SizedBox(height: 40), - Text('Nome do Local', + const SizedBox(height: 40), + const Text('Nome do Local', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), - SizedBox(height: 10), + const SizedBox(height: 10), Container( decoration: BoxDecoration( - color: Colors.grey[200], // Cinza claro - borderRadius: - BorderRadius.circular(10), // Bordas arredondadas + color: Colors.grey[200], + borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _nameController, - decoration: InputDecoration( + controller: nameController, + decoration: const InputDecoration( hintText: 'Digite o nome do local', - border: InputBorder.none, // Sem bordas internas - contentPadding: EdgeInsets.symmetric( - horizontal: 10), // Padding interno + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), - SizedBox(height: 60), + const SizedBox(height: 60), Center( child: ElevatedButton( style: ButtonStyle( @@ -146,41 +141,43 @@ class _NewPlaceState extends State { MaterialStateProperty.all(AppColors.sigeIeYellow), foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: MaterialStateProperty.all( - Size(200, 50)), // Tamanho maior para o botão + minimumSize: + MaterialStateProperty.all(const Size(200, 50)), textStyle: MaterialStateProperty.all( - TextStyle( - fontSize: 18, // Aumentar o tamanho do texto - fontWeight: - FontWeight.bold, // Deixar o texto em negrito + const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), ), shape: MaterialStateProperty.all( RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 8), // Bordas arredondadas com raio de 25 + borderRadius: BorderRadius.circular(8), ), ), ), - onPressed: () { - if (coord && _nameController.text.trim().isNotEmpty) { - // Código para registrar o local - print('Local Registrado: ${_nameController.text}'); + onPressed: () async { + if (coord && nameController.text.trim().isNotEmpty) { + final place = PlaceRequestModel( + name: nameController.text, + lon: positionController.lon, + lat: positionController.lat); + + bool success = await placeService.register(place); + print('Local Registrado: ${nameController.text}'); Navigator.of(context).pushNamed('/roomlocation'); - } else if (_nameController.text.trim().isEmpty) { + } else if (nameController.text.trim().isEmpty) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: Text("Erro"), - content: Text( + title: const Text("Erro"), + content: const Text( "Por favor, insira um nome para o local"), actions: [ TextButton( - child: Text("OK"), + child: const Text("OK"), onPressed: () { - Navigator.of(context) - .pop(); // Fecha o dialogo + Navigator.of(context).pop(); }, ), ], @@ -189,7 +186,7 @@ class _NewPlaceState extends State { ); } }, - child: Text('Registrar'), + child: const Text('Registrar'), )), ], ), diff --git a/frontend/sige_ie/lib/places/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart similarity index 100% rename from frontend/sige_ie/lib/places/register/room_state.dart rename to frontend/sige_ie/lib/places/feature/register/room_state.dart diff --git a/frontend/sige_ie/lib/users/data/user_request_model.dart b/frontend/sige_ie/lib/users/data/user_request_model.dart new file mode 100644 index 00000000..3fa0350a --- /dev/null +++ b/frontend/sige_ie/lib/users/data/user_request_model.dart @@ -0,0 +1,21 @@ +class UserRequestModel { + String username; + String name; + String password; + String email; + + UserRequestModel( + {required this.username, + required this.name, + required this.password, + required this.email}); + + Map toJson() { + return { + 'username': name, + 'name': name, + 'password': password, + 'email': email, + }; + } +} diff --git a/frontend/sige_ie/lib/users/data/user_model.dart b/frontend/sige_ie/lib/users/data/user_response_model.dart similarity index 53% rename from frontend/sige_ie/lib/users/data/user_model.dart rename to frontend/sige_ie/lib/users/data/user_response_model.dart index 414ea7d7..19dcf868 100644 --- a/frontend/sige_ie/lib/users/data/user_model.dart +++ b/frontend/sige_ie/lib/users/data/user_response_model.dart @@ -1,13 +1,16 @@ -class UserModel { +class UserResponseModel { String id; String username; String firstname; String email; - UserModel( - {required this.id, required this.username, required this.firstname, required this.email}); + UserResponseModel( + {required this.id, + required this.username, + required this.firstname, + required this.email}); - UserModel.fromJson(Map json) + UserResponseModel.fromJson(Map json) : id = json['id'].toString(), username = json['username'].toString(), firstname = json['first_name'].toString(), diff --git a/frontend/sige_ie/lib/users/data/user_service.dart b/frontend/sige_ie/lib/users/data/user_service.dart index c029ef79..4f77b25e 100644 --- a/frontend/sige_ie/lib/users/data/user_service.dart +++ b/frontend/sige_ie/lib/users/data/user_service.dart @@ -2,35 +2,21 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; + import 'package:sige_ie/main.dart'; -import 'package:sige_ie/users/data/user_model.dart'; +import 'package:sige_ie/users/data/user_request_model.dart'; +import 'package:sige_ie/users/data/user_response_model.dart'; class UserService { - static Future register( - String username, String firstName, String password, String email) async { - //var csrfToken = await AuthService.fetchCsrfToken(); + Future register(UserRequestModel userRequestModel) async { var url = Uri.parse('http://10.0.2.2:8000/api/users/'); - try { - var response = await http.post(url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - 'username': username, - 'first_name': firstName, - 'password': password, - 'email': email, - })); - if (response.statusCode == 200 || response.statusCode == 201) { - var data = jsonDecode(response.body); - print("Registro bem-sucedido: $data"); - return true; - } else { - print("Falha no registro: ${response.body}"); - return false; - } - } catch (e) { - print("Erro ao tentar registrar: $e"); - return false; - } + + var response = await http.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(userRequestModel.toJson()), + ); + return response.statusCode == 201; } Future update(String id, String firstName, String email) async { @@ -38,8 +24,6 @@ class UserService { interceptors: [AuthInterceptor(cookieJar)], ); - //var csrfToken = await AuthService.fetchCsrfToken(); - var url = Uri.parse('http://10.0.2.2:8000/api/users/$id/'); try { var response = await client.put(url, @@ -49,15 +33,14 @@ class UserService { 'email': email, })); if (response.statusCode == 200 || response.statusCode == 201) { - var data = jsonDecode(response.body); - print("Atualizado com sucesso: $data"); + //print("Atualizado com sucesso: $data"); return true; } else { - print("Falha: ${response.body}"); + //print("Falha: ${response.body}"); return false; } } catch (e) { - print("Erro ao tentar registrar: $e"); + //print("Erro ao tentar registrar: $e"); return false; } } @@ -67,27 +50,24 @@ class UserService { interceptors: [AuthInterceptor(cookieJar)], ); - //var csrfToken = await AuthService.fetchCsrfToken(); - var url = Uri.parse('http://10.0.2.2:8000/api/users/$id/'); try { - var response = - await client.delete(url, headers: {'Content-Type': 'application/json'}); + var response = await client + .delete(url, headers: {'Content-Type': 'application/json'}); if (response.statusCode == 204) { - var data = jsonDecode(response.body); - print("Excluido com sucesso: $data"); + //print("Excluido com sucesso: $data"); return true; } else { - print("Falha: ${response.body}"); + //print("Falha: ${response.body}"); return false; } } catch (e) { - print("Erro ao tentar excluir: $e"); + //print("Erro ao tentar excluir: $e"); return false; } } - Future fetchProfileData() async { + Future fetchProfileData() async { var client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); @@ -97,7 +77,7 @@ class UserService { if (response.statusCode == 200) { var data = jsonDecode(response.body); - return UserModel.fromJson(data); + return UserResponseModel.fromJson(data); } else { throw Exception('Falha ao carregar dados do perfil'); } diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index 5ffc8d13..661529c2 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/data/auth_service.dart'; -import 'package:sige_ie/users/data/user_model.dart'; +import 'package:sige_ie/users/data/user_response_model.dart'; import 'package:sige_ie/users/data/user_service.dart'; import 'package:sige_ie/config/app_styles.dart'; @@ -12,18 +12,17 @@ class ProfilePage extends StatefulWidget { class _ProfilePageState extends State { UserService userService = UserService(); AuthService authService = AuthService(); - UserModel userModel = - UserModel(id: '', email: '', firstname: '', username: ''); + UserResponseModel userResponseModel = + UserResponseModel(id: '', email: '', firstname: '', username: ''); @override void initState() { super.initState(); userService.fetchProfileData().then((userModel) { setState(() { - this.userModel = userModel; + userResponseModel = userResponseModel; }); }).catchError((error) { - // Handle error print(error); }); } @@ -32,7 +31,7 @@ class _ProfilePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text( + title: const Text( 'Editar Perfil', style: TextStyle(fontSize: 24), ), @@ -40,49 +39,50 @@ class _ProfilePageState extends State { automaticallyImplyLeading: false, ), body: SingleChildScrollView( - padding: EdgeInsets.all(16), + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( - decoration: InputDecoration(labelText: 'Email'), - controller: TextEditingController(text: userModel.email), - onChanged: (value) => userModel.email = value, + decoration: const InputDecoration(labelText: 'Email'), + controller: TextEditingController(text: userResponseModel.email), + onChanged: (value) => userResponseModel.email = value, ), - SizedBox(height: 10), + const SizedBox(height: 10), TextField( - decoration: InputDecoration(labelText: 'Nome'), - controller: TextEditingController(text: userModel.firstname), - onChanged: (value) => userModel.firstname = value, + decoration: const InputDecoration(labelText: 'Nome'), + controller: + TextEditingController(text: userResponseModel.firstname), + onChanged: (value) => userResponseModel.firstname = value, ), - SizedBox(height: 10), + const SizedBox(height: 10), TextField( - decoration: InputDecoration(labelText: 'Username'), - controller: TextEditingController(text: userModel.username), + decoration: const InputDecoration(labelText: 'Username'), + controller: + TextEditingController(text: userResponseModel.username), enabled: false, ), - SizedBox(height: 20), + const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ InkWell( onTap: () {}, - child: Text( + child: const Text( 'Mudar senha', style: TextStyle(color: Colors.blue), ), ), InkWell( onTap: () {}, - child: Text( + child: const Text( 'Mudar username', - style: TextStyle( - color: const Color.fromARGB(255, 33, 150, 243)), + style: TextStyle(color: Color.fromARGB(255, 33, 150, 243)), ), ), ], ), - SizedBox(height: 80), + const SizedBox(height: 80), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -91,10 +91,10 @@ class _ProfilePageState extends State { height: 50, // Define a altura uniforme child: ElevatedButton( onPressed: () async { - await userService.update( - userModel.id, userModel.firstname, userModel.email); + await userService.update(userResponseModel.id, + userResponseModel.firstname, userResponseModel.email); }, - child: Text('Salvar', + child: const Text('Salvar', style: TextStyle(color: AppColors.dartText)), style: ElevatedButton.styleFrom( backgroundColor: const Color.fromARGB(255, 224, 221, 221), @@ -112,10 +112,10 @@ class _ProfilePageState extends State { await authService.logout(); Navigator.pushReplacementNamed(context, '/loginScreen'); }, - child: Text('Sair da Conta', + child: const Text('Sair da Conta', style: TextStyle(color: AppColors.dartText)), style: ElevatedButton.styleFrom( - backgroundColor: Color.fromARGB(255, 153, 163, 168), + backgroundColor: const Color.fromARGB(255, 153, 163, 168), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -124,7 +124,7 @@ class _ProfilePageState extends State { ), ], ), - SizedBox(height: 80), + const SizedBox(height: 80), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -138,8 +138,8 @@ class _ProfilePageState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Excluir Conta'), - content: Text( + title: const Text('Excluir Conta'), + content: const Text( 'Tem certeza que deseja excluir sua conta?'), actions: [ TextButton( @@ -147,14 +147,14 @@ class _ProfilePageState extends State { Navigator.of(context).pop( false); // Retorna falso para indicar que a exclusão não foi confirmada }, - child: Text('Cancelar'), + child: const Text('Cancelar'), ), TextButton( onPressed: () { Navigator.of(context).pop( true); // Retorna verdadeiro para indicar que a exclusão foi confirmada }, - child: Text('Confirmar'), + child: const Text('Confirmar'), ), ], ); @@ -163,12 +163,12 @@ class _ProfilePageState extends State { // Se a exclusão for confirmada, exclua a conta if (deleteConfirmed) { - await userService.delete(userModel.id); + await userService.delete(userResponseModel.id); Navigator.pushReplacementNamed( context, '/loginScreen'); } }, - child: Text('Excluir Conta', + child: const Text('Excluir Conta', style: TextStyle(color: AppColors.lightText)), style: AppButtonStyles.warnButton), ), From 72ce30e259160f85676f192bff46f33ceea5b9b5 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 1 May 2024 15:49:37 -0300 Subject: [PATCH 077/351] frontend: fix profile --- frontend/sige_ie/lib/users/feature/profile.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index 661529c2..c8801fc1 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -18,9 +18,9 @@ class _ProfilePageState extends State { @override void initState() { super.initState(); - userService.fetchProfileData().then((userModel) { + userService.fetchProfileData().then((userResponseModel) { setState(() { - userResponseModel = userResponseModel; + this.userResponseModel = userResponseModel; }); }).catchError((error) { print(error); From 6f2f9f1af3f4c812e10016392398ecab45f5c76c Mon Sep 17 00:00:00 2001 From: ramires Date: Thu, 2 May 2024 11:37:51 -0300 Subject: [PATCH 078/351] telas finalizadas --- frontend/sige_ie/lib/main.dart | 3 +- .../feature/manage/manage_locations.dart | 0 .../feature/manage/systemConfiguration.dart | 71 +++++++++ .../places/feature/register/room_state.dart | 150 +++++++++++------- 4 files changed, 166 insertions(+), 58 deletions(-) delete mode 100644 frontend/sige_ie/lib/places/feature/manage/manage_locations.dart create mode 100644 frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 088c7a73..c38d14df 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -30,7 +30,8 @@ class MyApp extends StatelessWidget { '/MapsPage': (context) => MapsPage(), '/profileScreen': (context) => HomePage(), '/newLocation': (context) => NewPlace(), - '/roomlocation': (context) => Roomlocation() + '/roomlocation': (context) => RoomLocation(), + '/SystemConfiguration':(context)=> SystemConfiguration(), }, ); } diff --git a/frontend/sige_ie/lib/places/feature/manage/manage_locations.dart b/frontend/sige_ie/lib/places/feature/manage/manage_locations.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart new file mode 100644 index 00000000..e19814b0 --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class SystemConfiguration extends StatefulWidget { + @override + _SystemConfigurationState createState() => _SystemConfigurationState(); +} + +class _SystemConfigurationState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, // Substitua por sua cor personalizada. + title: const Text('Local'), + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: const Text( + 'Quais sistemas deseja configurar?', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + SystemButton(title: 'Baixa Tensão', icon: Icons.flash_on), + SystemButton(title: 'Cabeamento Estruturado', icon: Icons.settings_ethernet), + SystemButton(title: 'Descargas Atmosféricas', icon: Icons.cloud), + SystemButton(title: 'Alarme de Incêndio', icon: Icons.warning), + ], + ), + ), + ); + } +} + +class SystemButton extends StatelessWidget { + final String title; + final IconData icon; + + const SystemButton({Key? key, required this.title, required this.icon}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + width: double.infinity, + child: ElevatedButton.icon( + icon: Icon(icon, color: Colors.black), + label: Text(title, style: const TextStyle(color: Colors.black, fontSize: 18)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.yellow), // Cor de fundo do botão + foregroundColor: MaterialStateProperty.all(Colors.black), // Cor do texto e ícone + padding: MaterialStateProperty.all(const EdgeInsets.symmetric(vertical: 15)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: () { + // Aqui pode-se adicionar a lógica para o que acontece ao pressionar o botão + print('Configuring $title'); + }, + ), + ); + } +} \ No newline at end of file diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index ddc6433c..9d8c7b85 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -1,29 +1,29 @@ import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; - -class Roomlocation extends StatefulWidget { +class RoomLocation extends StatefulWidget { @override - _RoomlocationState createState() => _RoomlocationState(); + _RoomLocationState createState() => _RoomLocationState(); } -class _RoomlocationState extends State{ - +class _RoomLocationState extends State { String? selectedFloor; - final List floors = ['Andar 1','Andar 2','Andar 3','Andar 4','Andar 5']; + final TextEditingController roomController = TextEditingController(); + final List floors = ['Andar 1', 'Andar 2', 'Andar 3', 'Andar 4', 'Andar 5']; @override - Widget build(BuildContext context){ + Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, leading: IconButton( - icon: Icon(Icons.arrow_back,color:Colors.white), - onPressed: ()=> Navigator.of(context).pop(), + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), ), ), body: SingleChildScrollView( - child:Column( + child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Container( @@ -31,36 +31,24 @@ class _RoomlocationState extends State{ padding: EdgeInsets.fromLTRB(10, 10, 10, 35), decoration: BoxDecoration( color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), + borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child:Text('Local-Sala', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color:Colors.white )), + child: Text('Local-Sala', + style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold, color: Colors.white)), ), ), - SizedBox(height:60), - Padding( - padding:const EdgeInsets.symmetric(horizontal: 16.0), + SizedBox(height: 60), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children:[ - Text('Andar', - style: TextStyle( - fontSize:16, - fontWeight: FontWeight.bold, - color: Colors.black )), - SizedBox(height:10), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Andar', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), + SizedBox(height: 10), Container( - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: - BorderRadius.circular(10), - ), - child: DropdownButtonHideUnderline( + decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(10)), + child: DropdownButtonHideUnderline( child: Padding( padding: EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( @@ -82,32 +70,80 @@ class _RoomlocationState extends State{ ), ), ), - SizedBox(height:40), - Text('Sala', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color:Colors.black)), - SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: - BorderRadius.circular(10), + SizedBox(height: 40), + Text('Sala', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), + SizedBox(height: 10), + Container( + decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(10)), + child: TextField( + controller: roomController, + decoration: InputDecoration( + hintText: 'Digite o nome da Sala', + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 10), ), - child: TextField( - decoration: InputDecoration( - hintText: 'Digite o nome da Sala', - border:InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10), + ), + ), + SizedBox(height: 60), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: MaterialStateProperty.all(Size(150, 50)), + textStyle: MaterialStateProperty.all( + TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), ), + onPressed: () { + if (selectedFloor != null && roomController.text.isNotEmpty) { + // Se desejar, insira a lógica de navegação para outra tela aqui + print('Sala Registrada: ${roomController.text} no ${selectedFloor}'); + // Por exemplo, mudar para uma nova rota: + Navigator.of(context).pushNamed('/systemlocation'); + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Erro'), + content: Text("Por favor, selecione um andar e digite o nome da sala"), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + }, + child: Text('CONTINUAR'), ), - ), - SizedBox(height: 60), - - ], - ), ) + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.red), + foregroundColor: MaterialStateProperty.all(Colors.white), + minimumSize: MaterialStateProperty.all(Size(150, 50)), + textStyle: MaterialStateProperty.all( + TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + ), + onPressed: () => Navigator.of(context).pop(), + child: Text('ENCERRAR'), + ), + ], + ), + ], + ), + ) ], ), ), From 177e3de7ed1316e900905140f48b3b2a652fec7a Mon Sep 17 00:00:00 2001 From: ramires Date: Thu, 2 May 2024 13:50:42 -0300 Subject: [PATCH 079/351] rota corrigida --- frontend/sige_ie/lib/main.dart | 3 ++- frontend/sige_ie/lib/places/feature/register/room_state.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index c38d14df..144434e5 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -5,6 +5,7 @@ import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; +import 'package:sige_ie/places/feature/manage/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/places/feature/register/room_state.dart'; import 'core/feature/login/login.dart'; @@ -31,7 +32,7 @@ class MyApp extends StatelessWidget { '/profileScreen': (context) => HomePage(), '/newLocation': (context) => NewPlace(), '/roomlocation': (context) => RoomLocation(), - '/SystemConfiguration':(context)=> SystemConfiguration(), + '/systemLocation':(context)=> SystemConfiguration(), }, ); } diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index 9d8c7b85..aae4dfef 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -103,7 +103,7 @@ class _RoomLocationState extends State { // Se desejar, insira a lógica de navegação para outra tela aqui print('Sala Registrada: ${roomController.text} no ${selectedFloor}'); // Por exemplo, mudar para uma nova rota: - Navigator.of(context).pushNamed('/systemlocation'); + Navigator.of(context).pushNamed('/systemLocation'); } else { showDialog( context: context, From f2a8f06dbad3e44c339d282dd1fd424e14830279 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 2 May 2024 19:43:47 -0300 Subject: [PATCH 080/351] =?UTF-8?q?Front:=20Resolu=C3=A7=C3=A3o=20de=20bug?= =?UTF-8?q?=20commit=2008df1fd,=20complementa=C3=A7=C3=A3o=20commit=206f2f?= =?UTF-8?q?9f1=20e=20ajuste=20em=20new=5Fplace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 230 ++++++++++-------- .../lib/core/feature/register/register.dart | 2 +- frontend/sige_ie/lib/main.dart | 10 +- .../feature/manage/systemConfiguration.dart | 25 +- .../places/feature/register/new_place.dart | 14 +- .../places/feature/register/room_state.dart | 166 +++++-------- .../lib/users/data/user_request_model.dart | 8 +- 7 files changed, 219 insertions(+), 236 deletions(-) diff --git a/README.md b/README.md index 25c02038..b54c1096 100644 --- a/README.md +++ b/README.md @@ -3,52 +3,60 @@ ![Versões do Django](./doc/img/sige_ie_logo.jpeg) SIGE IE + ## Fase + Release 1 ✔️ Ir para milestone da release 1 Release 2 (atual) Ir para milestone da release 2 ## Visão geral do produto -### Sobre +### Sobre + Aplicativo web mobile desenvolvido para a Universidade de Brasília com objetivo de gerenciar as instalações elétricas e dar suporte ao retrofitting das instalações. ### Posição + O SIGE IE é um sistema da Universidade de Brasília para o gerenciamento de instalações elétricas com o objetivo de facilitar o cadastro das informações de instalação elétrica para ajudar na reforma da parte elétrica dos prédios e salas. Ele permite a automatização da geração de relatórios das instalações elétricas de cada lugar e a centralização dessas informações para uso dos responsáveis pelas instalações. As pessoas devem usar o SIGE IE porque ele simplifica e agiliza o processo de gerenciamento, principalmente do retrofitting de instalações elétricas, garantindo maior eficiência e segurança. ### Objetivos + Simplificar o cadastro e gerenciamento de informações de instalações elétricas e automatizar a geração de relatórios. + ### Tecnologias + #### Back-end
-| Nome | Versão | Uso | Configuração | -|---|---|---|---| -| Python | 3.11.8| Linguagem | [Site oficial do Python](https://www.python.org/downloads/) Ou veja na seção "Como subir o back-end" | -| Django | 4.2 (LTS) | Framework web | Automática | -| Django REST framework | 3.14 | API REST | Automática | -| Docker | 25.0.4 | Conteiner e imagem | [Site oficial do Docker](https://docs.docker.com/desktop/install/ubuntu/) | -| Redis | 7.2 | Banco de dados cache para sessão | Automática via Docker | -| MySQL | 8.1 | Banco de dados | Automática via Docker | -| Cabeçalhos do Python3 e do MySQL | - | Cabeçalhos de desenvolvimento e bibliotecas | [Site do Pypi com as configurações](https://pypi.org/project/mysqlclient/) Ou veja na seção "Como subir o back-end" +| Nome | Versão | Uso | Configuração | +| -------------------------------- | --------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| Python | 3.11.8 | Linguagem | [Site oficial do Python](https://www.python.org/downloads/) Ou veja na seção "Como subir o back-end" | +| Django | 4.2 (LTS) | Framework web | Automática | +| Django REST framework | 3.14 | API REST | Automática | +| Docker | 25.0.4 | Conteiner e imagem | [Site oficial do Docker](https://docs.docker.com/desktop/install/ubuntu/) | +| Redis | 7.2 | Banco de dados cache para sessão | Automática via Docker | +| MySQL | 8.1 | Banco de dados | Automática via Docker | +| Cabeçalhos do Python3 e do MySQL | - | Cabeçalhos de desenvolvimento e bibliotecas | [Site do Pypi com as configurações](https://pypi.org/project/mysqlclient/) Ou veja na seção "Como subir o back-end" |
##### Observação + Atualmente o Django REST Framework suporta as seguintes versões do Python e do Django:
-| Python | 3.6 | 3.7 | 3.8 | 3.9 | 3.10 | 3.11 | -|--------|-----|-----|-----|-----|------|------| +| Python | 3.6 | 3.7 | 3.8 | 3.9 | 3.10 | 3.11 | +| ------ | --- | --- | --- | --- | ---- | --------- | | Django | 3.0 | 3.1 | 3.2 | 4.0 | 4.1 | 4.2 (LTS) |
-Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar o projeto usando Python 3.11. +Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar o projeto usando Python 3.11. #### Front-end mobile @@ -61,7 +69,8 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar -### Contribuidores +### Contribuidores +
@@ -96,57 +105,66 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar ## Visão geral do projeto -### Organização +### Organização +
-| Papel | Atribuições | Responsável | Participantes | -| --- | --- | --- | --- | -| Cliente | Validar as entregas | Loana | Loana, Alex | -| Desenvolvedor back-end | Codificar o backend, configurar a infraestrutura | Pedro | Pedro, Kauan, Oscar | -| Desenvolvedor frontend | Codificar o frontend, realizar integração com backend | Danilo | Danilo, Ramires, Pedro | -| UX design | Projetar a interface do usuário, criar protótipos e realizar entrevistas com os clientes | Danilo | Danilo | -| Analista de requisitos | Levantar requisitos, gerenciar a documentação, validar com cliente | Oscar | Oscar, Ramires, Pedro | +| Papel | Atribuições | Responsável | Participantes | +| ---------------------- | ---------------------------------------------------------------------------------------- | ----------- | --------------------- | +| Cliente | Validar as entregas | Loana | Loana, Alex | +| Desenvolvedor back-end | Codificar o backend, configurar a infraestrutura | Pedro | Pedro, Kauan, Oscar | +| Desenvolvedor frontend | Codificar o frontend, realizar integração com backend | Danilo | Danilo, Ramires | +| UX design | Projetar a interface do usuário, criar protótipos e realizar entrevistas com os clientes | Danilo | Danilo | +| Analista de requisitos | Levantar requisitos, gerenciar a documentação, validar com cliente | Oscar | Oscar, Ramires, Pedro |
## Configuração do ambiente + ### Como subir o projeto + Estas etapas são válidas para Linux OS e WSL. + #### Como subir o back-end: ##### Pela primeira vez Primeiramente, interrompa qualquer processo que use o porto 8080, 3306 e 6379. Então atualize o seu sistema: - ``` - sudo apt-get update - ``` - ``` - sudo apt-get upgrade - ``` +``` +sudo apt-get update +``` + +``` +sudo apt-get upgrade +``` Em seguida, caso já não tenha instalado: -- Instale o Python, Pip e os cabeçalhos do Python e MySQL: +- Instale o Python, Pip e os cabeçalhos do Python e MySQL: + + Python: + + ``` + sudo apt-get install python3.11 + ``` + + Pip: - Python: - ``` - sudo apt-get install python3.11 - ``` + ``` + sudo apt-get install python3-pip + ``` - Pip: - ``` - sudo apt-get install python3-pip - ``` + Cabeçalhos: - Cabeçalhos: - ``` - sudo apt-get install python3.11-dev default-libmysqlclient-dev build-essential pkg-config - ``` + ``` + sudo apt-get install python3.11-dev default-libmysqlclient-dev build-essential pkg-config + ``` -- Instale o virtualenv para criar um ambiente virtual do projeto: +- Instale o virtualenv para criar um ambiente virtual do projeto: Virtualenv: + ``` sudo pip3 install virtualenv ``` @@ -156,36 +174,38 @@ Vá para dentro da pasta raiz `api`: 1. Cria o ambiente virtual e ative-o: Criar ambiente virtual: - ``` - virtualenv -p python3.11 venv - ``` - + + ``` + virtualenv -p python3.11 venv + ``` + Ativar ambiente: - ``` - source venv/bin/activate - ``` + + ``` + source venv/bin/activate + ``` 2. Com o ambiente virtual ativado, instale as dependências: - ``` - pip install -r requirements.txt - ``` + ``` + pip install -r requirements.txt + ``` 3. Inicie o Docker, depois vá para o diretório `api/sigeie` e crie a imagem do banco de dados pela primeira vez: - ``` - docker-compose up -d - ``` + ``` + docker-compose up -d + ``` 4. Ainda no mesmo terminal, retorne para o diretório raiz `api` e aplique as migrações: - ``` - python manage.py makemigrations - ``` + ``` + python manage.py makemigrations + ``` - ``` - python manage.py migrate - ``` + ``` + python manage.py migrate + ``` 5. Inicie o servidor: @@ -199,14 +219,14 @@ Pronto, o servidor já está rodando com o banco de dados configurado. Garanta que não haja nenhum processo que use o porto 8080, 3306 e 6379. Por fim, com todas as dependências configuradas, basta: -- Inicar o Docker e o container `sigeie`; -- Baixar as atualizações (caso haja): +- Inicar o Docker e o container `sigeie`; +- Baixar as atualizações (caso haja): - ``` - git pull - ``` + ``` + git pull + ``` -- Atualizar as dependências, fazer as migrações e iniciar o servidor: +- Atualizar as dependências, fazer as migrações e iniciar o servidor: ``` source venv/bin/activate && pip install -r requirements.txt && python manage.py makemigrations && python manage.py migrate && python manage.py runserver @@ -220,37 +240,40 @@ Antes de começar, verifique se o Flutter SDK está atualizado e compatível com Caso ainda não tenha feito, instale os seguintes requisitos em sua máquina: -- **Flutter SDK**: - Siga as instruções de instalação para a sua plataforma. +- **Flutter SDK**: + Siga as instruções de instalação para a sua plataforma. -- **Android Studio ou Visual Studio Code**: +- **Android Studio ou Visual Studio Code**: - - Android Studio: - ``` - sudo snap install android-studio --classic - ``` + - Android Studio: - - Visual Studio Code: - ``` - sudo snap install code --classic - ``` - Para o VS Code, instale as extensões do Flutter e Dart disponíveis na aba de extensões do editor. + ``` + sudo snap install android-studio --classic + ``` -- **Emulador Android ou um dispositivo físico**: - Configure um emulador usando o AVD Manager do Android Studio ou [configure seu dispositivo Android para depuração USB](https://developer.android.com/studio/debug/dev-options). + - Visual Studio Code: + ``` + sudo snap install code --classic + ``` + Para o VS Code, instale as extensões do Flutter e Dart disponíveis na aba de extensões do editor. + +- **Emulador Android ou um dispositivo físico**: + Configure um emulador usando o AVD Manager do Android Studio ou [configure seu dispositivo Android para depuração USB](https://developer.android.com/studio/debug/dev-options). Com o ambiente preparado, siga os passos abaixo: 1. **Clone o Repositório do Front-end**: + ``` git clone https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica.git ``` 2. **Abra o Projeto no Editor**: - Abra a pasta clonada no Android Studio ou no Visual Studio Code. + Abra a pasta clonada no Android Studio ou no Visual Studio Code. 3. **Baixe as Dependências**: - Abra um terminal no editor e execute o comando: + Abra um terminal no editor e execute o comando: + ``` flutter pub get ``` @@ -262,28 +285,29 @@ Com o ambiente preparado, siga os passos abaixo: Pronto, o Front end já está rodando e você pode utilizá-lo. ## Contribuição + ### Como contribuir + 1. Faça um fork do repositório do projeto. 2. Clone o fork na sua máquina: - - ``` - git clone https://github.com/{seu-usuario}/T2G3-Sistema-Instalacao-Eletrica.git - ``` - -4. Comente na issue que deseja contribuir ou crie uma issue nova. -5. Entre no repositório clonado na sua máquina: - - ``` - cd T2G3-Sistema-Instalacao-Eletrica - ``` - -7. Após enviar suas contribuições para o fork do seu repositório, faça um pull request. -8. Aguarde a revisão. + + ``` + git clone https://github.com/{seu-usuario}/T2G3-Sistema-Instalacao-Eletrica.git + ``` + +3. Comente na issue que deseja contribuir ou crie uma issue nova. +4. Entre no repositório clonado na sua máquina: + ``` + cd T2G3-Sistema-Instalacao-Eletrica + ``` +5. Após enviar suas contribuições para o fork do seu repositório, faça um pull request. +6. Aguarde a revisão. ## Documentação -- [Requisitos de software](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/1) -- [Cronograma](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/3) -- [Backlog do produto](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/9) -- [Releases](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/milestones) -- [Arquitetura](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/2) -- [Atas de reunião](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/4) + +- [Requisitos de software](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/1) +- [Cronograma](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/3) +- [Backlog do produto](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/9) +- [Releases](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/milestones) +- [Arquitetura](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/2) +- [Atas de reunião](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/4) diff --git a/frontend/sige_ie/lib/core/feature/register/register.dart b/frontend/sige_ie/lib/core/feature/register/register.dart index a21783be..344abb74 100644 --- a/frontend/sige_ie/lib/core/feature/register/register.dart +++ b/frontend/sige_ie/lib/core/feature/register/register.dart @@ -250,7 +250,7 @@ class _RegisterScreenState extends State { final user = UserRequestModel( username: usernameController.text, - name: nameController.text, + firstname: nameController.text, password: passwordController.text, email: emailController.text, ); diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 144434e5..04cdc07e 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -11,28 +11,30 @@ import 'package:sige_ie/places/feature/register/room_state.dart'; import 'core/feature/login/login.dart'; void main() { - runApp(MyApp()); + runApp(const MyApp()); } final cookieJar = CookieJar(); class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override Widget build(BuildContext context) { return MaterialApp( initialRoute: '/', routes: { '/': (context) => SplashScreen(), - '/loginScreen': (context) => LoginScreen(), + '/loginScreen': (context) => const LoginScreen(), '/first': (context) => FirstScreen(), - '/registerScreen': (context) => RegisterScreen(), + '/registerScreen': (context) => const RegisterScreen(), '/homeScreen': (context) => HomePage(), '/facilitiesScreen': (context) => HomePage(), '/MapsPage': (context) => MapsPage(), '/profileScreen': (context) => HomePage(), '/newLocation': (context) => NewPlace(), '/roomlocation': (context) => RoomLocation(), - '/systemLocation':(context)=> SystemConfiguration(), + '/systemLocation': (context) => SystemConfiguration(), }, ); } diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart index e19814b0..6ab0a49a 100644 --- a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/config/app_styles.dart'; class SystemConfiguration extends StatefulWidget { @override @@ -11,7 +11,8 @@ class _SystemConfigurationState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, // Substitua por sua cor personalizada. + backgroundColor: + AppColors.sigeIeBlue, // Substitua por sua cor personalizada. title: const Text('Local'), leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), @@ -29,7 +30,8 @@ class _SystemConfigurationState extends State { ), ), SystemButton(title: 'Baixa Tensão', icon: Icons.flash_on), - SystemButton(title: 'Cabeamento Estruturado', icon: Icons.settings_ethernet), + SystemButton( + title: 'Cabeamento Estruturado', icon: Icons.settings_ethernet), SystemButton(title: 'Descargas Atmosféricas', icon: Icons.cloud), SystemButton(title: 'Alarme de Incêndio', icon: Icons.warning), ], @@ -43,7 +45,8 @@ class SystemButton extends StatelessWidget { final String title; final IconData icon; - const SystemButton({Key? key, required this.title, required this.icon}) : super(key: key); + const SystemButton({Key? key, required this.title, required this.icon}) + : super(key: key); @override Widget build(BuildContext context) { @@ -52,11 +55,15 @@ class SystemButton extends StatelessWidget { width: double.infinity, child: ElevatedButton.icon( icon: Icon(icon, color: Colors.black), - label: Text(title, style: const TextStyle(color: Colors.black, fontSize: 18)), + label: Text(title, + style: const TextStyle(color: Colors.black, fontSize: 18)), style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Colors.yellow), // Cor de fundo do botão - foregroundColor: MaterialStateProperty.all(Colors.black), // Cor do texto e ícone - padding: MaterialStateProperty.all(const EdgeInsets.symmetric(vertical: 15)), + backgroundColor: + MaterialStateProperty.all(Colors.yellow), // Cor de fundo do botão + foregroundColor: + MaterialStateProperty.all(Colors.black), // Cor do texto e ícone + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 15)), shape: MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), )), @@ -68,4 +75,4 @@ class SystemButton extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index e9af1a01..6327c77e 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -15,11 +15,13 @@ class NewPlaceState extends State { bool coord = false; final nameController = TextEditingController(); late PositionController positionController; + late TextEditingController coordinatesController; @override void initState() { super.initState(); positionController = PositionController(); + coordinatesController = TextEditingController(); } void _getCoordinates() { @@ -28,15 +30,18 @@ class NewPlaceState extends State { if (positionController.error.isEmpty) { coordinates = "Latitude: ${positionController.lat}, Longitude: ${positionController.lon}"; + coordinatesController.text = coordinates; coord = true; } else { coordinates = "Erro: ${positionController.error}"; + coordinatesController.text = coordinates; coord = false; } }); }).catchError((e) { setState(() { coordinates = "Erro ao obter localização: $e"; + coordinatesController.text = coordinates; coord = false; }); }); @@ -100,8 +105,7 @@ class NewPlaceState extends State { contentPadding: EdgeInsets.symmetric(horizontal: 10), ), - controller: - TextEditingController(text: coordinates), + controller: coordinatesController, enabled: false, ), ), @@ -163,8 +167,10 @@ class NewPlaceState extends State { lat: positionController.lat); bool success = await placeService.register(place); - print('Local Registrado: ${nameController.text}'); - Navigator.of(context).pushNamed('/roomlocation'); + if (success) { + print('Local Registrado: ${nameController.text}'); + Navigator.of(context).pushNamed('/roomlocation'); + } } else if (nameController.text.trim().isEmpty) { showDialog( context: context, diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index aae4dfef..bc2fc8ff 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; class RoomLocation extends StatefulWidget { @@ -10,7 +9,13 @@ class RoomLocation extends StatefulWidget { class _RoomLocationState extends State { String? selectedFloor; final TextEditingController roomController = TextEditingController(); - final List floors = ['Andar 1', 'Andar 2', 'Andar 3', 'Andar 4', 'Andar 5']; + final List floors = [ + 'Andar 1', + 'Andar 2', + 'Andar 3', + 'Andar 4', + 'Andar 5' + ]; @override Widget build(BuildContext context) { @@ -31,11 +36,15 @@ class _RoomLocationState extends State { padding: EdgeInsets.fromLTRB(10, 10, 10, 35), decoration: BoxDecoration( color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( child: Text('Local-Sala', - style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold, color: Colors.white)), + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), ), ), SizedBox(height: 60), @@ -44,10 +53,16 @@ class _RoomLocationState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Andar', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), + Text('Andar', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black)), SizedBox(height: 10), Container( - decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(10)), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(10)), child: DropdownButtonHideUnderline( child: Padding( padding: EdgeInsets.symmetric(horizontal: 10), @@ -55,7 +70,8 @@ class _RoomLocationState extends State { value: selectedFloor, hint: Text('Selecione o Andar'), isExpanded: true, - items: floors.map>((String value) { + items: floors + .map>((String value) { return DropdownMenuItem( value: value, child: Text(value), @@ -71,10 +87,16 @@ class _RoomLocationState extends State { ), ), SizedBox(height: 40), - Text('Sala', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), + Text('Sala', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black)), SizedBox(height: 10), Container( - decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(10)), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(10)), child: TextField( controller: roomController, decoration: InputDecoration( @@ -90,18 +112,25 @@ class _RoomLocationState extends State { children: [ ElevatedButton( style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), minimumSize: MaterialStateProperty.all(Size(150, 50)), textStyle: MaterialStateProperty.all( - TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), ), - shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8))), ), onPressed: () { - if (selectedFloor != null && roomController.text.isNotEmpty) { + if (selectedFloor != null && + roomController.text.isNotEmpty) { // Se desejar, insira a lógica de navegação para outra tela aqui - print('Sala Registrada: ${roomController.text} no ${selectedFloor}'); + print( + 'Sala Registrada: ${roomController.text} no ${selectedFloor}'); // Por exemplo, mudar para uma nova rota: Navigator.of(context).pushNamed('/systemLocation'); } else { @@ -110,7 +139,8 @@ class _RoomLocationState extends State { builder: (BuildContext context) { return AlertDialog( title: Text('Erro'), - content: Text("Por favor, selecione um andar e digite o nome da sala"), + content: Text( + "Por favor, selecione um andar e digite o nome da sala"), actions: [ TextButton( child: Text("OK"), @@ -128,13 +158,18 @@ class _RoomLocationState extends State { ), ElevatedButton( style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Colors.red), - foregroundColor: MaterialStateProperty.all(Colors.white), + backgroundColor: + MaterialStateProperty.all(Colors.red), + foregroundColor: + MaterialStateProperty.all(Colors.white), minimumSize: MaterialStateProperty.all(Size(150, 50)), textStyle: MaterialStateProperty.all( - TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), ), - shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8))), ), onPressed: () => Navigator.of(context).pop(), child: Text('ENCERRAR'), @@ -150,94 +185,3 @@ class _RoomLocationState extends State { ); } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/sige_ie/lib/users/data/user_request_model.dart b/frontend/sige_ie/lib/users/data/user_request_model.dart index 3fa0350a..082a00ea 100644 --- a/frontend/sige_ie/lib/users/data/user_request_model.dart +++ b/frontend/sige_ie/lib/users/data/user_request_model.dart @@ -1,19 +1,19 @@ class UserRequestModel { String username; - String name; + String firstname; String password; String email; UserRequestModel( {required this.username, - required this.name, + required this.firstname, required this.password, required this.email}); Map toJson() { return { - 'username': name, - 'name': name, + 'username': username, + 'first_name': firstname, 'password': password, 'email': email, }; From 2a31fc308d1721f11a66fcca059ded2c1d31d6a0 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia Date: Thu, 2 May 2024 20:36:49 -0300 Subject: [PATCH 081/351] Restaura README.md --- README.md | 230 ++++++++++++++++++++++++------------------------------ 1 file changed, 103 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index b54c1096..25c02038 100644 --- a/README.md +++ b/README.md @@ -3,60 +3,52 @@ ![Versões do Django](./doc/img/sige_ie_logo.jpeg) SIGE IE - ## Fase - Release 1 ✔️ Ir para milestone da release 1 Release 2 (atual) Ir para milestone da release 2 ## Visão geral do produto -### Sobre - +### Sobre Aplicativo web mobile desenvolvido para a Universidade de Brasília com objetivo de gerenciar as instalações elétricas e dar suporte ao retrofitting das instalações. ### Posição - O SIGE IE é um sistema da Universidade de Brasília para o gerenciamento de instalações elétricas com o objetivo de facilitar o cadastro das informações de instalação elétrica para ajudar na reforma da parte elétrica dos prédios e salas. Ele permite a automatização da geração de relatórios das instalações elétricas de cada lugar e a centralização dessas informações para uso dos responsáveis pelas instalações. As pessoas devem usar o SIGE IE porque ele simplifica e agiliza o processo de gerenciamento, principalmente do retrofitting de instalações elétricas, garantindo maior eficiência e segurança. ### Objetivos - Simplificar o cadastro e gerenciamento de informações de instalações elétricas e automatizar a geração de relatórios. - ### Tecnologias - #### Back-end
-| Nome | Versão | Uso | Configuração | -| -------------------------------- | --------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| Python | 3.11.8 | Linguagem | [Site oficial do Python](https://www.python.org/downloads/) Ou veja na seção "Como subir o back-end" | -| Django | 4.2 (LTS) | Framework web | Automática | -| Django REST framework | 3.14 | API REST | Automática | -| Docker | 25.0.4 | Conteiner e imagem | [Site oficial do Docker](https://docs.docker.com/desktop/install/ubuntu/) | -| Redis | 7.2 | Banco de dados cache para sessão | Automática via Docker | -| MySQL | 8.1 | Banco de dados | Automática via Docker | -| Cabeçalhos do Python3 e do MySQL | - | Cabeçalhos de desenvolvimento e bibliotecas | [Site do Pypi com as configurações](https://pypi.org/project/mysqlclient/) Ou veja na seção "Como subir o back-end" | +| Nome | Versão | Uso | Configuração | +|---|---|---|---| +| Python | 3.11.8| Linguagem | [Site oficial do Python](https://www.python.org/downloads/) Ou veja na seção "Como subir o back-end" | +| Django | 4.2 (LTS) | Framework web | Automática | +| Django REST framework | 3.14 | API REST | Automática | +| Docker | 25.0.4 | Conteiner e imagem | [Site oficial do Docker](https://docs.docker.com/desktop/install/ubuntu/) | +| Redis | 7.2 | Banco de dados cache para sessão | Automática via Docker | +| MySQL | 8.1 | Banco de dados | Automática via Docker | +| Cabeçalhos do Python3 e do MySQL | - | Cabeçalhos de desenvolvimento e bibliotecas | [Site do Pypi com as configurações](https://pypi.org/project/mysqlclient/) Ou veja na seção "Como subir o back-end"
##### Observação - Atualmente o Django REST Framework suporta as seguintes versões do Python e do Django:
-| Python | 3.6 | 3.7 | 3.8 | 3.9 | 3.10 | 3.11 | -| ------ | --- | --- | --- | --- | ---- | --------- | +| Python | 3.6 | 3.7 | 3.8 | 3.9 | 3.10 | 3.11 | +|--------|-----|-----|-----|-----|------|------| | Django | 3.0 | 3.1 | 3.2 | 4.0 | 4.1 | 4.2 (LTS) |
-Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar o projeto usando Python 3.11. +Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar o projeto usando Python 3.11. #### Front-end mobile @@ -69,8 +61,7 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar -### Contribuidores - +### Contribuidores
@@ -105,66 +96,57 @@ Como a versão LTS mais recente do Django (2024) é a 4.2, escolhemos configurar ## Visão geral do projeto -### Organização - +### Organização
-| Papel | Atribuições | Responsável | Participantes | -| ---------------------- | ---------------------------------------------------------------------------------------- | ----------- | --------------------- | -| Cliente | Validar as entregas | Loana | Loana, Alex | -| Desenvolvedor back-end | Codificar o backend, configurar a infraestrutura | Pedro | Pedro, Kauan, Oscar | -| Desenvolvedor frontend | Codificar o frontend, realizar integração com backend | Danilo | Danilo, Ramires | -| UX design | Projetar a interface do usuário, criar protótipos e realizar entrevistas com os clientes | Danilo | Danilo | -| Analista de requisitos | Levantar requisitos, gerenciar a documentação, validar com cliente | Oscar | Oscar, Ramires, Pedro | +| Papel | Atribuições | Responsável | Participantes | +| --- | --- | --- | --- | +| Cliente | Validar as entregas | Loana | Loana, Alex | +| Desenvolvedor back-end | Codificar o backend, configurar a infraestrutura | Pedro | Pedro, Kauan, Oscar | +| Desenvolvedor frontend | Codificar o frontend, realizar integração com backend | Danilo | Danilo, Ramires, Pedro | +| UX design | Projetar a interface do usuário, criar protótipos e realizar entrevistas com os clientes | Danilo | Danilo | +| Analista de requisitos | Levantar requisitos, gerenciar a documentação, validar com cliente | Oscar | Oscar, Ramires, Pedro |
## Configuração do ambiente - ### Como subir o projeto - Estas etapas são válidas para Linux OS e WSL. - #### Como subir o back-end: ##### Pela primeira vez Primeiramente, interrompa qualquer processo que use o porto 8080, 3306 e 6379. Então atualize o seu sistema: + ``` + sudo apt-get update + ``` -``` -sudo apt-get update -``` - -``` -sudo apt-get upgrade -``` + ``` + sudo apt-get upgrade + ``` Em seguida, caso já não tenha instalado: -- Instale o Python, Pip e os cabeçalhos do Python e MySQL: - - Python: - - ``` - sudo apt-get install python3.11 - ``` - - Pip: +- Instale o Python, Pip e os cabeçalhos do Python e MySQL: - ``` - sudo apt-get install python3-pip - ``` + Python: + ``` + sudo apt-get install python3.11 + ``` - Cabeçalhos: + Pip: + ``` + sudo apt-get install python3-pip + ``` - ``` - sudo apt-get install python3.11-dev default-libmysqlclient-dev build-essential pkg-config - ``` + Cabeçalhos: + ``` + sudo apt-get install python3.11-dev default-libmysqlclient-dev build-essential pkg-config + ``` -- Instale o virtualenv para criar um ambiente virtual do projeto: +- Instale o virtualenv para criar um ambiente virtual do projeto: Virtualenv: - ``` sudo pip3 install virtualenv ``` @@ -174,38 +156,36 @@ Vá para dentro da pasta raiz `api`: 1. Cria o ambiente virtual e ative-o: Criar ambiente virtual: - - ``` - virtualenv -p python3.11 venv - ``` - + ``` + virtualenv -p python3.11 venv + ``` + Ativar ambiente: - - ``` - source venv/bin/activate - ``` + ``` + source venv/bin/activate + ``` 2. Com o ambiente virtual ativado, instale as dependências: - ``` - pip install -r requirements.txt - ``` + ``` + pip install -r requirements.txt + ``` 3. Inicie o Docker, depois vá para o diretório `api/sigeie` e crie a imagem do banco de dados pela primeira vez: - ``` - docker-compose up -d - ``` + ``` + docker-compose up -d + ``` 4. Ainda no mesmo terminal, retorne para o diretório raiz `api` e aplique as migrações: - ``` - python manage.py makemigrations - ``` + ``` + python manage.py makemigrations + ``` - ``` - python manage.py migrate - ``` + ``` + python manage.py migrate + ``` 5. Inicie o servidor: @@ -219,14 +199,14 @@ Pronto, o servidor já está rodando com o banco de dados configurado. Garanta que não haja nenhum processo que use o porto 8080, 3306 e 6379. Por fim, com todas as dependências configuradas, basta: -- Inicar o Docker e o container `sigeie`; -- Baixar as atualizações (caso haja): +- Inicar o Docker e o container `sigeie`; +- Baixar as atualizações (caso haja): - ``` - git pull - ``` + ``` + git pull + ``` -- Atualizar as dependências, fazer as migrações e iniciar o servidor: +- Atualizar as dependências, fazer as migrações e iniciar o servidor: ``` source venv/bin/activate && pip install -r requirements.txt && python manage.py makemigrations && python manage.py migrate && python manage.py runserver @@ -240,40 +220,37 @@ Antes de começar, verifique se o Flutter SDK está atualizado e compatível com Caso ainda não tenha feito, instale os seguintes requisitos em sua máquina: -- **Flutter SDK**: - Siga as instruções de instalação para a sua plataforma. +- **Flutter SDK**: + Siga as instruções de instalação para a sua plataforma. -- **Android Studio ou Visual Studio Code**: +- **Android Studio ou Visual Studio Code**: - - Android Studio: - - ``` - sudo snap install android-studio --classic - ``` + - Android Studio: + ``` + sudo snap install android-studio --classic + ``` - - Visual Studio Code: - ``` - sudo snap install code --classic - ``` - Para o VS Code, instale as extensões do Flutter e Dart disponíveis na aba de extensões do editor. + - Visual Studio Code: + ``` + sudo snap install code --classic + ``` + Para o VS Code, instale as extensões do Flutter e Dart disponíveis na aba de extensões do editor. -- **Emulador Android ou um dispositivo físico**: - Configure um emulador usando o AVD Manager do Android Studio ou [configure seu dispositivo Android para depuração USB](https://developer.android.com/studio/debug/dev-options). +- **Emulador Android ou um dispositivo físico**: + Configure um emulador usando o AVD Manager do Android Studio ou [configure seu dispositivo Android para depuração USB](https://developer.android.com/studio/debug/dev-options). Com o ambiente preparado, siga os passos abaixo: 1. **Clone o Repositório do Front-end**: - ``` git clone https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica.git ``` 2. **Abra o Projeto no Editor**: - Abra a pasta clonada no Android Studio ou no Visual Studio Code. + Abra a pasta clonada no Android Studio ou no Visual Studio Code. 3. **Baixe as Dependências**: - Abra um terminal no editor e execute o comando: - + Abra um terminal no editor e execute o comando: ``` flutter pub get ``` @@ -285,29 +262,28 @@ Com o ambiente preparado, siga os passos abaixo: Pronto, o Front end já está rodando e você pode utilizá-lo. ## Contribuição - ### Como contribuir - 1. Faça um fork do repositório do projeto. 2. Clone o fork na sua máquina: - - ``` - git clone https://github.com/{seu-usuario}/T2G3-Sistema-Instalacao-Eletrica.git - ``` - -3. Comente na issue que deseja contribuir ou crie uma issue nova. -4. Entre no repositório clonado na sua máquina: - ``` - cd T2G3-Sistema-Instalacao-Eletrica - ``` -5. Após enviar suas contribuições para o fork do seu repositório, faça um pull request. -6. Aguarde a revisão. + + ``` + git clone https://github.com/{seu-usuario}/T2G3-Sistema-Instalacao-Eletrica.git + ``` + +4. Comente na issue que deseja contribuir ou crie uma issue nova. +5. Entre no repositório clonado na sua máquina: + + ``` + cd T2G3-Sistema-Instalacao-Eletrica + ``` + +7. Após enviar suas contribuições para o fork do seu repositório, faça um pull request. +8. Aguarde a revisão. ## Documentação - -- [Requisitos de software](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/1) -- [Cronograma](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/3) -- [Backlog do produto](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/9) -- [Releases](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/milestones) -- [Arquitetura](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/2) -- [Atas de reunião](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/4) +- [Requisitos de software](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/1) +- [Cronograma](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/3) +- [Backlog do produto](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/9) +- [Releases](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/milestones) +- [Arquitetura](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/2) +- [Atas de reunião](https://github.com/ResidenciaTICBrisa/T2G3-Sistema-Instalacao-Eletrica/issues/4) From e9357bd970802681def832e1e10f86a36ffd186b Mon Sep 17 00:00:00 2001 From: EngDann Date: Fri, 3 May 2024 10:01:36 -0300 Subject: [PATCH 082/351] =?UTF-8?q?complementa=C3=A7=C3=A3o=20commit=206f2?= =?UTF-8?q?f9f1,=20altera=C3=A7=C3=B5es=20visuais=20nas=20telas=20da=20seg?= =?UTF-8?q?unda=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/config/app_styles.dart | 2 +- frontend/sige_ie/lib/main.dart | 56 ++++++++++++++---- .../feature/manage/systemConfiguration.dart | 59 ++++++++++++------- .../places/feature/register/new_place.dart | 9 +-- .../places/feature/register/room_state.dart | 27 +++++---- 5 files changed, 102 insertions(+), 51 deletions(-) diff --git a/frontend/sige_ie/lib/config/app_styles.dart b/frontend/sige_ie/lib/config/app_styles.dart index aefd2003..beda3627 100644 --- a/frontend/sige_ie/lib/config/app_styles.dart +++ b/frontend/sige_ie/lib/config/app_styles.dart @@ -4,7 +4,7 @@ class AppColors { static const Color sigeIeYellow = Color(0xFFF1F60E); static const Color sigeIeBlue = Color(0xff123c75); static const Color dartText = Color.fromRGBO(22, 22, 22, 1); - static const Color lightText = Color.fromARGB(255, 233, 226, 226); + static const Color lightText = Color.fromARGB(255, 238, 233, 233); static const Color warn = Color.fromARGB(255, 231, 27, 27); static const Color accent = Color.fromARGB(255, 231, 85, 27); } diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 04cdc07e..20413157 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -23,19 +23,51 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( initialRoute: '/', - routes: { - '/': (context) => SplashScreen(), - '/loginScreen': (context) => const LoginScreen(), - '/first': (context) => FirstScreen(), - '/registerScreen': (context) => const RegisterScreen(), - '/homeScreen': (context) => HomePage(), - '/facilitiesScreen': (context) => HomePage(), - '/MapsPage': (context) => MapsPage(), - '/profileScreen': (context) => HomePage(), - '/newLocation': (context) => NewPlace(), - '/roomlocation': (context) => RoomLocation(), - '/systemLocation': (context) => SystemConfiguration(), + onGenerateRoute: (settings) { + switch (settings.name) { + case '/': + return MaterialPageRoute(builder: (context) => SplashScreen()); + case '/loginScreen': + return MaterialPageRoute(builder: (context) => const LoginScreen()); + case '/first': + return MaterialPageRoute(builder: (context) => FirstScreen()); + case '/registerScreen': + return MaterialPageRoute( + builder: (context) => const RegisterScreen()); + case '/homeScreen': + return MaterialPageRoute(builder: (context) => HomePage()); + case '/MapsPage': + return MaterialPageRoute(builder: (context) => MapsPage()); + case '/newLocation': + return MaterialPageRoute(builder: (context) => NewPlace()); + case '/roomlocation': + return MaterialPageRoute(builder: (context) => RoomLocation()); + case '/systemLocation': + if (settings.arguments is String) { + final roomName = settings.arguments as String; + return MaterialPageRoute( + builder: (context) => + SystemConfiguration(roomName: roomName)); + } + throw Exception( + 'Invalid route: Expected string argument for /systemLocation.'); + default: + return MaterialPageRoute( + builder: (context) => UndefinedView(name: settings.name)); + } }, ); } } + +class UndefinedView extends StatelessWidget { + final String? name; + const UndefinedView({Key? key, this.name}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('No route defined for ${name ?? "unknown"}')), + ); + } +} diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart index 6ab0a49a..f6cbd7c2 100644 --- a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart @@ -2,6 +2,10 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; class SystemConfiguration extends StatefulWidget { + final String roomName; + + SystemConfiguration({Key? key, required this.roomName}) : super(key: key); + @override _SystemConfigurationState createState() => _SystemConfigurationState(); } @@ -11,9 +15,7 @@ class _SystemConfigurationState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - backgroundColor: - AppColors.sigeIeBlue, // Substitua por sua cor personalizada. - title: const Text('Local'), + backgroundColor: AppColors.sigeIeBlue, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.of(context).pop(), @@ -22,18 +24,33 @@ class _SystemConfigurationState extends State { body: SingleChildScrollView( child: Column( children: [ + Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text(widget.roomName, + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(30.0), child: const Text( 'Quais sistemas deseja configurar?', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), - SystemButton(title: 'Baixa Tensão', icon: Icons.flash_on), - SystemButton( - title: 'Cabeamento Estruturado', icon: Icons.settings_ethernet), - SystemButton(title: 'Descargas Atmosféricas', icon: Icons.cloud), - SystemButton(title: 'Alarme de Incêndio', icon: Icons.warning), + SystemButton(title: 'BAIXA TENSÃO'), + SystemButton(title: 'CABEAMENTO ESTRUTURADO'), + SystemButton(title: 'DESCARGAS ATMOSFÉRICAS'), + SystemButton(title: 'ALARME DE INCÊNDIO'), ], ), ), @@ -43,33 +60,31 @@ class _SystemConfigurationState extends State { class SystemButton extends StatelessWidget { final String title; - final IconData icon; - const SystemButton({Key? key, required this.title, required this.icon}) - : super(key: key); + const SystemButton({Key? key, required this.title}) : super(key: key); @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), width: double.infinity, - child: ElevatedButton.icon( - icon: Icon(icon, color: Colors.black), - label: Text(title, - style: const TextStyle(color: Colors.black, fontSize: 18)), + child: ElevatedButton( + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.bold)), style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(Colors.yellow), // Cor de fundo do botão - foregroundColor: - MaterialStateProperty.all(Colors.black), // Cor do texto e ícone + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 15)), + const EdgeInsets.symmetric(vertical: 25)), shape: MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), )), ), onPressed: () { - // Aqui pode-se adicionar a lógica para o que acontece ao pressionar o botão + // Adicione a lógica para o que acontece ao pressionar o botão print('Configuring $title'); }, ), diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index 6327c77e..53172535 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -149,7 +149,7 @@ class NewPlaceState extends State { MaterialStateProperty.all(const Size(200, 50)), textStyle: MaterialStateProperty.all( const TextStyle( - fontSize: 18, + fontSize: 16, fontWeight: FontWeight.bold, ), ), @@ -168,7 +168,8 @@ class NewPlaceState extends State { bool success = await placeService.register(place); if (success) { - print('Local Registrado: ${nameController.text}'); + print( + 'Local Registrado: ${nameController.text} em latitude: ${positionController.lat} e longitude: ${positionController.lon}'); Navigator.of(context).pushNamed('/roomlocation'); } } else if (nameController.text.trim().isEmpty) { @@ -178,7 +179,7 @@ class NewPlaceState extends State { return AlertDialog( title: const Text("Erro"), content: const Text( - "Por favor, insira um nome para o local"), + "Por favor, clique na lupa e insira um nome para o local"), actions: [ TextButton( child: const Text("OK"), @@ -192,7 +193,7 @@ class NewPlaceState extends State { ); } }, - child: const Text('Registrar'), + child: const Text('REGISTRAR'), )), ], ), diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index bc2fc8ff..172835b3 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -40,7 +40,7 @@ class _RoomLocationState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('Local-Sala', + child: Text('Local - Sala', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -116,23 +116,26 @@ class _RoomLocationState extends State { MaterialStateProperty.all(AppColors.sigeIeYellow), foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: MaterialStateProperty.all(Size(150, 50)), + minimumSize: MaterialStateProperty.all(Size(165, 50)), textStyle: MaterialStateProperty.all( TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), + fontSize: 15, fontWeight: FontWeight.bold), ), shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8))), + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + ), ), onPressed: () { if (selectedFloor != null && roomController.text.isNotEmpty) { - // Se desejar, insira a lógica de navegação para outra tela aqui print( 'Sala Registrada: ${roomController.text} no ${selectedFloor}'); - // Por exemplo, mudar para uma nova rota: - Navigator.of(context).pushNamed('/systemLocation'); + Navigator.pushNamed( + context, + '/systemLocation', + arguments: roomController.text, + ); } else { showDialog( context: context, @@ -159,13 +162,13 @@ class _RoomLocationState extends State { ElevatedButton( style: ButtonStyle( backgroundColor: - MaterialStateProperty.all(Colors.red), + MaterialStateProperty.all(AppColors.warn), foregroundColor: - MaterialStateProperty.all(Colors.white), - minimumSize: MaterialStateProperty.all(Size(150, 50)), + MaterialStateProperty.all(AppColors.lightText), + minimumSize: MaterialStateProperty.all(Size(165, 50)), textStyle: MaterialStateProperty.all( TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), + fontSize: 15, fontWeight: FontWeight.bold), ), shape: MaterialStateProperty.all( RoundedRectangleBorder( From 217d49e7a01fab28f937bcd6adf856503f0cd288 Mon Sep 17 00:00:00 2001 From: EngDann Date: Fri, 3 May 2024 11:25:39 -0300 Subject: [PATCH 083/351] Front: nome do user, nome do local e nome da sala implementados --- frontend/sige_ie/lib/home/ui/home.dart | 145 +++++++++++------- frontend/sige_ie/lib/main.dart | 10 +- .../places/feature/register/new_place.dart | 3 +- .../places/feature/register/room_state.dart | 5 +- 4 files changed, 102 insertions(+), 61 deletions(-) diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 13f318c9..b6a90808 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -3,6 +3,8 @@ import 'package:sige_ie/config/app_styles.dart'; import '../../users/feature/profile.dart'; import '../../core/ui/facilities.dart'; import '../../maps/feature/maps.dart'; +import 'package:sige_ie/users/data/user_response_model.dart'; +import 'package:sige_ie/users/data/user_service.dart'; class HomePage extends StatefulWidget { @override @@ -12,6 +14,19 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { int _selectedIndex = 0; PageController _pageController = PageController(); + UserService userService = UserService(); + late UserResponseModel user; + + @override + void initState() { + super.initState(); + _pageController = PageController(); + userService.fetchProfileData().then((fetchedUser) { + setState(() { + user = fetchedUser; + }); + }); + } @override void dispose() { @@ -48,70 +63,84 @@ class _HomePageState extends State { } Widget buildHomePage(BuildContext context) { - return Column( - children: [ - AppBar( - automaticallyImplyLeading: false, - backgroundColor: const Color(0xff123c75), - elevation: 0, - ), - Expanded( - flex: 3, - child: Container( - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20), + return FutureBuilder( + future: userService.fetchProfileData(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Erro ao carregar os dados')); + } else if (snapshot.hasData) { + var user = snapshot.data!; + return Column( + children: [ + AppBar( + automaticallyImplyLeading: false, + backgroundColor: const Color(0xff123c75), + elevation: 0, ), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/1000x1000.png'), - fit: BoxFit.cover, - ), + Expanded( + flex: 3, + child: Container( + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), ), ), - ), - Container( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(left: 20), - child: const Text( - 'Olá, ', - style: TextStyle( - color: AppColors.sigeIeYellow, - fontSize: 20, - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/1000x1000.png'), + fit: BoxFit.cover, + ), + ), + ), + ), + Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 20), + child: Text( + 'Olá, ${user.firstname}', + style: TextStyle( + color: AppColors.sigeIeYellow, + fontSize: 20, + ), + ), + ), + const SizedBox(height: 10), + ], ), ), - const SizedBox(height: 10), - ], - ), - ), - ), - Expanded( - flex: 6, - child: Column( - children: [ - const Spacer(), - buildSmallRectangle(context, 'Registrar novo local', 'Registrar', - () { - Navigator.of(context).pushNamed('/newLocation'); - //print("Registrar novo local clicado"); - }), - buildSmallRectangle(context, 'Gerenciar locais', 'Gerenciar', () { - //print("Gerenciar locais clicado"); - }), - const Spacer(), + ), + Expanded( + flex: 6, + child: Column( + children: [ + const Spacer(), + buildSmallRectangle( + context, 'Registrar novo local', 'Registrar', () { + Navigator.of(context).pushNamed('/newLocation'); + }), + buildSmallRectangle( + context, 'Gerenciar locais', 'Gerenciar', () { + // Código aqui. + }), + const Spacer(), + ], + ), + ), ], - ), - ), - ], + ); + } else { + return Center(child: Text('Estado desconhecido')); + } + }, ); } diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 20413157..9817264d 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -41,7 +41,15 @@ class MyApp extends StatelessWidget { case '/newLocation': return MaterialPageRoute(builder: (context) => NewPlace()); case '/roomlocation': - return MaterialPageRoute(builder: (context) => RoomLocation()); + if (settings.arguments is String) { + final localName = settings.arguments as String; + return MaterialPageRoute( + builder: (context) => RoomLocation(localName: localName), + ); + } + throw Exception( + 'Invalid route: Expected string argument for /roomlocation.'); + case '/systemLocation': if (settings.arguments is String) { final roomName = settings.arguments as String; diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index 53172535..90169278 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -170,7 +170,8 @@ class NewPlaceState extends State { if (success) { print( 'Local Registrado: ${nameController.text} em latitude: ${positionController.lat} e longitude: ${positionController.lon}'); - Navigator.of(context).pushNamed('/roomlocation'); + Navigator.of(context).pushNamed('/roomlocation', + arguments: nameController.text.trim()); } } else if (nameController.text.trim().isEmpty) { showDialog( diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index 172835b3..15238cc1 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; class RoomLocation extends StatefulWidget { + final String localName; + const RoomLocation({Key? key, required this.localName}) : super(key: key); + @override _RoomLocationState createState() => _RoomLocationState(); } @@ -40,7 +43,7 @@ class _RoomLocationState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('Local - Sala', + child: Text('${widget.localName} - Sala', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, From f0d2b2c994f29ae6c3d975f4d0213f439490fca1 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 3 May 2024 12:41:39 -0300 Subject: [PATCH 084/351] =?UTF-8?q?backend:=20refatora=20equipments=20e=20?= =?UTF-8?q?area,=20adiciona=20rela=C3=A7=C3=A3o=20entre=20system=20e=20equ?= =?UTF-8?q?ipments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...hericdischargeequipment_system_and_more.py | 55 +++++++++++++++++++ ...hericdischargeequipment_system_and_more.py | 55 +++++++++++++++++++ ...ent_structuredcablingequipment_and_more.py | 47 ++++++++++++++++ api/equipments/models.py | 41 ++++++++++++-- api/equipments/serializers.py | 54 ++++++++++++------ api/equipments/urls.py | 4 +- api/equipments/views.py | 14 ++--- .../migrations/0025_remove_area_systems.py | 17 ++++++ .../migrations/0026_alter_area_place.py | 19 +++++++ api/places/models.py | 5 +- api/places/serializers.py | 2 +- 11 files changed, 278 insertions(+), 35 deletions(-) create mode 100644 api/equipments/migrations/0013_atmosphericdischargeequipment_system_and_more.py create mode 100644 api/equipments/migrations/0014_alter_atmosphericdischargeequipment_system_and_more.py create mode 100644 api/equipments/migrations/0015_rename_sructeredcablingequipment_structuredcablingequipment_and_more.py create mode 100644 api/places/migrations/0025_remove_area_systems.py create mode 100644 api/places/migrations/0026_alter_area_place.py diff --git a/api/equipments/migrations/0013_atmosphericdischargeequipment_system_and_more.py b/api/equipments/migrations/0013_atmosphericdischargeequipment_system_and_more.py new file mode 100644 index 00000000..d5222883 --- /dev/null +++ b/api/equipments/migrations/0013_atmosphericdischargeequipment_system_and_more.py @@ -0,0 +1,55 @@ +# Generated by Django 4.2 on 2024-05-03 13:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('equipments', '0012_sructeredcablingequipment_iluminationequipment_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='atmosphericdischargeequipment', + name='system', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AddField( + model_name='distributionboardequipment', + name='system', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AddField( + model_name='electricalcircuitequipment', + name='system', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AddField( + model_name='electricallineequipment', + name='system', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AddField( + model_name='electricalloadequipment', + name='system', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AddField( + model_name='firealarmequipment', + name='system', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AddField( + model_name='iluminationequipment', + name='system', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AddField( + model_name='sructeredcablingequipment', + name='system', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + ] diff --git a/api/equipments/migrations/0014_alter_atmosphericdischargeequipment_system_and_more.py b/api/equipments/migrations/0014_alter_atmosphericdischargeequipment_system_and_more.py new file mode 100644 index 00000000..88952f33 --- /dev/null +++ b/api/equipments/migrations/0014_alter_atmosphericdischargeequipment_system_and_more.py @@ -0,0 +1,55 @@ +# Generated by Django 4.2 on 2024-05-03 13:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('equipments', '0013_atmosphericdischargeequipment_system_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosphericdischargeequipment', + name='system', + field=models.OneToOneField(default=7, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='distributionboardequipment', + name='system', + field=models.OneToOneField(default=5, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='electricalcircuitequipment', + name='system', + field=models.OneToOneField(default=4, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='electricallineequipment', + name='system', + field=models.OneToOneField(default=3, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='electricalloadequipment', + name='system', + field=models.OneToOneField(default=2, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='firealarmequipment', + name='system', + field=models.OneToOneField(default=8, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='iluminationequipment', + name='system', + field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='sructeredcablingequipment', + name='system', + field=models.OneToOneField(default=6, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + ] diff --git a/api/equipments/migrations/0015_rename_sructeredcablingequipment_structuredcablingequipment_and_more.py b/api/equipments/migrations/0015_rename_sructeredcablingequipment_structuredcablingequipment_and_more.py new file mode 100644 index 00000000..01b2e558 --- /dev/null +++ b/api/equipments/migrations/0015_rename_sructeredcablingequipment_structuredcablingequipment_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2 on 2024-05-03 15:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('places', '0026_alter_area_place'), + ('equipments', '0014_alter_atmosphericdischargeequipment_system_and_more'), + ] + + operations = [ + migrations.RenameModel( + old_name='SructeredCablingEquipment', + new_name='StructuredCablingEquipment', + ), + migrations.AlterModelTable( + name='distributionboardequipment', + table='equipments_distribution_board_equipments', + ), + migrations.AlterModelTable( + name='electricalcircuitequipment', + table='equipments_electrical_circuit_equipments', + ), + migrations.AlterModelTable( + name='electricallineequipment', + table='equipments_electrical_line_equipments', + ), + migrations.AlterModelTable( + name='electricalloadequipment', + table='equipments_electrical_load_equipments', + ), + migrations.AlterModelTable( + name='firealarmequipment', + table='equipments_fire_alarm_equipments', + ), + migrations.AlterModelTable( + name='iluminationequipment', + table='equipments_ilumination_equipments', + ), + migrations.AlterModelTable( + name='structuredcablingequipment', + table='equipments_structured_cabling_equipments', + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index c1e2c34d..5ed1cf31 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -16,46 +16,75 @@ class Meta: class EquipmentDetail(models.Model): place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) - equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) + equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) def __str__(self):return self.description class Meta: db_table = 'equipments_equipment_details' +class FireAlarmEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.OneToOneField(System, on_delete=models.CASCADE, default=8) + + class Meta: + db_table = 'equipments_fire_alarm_equipments' + class AtmosphericDischargeEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.OneToOneField(System, on_delete=models.CASCADE, default=7) class Meta: db_table = 'equipments_atmospheric_discharge_equipments' -class FireAlarmEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) - equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - -class SructeredCablingEquipment(models.Model): +class StructuredCablingEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.OneToOneField(System, on_delete=models.CASCADE, default=6) + + class Meta: + db_table = 'equipments_structured_cabling_equipments' class DistributionBoardEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.OneToOneField(System, on_delete=models.CASCADE, default=5) + + class Meta: + db_table = 'equipments_distribution_board_equipments' class ElectricalCircuitEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.OneToOneField(System, on_delete=models.CASCADE, default=4) + + class Meta: + db_table = 'equipments_electrical_circuit_equipments' class ElectricalLineEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.OneToOneField(System, on_delete=models.CASCADE, default=3) + + class Meta: + db_table = 'equipments_electrical_line_equipments' class ElectricalLoadEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.OneToOneField(System, on_delete=models.CASCADE, default=2) + + class Meta: + db_table = 'equipments_electrical_load_equipments' class IluminationEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.OneToOneField(System, on_delete=models.CASCADE, default=1) + + class Meta: + db_table = 'equipments_ilumination_equipments' diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 9d7a6979..acf7df49 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -13,52 +13,74 @@ class Meta: model = EquipmentDetail fields = '__all__' -class AtmosphericDischargeEquipmentSerializer(serializers.ModelSerializer): +class FireAlarmEquipmentSerializer(serializers.ModelSerializer): class Meta: - model = AtmosphericDischargeEquipment - fields = '__all__' + model = FireAlarmEquipment + fields = '__all__' + extra_kwargs = { + 'system': {'read_only': True} + } -class FireAlarmEquipmentSerializer(serializers.ModelSerializer): +class AtmosphericDischargeEquipmentSerializer(serializers.ModelSerializer): class Meta: - model = FireAlarmEquipment - fields = '__all__' + model = AtmosphericDischargeEquipment + fields = '__all__' + extra_kwargs = { + 'system': {'read_only': True} + } -class SructeredCablingEquipmentSerializer(serializers.ModelSerializer): +class StructuredCablingEquipmentSerializer(serializers.ModelSerializer): class Meta: - model = SructeredCablingEquipment - fields = '__all__' + model = StructuredCablingEquipment + fields = '__all__' + extra_kwargs = { + 'system': {'read_only': True} + } class DistributionBoardEquipmentSerializer(serializers.ModelSerializer): class Meta: model = DistributionBoardEquipment - fields = '__all__' - + fields = '__all__' + extra_kwargs = { + 'system': {'read_only': True} + } + class ElectricalCircuitEquipmentSerializer(serializers.ModelSerializer): class Meta: model = ElectricalCircuitEquipment - fields = '__all__' - + fields = '__all__' + extra_kwargs = { + 'system': {'read_only': True} + } class ElectricalLineEquipmentSerializer(serializers.ModelSerializer): class Meta: model = ElectricalLineEquipment - fields = '__all__' + fields = '__all__' + extra_kwargs = { + 'system': {'read_only': True} + } class ElectricalLoadEquipmentSerializer(serializers.ModelSerializer): class Meta: model = ElectricalLoadEquipment - fields = '__all__' + fields = '__all__' + extra_kwargs = { + 'system': {'read_only': True} + } class IluminationEquipmentSerializer(serializers.ModelSerializer): class Meta: model = IluminationEquipment fields = '__all__' - \ No newline at end of file + extra_kwargs = { + 'system': {'read_only': True} + } diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 74d58ca7..e4d131d3 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -10,8 +10,8 @@ path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()), path('fire-alarms/', FireAlarmEquipmentList.as_view()), path('fire-alarms//', FireAlarmEquipmentDetail.as_view()), - path('sructered-cabling/', SructeredCablingEquipmentList.as_view()), - path('sructered-cabling//', SructeredCablingEquipmentDetail.as_view()), + path('structured-cabling/', StructuredCablingEquipmentList.as_view()), + path('structured-cabling//', StructuredCablingEquipmentDetail.as_view()), path('distribution-boards/', DistributionBoardEquipmentList.as_view()), path('distribution-boards//', DistributionBoardEquipmentDetail.as_view()), path('electrical-circuits/', ElectricalCircuitEquipmentList.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index 35425b77..801f7a80 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -102,14 +102,14 @@ class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): serializer_class = FireAlarmEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] -class SructeredCablingEquipmentList(generics.ListCreateAPIView): - queryset = SructeredCablingEquipment.objects.all() - serializer_class = SructeredCablingEquipmentSerializer +class StructuredCablingEquipmentList(generics.ListCreateAPIView): + queryset = StructuredCablingEquipment.objects.all() + serializer_class = StructuredCablingEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] def get_queryset(self): user = self.request.user - return SructeredCablingEquipment.objects.filter(area__place__place_owner=user.placeowner) + return StructuredCablingEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): area_id = request.data.get('area') @@ -124,9 +124,9 @@ def create(self, request, *args, **kwargs): else: return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) -class SructeredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = SructeredCablingEquipment.objects.all() - serializer_class = SructeredCablingEquipmentSerializer +class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = StructuredCablingEquipment.objects.all() + serializer_class = StructuredCablingEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] class DistributionBoardEquipmentList(generics.ListCreateAPIView): diff --git a/api/places/migrations/0025_remove_area_systems.py b/api/places/migrations/0025_remove_area_systems.py new file mode 100644 index 00000000..574f6279 --- /dev/null +++ b/api/places/migrations/0025_remove_area_systems.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2 on 2024-05-03 13:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0024_place_lat_place_lon'), + ] + + operations = [ + migrations.RemoveField( + model_name='area', + name='systems', + ), + ] diff --git a/api/places/migrations/0026_alter_area_place.py b/api/places/migrations/0026_alter_area_place.py new file mode 100644 index 00000000..4b7a7943 --- /dev/null +++ b/api/places/migrations/0026_alter_area_place.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2 on 2024-05-03 13:55 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0025_remove_area_systems'), + ] + + operations = [ + migrations.AlterField( + model_name='area', + name='place', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='areas', to='places.place'), + ), + ] diff --git a/api/places/models.py b/api/places/models.py index 19cdb863..c79b5391 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -16,8 +16,7 @@ class Area(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) - place = models.ForeignKey(Place, on_delete=models.CASCADE, null=True) - systems = models.ManyToManyField('systems.System') - + place = models.ForeignKey(Place, on_delete=models.CASCADE, null=True, related_name='areas') + def __str__(self): return self.name diff --git a/api/places/serializers.py b/api/places/serializers.py index 53b2ce8d..5fdc9575 100644 --- a/api/places/serializers.py +++ b/api/places/serializers.py @@ -14,7 +14,7 @@ class Meta: class AreaSerializer(serializers.ModelSerializer): class Meta: model = Area - fields = ['id', 'name', 'floor', 'systems', 'place'] + fields = ['id', 'name', 'floor', 'place'] extra_kwargs = { 'name': {'required': True}, 'floor': {'required': True}, From 572c7d014b6c8bfa9a914af9f7f7f6c48daf037c Mon Sep 17 00:00:00 2001 From: EngDann Date: Fri, 3 May 2024 13:06:32 -0300 Subject: [PATCH 085/351] =?UTF-8?q?Telas=20Baixa=20tens=C3=A3o,=20cabeamen?= =?UTF-8?q?to=20estruturado,=20descargas=20atmosf=C3=A9ricas=20e=20alarme?= =?UTF-8?q?=20de=20inc=C3=AAndio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/main.dart | 24 ++++ .../feature/manage/atmosphericDischarges.dart | 107 ++++++++++++++++++ .../lib/places/feature/manage/fireAlarm.dart | 104 +++++++++++++++++ .../lib/places/feature/manage/lowVoltage.dart | 104 +++++++++++++++++ .../feature/manage/structuredCabling.dart | 106 +++++++++++++++++ .../feature/manage/systemConfiguration.dart | 62 ++++++++-- 6 files changed, 496 insertions(+), 11 deletions(-) create mode 100644 frontend/sige_ie/lib/places/feature/manage/atmosphericDischarges.dart create mode 100644 frontend/sige_ie/lib/places/feature/manage/fireAlarm.dart create mode 100644 frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart create mode 100644 frontend/sige_ie/lib/places/feature/manage/structuredCabling.dart diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 9817264d..e54ea180 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -5,6 +5,10 @@ import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; +import 'package:sige_ie/places/feature/manage/atmosphericDischarges.dart'; +import 'package:sige_ie/places/feature/manage/fireAlarm.dart'; +import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; +import 'package:sige_ie/places/feature/manage/structuredCabling.dart'; import 'package:sige_ie/places/feature/manage/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/places/feature/register/room_state.dart'; @@ -59,6 +63,26 @@ class MyApp extends StatelessWidget { } throw Exception( 'Invalid route: Expected string argument for /systemLocation.'); + case '/lowVoltage': + return MaterialPageRoute( + builder: (context) => const LowVoltageScreen( + roomName: '', + )); + case '/structuredCabling': + return MaterialPageRoute( + builder: (context) => const StructuredCablingScreen( + roomName: '', + )); + case '/atmosphericDischarges': + return MaterialPageRoute( + builder: (context) => const AtmosphericDischargesScreen( + roomName: '', + )); + case '/fireAlarm': + return MaterialPageRoute( + builder: (context) => const FireAlarmScreen( + roomName: '', + )); default: return MaterialPageRoute( builder: (context) => UndefinedView(name: settings.name)); diff --git a/frontend/sige_ie/lib/places/feature/manage/atmosphericDischarges.dart b/frontend/sige_ie/lib/places/feature/manage/atmosphericDischarges.dart new file mode 100644 index 00000000..b4f84560 --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/atmosphericDischarges.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class AtmosphericDischargesScreen extends StatefulWidget { + final String roomName; + + const AtmosphericDischargesScreen({Key? key, required this.roomName}) + : super(key: key); + + @override + _AtmosphericDischargesScreenState createState() => + _AtmosphericDischargesScreenState(); +} + +class _AtmosphericDischargesScreenState + extends State { + void navigateTo(String routeName) { + Navigator.pushNamed(context, routeName); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('${widget.roomName} - Descargas Atmosféricas', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(15.0), + ), + OptionButton( + title: 'ILUMINAÇÃO', onPressed: () => navigateTo('/option1')), + OptionButton( + title: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/option2')), + OptionButton( + title: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/option3')), + OptionButton( + title: 'CIRCUITOS', onPressed: () => navigateTo('/option4')), + OptionButton( + title: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/option5')), + ], + ), + ), + ); + } +} + +class OptionButton extends StatelessWidget { + final String title; + final VoidCallback onPressed; + + const OptionButton({ + Key? key, + required this.title, + required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + width: double.infinity, + child: ElevatedButton( + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.w900)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 25)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: onPressed, + ), + ); + } +} diff --git a/frontend/sige_ie/lib/places/feature/manage/fireAlarm.dart b/frontend/sige_ie/lib/places/feature/manage/fireAlarm.dart new file mode 100644 index 00000000..c6003ccc --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/fireAlarm.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class FireAlarmScreen extends StatefulWidget { + final String roomName; + + const FireAlarmScreen({Key? key, required this.roomName}) : super(key: key); + + @override + _FireAlarmScreenState createState() => _FireAlarmScreenState(); +} + +class _FireAlarmScreenState extends State { + void navigateTo(String routeName) { + Navigator.pushNamed(context, routeName); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('${widget.roomName} - Alarme de Incêndio', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(15.0), + ), + OptionButton( + title: 'ILUMINAÇÃO', onPressed: () => navigateTo('/option1')), + OptionButton( + title: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/option2')), + OptionButton( + title: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/option3')), + OptionButton( + title: 'CIRCUITOS', onPressed: () => navigateTo('/option4')), + OptionButton( + title: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/option5')), + ], + ), + ), + ); + } +} + +class OptionButton extends StatelessWidget { + final String title; + final VoidCallback onPressed; + + const OptionButton({ + Key? key, + required this.title, + required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + width: double.infinity, + child: ElevatedButton( + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.w900)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 25)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: onPressed, + ), + ); + } +} diff --git a/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart b/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart new file mode 100644 index 00000000..549a9c74 --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class LowVoltageScreen extends StatefulWidget { + final String roomName; + + const LowVoltageScreen({Key? key, required this.roomName}) : super(key: key); + + @override + _LowVoltageScreenState createState() => _LowVoltageScreenState(); +} + +class _LowVoltageScreenState extends State { + void navigateTo(String routeName) { + Navigator.pushNamed(context, routeName); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('${widget.roomName} - Baixa Tensão', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(15.0), + ), + OptionButton( + title: 'ILUMINAÇÃO', onPressed: () => navigateTo('/option1')), + OptionButton( + title: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/option2')), + OptionButton( + title: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/option3')), + OptionButton( + title: 'CIRCUITOS', onPressed: () => navigateTo('/option4')), + OptionButton( + title: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/option5')), + ], + ), + ), + ); + } +} + +class OptionButton extends StatelessWidget { + final String title; + final VoidCallback onPressed; + + const OptionButton({ + Key? key, + required this.title, + required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + width: double.infinity, + child: ElevatedButton( + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.w900)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 25)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: onPressed, + ), + ); + } +} diff --git a/frontend/sige_ie/lib/places/feature/manage/structuredCabling.dart b/frontend/sige_ie/lib/places/feature/manage/structuredCabling.dart new file mode 100644 index 00000000..03b595c1 --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/structuredCabling.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class StructuredCablingScreen extends StatefulWidget { + final String roomName; + + const StructuredCablingScreen({Key? key, required this.roomName}) + : super(key: key); + + @override + _StructuredCablingScreenState createState() => + _StructuredCablingScreenState(); +} + +class _StructuredCablingScreenState extends State { + void navigateTo(String routeName) { + Navigator.pushNamed(context, routeName); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('${widget.roomName} - Cabeamento Estruturado', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(15.0), + ), + OptionButton( + title: 'ILUMINAÇÃO', onPressed: () => navigateTo('/option1')), + OptionButton( + title: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/option2')), + OptionButton( + title: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/option3')), + OptionButton( + title: 'CIRCUITOS', onPressed: () => navigateTo('/option4')), + OptionButton( + title: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/option5')), + ], + ), + ), + ); + } +} + +class OptionButton extends StatelessWidget { + final String title; + final VoidCallback onPressed; + + const OptionButton({ + Key? key, + required this.title, + required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + width: double.infinity, + child: ElevatedButton( + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.w900)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 25)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: onPressed, + ), + ); + } +} diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart index f6cbd7c2..303b4f8f 100644 --- a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/places/feature/manage/fireAlarm.dart'; +import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; +import 'package:sige_ie/places/feature/manage/structuredCabling.dart'; +import 'atmosphericDischarges.dart'; class SystemConfiguration extends StatefulWidget { final String roomName; @@ -11,6 +15,30 @@ class SystemConfiguration extends StatefulWidget { } class _SystemConfigurationState extends State { + void navigateTo(String routeName, String roomName) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + switch (routeName) { + case '/lowVoltage': + return LowVoltageScreen(roomName: roomName); + case '/structuredCabling': + return StructuredCablingScreen(roomName: roomName); + case '/atmosphericDischarges': + return AtmosphericDischargesScreen(roomName: roomName); + case '/fireAlarm': + return FireAlarmScreen(roomName: roomName); + default: + return Scaffold( + body: Center(child: Text('No route defined for $routeName')), + ); + } + }, + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -47,10 +75,20 @@ class _SystemConfigurationState extends State { style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), - SystemButton(title: 'BAIXA TENSÃO'), - SystemButton(title: 'CABEAMENTO ESTRUTURADO'), - SystemButton(title: 'DESCARGAS ATMOSFÉRICAS'), - SystemButton(title: 'ALARME DE INCÊNDIO'), + SystemButton( + title: 'BAIXA TENSÃO', + onPressed: () => navigateTo('/lowVoltage', widget.roomName)), + SystemButton( + title: 'CABEAMENTO ESTRUTURADO', + onPressed: () => + navigateTo('/structuredCabling', widget.roomName)), + SystemButton( + title: 'DESCARGAS ATMOSFÉRICAS', + onPressed: () => + navigateTo('/atmosphericDischarges', widget.roomName)), + SystemButton( + title: 'ALARME DE INCÊNDIO', + onPressed: () => navigateTo('/fireAlarm', widget.roomName)), ], ), ), @@ -60,20 +98,25 @@ class _SystemConfigurationState extends State { class SystemButton extends StatelessWidget { final String title; + final VoidCallback onPressed; - const SystemButton({Key? key, required this.title}) : super(key: key); + const SystemButton({ + Key? key, + required this.title, + required this.onPressed, + }) : super(key: key); @override Widget build(BuildContext context) { return Container( - margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), width: double.infinity, child: ElevatedButton( child: Text(title, style: const TextStyle( color: AppColors.sigeIeBlue, fontSize: 18, - fontWeight: FontWeight.bold)), + fontWeight: FontWeight.w900)), style: ButtonStyle( backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), @@ -83,10 +126,7 @@ class SystemButton extends StatelessWidget { borderRadius: BorderRadius.circular(10), )), ), - onPressed: () { - // Adicione a lógica para o que acontece ao pressionar o botão - print('Configuring $title'); - }, + onPressed: onPressed, ), ); } From ece8e0b7c7ea05be60131231360b36be5c1c6bd8 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 3 May 2024 13:57:54 -0300 Subject: [PATCH 086/351] backend: refatora system em equipments para cardinalidade n:1 e simplifica views com mixins --- ...er_atmosphericdischargeequipment_system.py | 20 ++ ...tributionboardequipment_system_and_more.py | 50 +++++ api/equipments/mixins.py | 33 ++++ api/equipments/models.py | 20 +- api/equipments/serializers.py | 17 +- api/equipments/views.py | 171 ++++++------------ 6 files changed, 178 insertions(+), 133 deletions(-) create mode 100644 api/equipments/migrations/0016_alter_atmosphericdischargeequipment_system.py create mode 100644 api/equipments/migrations/0017_alter_distributionboardequipment_system_and_more.py create mode 100644 api/equipments/mixins.py diff --git a/api/equipments/migrations/0016_alter_atmosphericdischargeequipment_system.py b/api/equipments/migrations/0016_alter_atmosphericdischargeequipment_system.py new file mode 100644 index 00000000..e8356204 --- /dev/null +++ b/api/equipments/migrations/0016_alter_atmosphericdischargeequipment_system.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-05-03 16:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('equipments', '0015_rename_sructeredcablingequipment_structuredcablingequipment_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosphericdischargeequipment', + name='system', + field=models.ForeignKey(default=7, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + ] diff --git a/api/equipments/migrations/0017_alter_distributionboardequipment_system_and_more.py b/api/equipments/migrations/0017_alter_distributionboardequipment_system_and_more.py new file mode 100644 index 00000000..4684b905 --- /dev/null +++ b/api/equipments/migrations/0017_alter_distributionboardequipment_system_and_more.py @@ -0,0 +1,50 @@ +# Generated by Django 4.2 on 2024-05-03 16:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('equipments', '0016_alter_atmosphericdischargeequipment_system'), + ] + + operations = [ + migrations.AlterField( + model_name='distributionboardequipment', + name='system', + field=models.ForeignKey(default=5, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='electricalcircuitequipment', + name='system', + field=models.ForeignKey(default=4, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='electricallineequipment', + name='system', + field=models.ForeignKey(default=3, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='electricalloadequipment', + name='system', + field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='firealarmequipment', + name='system', + field=models.ForeignKey(default=8, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='iluminationequipment', + name='system', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterField( + model_name='structuredcablingequipment', + name='system', + field=models.ForeignKey(default=6, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + ] diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py new file mode 100644 index 00000000..eb25d852 --- /dev/null +++ b/api/equipments/mixins.py @@ -0,0 +1,33 @@ +from .models import EquipmentDetail +from rest_framework import serializers + +class ValidateEquipmentDetailAndAreaMixin: + + def validate_equipment_detail(self, value): + """ + Garante que o equipment detail pertence ao system. + """ + system_id = self.instance.system_id if self.instance else 7 + equipment_detail_id = value.id + equipment_detail = EquipmentDetail.objects.filter(id=equipment_detail_id, equipmentType__system_id=system_id).first() + + if not equipment_detail: + raise serializers.ValidationError("The equipment detail does not belong to the specified system") + + """ + Garante que o equipment detail pertence ao place owner. + """ + user = self.context['request'].user + if equipment_detail.place_owner != user.placeowner: + raise serializers.ValidationError("You are not the owner of the equipment detail's place") + + return value + + def validate_area(self, value): + """ + Garante que a area pertence ao place owner. + """ + user = self.context['request'].user + if value.place.place_owner != user.placeowner: + raise serializers.ValidationError("You are not the owner of this place") + return value \ No newline at end of file diff --git a/api/equipments/models.py b/api/equipments/models.py index 5ed1cf31..bff9e47d 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -28,15 +28,15 @@ class Meta: class FireAlarmEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - system = models.OneToOneField(System, on_delete=models.CASCADE, default=8) - + system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) + class Meta: db_table = 'equipments_fire_alarm_equipments' class AtmosphericDischargeEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - system = models.OneToOneField(System, on_delete=models.CASCADE, default=7) + system = models.ForeignKey(System, on_delete=models.CASCADE, default=7) class Meta: db_table = 'equipments_atmospheric_discharge_equipments' @@ -44,7 +44,7 @@ class Meta: class StructuredCablingEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - system = models.OneToOneField(System, on_delete=models.CASCADE, default=6) + system = models.ForeignKey(System, on_delete=models.CASCADE, default=6) class Meta: db_table = 'equipments_structured_cabling_equipments' @@ -52,7 +52,7 @@ class Meta: class DistributionBoardEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - system = models.OneToOneField(System, on_delete=models.CASCADE, default=5) + system = models.ForeignKey(System, on_delete=models.CASCADE, default=5) class Meta: db_table = 'equipments_distribution_board_equipments' @@ -60,7 +60,7 @@ class Meta: class ElectricalCircuitEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - system = models.OneToOneField(System, on_delete=models.CASCADE, default=4) + system = models.ForeignKey(System, on_delete=models.CASCADE, default=4) class Meta: db_table = 'equipments_electrical_circuit_equipments' @@ -68,7 +68,7 @@ class Meta: class ElectricalLineEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - system = models.OneToOneField(System, on_delete=models.CASCADE, default=3) + system = models.ForeignKey(System, on_delete=models.CASCADE, default=3) class Meta: db_table = 'equipments_electrical_line_equipments' @@ -76,7 +76,7 @@ class Meta: class ElectricalLoadEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - system = models.OneToOneField(System, on_delete=models.CASCADE, default=2) + system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) class Meta: db_table = 'equipments_electrical_load_equipments' @@ -84,7 +84,7 @@ class Meta: class IluminationEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) - system = models.OneToOneField(System, on_delete=models.CASCADE, default=1) - + system = models.ForeignKey(System, on_delete=models.CASCADE, default=1) + class Meta: db_table = 'equipments_ilumination_equipments' diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index acf7df49..25ffda91 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers from .models import * +from .mixins import ValidateEquipmentDetailAndAreaMixin class EquipmentTypeSerializer(serializers.ModelSerializer): @@ -22,16 +23,16 @@ class Meta: 'system': {'read_only': True} } -class AtmosphericDischargeEquipmentSerializer(serializers.ModelSerializer): +class AtmosphericDischargeEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): class Meta: model = AtmosphericDischargeEquipment fields = '__all__' extra_kwargs = { 'system': {'read_only': True} - } + } -class StructuredCablingEquipmentSerializer(serializers.ModelSerializer): +class StructuredCablingEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): class Meta: model = StructuredCablingEquipment @@ -40,7 +41,7 @@ class Meta: 'system': {'read_only': True} } -class DistributionBoardEquipmentSerializer(serializers.ModelSerializer): +class DistributionBoardEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): class Meta: model = DistributionBoardEquipment @@ -49,7 +50,7 @@ class Meta: 'system': {'read_only': True} } -class ElectricalCircuitEquipmentSerializer(serializers.ModelSerializer): +class ElectricalCircuitEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalCircuitEquipment @@ -58,7 +59,7 @@ class Meta: 'system': {'read_only': True} } -class ElectricalLineEquipmentSerializer(serializers.ModelSerializer): +class ElectricalLineEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalLineEquipment @@ -67,7 +68,7 @@ class Meta: 'system': {'read_only': True} } -class ElectricalLoadEquipmentSerializer(serializers.ModelSerializer): +class ElectricalLoadEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalLoadEquipment @@ -76,7 +77,7 @@ class Meta: 'system': {'read_only': True} } -class IluminationEquipmentSerializer(serializers.ModelSerializer): +class IluminationEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): class Meta: model = IluminationEquipment diff --git a/api/equipments/views.py b/api/equipments/views.py index 801f7a80..37555d5e 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -41,65 +41,46 @@ class EquipmentDetailDetail(generics.RetrieveUpdateDestroyAPIView): serializer_class = EquipmentDetailSerializer permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] -class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): - queryset = AtmosphericDischargeEquipment.objects.all() - serializer_class = AtmosphericDischargeEquipmentSerializer +class FireAlarmEquipmentList(generics.ListCreateAPIView): + queryset = FireAlarmEquipment.objects.all() + serializer_class = FireAlarmEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] def get_queryset(self): user = self.request.user - return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=user.placeowner) + return FireAlarmEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - area_id = request.data.get('area') - area = Area.objects.filter(id=area_id).first() - - if area and area.place.place_owner == request.user.placeowner: - system_id = 8 - equipment_detail_id = request.data.get('equipment_detail') - equipment_detail = EquipmentDetail.objects.filter(id=equipment_detail_id, equipmentType__system_id=system_id).first() - - if equipment_detail: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "The equipment detail does not belong to the specified system"}, status=status.HTTP_400_BAD_REQUEST) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) -class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = AtmosphericDischargeEquipment.objects.all() - serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] - -class FireAlarmEquipmentList(generics.ListCreateAPIView): +class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] +class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): + queryset = AtmosphericDischargeEquipment.objects.all() + serializer_class = AtmosphericDischargeEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + def get_queryset(self): user = self.request.user - return FireAlarmEquipment.objects.filter(area__place__place_owner=user.placeowner) + return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - area_id = request.data.get('area') - area = Area.objects.filter(id=area_id).first() - - if area and area.place.place_owner == request.user.placeowner: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) -class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = FireAlarmEquipment.objects.all() - serializer_class = FireAlarmEquipmentSerializer +class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = AtmosphericDischargeEquipment.objects.all() + serializer_class = AtmosphericDischargeEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] class StructuredCablingEquipmentList(generics.ListCreateAPIView): @@ -112,17 +93,11 @@ def get_queryset(self): return StructuredCablingEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - area_id = request.data.get('area') - area = Area.objects.filter(id=area_id).first() - - if area is not None and area.place.place_owner == request.user.placeowner: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() @@ -139,17 +114,11 @@ def get_queryset(self): return DistributionBoardEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - area_id = request.data.get('area') - area = Area.objects.filter(id=area_id).first() - - if area is not None and area.place.place_owner == request.user.placeowner: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = DistributionBoardEquipment.objects.all() @@ -166,17 +135,11 @@ def get_queryset(self): return ElectricalCircuitEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - area_id = request.data.get('area') - area = Area.objects.filter(id=area_id).first() - - if area is not None and area.place.place_owner == request.user.placeowner: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() @@ -193,17 +156,11 @@ def get_queryset(self): return ElectricalLineEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - area_id = request.data.get('area') - area = Area.objects.filter(id=area_id).first() - - if area is not None and area.place.place_owner == request.user.placeowner: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLineEquipment.objects.all() @@ -220,17 +177,11 @@ def get_queryset(self): return ElectricalLoadEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - area_id = request.data.get('area') - area = Area.objects.filter(id=area_id).first() - - if area is not None and area.place.place_owner == request.user.placeowner: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLoadEquipment.objects.all() @@ -245,25 +196,15 @@ class IluminationEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user return IluminationEquipment.objects.filter(area__place__place_owner=user.placeowner) - - def create(self, request, *args, **kwargs): - area_id = request.data.get('area') - area = Area.objects.filter(id=area_id).first() - if area is not None and area.place.place_owner == request.user.placeowner: - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] - - - - + permission_classes = [IsPlaceOwner, IsAuthenticated] \ No newline at end of file From a232479843c3e7af1ba437cb3d43f4a4e195555b Mon Sep 17 00:00:00 2001 From: EngDann Date: Fri, 3 May 2024 20:38:53 -0300 Subject: [PATCH 087/351] Telas segunda release parcialmente finalizadas --- frontend/sige_ie/lib/main.dart | 18 +-- .../feature/manage/EquipmentScreen.dart | 100 +++++++++++++++ .../feature/manage/addEquipmentScreen.dart | 121 ++++++++++++++++++ .../feature/manage/atmosphericDischarges.dart | 107 ---------------- .../lib/places/feature/manage/fireAlarm.dart | 104 --------------- .../feature/manage/structuredCabling.dart | 106 --------------- .../feature/manage/systemConfiguration.dart | 10 +- .../feature/manage/viewEquipmentScreen.dart | 55 ++++++++ 8 files changed, 283 insertions(+), 338 deletions(-) create mode 100644 frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart create mode 100644 frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart delete mode 100644 frontend/sige_ie/lib/places/feature/manage/atmosphericDischarges.dart delete mode 100644 frontend/sige_ie/lib/places/feature/manage/fireAlarm.dart delete mode 100644 frontend/sige_ie/lib/places/feature/manage/structuredCabling.dart create mode 100644 frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index e54ea180..6f4b5e0d 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -5,10 +5,8 @@ import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/places/feature/manage/atmosphericDischarges.dart'; -import 'package:sige_ie/places/feature/manage/fireAlarm.dart'; +import 'package:sige_ie/places/feature/manage/EquipmentScreen.dart'; import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; -import 'package:sige_ie/places/feature/manage/structuredCabling.dart'; import 'package:sige_ie/places/feature/manage/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/places/feature/register/room_state.dart'; @@ -68,19 +66,9 @@ class MyApp extends StatelessWidget { builder: (context) => const LowVoltageScreen( roomName: '', )); - case '/structuredCabling': + case '/equipamentScreen': return MaterialPageRoute( - builder: (context) => const StructuredCablingScreen( - roomName: '', - )); - case '/atmosphericDischarges': - return MaterialPageRoute( - builder: (context) => const AtmosphericDischargesScreen( - roomName: '', - )); - case '/fireAlarm': - return MaterialPageRoute( - builder: (context) => const FireAlarmScreen( + builder: (context) => EquipmentScreen( roomName: '', )); default: diff --git a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart new file mode 100644 index 00000000..333af108 --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/places/feature/manage/addEquipmentScreen.dart'; +import 'package:sige_ie/places/feature/manage/viewEquipmentScreen.dart'; + +class EquipmentScreen extends StatelessWidget { + final String roomName; + + EquipmentScreen({Key? key, required this.roomName}) : super(key: key); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddEquipmentScreen(roomName: roomName), + ), + ); + } + + void navigateToViewEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ViewEquipmentScreen(roomName: roomName), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, // Stretches horizontally + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('${roomName}', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + EquipmentButton( + title: 'ADICIONAR EQUIPAMENTOS', + onPressed: () => navigateToAddEquipment(context), + ), + EquipmentButton( + title: 'VER EQUIPAMENTOS', + onPressed: () => navigateToViewEquipment(context), + ), + ], + ), + ); + } +} + +class EquipmentButton extends StatelessWidget { + final String title; + final VoidCallback onPressed; + + EquipmentButton({Key? key, required this.title, required this.onPressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + child: ElevatedButton( + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeYellow, + fontSize: 18, + fontWeight: FontWeight.w900)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 25)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: onPressed, + ), + ); + } +} diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart new file mode 100644 index 00000000..f503d830 --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class AddEquipmentScreen extends StatefulWidget { + final String roomName; + + AddEquipmentScreen({Key? key, required this.roomName}) : super(key: key); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentNameController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedLocation; + List equipmentTypes = ['Tipo 1', 'Tipo 2', 'Tipo 3']; + + void _addNewEquipmentType() { + // Logic to add new equipment type, perhaps a dialog input + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("Adicionar Novo Tipo de Equipamento"), + content: TextField( + autofocus: true, + decoration: + const InputDecoration(hintText: "Digite o tipo de equipamento"), + onSubmitted: (value) { + if (value.isNotEmpty) { + setState(() { + equipmentTypes.add(value); + }); + Navigator.pop(context); + } + }, + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('${widget.roomName} - Adicionar Equipamento'), + backgroundColor: AppColors.sigeIeBlue, + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: _equipmentNameController, + decoration: const InputDecoration( + labelText: 'Nome do equipamento', + ), + ), + const SizedBox(height: 20), + DropdownButtonFormField( + decoration: const InputDecoration( + labelText: 'Tipo de equipamento', + ), + value: _selectedType, + items: equipmentTypes.map((String type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + }); + }, + ), + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + const SizedBox(height: 20), + TextField( + controller: _equipmentQuantityController, + decoration: const InputDecoration( + labelText: 'Quantidade', + ), + keyboardType: TextInputType.number, + ), + const SizedBox(height: 20), + DropdownButtonFormField( + decoration: const InputDecoration( + labelText: 'Interno ou Externo', + ), + value: _selectedLocation, + items: const [ + DropdownMenuItem(value: 'Interno', child: Text('Interno')), + DropdownMenuItem(value: 'Externo', child: Text('Externo')), + ], + onChanged: (newValue) { + setState(() { + _selectedLocation = newValue; + }); + }, + ), + const SizedBox(height: 20), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: () { + // Logic to handle camera access + }, + ), + ], + ), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/places/feature/manage/atmosphericDischarges.dart b/frontend/sige_ie/lib/places/feature/manage/atmosphericDischarges.dart deleted file mode 100644 index b4f84560..00000000 --- a/frontend/sige_ie/lib/places/feature/manage/atmosphericDischarges.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; - -class AtmosphericDischargesScreen extends StatefulWidget { - final String roomName; - - const AtmosphericDischargesScreen({Key? key, required this.roomName}) - : super(key: key); - - @override - _AtmosphericDischargesScreenState createState() => - _AtmosphericDischargesScreenState(); -} - -class _AtmosphericDischargesScreenState - extends State { - void navigateTo(String routeName) { - Navigator.pushNamed(context, routeName); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text('${widget.roomName} - Descargas Atmosféricas', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - Padding( - padding: const EdgeInsets.all(15.0), - ), - OptionButton( - title: 'ILUMINAÇÃO', onPressed: () => navigateTo('/option1')), - OptionButton( - title: 'CARGAS ELÉTRICAS', - onPressed: () => navigateTo('/option2')), - OptionButton( - title: 'LINHAS ELÉTRICAS', - onPressed: () => navigateTo('/option3')), - OptionButton( - title: 'CIRCUITOS', onPressed: () => navigateTo('/option4')), - OptionButton( - title: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/option5')), - ], - ), - ), - ); - } -} - -class OptionButton extends StatelessWidget { - final String title; - final VoidCallback onPressed; - - const OptionButton({ - Key? key, - required this.title, - required this.onPressed, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), - width: double.infinity, - child: ElevatedButton( - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 25)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: onPressed, - ), - ); - } -} diff --git a/frontend/sige_ie/lib/places/feature/manage/fireAlarm.dart b/frontend/sige_ie/lib/places/feature/manage/fireAlarm.dart deleted file mode 100644 index c6003ccc..00000000 --- a/frontend/sige_ie/lib/places/feature/manage/fireAlarm.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; - -class FireAlarmScreen extends StatefulWidget { - final String roomName; - - const FireAlarmScreen({Key? key, required this.roomName}) : super(key: key); - - @override - _FireAlarmScreenState createState() => _FireAlarmScreenState(); -} - -class _FireAlarmScreenState extends State { - void navigateTo(String routeName) { - Navigator.pushNamed(context, routeName); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text('${widget.roomName} - Alarme de Incêndio', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - Padding( - padding: const EdgeInsets.all(15.0), - ), - OptionButton( - title: 'ILUMINAÇÃO', onPressed: () => navigateTo('/option1')), - OptionButton( - title: 'CARGAS ELÉTRICAS', - onPressed: () => navigateTo('/option2')), - OptionButton( - title: 'LINHAS ELÉTRICAS', - onPressed: () => navigateTo('/option3')), - OptionButton( - title: 'CIRCUITOS', onPressed: () => navigateTo('/option4')), - OptionButton( - title: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/option5')), - ], - ), - ), - ); - } -} - -class OptionButton extends StatelessWidget { - final String title; - final VoidCallback onPressed; - - const OptionButton({ - Key? key, - required this.title, - required this.onPressed, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), - width: double.infinity, - child: ElevatedButton( - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 25)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: onPressed, - ), - ); - } -} diff --git a/frontend/sige_ie/lib/places/feature/manage/structuredCabling.dart b/frontend/sige_ie/lib/places/feature/manage/structuredCabling.dart deleted file mode 100644 index 03b595c1..00000000 --- a/frontend/sige_ie/lib/places/feature/manage/structuredCabling.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; - -class StructuredCablingScreen extends StatefulWidget { - final String roomName; - - const StructuredCablingScreen({Key? key, required this.roomName}) - : super(key: key); - - @override - _StructuredCablingScreenState createState() => - _StructuredCablingScreenState(); -} - -class _StructuredCablingScreenState extends State { - void navigateTo(String routeName) { - Navigator.pushNamed(context, routeName); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text('${widget.roomName} - Cabeamento Estruturado', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - Padding( - padding: const EdgeInsets.all(15.0), - ), - OptionButton( - title: 'ILUMINAÇÃO', onPressed: () => navigateTo('/option1')), - OptionButton( - title: 'CARGAS ELÉTRICAS', - onPressed: () => navigateTo('/option2')), - OptionButton( - title: 'LINHAS ELÉTRICAS', - onPressed: () => navigateTo('/option3')), - OptionButton( - title: 'CIRCUITOS', onPressed: () => navigateTo('/option4')), - OptionButton( - title: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/option5')), - ], - ), - ), - ); - } -} - -class OptionButton extends StatelessWidget { - final String title; - final VoidCallback onPressed; - - const OptionButton({ - Key? key, - required this.title, - required this.onPressed, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), - width: double.infinity, - child: ElevatedButton( - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 25)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: onPressed, - ), - ); - } -} diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart index 303b4f8f..492a1a5c 100644 --- a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/feature/manage/fireAlarm.dart'; +import 'package:sige_ie/places/feature/manage/EquipmentScreen.dart'; import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; -import 'package:sige_ie/places/feature/manage/structuredCabling.dart'; -import 'atmosphericDischarges.dart'; class SystemConfiguration extends StatefulWidget { final String roomName; @@ -24,11 +22,11 @@ class _SystemConfigurationState extends State { case '/lowVoltage': return LowVoltageScreen(roomName: roomName); case '/structuredCabling': - return StructuredCablingScreen(roomName: roomName); + return EquipmentScreen(roomName: roomName); case '/atmosphericDischarges': - return AtmosphericDischargesScreen(roomName: roomName); + return EquipmentScreen(roomName: roomName); case '/fireAlarm': - return FireAlarmScreen(roomName: roomName); + return EquipmentScreen(roomName: roomName); default: return Scaffold( body: Center(child: Text('No route defined for $routeName')), diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart new file mode 100644 index 00000000..fd80f3a3 --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ViewEquipmentScreen extends StatefulWidget { + final String roomName; + + ViewEquipmentScreen({Key? key, required this.roomName}) : super(key: key); + + @override + _ViewEquipmentScreenState createState() => _ViewEquipmentScreenState(); +} + +class _ViewEquipmentScreenState extends State { + String? _selectedEquipment; + List equipmentList = [ + 'Equipamento 1', + 'Equipamento 2', + 'Equipamento 3' + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('${widget.roomName} - Equipamentos'), + backgroundColor: AppColors.sigeIeBlue, + ), + body: Center( + child: DropdownButtonFormField( + decoration: const InputDecoration( + labelText: 'Selecione um equipamento', + filled: true, + fillColor: Colors.white, + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + ), + value: _selectedEquipment, + onChanged: (newValue) { + setState(() { + _selectedEquipment = newValue; + }); + }, + items: equipmentList.map((String equipment) { + return DropdownMenuItem( + value: equipment, + child: Text(equipment), + ); + }).toList(), + ), + ), + ); + } +} From ea4ffd807337d092509a5af6b0f668dd93a0a600 Mon Sep 17 00:00:00 2001 From: EngDann Date: Sat, 4 May 2024 20:25:02 -0300 Subject: [PATCH 088/351] Front: Complementando telas --- .../feature/manage/EquipmentScreen.dart | 1 + .../feature/manage/addEquipmentScreen.dart | 208 +++++++++++++----- .../feature/manage/viewEquipmentScreen.dart | 105 +++++++-- .../places/feature/register/new_place.dart | 2 +- .../places/feature/register/room_state.dart | 129 +++++------ 5 files changed, 301 insertions(+), 144 deletions(-) diff --git a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart index 333af108..1b42b848 100644 --- a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart @@ -54,6 +54,7 @@ class EquipmentScreen extends StatelessWidget { color: AppColors.lightText)), ), ), + SizedBox(height: 150), // Define the height for spacing as needed EquipmentButton( title: 'ADICIONAR EQUIPAMENTOS', onPressed: () => navigateToAddEquipment(context), diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart index f503d830..27d4fbfe 100644 --- a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart @@ -18,7 +18,6 @@ class _AddEquipmentScreenState extends State { List equipmentTypes = ['Tipo 1', 'Tipo 2', 'Tipo 3']; void _addNewEquipmentType() { - // Logic to add new equipment type, perhaps a dialog input showDialog( context: context, builder: (BuildContext context) { @@ -46,76 +45,171 @@ class _AddEquipmentScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('${widget.roomName} - Adicionar Equipamento'), backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, ), body: SingleChildScrollView( - padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextField( - controller: _equipmentNameController, - decoration: const InputDecoration( - labelText: 'Nome do equipamento', + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), ), - ), - const SizedBox(height: 20), - DropdownButtonFormField( - decoration: const InputDecoration( - labelText: 'Tipo de equipamento', - ), - value: _selectedType, - items: equipmentTypes.map((String type) { - return DropdownMenuItem( - value: type, - child: Text(type), - ); - }).toList(), - onChanged: (newValue) { - setState(() { - _selectedType = newValue; - }); - }, - ), - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - const SizedBox(height: 20), - TextField( - controller: _equipmentQuantityController, - decoration: const InputDecoration( - labelText: 'Quantidade', + child: const Center( + child: Text('Registrar Novo Equipamento', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), ), - keyboardType: TextInputType.number, ), - const SizedBox(height: 20), - DropdownButtonFormField( - decoration: const InputDecoration( - labelText: 'Interno ou Externo', + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + const Text('Nome do equipamento', + style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentNameController, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Tipo de equipamento', + style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + _buildDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + }); + }, + addNew: _addNewEquipmentType, + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Localização (Interno ou Externo)', + style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + _buildDropdown( + items: const ['Interno', 'Externo'], + value: _selectedLocation, + onChanged: (newValue) { + setState(() { + _selectedLocation = newValue; + }); + }, + ), + const SizedBox(height: 20), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: () { + // lógica de evento aqui + }, + ), + const SizedBox(height: 20), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(165, 50)), + textStyle: MaterialStateProperty.all( + const TextStyle( + fontSize: 15, fontWeight: FontWeight.bold), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + onPressed: () { + // lógica de evento aqui + }, + child: const Text('CONTINUAR'), + ), + ), + ], ), - value: _selectedLocation, - items: const [ - DropdownMenuItem(value: 'Interno', child: Text('Interno')), - DropdownMenuItem(value: 'Externo', child: Text('Externo')), - ], - onChanged: (newValue) { - setState(() { - _selectedLocation = newValue; - }); - }, - ), - const SizedBox(height: 20), - IconButton( - icon: const Icon(Icons.camera_alt), - onPressed: () { - // Logic to handle camera access - }, ), ], ), ), ); } + + Widget _buildDropdown({ + required List items, + required String? value, + required void Function(String?) onChanged, + VoidCallback? addNew, + }) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButtonHideUnderline( + child: DropdownButtonFormField( + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 4), + ), + value: value, + isExpanded: true, + items: items.map((String item) { + return DropdownMenuItem( + value: item, + child: Text(item), + ); + }).toList(), + onChanged: onChanged, + style: TextStyle(color: Colors.black), + dropdownColor: Colors.grey[200], + ), + ), + ); + } } diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart index fd80f3a3..513f7756 100644 --- a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart @@ -22,32 +22,91 @@ class _ViewEquipmentScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('${widget.roomName} - Equipamentos'), backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, ), - body: Center( - child: DropdownButtonFormField( - decoration: const InputDecoration( - labelText: 'Selecione um equipamento', - filled: true, - fillColor: Colors.white, - contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(8.0)), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Visualizar Equipamentos', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), ), - ), - value: _selectedEquipment, - onChanged: (newValue) { - setState(() { - _selectedEquipment = newValue; - }); - }, - items: equipmentList.map((String equipment) { - return DropdownMenuItem( - value: equipment, - child: Text(equipment), - ); - }).toList(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + SizedBox(height: 150), + Center( + child: DropdownButtonFormField( + decoration: const InputDecoration( + labelText: 'Selecione um equipamento', + filled: true, + fillColor: Colors.white, + contentPadding: + EdgeInsets.symmetric(horizontal: 12, vertical: 10), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + ), + value: _selectedEquipment, + onChanged: (newValue) { + setState(() { + _selectedEquipment = newValue; + }); + }, + items: equipmentList.map((String equipment) { + return DropdownMenuItem( + value: equipment, + child: Text(equipment), + ); + }).toList(), + ), + ), + SizedBox(height: 150), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(165, 50)), + textStyle: MaterialStateProperty.all( + const TextStyle( + fontSize: 15, fontWeight: FontWeight.bold), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + onPressed: () { + // Lógica do evento aqui + }, + child: const Text('CONTINUAR'), + ), + ), + SizedBox(height: 20), + ], + ), + ), + ], ), ), ); diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index 90169278..1a3225b1 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -173,7 +173,7 @@ class NewPlaceState extends State { Navigator.of(context).pushNamed('/roomlocation', arguments: nameController.text.trim()); } - } else if (nameController.text.trim().isEmpty) { + } else if (nameController.text.trim().isEmpty || !coord) { showDialog( context: context, builder: (BuildContext context) { diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index 15238cc1..6718e05d 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -32,7 +32,6 @@ class _RoomLocationState extends State { ), body: SingleChildScrollView( child: Column( - mainAxisAlignment: MainAxisAlignment.start, children: [ Container( width: double.infinity, @@ -62,33 +61,14 @@ class _RoomLocationState extends State { fontWeight: FontWeight.bold, color: Colors.black)), SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(10)), - child: DropdownButtonHideUnderline( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 10), - child: DropdownButton( - value: selectedFloor, - hint: Text('Selecione o Andar'), - isExpanded: true, - items: floors - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - onChanged: (newValue) { - setState(() { - selectedFloor = newValue; - }); - }, - ), - ), - ), - ), + _buildDropdown( + items: floors, + value: selectedFloor, + onChanged: (String? newValue) { + setState(() { + selectedFloor = newValue; + }); + }), SizedBox(height: 40), Text('Sala', style: TextStyle( @@ -98,7 +78,7 @@ class _RoomLocationState extends State { SizedBox(height: 10), Container( decoration: BoxDecoration( - color: Colors.grey[200], + color: Colors.grey[300], borderRadius: BorderRadius.circular(10)), child: TextField( controller: roomController, @@ -112,21 +92,17 @@ class _RoomLocationState extends State { SizedBox(height: 60), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ + children: [ ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: MaterialStateProperty.all(Size(165, 50)), - textStyle: MaterialStateProperty.all( - TextStyle( - fontSize: 15, fontWeight: FontWeight.bold), + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.sigeIeBlue, + backgroundColor: AppColors.sigeIeYellow, + minimumSize: Size(165, 50), + textStyle: TextStyle( + fontWeight: FontWeight.bold, ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), ), onPressed: () { @@ -134,11 +110,8 @@ class _RoomLocationState extends State { roomController.text.isNotEmpty) { print( 'Sala Registrada: ${roomController.text} no ${selectedFloor}'); - Navigator.pushNamed( - context, - '/systemLocation', - arguments: roomController.text, - ); + Navigator.pushNamed(context, '/systemLocation', + arguments: roomController.text); } else { showDialog( context: context, @@ -150,9 +123,8 @@ class _RoomLocationState extends State { actions: [ TextButton( child: Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - }, + onPressed: () => + Navigator.of(context).pop(), ), ], ); @@ -163,19 +135,16 @@ class _RoomLocationState extends State { child: Text('CONTINUAR'), ), ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.warn), - foregroundColor: - MaterialStateProperty.all(AppColors.lightText), - minimumSize: MaterialStateProperty.all(Size(165, 50)), - textStyle: MaterialStateProperty.all( - TextStyle( - fontSize: 15, fontWeight: FontWeight.bold), + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.lightText, + backgroundColor: AppColors.warn, + minimumSize: Size(165, 50), + textStyle: TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8))), ), onPressed: () => Navigator.of(context).pop(), child: Text('ENCERRAR'), @@ -184,10 +153,44 @@ class _RoomLocationState extends State { ), ], ), - ) + ), ], ), ), ); } + + Widget _buildDropdown({ + required List items, + required String? value, + required void Function(String?) onChanged, + VoidCallback? addNew, + }) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButtonHideUnderline( + child: DropdownButtonFormField( + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 4), + ), + value: value, + isExpanded: true, + items: items.map((String item) { + return DropdownMenuItem( + value: item, + child: Text(item), + ); + }).toList(), + onChanged: onChanged, + style: TextStyle(color: Colors.black), + dropdownColor: Colors.grey[200], + ), + ), + ); + } } From 8b818b22ae92c40c4c3aeb14e7babd28ee4015ae Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 6 May 2024 11:51:57 -0300 Subject: [PATCH 089/351] =?UTF-8?q?Front:=20L=C3=B3gica=20de=20categorias?= =?UTF-8?q?=20de=20equipamentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/main.dart | 8 +- .../feature/manage/EquipmentScreen.dart | 24 +++- .../feature/manage/addEquipmentScreen.dart | 6 +- .../feature/manage/equipment_manager.dart | 30 ++++ .../lib/places/feature/manage/lowVoltage.dart | 4 +- .../feature/manage/systemConfiguration.dart | 23 +-- .../feature/manage/viewEquipmentScreen.dart | 131 +++++++++--------- 7 files changed, 135 insertions(+), 91 deletions(-) create mode 100644 frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 6f4b5e0d..f794a359 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -56,8 +56,10 @@ class MyApp extends StatelessWidget { if (settings.arguments is String) { final roomName = settings.arguments as String; return MaterialPageRoute( - builder: (context) => - SystemConfiguration(roomName: roomName)); + builder: (context) => SystemConfiguration( + roomName: roomName, + categoryNumber: 0, + )); } throw Exception( 'Invalid route: Expected string argument for /systemLocation.'); @@ -65,11 +67,13 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => const LowVoltageScreen( roomName: '', + categoryNumber: 0, )); case '/equipamentScreen': return MaterialPageRoute( builder: (context) => EquipmentScreen( roomName: '', + categoryNumber: 0, )); default: return MaterialPageRoute( diff --git a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart index 1b42b848..f1f97e2e 100644 --- a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart @@ -5,14 +5,22 @@ import 'package:sige_ie/places/feature/manage/viewEquipmentScreen.dart'; class EquipmentScreen extends StatelessWidget { final String roomName; + final int categoryNumber; - EquipmentScreen({Key? key, required this.roomName}) : super(key: key); + EquipmentScreen({ + Key? key, + required this.roomName, + required this.categoryNumber, + }) : super(key: key); void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( - builder: (context) => AddEquipmentScreen(roomName: roomName), + builder: (context) => AddEquipmentScreen( + roomName: roomName, + categoryNumber: categoryNumber, + ), ), ); } @@ -21,7 +29,10 @@ class EquipmentScreen extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => ViewEquipmentScreen(roomName: roomName), + builder: (context) => ViewEquipmentScreen( + roomName: roomName, + categoryNumber: categoryNumber, + ), ), ); } @@ -37,8 +48,7 @@ class EquipmentScreen extends StatelessWidget { ), ), body: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, // Stretches horizontally + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), @@ -47,14 +57,14 @@ class EquipmentScreen extends StatelessWidget { borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('${roomName}', + child: Text(roomName, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, color: AppColors.lightText)), ), ), - SizedBox(height: 150), // Define the height for spacing as needed + SizedBox(height: 150), EquipmentButton( title: 'ADICIONAR EQUIPAMENTOS', onPressed: () => navigateToAddEquipment(context), diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart index 27d4fbfe..c7cc2fcc 100644 --- a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart @@ -3,9 +3,11 @@ import 'package:sige_ie/config/app_styles.dart'; class AddEquipmentScreen extends StatefulWidget { final String roomName; + final int categoryNumber; - AddEquipmentScreen({Key? key, required this.roomName}) : super(key: key); - + AddEquipmentScreen( + {Key? key, required this.roomName, required this.categoryNumber}) + : super(key: key); @override _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } diff --git a/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart b/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart new file mode 100644 index 00000000..bfbe81fb --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart @@ -0,0 +1,30 @@ +class EquipmentManager { + static const Map categoryMap = { + 1: 'Cabeamento Estruturado', + 2: 'Descargas Atmosféricas', + 3: 'Alarme de Incêndio' + }; + + static List getCategoryList() { + return categoryMap.values.toList(); + } + + static List getEquipmentList(int categoryId) { + switch (categoryId) { + case 1: + return ['Eletroduto', 'Eletrocalha', 'Dimensão']; + case 2: + return ['Para Raios', 'Captação', 'Subsistemas']; + case 3: + return [ + 'Alarme de Incêndio', + 'Sensor de Fumaça', + 'Sensor de Temperatura', + 'Acionadores', + 'Central de alarme de Incêndio' + ]; + default: + return []; + } + } +} diff --git a/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart b/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart index 549a9c74..e33370a4 100644 --- a/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart +++ b/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart @@ -4,7 +4,9 @@ import 'package:sige_ie/config/app_styles.dart'; class LowVoltageScreen extends StatefulWidget { final String roomName; - const LowVoltageScreen({Key? key, required this.roomName}) : super(key: key); + const LowVoltageScreen( + {Key? key, required this.roomName, required int categoryNumber}) + : super(key: key); @override _LowVoltageScreenState createState() => _LowVoltageScreenState(); diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart index 492a1a5c..d4eac3e8 100644 --- a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart @@ -5,28 +5,31 @@ import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; class SystemConfiguration extends StatefulWidget { final String roomName; + final int categoryNumber; - SystemConfiguration({Key? key, required this.roomName}) : super(key: key); + SystemConfiguration( + {Key? key, required this.roomName, required this.categoryNumber}) + : super(key: key); @override _SystemConfigurationState createState() => _SystemConfigurationState(); } class _SystemConfigurationState extends State { - void navigateTo(String routeName, String roomName) { + void navigateTo(String routeName, String roomName, [int categoryNumber = 0]) { Navigator.push( context, MaterialPageRoute( builder: (context) { switch (routeName) { case '/lowVoltage': - return LowVoltageScreen(roomName: roomName); + return LowVoltageScreen( + roomName: roomName, categoryNumber: categoryNumber); case '/structuredCabling': - return EquipmentScreen(roomName: roomName); case '/atmosphericDischarges': - return EquipmentScreen(roomName: roomName); case '/fireAlarm': - return EquipmentScreen(roomName: roomName); + return EquipmentScreen( + roomName: roomName, categoryNumber: categoryNumber); default: return Scaffold( body: Center(child: Text('No route defined for $routeName')), @@ -75,18 +78,18 @@ class _SystemConfigurationState extends State { ), SystemButton( title: 'BAIXA TENSÃO', - onPressed: () => navigateTo('/lowVoltage', widget.roomName)), + onPressed: () => navigateTo('/lowVoltage', widget.roomName, 0)), SystemButton( title: 'CABEAMENTO ESTRUTURADO', onPressed: () => - navigateTo('/structuredCabling', widget.roomName)), + navigateTo('/structuredCabling', widget.roomName, 1)), SystemButton( title: 'DESCARGAS ATMOSFÉRICAS', onPressed: () => - navigateTo('/atmosphericDischarges', widget.roomName)), + navigateTo('/atmosphericDischarges', widget.roomName, 2)), SystemButton( title: 'ALARME DE INCÊNDIO', - onPressed: () => navigateTo('/fireAlarm', widget.roomName)), + onPressed: () => navigateTo('/fireAlarm', widget.roomName, 3)), ], ), ), diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart index 513f7756..7a70eecc 100644 --- a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/places/feature/manage/equipment_manager.dart'; class ViewEquipmentScreen extends StatefulWidget { final String roomName; + final int categoryNumber; - ViewEquipmentScreen({Key? key, required this.roomName}) : super(key: key); + ViewEquipmentScreen( + {Key? key, required this.roomName, required this.categoryNumber}) + : super(key: key); @override _ViewEquipmentScreenState createState() => _ViewEquipmentScreenState(); @@ -12,11 +16,6 @@ class ViewEquipmentScreen extends StatefulWidget { class _ViewEquipmentScreenState extends State { String? _selectedEquipment; - List equipmentList = [ - 'Equipamento 1', - 'Equipamento 2', - 'Equipamento 3' - ]; @override Widget build(BuildContext context) { @@ -29,80 +28,74 @@ class _ViewEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: const Center( - child: Text('Visualizar Equipamentos', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.all(20), child: Column( children: [ - SizedBox(height: 150), - Center( - child: DropdownButtonFormField( - decoration: const InputDecoration( - labelText: 'Selecione um equipamento', - filled: true, - fillColor: Colors.white, - contentPadding: - EdgeInsets.symmetric(horizontal: 12, vertical: 10), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(8.0)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Categoria: ', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + Expanded( + child: Text( + EquipmentManager.categoryMap[widget.categoryNumber]!, + style: TextStyle( + fontSize: 18, color: AppColors.sigeIeBlue), ), ), - value: _selectedEquipment, - onChanged: (newValue) { - setState(() { - _selectedEquipment = newValue; - }); - }, - items: equipmentList.map((String equipment) { - return DropdownMenuItem( - value: equipment, - child: Text(equipment), - ); - }).toList(), + ], + ), + const SizedBox(height: 20), + DropdownButtonFormField( + decoration: const InputDecoration( + labelText: 'Selecione um equipamento', + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder(), ), + value: _selectedEquipment, + onChanged: (newValue) { + setState(() { + _selectedEquipment = newValue; + }); + }, + items: + EquipmentManager.getEquipmentList(widget.categoryNumber) + .map((String equipment) { + return DropdownMenuItem( + value: equipment, + child: Text(equipment), + ); + }).toList(), ), - SizedBox(height: 150), - Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: - MaterialStateProperty.all(const Size(165, 50)), - textStyle: MaterialStateProperty.all( - const TextStyle( - fontSize: 15, fontWeight: FontWeight.bold), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + const SizedBox(height: 50), + ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(165, 50)), + textStyle: MaterialStateProperty.all( + const TextStyle( + fontSize: 15, fontWeight: FontWeight.bold), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), ), - onPressed: () { - // Lógica do evento aqui - }, - child: const Text('CONTINUAR'), ), + onPressed: () { + // Lógica do evento aqui + }, + child: const Text('CONTINUAR'), ), - SizedBox(height: 20), ], ), ), From 0c4e6a310bd6d32d5e148981d209dd085d9b76a8 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 6 May 2024 14:01:04 -0300 Subject: [PATCH 090/351] =?UTF-8?q?Front:=20Adicionar=20e=20visualizar=20e?= =?UTF-8?q?quipamentos=20do=20cabeamento,=20descargas=20atmosf=C3=A9ricas?= =?UTF-8?q?=20e=20alarme=20de=20inc=C3=AAndio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/app/src/main/AndroidManifest.xml | 2 + .../feature/manage/addEquipmentScreen.dart | 181 +++++++++++++----- .../feature/manage/viewEquipmentScreen.dart | 26 ++- .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + frontend/sige_ie/pubspec.lock | 120 ++++++++++++ frontend/sige_ie/pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 10 files changed, 287 insertions(+), 54 deletions(-) diff --git a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml index 8f00cf37..5abd1764 100644 --- a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml +++ b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + _AddEquipmentScreenState(); } @@ -17,27 +23,65 @@ class _AddEquipmentScreenState extends State { final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedLocation; - List equipmentTypes = ['Tipo 1', 'Tipo 2', 'Tipo 3']; + List equipmentTypes = []; // A lista agora é inicializada vazia + Future _pickImage() async { + try { + final picker = ImagePicker(); + final pickedFile = await picker.pickImage(source: ImageSource.camera); + + if (pickedFile != null) { + setState(() { + _image = File(pickedFile.path); + }); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + @override + void initState() { + super.initState(); + // Carrega os tipos de equipamento com base na categoria selecionada ao iniciar a tela + equipmentTypes = EquipmentManager.getEquipmentList(widget.categoryNumber); + } void _addNewEquipmentType() { showDialog( context: context, builder: (BuildContext context) { + TextEditingController newTypeController = TextEditingController(); + return AlertDialog( title: const Text("Adicionar Novo Tipo de Equipamento"), content: TextField( + controller: newTypeController, autofocus: true, decoration: const InputDecoration(hintText: "Digite o tipo de equipamento"), - onSubmitted: (value) { - if (value.isNotEmpty) { - setState(() { - equipmentTypes.add(value); - }); - Navigator.pop(context); - } - }, ), + actions: [ + TextButton( + child: const Text("Cancelar"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text("Adicionar"), + onPressed: () { + String newType = newTypeController.text; + if (newType.isNotEmpty) { + setState(() { + equipmentTypes.add(newType); + // Opcionalmente, pode-se definir o novo tipo como o selecionado + _selectedType = newType; + }); + Navigator.of(context).pop(); + } + }, + ) + ], ); }, ); @@ -75,7 +119,41 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Categoria: ', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + Expanded( + child: Text( + EquipmentManager.categoryMap[widget.categoryNumber]!, + style: TextStyle( + fontSize: 18, + color: AppColors.sigeIeBlue, + fontWeight: FontWeight.w500), + ), + ), + ], + ), + const SizedBox(height: 15), + const Text('Equipamento', + style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + _buildDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + }); + }, + addNew: _addNewEquipmentType, + showAddButton: true, // Ative o botão de adição + ), + const SizedBox(height: 30), const Text('Nome do equipamento', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), @@ -94,20 +172,6 @@ class _AddEquipmentScreenState extends State { ), ), const SizedBox(height: 30), - const Text('Tipo de equipamento', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - _buildDropdown( - items: equipmentTypes, - value: _selectedType, - onChanged: (newValue) { - setState(() { - _selectedType = newValue; - }); - }, - addNew: _addNewEquipmentType, - ), - const SizedBox(height: 30), const Text('Quantidade', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), @@ -139,14 +203,13 @@ class _AddEquipmentScreenState extends State { }); }, ), - const SizedBox(height: 20), + const SizedBox(height: 15), IconButton( icon: const Icon(Icons.camera_alt), - onPressed: () { - // lógica de evento aqui - }, + onPressed: + _pickImage, // Atualiza aqui para chamar _pickImage quando clicado ), - const SizedBox(height: 20), + const SizedBox(height: 15), Center( child: ElevatedButton( style: ButtonStyle( @@ -167,7 +230,7 @@ class _AddEquipmentScreenState extends State { ), ), onPressed: () { - // lógica de evento aqui + // Lógica de evento aqui }, child: const Text('CONTINUAR'), ), @@ -186,32 +249,44 @@ class _AddEquipmentScreenState extends State { required String? value, required void Function(String?) onChanged, VoidCallback? addNew, + bool showAddButton = false, }) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10), - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: DropdownButtonHideUnderline( - child: DropdownButtonFormField( - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 4), + return Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButtonHideUnderline( + child: DropdownButtonFormField( + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 4), + ), + value: value, + isExpanded: true, + items: items.map((String item) { + return DropdownMenuItem( + value: item, + child: Text(item), + ); + }).toList(), + onChanged: onChanged, + style: TextStyle(color: Colors.black), + dropdownColor: Colors.grey[200], + ), + ), ), - value: value, - isExpanded: true, - items: items.map((String item) { - return DropdownMenuItem( - value: item, - child: Text(item), - ); - }).toList(), - onChanged: onChanged, - style: TextStyle(color: Colors.black), - dropdownColor: Colors.grey[200], ), - ), + if (showAddButton) + IconButton( + icon: const Icon(Icons.add), + onPressed: addNew, + ), + ], ); } } diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart index 7a70eecc..be1ce640 100644 --- a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart @@ -28,6 +28,25 @@ class _ViewEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Equipamentos', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + SizedBox( + height: 20, + ), Padding( padding: const EdgeInsets.all(20), child: Column( @@ -44,7 +63,9 @@ class _ViewEquipmentScreenState extends State { child: Text( EquipmentManager.categoryMap[widget.categoryNumber]!, style: TextStyle( - fontSize: 18, color: AppColors.sigeIeBlue), + fontSize: 18, + color: AppColors.sigeIeBlue, + fontWeight: FontWeight.w500), ), ), ], @@ -72,6 +93,9 @@ class _ViewEquipmentScreenState extends State { ); }).toList(), ), + SizedBox( + height: 40, + ), const SizedBox(height: 50), ElevatedButton( style: ButtonStyle( diff --git a/frontend/sige_ie/linux/flutter/generated_plugin_registrant.cc b/frontend/sige_ie/linux/flutter/generated_plugin_registrant.cc index e71a16d2..64a0ecea 100644 --- a/frontend/sige_ie/linux/flutter/generated_plugin_registrant.cc +++ b/frontend/sige_ie/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); } diff --git a/frontend/sige_ie/linux/flutter/generated_plugins.cmake b/frontend/sige_ie/linux/flutter/generated_plugins.cmake index 2e1de87a..2db3c22a 100644 --- a/frontend/sige_ie/linux/flutter/generated_plugins.cmake +++ b/frontend/sige_ie/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift index e5a3e464..af3162f6 100644 --- a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,11 +5,13 @@ import FlutterMacOS import Foundation +import file_selector_macos import geolocator_apple import shared_preferences_foundation import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 8e0ec0fd..9683b3ff 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.8" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" crypto: dependency: transitive description: @@ -97,6 +105,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" fixnum: dependency: transitive description: @@ -118,6 +158,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + url: "https://pub.dev" + source: hosted + version: "2.0.19" flutter_test: dependency: "direct dev" description: flutter @@ -208,6 +256,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c + url: "https://pub.dev" + source: hosted + version: "0.8.9" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "40e24f467b75cd6f4a92ee93dd13d1a7bcb4523a84fd95f00c755f01f42398c8" + url: "https://pub.dev" + source: hosted + version: "0.8.11" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: f74064bc548b5164a033ec05638e23c91be1a249c255e0f56319dddffd759794 + url: "https://pub.dev" + source: hosted + version: "0.8.10+1" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" leak_tracker: dependency: transitive description: @@ -264,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" path: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index cae53499..87c6239e 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: http: ^0.13.3 cookie_jar: ^4.0.8 http_interceptor: ^1.0.1 + image_picker: ^0.8.5+3 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc b/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc index 1ece8f25..f35b3a66 100644 --- a/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc +++ b/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); } diff --git a/frontend/sige_ie/windows/flutter/generated_plugins.cmake b/frontend/sige_ie/windows/flutter/generated_plugins.cmake index 7f101a77..389222ba 100644 --- a/frontend/sige_ie/windows/flutter/generated_plugins.cmake +++ b/frontend/sige_ie/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows geolocator_windows ) From 34a5911e3aff6bd1add01f12c21ae73794b79cf0 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 6 May 2024 17:01:31 -0300 Subject: [PATCH 091/351] backend: fix mixin de equipments --- api/equipments/mixins.py | 18 ++++++-------- api/equipments/models.py | 5 +++- api/equipments/serializers.py | 46 +++++++++-------------------------- 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index eb25d852..21e75774 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -1,18 +1,17 @@ from .models import EquipmentDetail from rest_framework import serializers -class ValidateEquipmentDetailAndAreaMixin: +class ValidateAreaMixin: - def validate_equipment_detail(self, value): + def validate(self, data): """ Garante que o equipment detail pertence ao system. """ - system_id = self.instance.system_id if self.instance else 7 - equipment_detail_id = value.id - equipment_detail = EquipmentDetail.objects.filter(id=equipment_detail_id, equipmentType__system_id=system_id).first() - - if not equipment_detail: - raise serializers.ValidationError("The equipment detail does not belong to the specified system") + equipment_detail = data.get('equipment_detail') + if equipment_detail: + equipment_type_system = equipment_detail.equipmentType.system + if equipment_type_system != data['system']: + raise serializers.ValidationError("The equipment type's system must match the equipment's system.") """ Garante que o equipment detail pertence ao place owner. @@ -20,8 +19,7 @@ def validate_equipment_detail(self, value): user = self.context['request'].user if equipment_detail.place_owner != user.placeowner: raise serializers.ValidationError("You are not the owner of the equipment detail's place") - - return value + return data def validate_area(self, value): """ diff --git a/api/equipments/models.py b/api/equipments/models.py index bff9e47d..b221bf23 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -4,6 +4,7 @@ from users.models import PlaceOwner class EquipmentType(models.Model): + name = models.CharField(max_length=50) system = models.ForeignKey(System, on_delete=models.CASCADE) @@ -20,12 +21,14 @@ class EquipmentDetail(models.Model): description = models.CharField(max_length=50) equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) - def __str__(self):return self.description + def __str__(self): + return self.description class Meta: db_table = 'equipments_equipment_details' class FireAlarmEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 25ffda91..5af3c58e 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from .models import * -from .mixins import ValidateEquipmentDetailAndAreaMixin +from .mixins import ValidateAreaMixin class EquipmentTypeSerializer(serializers.ModelSerializer): @@ -14,74 +14,50 @@ class Meta: model = EquipmentDetail fields = '__all__' -class FireAlarmEquipmentSerializer(serializers.ModelSerializer): +class FireAlarmEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = FireAlarmEquipment fields = '__all__' - extra_kwargs = { - 'system': {'read_only': True} - } -class AtmosphericDischargeEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): +class AtmosphericDischargeEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = AtmosphericDischargeEquipment fields = '__all__' - extra_kwargs = { - 'system': {'read_only': True} - } -class StructuredCablingEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): +class StructuredCablingEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = StructuredCablingEquipment fields = '__all__' - extra_kwargs = { - 'system': {'read_only': True} - } -class DistributionBoardEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): +class DistributionBoardEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = DistributionBoardEquipment fields = '__all__' - extra_kwargs = { - 'system': {'read_only': True} - } -class ElectricalCircuitEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): +class ElectricalCircuitEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalCircuitEquipment fields = '__all__' - extra_kwargs = { - 'system': {'read_only': True} - } -class ElectricalLineEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): +class ElectricalLineEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalLineEquipment fields = '__all__' - extra_kwargs = { - 'system': {'read_only': True} - } - -class ElectricalLoadEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): + +class ElectricalLoadEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalLoadEquipment fields = '__all__' - extra_kwargs = { - 'system': {'read_only': True} - } - -class IluminationEquipmentSerializer(ValidateEquipmentDetailAndAreaMixin, serializers.ModelSerializer): + +class IluminationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = IluminationEquipment fields = '__all__' - extra_kwargs = { - 'system': {'read_only': True} - } From 1b481d798f56374c3c6b1f9a2fdff277d5724acc Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 6 May 2024 18:04:42 -0300 Subject: [PATCH 092/351] Adicionar e remover equipamentos --- .../feature/manage/EquipmentScreen.dart | 4 +- .../feature/manage/addEquipmentScreen.dart | 195 ++++++++---------- .../feature/manage/equipment_manager.dart | 10 +- .../feature/manage/viewEquipmentScreen.dart | 171 ++++++++++----- .../places/feature/register/room_state.dart | 10 +- frontend/sige_ie/pubspec.yaml | 3 +- 6 files changed, 215 insertions(+), 178 deletions(-) diff --git a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart index f1f97e2e..12cfa29d 100644 --- a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart @@ -66,11 +66,11 @@ class EquipmentScreen extends StatelessWidget { ), SizedBox(height: 150), EquipmentButton( - title: 'ADICIONAR EQUIPAMENTOS', + title: 'EQUIPAMENTOS NA SALA', onPressed: () => navigateToAddEquipment(context), ), EquipmentButton( - title: 'VER EQUIPAMENTOS', + title: 'GERENCIAR EQUIPAMENTOS', onPressed: () => navigateToViewEquipment(context), ), ], diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart index de8c7c31..7fe44d6a 100644 --- a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart @@ -1,10 +1,18 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/feature/manage/equipment_manager.dart'; // Certifique-se de que este import esteja correto para seu projeto +import 'package:sige_ie/places/feature/manage/equipment_manager.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; +import 'dart:math'; -File? _image; +class ImageData { + File imageFile; + int id; + + ImageData(this.imageFile) : id = Random().nextInt(1000000); +} + +List _images = []; class AddEquipmentScreen extends StatefulWidget { final String roomName; @@ -23,15 +31,15 @@ class _AddEquipmentScreenState extends State { final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedLocation; - List equipmentTypes = []; // A lista agora é inicializada vazia + List equipmentTypes = []; + Future _pickImage() async { + final picker = ImagePicker(); try { - final picker = ImagePicker(); final pickedFile = await picker.pickImage(source: ImageSource.camera); - if (pickedFile != null) { setState(() { - _image = File(pickedFile.path); + _images.add(ImageData(File(pickedFile.path))); }); } } catch (e) { @@ -42,51 +50,9 @@ class _AddEquipmentScreenState extends State { @override void initState() { super.initState(); - // Carrega os tipos de equipamento com base na categoria selecionada ao iniciar a tela equipmentTypes = EquipmentManager.getEquipmentList(widget.categoryNumber); } - void _addNewEquipmentType() { - showDialog( - context: context, - builder: (BuildContext context) { - TextEditingController newTypeController = TextEditingController(); - - return AlertDialog( - title: const Text("Adicionar Novo Tipo de Equipamento"), - content: TextField( - controller: newTypeController, - autofocus: true, - decoration: - const InputDecoration(hintText: "Digite o tipo de equipamento"), - ), - actions: [ - TextButton( - child: const Text("Cancelar"), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text("Adicionar"), - onPressed: () { - String newType = newTypeController.text; - if (newType.isNotEmpty) { - setState(() { - equipmentTypes.add(newType); - // Opcionalmente, pode-se definir o novo tipo como o selecionado - _selectedType = newType; - }); - Navigator.of(context).pop(); - } - }, - ) - ], - ); - }, - ); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -107,7 +73,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Registrar Novo Equipamento', + child: Text('Equipamentos', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -138,8 +104,8 @@ class _AddEquipmentScreenState extends State { ), ], ), - const SizedBox(height: 15), - const Text('Equipamento', + const SizedBox(height: 20), + const Text('Equipamentos', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), _buildDropdown( @@ -150,8 +116,6 @@ class _AddEquipmentScreenState extends State { _selectedType = newValue; }); }, - addNew: _addNewEquipmentType, - showAddButton: true, // Ative o botão de adição ), const SizedBox(height: 30), const Text('Nome do equipamento', @@ -206,33 +170,55 @@ class _AddEquipmentScreenState extends State { const SizedBox(height: 15), IconButton( icon: const Icon(Icons.camera_alt), - onPressed: - _pickImage, // Atualiza aqui para chamar _pickImage quando clicado + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + IconButton( + icon: Icon(Icons.remove_circle, color: Colors.red), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), ), const SizedBox(height: 15), Center( child: ElevatedButton( style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: - MaterialStateProperty.all(const Size(165, 50)), - textStyle: MaterialStateProperty.all( - const TextStyle( - fontSize: 15, fontWeight: FontWeight.bold), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(165, 50)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: () {}, + child: const Text( + 'Adicionar Equipamento', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), ), - onPressed: () { - // Lógica de evento aqui - }, - child: const Text('CONTINUAR'), ), ), ], @@ -247,46 +233,35 @@ class _AddEquipmentScreenState extends State { Widget _buildDropdown({ required List items, required String? value, - required void Function(String?) onChanged, - VoidCallback? addNew, - bool showAddButton = false, + required Function(String?) onChanged, }) { - return Row( - children: [ - Expanded( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10), - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: DropdownButtonHideUnderline( - child: DropdownButtonFormField( - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 4), - ), - value: value, - isExpanded: true, - items: items.map((String item) { - return DropdownMenuItem( - value: item, - child: Text(item), - ); - }).toList(), - onChanged: onChanged, - style: TextStyle(color: Colors.black), - dropdownColor: Colors.grey[200], + return Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: value, + iconSize: 30, + iconEnabledColor: AppColors.sigeIeBlue, + isExpanded: true, + items: items.map>((String value) { + return DropdownMenuItem( + value: value, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: Text(value), ), - ), + ); + }).toList(), + onChanged: (String? newValue) => onChanged(newValue), + hint: Padding( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Text('Selecione uma opção'), ), ), - if (showAddButton) - IconButton( - icon: const Icon(Icons.add), - onPressed: addNew, - ), - ], + ), ); } } diff --git a/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart b/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart index bfbe81fb..770ec386 100644 --- a/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart +++ b/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart @@ -14,14 +14,14 @@ class EquipmentManager { case 1: return ['Eletroduto', 'Eletrocalha', 'Dimensão']; case 2: - return ['Para Raios', 'Captação', 'Subsistemas']; + return ['Para-raios', 'Captação', 'Subsistemas']; case 3: return [ - 'Alarme de Incêndio', - 'Sensor de Fumaça', - 'Sensor de Temperatura', + 'Alarme de incêndio', + 'Sensor de fumaça', + 'Sensor de temperatura', 'Acionadores', - 'Central de alarme de Incêndio' + 'Central de alarme de incêndio' ]; default: return []; diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart index be1ce640..fb6805de 100644 --- a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart @@ -37,19 +37,20 @@ class _ViewEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Equipamentos', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), + child: Text( + 'Gerenciar equipamentos', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white), + ), ), ), - SizedBox( - height: 20, - ), + SizedBox(height: 20), Padding( padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -70,56 +71,79 @@ class _ViewEquipmentScreenState extends State { ), ], ), - const SizedBox(height: 20), - DropdownButtonFormField( - decoration: const InputDecoration( - labelText: 'Selecione um equipamento', - filled: true, - fillColor: Colors.white, - border: OutlineInputBorder(), - ), - value: _selectedEquipment, - onChanged: (newValue) { - setState(() { - _selectedEquipment = newValue; - }); - }, - items: - EquipmentManager.getEquipmentList(widget.categoryNumber) - .map((String equipment) { - return DropdownMenuItem( - value: equipment, - child: Text(equipment), - ); - }).toList(), - ), - SizedBox( - height: 40, - ), - const SizedBox(height: 50), - ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: - MaterialStateProperty.all(const Size(165, 50)), - textStyle: MaterialStateProperty.all( - const TextStyle( - fontSize: 15, fontWeight: FontWeight.bold), - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + SizedBox(height: 30), + Text('Equipamentos', + style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox(height: 8), + Row( + children: [ + Expanded( + child: DropdownButtonHideUnderline( + child: ButtonTheme( + alignedDropdown: true, + child: DropdownButtonFormField( + decoration: InputDecoration( + filled: true, + fillColor: Colors.grey[300], + contentPadding: EdgeInsets.symmetric( + vertical: 10, horizontal: 15), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + borderSide: BorderSide.none, + ), + ), + value: _selectedEquipment, + onChanged: (newValue) { + setState(() { + _selectedEquipment = newValue; + }); + }, + items: EquipmentManager.getEquipmentList( + widget.categoryNumber) + .map((String equipment) { + return DropdownMenuItem( + value: equipment, + child: Text(equipment, + style: TextStyle(color: Colors.black), + textAlign: TextAlign.left), + ); + }).toList(), + dropdownColor: Colors.grey[200], + menuMaxHeight: 200, + ), + ), ), ), + IconButton( + icon: Icon(Icons.add), + onPressed: () { + _addNewEquipmentType(); + }, + ), + ], + ), + SizedBox(height: 40), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(165, 50)), + textStyle: MaterialStateProperty.all(const TextStyle( + fontSize: 15, fontWeight: FontWeight.bold)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8))), + ), + onPressed: () { + // Lógica do evento aqui + }, + child: const Text('CONTINUAR'), ), - onPressed: () { - // Lógica do evento aqui - }, - child: const Text('CONTINUAR'), ), + SizedBox(height: 50), ], ), ), @@ -128,4 +152,43 @@ class _ViewEquipmentScreenState extends State { ), ); } + + void _addNewEquipmentType() { + showDialog( + context: context, + builder: (BuildContext context) { + TextEditingController newTypeController = TextEditingController(); + return AlertDialog( + title: const Text("Adicionar Novo Tipo de Equipamento"), + content: TextField( + controller: newTypeController, + autofocus: true, + decoration: + const InputDecoration(hintText: "Digite o tipo de equipamento"), + ), + actions: [ + TextButton( + child: const Text("Cancelar"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text("Adicionar"), + onPressed: () { + String newType = newTypeController.text; + if (newType.isNotEmpty) { + setState(() { + // Adicione o novo tipo de equipamento à lista + _selectedEquipment = newType; + }); + Navigator.of(context).pop(); + } + }, + ) + ], + ); + }, + ); + } } diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index 6718e05d..96ed91f7 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -13,11 +13,11 @@ class _RoomLocationState extends State { String? selectedFloor; final TextEditingController roomController = TextEditingController(); final List floors = [ - 'Andar 1', - 'Andar 2', - 'Andar 3', - 'Andar 4', - 'Andar 5' + '1° Andar', + '2° Andar', + '3° Andar', + '4° Andar', + '5° Andar' ]; @override diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 87c6239e..62d28c21 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -36,8 +36,7 @@ dependencies: http: ^0.13.3 cookie_jar: ^4.0.8 http_interceptor: ^1.0.1 - image_picker: ^0.8.5+3 - + image_picker: ^0.8.5 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 From 4fde4c0443fdde72577ffef10f6c0e2ad4035174 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Tue, 7 May 2024 09:28:43 -0300 Subject: [PATCH 093/351] backend: define o system de cada equipment direto nas views --- api/equipments/views.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/api/equipments/views.py b/api/equipments/views.py index 37555d5e..940780b6 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -51,7 +51,9 @@ def get_queryset(self): return FireAlarmEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + data = request.data.copy() + data["system"] = 8 + serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) @@ -72,7 +74,9 @@ def get_queryset(self): return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + data = request.data.copy() + data["system"] = 7 + serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) @@ -93,7 +97,9 @@ def get_queryset(self): return StructuredCablingEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + data = request.data.copy() + data["system"] = 6 + serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) @@ -114,7 +120,9 @@ def get_queryset(self): return DistributionBoardEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + data = request.data.copy() + data["system"] = 5 + serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) @@ -135,7 +143,9 @@ def get_queryset(self): return ElectricalCircuitEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + data = request.data.copy() + data["system"] = 4 + serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) @@ -156,7 +166,9 @@ def get_queryset(self): return ElectricalLineEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + data = request.data.copy() + data["system"] = 3 + serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) @@ -177,7 +189,9 @@ def get_queryset(self): return ElectricalLoadEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + data = request.data.copy() + data["system"] = 2 + serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) @@ -198,7 +212,9 @@ def get_queryset(self): return IluminationEquipment.objects.filter(area__place__place_owner=user.placeowner) def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + data = request.data.copy() + data["system"] = 1 + serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) From 04d0a772690d6f1f3448c77b305a868240aa50dd Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Tue, 7 May 2024 10:03:03 -0300 Subject: [PATCH 094/351] backend: fix floor para aceitar subsolos --- api/places/migrations/0027_alter_area_floor.py | 18 ++++++++++++++++++ api/places/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 api/places/migrations/0027_alter_area_floor.py diff --git a/api/places/migrations/0027_alter_area_floor.py b/api/places/migrations/0027_alter_area_floor.py new file mode 100644 index 00000000..678177b2 --- /dev/null +++ b/api/places/migrations/0027_alter_area_floor.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2024-05-07 13:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0026_alter_area_place'), + ] + + operations = [ + migrations.AlterField( + model_name='area', + name='floor', + field=models.IntegerField(default=0), + ), + ] diff --git a/api/places/models.py b/api/places/models.py index c79b5391..84e28390 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -15,7 +15,7 @@ def __str__(self): class Area(models.Model): name = models.CharField(max_length=50) - floor = models.IntegerField(default=0, validators=[MinValueValidator(0)]) + floor = models.IntegerField(default=0) place = models.ForeignKey(Place, on_delete=models.CASCADE, null=True, related_name='areas') def __str__(self): From d74eac29e87023ea4055f099ec0a9b5dca29bfc9 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 7 May 2024 14:06:41 -0300 Subject: [PATCH 095/351] Front: Telas finalizadas --- .../feature/manage/EquipmentScreen.dart | 4 +- .../feature/manage/addEquipmentScreen.dart | 25 ++- .../feature/manage/equipment_manager.dart | 2 + .../feature/manage/viewEquipmentScreen.dart | 154 +++++++++++++----- 4 files changed, 140 insertions(+), 45 deletions(-) diff --git a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart index 12cfa29d..ee835cc2 100644 --- a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart @@ -93,11 +93,11 @@ class EquipmentButton extends StatelessWidget { child: ElevatedButton( child: Text(title, style: const TextStyle( - color: AppColors.sigeIeYellow, + color: AppColors.sigeIeBlue, fontSize: 18, fontWeight: FontWeight.w900)), style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), padding: MaterialStateProperty.all( const EdgeInsets.symmetric(vertical: 25)), shape: MaterialStateProperty.all(RoundedRectangleBorder( diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart index 7fe44d6a..e017351f 100644 --- a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart @@ -13,6 +13,7 @@ class ImageData { } List _images = []; +Map> categoryImagesMap = {}; class AddEquipmentScreen extends StatefulWidget { final String roomName; @@ -39,7 +40,14 @@ class _AddEquipmentScreenState extends State { final pickedFile = await picker.pickImage(source: ImageSource.camera); if (pickedFile != null) { setState(() { - _images.add(ImageData(File(pickedFile.path))); + final imageData = ImageData(File(pickedFile.path)); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[ + categoryNumber]!; // Atualiza _images para a categoria corrente }); } } catch (e) { @@ -51,6 +59,8 @@ class _AddEquipmentScreenState extends State { void initState() { super.initState(); equipmentTypes = EquipmentManager.getEquipmentList(widget.categoryNumber); + _images = categoryImagesMap[widget.categoryNumber] ?? + []; // Inicializa _images com as imagens da categoria correta } @override @@ -73,7 +83,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Equipamentos', + child: Text('Equipamentos na sala', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -187,7 +197,8 @@ class _AddEquipmentScreenState extends State { ), ), IconButton( - icon: Icon(Icons.remove_circle, color: Colors.red), + icon: Icon(Icons.remove_circle, + color: AppColors.warn), onPressed: () { setState(() { _images.removeWhere( @@ -230,6 +241,7 @@ class _AddEquipmentScreenState extends State { ); } +// Este método não precisa alterar `_images` pois isso deve depender apenas da categoria, não do tipo de equipamento. Widget _buildDropdown({ required List items, required String? value, @@ -255,7 +267,12 @@ class _AddEquipmentScreenState extends State { ), ); }).toList(), - onChanged: (String? newValue) => onChanged(newValue), + onChanged: (String? newValue) { + setState(() { + _selectedType = newValue; + }); + onChanged(newValue); + }, hint: Padding( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), child: Text('Selecione uma opção'), diff --git a/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart b/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart index 770ec386..b123183e 100644 --- a/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart +++ b/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart @@ -27,4 +27,6 @@ class EquipmentManager { return []; } } + + static void deleteEquipment(int categoryNumber, String s) {} } diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart index fb6805de..8e06d9d9 100644 --- a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/places/feature/manage/EquipmentScreen.dart'; import 'package:sige_ie/places/feature/manage/equipment_manager.dart'; class ViewEquipmentScreen extends StatefulWidget { @@ -16,6 +17,21 @@ class ViewEquipmentScreen extends StatefulWidget { class _ViewEquipmentScreenState extends State { String? _selectedEquipment; + List equipmentList = []; + void navigateToEquipmentScreen() { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => EquipmentScreen( + roomName: widget.roomName, + categoryNumber: widget.categoryNumber, + ), + )); + } + + @override + void initState() { + super.initState(); + equipmentList = EquipmentManager.getEquipmentList(widget.categoryNumber); + } @override Widget build(BuildContext context) { @@ -46,7 +62,7 @@ class _ViewEquipmentScreenState extends State { ), ), ), - SizedBox(height: 20), + const SizedBox(height: 20), Padding( padding: const EdgeInsets.all(20), child: Column( @@ -55,7 +71,7 @@ class _ViewEquipmentScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( + const Text( 'Categoria: ', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold), @@ -63,7 +79,7 @@ class _ViewEquipmentScreenState extends State { Expanded( child: Text( EquipmentManager.categoryMap[widget.categoryNumber]!, - style: TextStyle( + style: const TextStyle( fontSize: 18, color: AppColors.sigeIeBlue, fontWeight: FontWeight.w500), @@ -71,58 +87,53 @@ class _ViewEquipmentScreenState extends State { ), ], ), - SizedBox(height: 30), - Text('Equipamentos', + const SizedBox(height: 30), + const Text('Equipamentos', style: TextStyle(fontWeight: FontWeight.bold)), - SizedBox(height: 8), + const SizedBox(height: 8), Row( children: [ Expanded( - child: DropdownButtonHideUnderline( - child: ButtonTheme( - alignedDropdown: true, - child: DropdownButtonFormField( - decoration: InputDecoration( - filled: true, - fillColor: Colors.grey[300], - contentPadding: EdgeInsets.symmetric( - vertical: 10, horizontal: 15), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: BorderSide.none, - ), - ), + child: Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10.0), + ), + padding: + EdgeInsets.symmetric(horizontal: 12, vertical: 4), + child: DropdownButtonHideUnderline( + child: DropdownButton( value: _selectedEquipment, + isExpanded: true, onChanged: (newValue) { setState(() { _selectedEquipment = newValue; }); }, - items: EquipmentManager.getEquipmentList( - widget.categoryNumber) - .map((String equipment) { + items: equipmentList.map((String equipment) { return DropdownMenuItem( value: equipment, child: Text(equipment, - style: TextStyle(color: Colors.black), - textAlign: TextAlign.left), + style: + const TextStyle(color: Colors.black)), ); }).toList(), - dropdownColor: Colors.grey[200], - menuMaxHeight: 200, + dropdownColor: Colors.grey[300], ), ), ), ), IconButton( - icon: Icon(Icons.add), - onPressed: () { - _addNewEquipmentType(); - }, + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: _deleteEquipmentType, ), ], ), - SizedBox(height: 40), + const SizedBox(height: 40), Center( child: ElevatedButton( style: ButtonStyle( @@ -137,13 +148,11 @@ class _ViewEquipmentScreenState extends State { shape: MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(8))), ), - onPressed: () { - // Lógica do evento aqui - }, - child: const Text('CONTINUAR'), + onPressed: navigateToEquipmentScreen, + child: const Text('SALVAR'), ), ), - SizedBox(height: 50), + const SizedBox(height: 50), ], ), ), @@ -159,7 +168,7 @@ class _ViewEquipmentScreenState extends State { builder: (BuildContext context) { TextEditingController newTypeController = TextEditingController(); return AlertDialog( - title: const Text("Adicionar Novo Tipo de Equipamento"), + title: const Text("Adicionar novo tipo de equipamento"), content: TextField( controller: newTypeController, autofocus: true, @@ -179,7 +188,7 @@ class _ViewEquipmentScreenState extends State { String newType = newTypeController.text; if (newType.isNotEmpty) { setState(() { - // Adicione o novo tipo de equipamento à lista + equipmentList.add(newType); _selectedEquipment = newType; }); Navigator.of(context).pop(); @@ -191,4 +200,71 @@ class _ViewEquipmentScreenState extends State { }, ); } + + void _deleteEquipmentType() { + String? localSelectedEquipment; + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder(builder: (context, setDialogState) { + return AlertDialog( + title: const Text("Excluir equipamento"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text("Selecione o equipamento que deseja excluir:"), + const SizedBox(height: 20), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10.0), + ), + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: localSelectedEquipment, + isExpanded: true, + onChanged: (newValue) { + setDialogState(() { + localSelectedEquipment = newValue; + }); + }, + items: equipmentList.map((String equipment) { + return DropdownMenuItem( + value: equipment, + child: Text(equipment, + style: const TextStyle(color: Colors.black)), + ); + }).toList(), + dropdownColor: Colors.grey[300], + ), + ), + ), + ], + ), + actions: [ + TextButton( + child: const Text("Cancelar"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text("Excluir"), + onPressed: () { + if (localSelectedEquipment != null) { + setState(() { + equipmentList.remove(localSelectedEquipment); + }); + Navigator.of(context).pop(); + } + }, + ) + ], + ); + }); + }, + ); + } } From 5b3f74c29dff3a25893fba9913f50b168e1e106e Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 7 May 2024 18:05:13 -0300 Subject: [PATCH 096/351] =?UTF-8?q?Front:=20Rotas=20corrigidas=20e=20alter?= =?UTF-8?q?a=C3=A7=C3=B5es=20visuais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/home/ui/home.dart | 16 +- .../feature/manage/EquipmentScreen.dart | 8 +- .../feature/manage/addEquipmentScreen.dart | 363 ++++++++++-------- .../feature/manage/systemConfiguration.dart | 29 +- .../feature/manage/viewEquipmentScreen.dart | 37 +- .../places/feature/register/room_state.dart | 12 +- .../sige_ie/lib/users/feature/profile.dart | 43 ++- 7 files changed, 296 insertions(+), 212 deletions(-) diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index b6a90808..71bfc473 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -167,19 +167,27 @@ class _HomePageState extends State { textAlign: TextAlign.center, style: const TextStyle( color: Colors.white, - fontSize: 18, + fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 10), ElevatedButton( - style: AppButtonStyles.standardButton, + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: MaterialStateProperty.all(const Size(140, 50)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), onPressed: onPress, child: Text( buttonText, style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + fontSize: 15, + fontWeight: FontWeight.w900, ), ), ) diff --git a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart index ee835cc2..2589b7ee 100644 --- a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart @@ -44,7 +44,13 @@ class EquipmentScreen extends StatelessWidget { backgroundColor: AppColors.sigeIeBlue, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: roomName, + ); + }, ), ), body: Column( diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart index e017351f..37ee4000 100644 --- a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart @@ -33,6 +33,13 @@ class _AddEquipmentScreenState extends State { String? _selectedType; String? _selectedLocation; List equipmentTypes = []; + @override + void dispose() { + _equipmentNameController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } Future _pickImage() async { final picker = ImagePicker(); @@ -46,8 +53,7 @@ class _AddEquipmentScreenState extends State { categoryImagesMap[categoryNumber] = []; } categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[ - categoryNumber]!; // Atualiza _images para a categoria corrente + _images = categoryImagesMap[categoryNumber]!; }); } } catch (e) { @@ -59,189 +65,212 @@ class _AddEquipmentScreenState extends State { void initState() { super.initState(); equipmentTypes = EquipmentManager.getEquipmentList(widget.categoryNumber); - _images = categoryImagesMap[widget.categoryNumber] ?? - []; // Inicializa _images com as imagens da categoria correta + _images = categoryImagesMap[widget.categoryNumber] ?? []; } @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - foregroundColor: Colors.white, - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: const Center( - child: Text('Equipamentos na sala', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), + return WillPopScope( + onWillPop: () async { + _equipmentNameController.clear(); + _equipmentQuantityController.clear(); + categoryImagesMap[widget.categoryNumber]?.clear(); + return true; + }, + child: Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + // Limpar antes de sair + _equipmentNameController.clear(); + _equipmentQuantityController.clear(); + categoryImagesMap[widget.categoryNumber]?.clear(); + Navigator.of(context).pop(); + }, ), - Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Categoria: ', + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Equipamentos na sala', style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Categoria: ', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + Expanded( + child: Text( + EquipmentManager + .categoryMap[widget.categoryNumber]!, + style: TextStyle( + fontSize: 18, + color: AppColors.sigeIeBlue, + fontWeight: FontWeight.w500), + ), + ), + ], + ), + const SizedBox(height: 20), + const Text('Equipamentos', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + }); + }, ), - Expanded( - child: Text( - EquipmentManager.categoryMap[widget.categoryNumber]!, + const SizedBox(height: 30), + const Text('Nome do equipamento', style: TextStyle( - fontSize: 18, - color: AppColors.sigeIeBlue, - fontWeight: FontWeight.w500), + fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentNameController, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, vertical: 15), + ), ), ), - ], - ), - const SizedBox(height: 20), - const Text('Equipamentos', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - _buildDropdown( - items: equipmentTypes, - value: _selectedType, - onChanged: (newValue) { - setState(() { - _selectedType = newValue; - }); - }, - ), - const SizedBox(height: 30), - const Text('Nome do equipamento', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: TextField( - controller: _equipmentNameController, - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 15), + const SizedBox(height: 30), + const Text('Quantidade', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, vertical: 15), + ), + ), ), - ), - ), - const SizedBox(height: 30), - const Text('Quantidade', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: TextField( - controller: _equipmentQuantityController, - keyboardType: TextInputType.number, - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 15), + const SizedBox(height: 30), + const Text('Localização (Interno ou Externo)', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildDropdown( + items: const ['Interno', 'Externo'], + value: _selectedLocation, + onChanged: (newValue) { + setState(() { + _selectedLocation = newValue; + }); + }, ), - ), - ), - const SizedBox(height: 30), - const Text('Localização (Interno ou Externo)', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - _buildDropdown( - items: const ['Interno', 'Externo'], - value: _selectedLocation, - onChanged: (newValue) { - setState(() { - _selectedLocation = newValue; - }); - }, - ), - const SizedBox(height: 15), - IconButton( - icon: const Icon(Icons.camera_alt), - onPressed: _pickImage, - ), - Wrap( - children: _images.map((imageData) { - return Stack( - alignment: Alignment.topRight, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - ), - IconButton( - icon: Icon(Icons.remove_circle, - color: AppColors.warn), - onPressed: () { - setState(() { - _images.removeWhere( - (element) => element.id == imageData.id); - }); - }, + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + IconButton( + icon: Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere((element) => + element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all( + AppColors.sigeIeBlue), + minimumSize: MaterialStateProperty.all( + const Size(185, 55)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: () { + Navigator.pushNamed(context, '/systemLocation', + arguments: widget.roomName); + }, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), ), - ], - ); - }).toList(), - ), - const SizedBox(height: 15), - Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: - MaterialStateProperty.all(const Size(165, 50)), - shape: - MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ))), - onPressed: () {}, - child: const Text( - 'Adicionar Equipamento', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), + ), ), - ), + ], ), - ], - ), + ), + ], ), - ], - ), - ), - ); + ), + )); } -// Este método não precisa alterar `_images` pois isso deve depender apenas da categoria, não do tipo de equipamento. Widget _buildDropdown({ required List items, required String? value, diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart index d4eac3e8..cc689c72 100644 --- a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart @@ -44,11 +44,8 @@ class _SystemConfigurationState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + automaticallyImplyLeading: false, backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), ), body: SingleChildScrollView( child: Column( @@ -90,6 +87,30 @@ class _SystemConfigurationState extends State { SystemButton( title: 'ALARME DE INCÊNDIO', onPressed: () => navigateTo('/fireAlarm', widget.roomName, 3)), + SizedBox( + height: 30, + ), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.warn), + foregroundColor: + MaterialStateProperty.all(AppColors.lightText), + minimumSize: MaterialStateProperty.all(const Size(175, 55)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: () { + Navigator.of(context) + .pushNamed('/roomlocation', arguments: widget.roomName); + }, + child: const Text( + 'ENCERRAR', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ), ], ), ), diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart index 8e06d9d9..77258da6 100644 --- a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart @@ -89,7 +89,8 @@ class _ViewEquipmentScreenState extends State { ), const SizedBox(height: 30), const Text('Equipamentos', - style: TextStyle(fontWeight: FontWeight.bold)), + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), Row( children: [ @@ -136,21 +137,25 @@ class _ViewEquipmentScreenState extends State { const SizedBox(height: 40), Center( child: ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: - MaterialStateProperty.all(const Size(165, 50)), - textStyle: MaterialStateProperty.all(const TextStyle( - fontSize: 15, fontWeight: FontWeight.bold)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8))), - ), - onPressed: navigateToEquipmentScreen, - child: const Text('SALVAR'), - ), + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(175, 55)), + textStyle: MaterialStateProperty.all(const TextStyle( + fontSize: 15, fontWeight: FontWeight.bold)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8))), + ), + onPressed: navigateToEquipmentScreen, + child: const Text( + 'SALVAR', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.bold), + )), ), const SizedBox(height: 50), ], diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index 96ed91f7..30d12b25 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -25,10 +25,7 @@ class _RoomLocationState extends State { return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), + automaticallyImplyLeading: false, ), body: SingleChildScrollView( child: Column( @@ -146,7 +143,12 @@ class _RoomLocationState extends State { borderRadius: BorderRadius.circular(8), ), ), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/', //Rota temporária, modificar depois + ); + }, child: Text('ENCERRAR'), ), ], diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index c8801fc1..314d4eaf 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -33,7 +33,7 @@ class _ProfilePageState extends State { appBar: AppBar( title: const Text( 'Editar Perfil', - style: TextStyle(fontSize: 24), + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), centerTitle: true, automaticallyImplyLeading: false, @@ -77,7 +77,7 @@ class _ProfilePageState extends State { onTap: () {}, child: const Text( 'Mudar username', - style: TextStyle(color: Color.fromARGB(255, 33, 150, 243)), + style: TextStyle(color: Colors.blue), ), ), ], @@ -87,38 +87,46 @@ class _ProfilePageState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ SizedBox( - width: 150, // Define a largura uniforme - height: 50, // Define a altura uniforme + width: 150, + height: 50, child: ElevatedButton( onPressed: () async { await userService.update(userResponseModel.id, userResponseModel.firstname, userResponseModel.email); }, child: const Text('Salvar', - style: TextStyle(color: AppColors.dartText)), + style: TextStyle( + color: AppColors.dartText, + fontSize: 15, + fontWeight: FontWeight.w900)), style: ElevatedButton.styleFrom( backgroundColor: const Color.fromARGB(255, 224, 221, 221), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(10), ), + minimumSize: const Size(140, 50), ), ), ), SizedBox( - width: 150, // Mesma largura para manter uniformidade - height: 50, // Mesma altura + width: 150, + height: 50, child: ElevatedButton( onPressed: () async { await authService.logout(); Navigator.pushReplacementNamed(context, '/loginScreen'); }, child: const Text('Sair da Conta', - style: TextStyle(color: AppColors.dartText)), + style: TextStyle( + color: AppColors.dartText, + fontSize: 15, + fontWeight: FontWeight.w900)), style: ElevatedButton.styleFrom( backgroundColor: const Color.fromARGB(255, 153, 163, 168), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(10), ), + minimumSize: const Size(140, 50), ), ), ), @@ -129,8 +137,8 @@ class _ProfilePageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( - width: 150, // Mantendo a consistência no tamanho - height: 50, // Altura uniforme para todos os botões + width: 150, + height: 50, child: ElevatedButton( onPressed: () async { // Mostrar o diálogo de confirmação @@ -138,7 +146,7 @@ class _ProfilePageState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir Conta'), + title: const Text('EXCLUIR'), content: const Text( 'Tem certeza que deseja excluir sua conta?'), actions: [ @@ -169,8 +177,13 @@ class _ProfilePageState extends State { } }, child: const Text('Excluir Conta', - style: TextStyle(color: AppColors.lightText)), - style: AppButtonStyles.warnButton), + style: TextStyle( + color: AppColors.lightText, + fontSize: 15, + fontWeight: FontWeight.w900)), + style: AppButtonStyles.warnButton.copyWith( + minimumSize: + MaterialStateProperty.all(const Size(140, 50)))), ), ], ) From e524a53c78b24b24ac7f987d1c4b4127b23cd9cf Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 7 May 2024 22:44:32 -0300 Subject: [PATCH 097/351] =?UTF-8?q?Profile=20editado=20e=20modifica=C3=A7?= =?UTF-8?q?=C3=B5es=20segunda=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/manage/addEquipmentScreen.dart | 28 +- .../feature/manage/viewEquipmentScreen.dart | 14 +- .../sige_ie/lib/users/feature/profile.dart | 368 +++++++++++------- 3 files changed, 248 insertions(+), 162 deletions(-) diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart index 37ee4000..d1639adf 100644 --- a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart @@ -33,6 +33,7 @@ class _AddEquipmentScreenState extends State { String? _selectedType; String? _selectedLocation; List equipmentTypes = []; + @override void dispose() { _equipmentNameController.dispose(); @@ -84,7 +85,6 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - // Limpar antes de sair _equipmentNameController.clear(); _equipmentQuantityController.clear(); categoryImagesMap[widget.categoryNumber]?.clear(); @@ -129,7 +129,7 @@ class _AddEquipmentScreenState extends State { child: Text( EquipmentManager .categoryMap[widget.categoryNumber]!, - style: TextStyle( + style: const TextStyle( fontSize: 18, color: AppColors.sigeIeBlue, fontWeight: FontWeight.w500), @@ -163,7 +163,7 @@ class _AddEquipmentScreenState extends State { ), child: TextField( controller: _equipmentNameController, - decoration: InputDecoration( + decoration: const InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 15), @@ -183,7 +183,7 @@ class _AddEquipmentScreenState extends State { child: TextField( controller: _equipmentQuantityController, keyboardType: TextInputType.number, - decoration: InputDecoration( + decoration: const InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 15), @@ -224,7 +224,7 @@ class _AddEquipmentScreenState extends State { ), ), IconButton( - icon: Icon(Icons.remove_circle, + icon: const Icon(Icons.remove_circle, color: AppColors.warn), onPressed: () { setState(() { @@ -261,7 +261,7 @@ class _AddEquipmentScreenState extends State { fontSize: 17, fontWeight: FontWeight.bold), ), ), - ), + ) ], ), ), @@ -273,9 +273,12 @@ class _AddEquipmentScreenState extends State { Widget _buildDropdown({ required List items, - required String? value, + String? value, required Function(String?) onChanged, }) { + String dropdownValue = value ?? 'Escolha um...'; + List dropdownItems = ['Escolha um...'] + items; + return Container( decoration: BoxDecoration( color: Colors.grey[300], @@ -283,11 +286,11 @@ class _AddEquipmentScreenState extends State { ), child: DropdownButtonHideUnderline( child: DropdownButton( - value: value, + value: dropdownValue, iconSize: 30, iconEnabledColor: AppColors.sigeIeBlue, isExpanded: true, - items: items.map>((String value) { + items: dropdownItems.map>((String value) { return DropdownMenuItem( value: value, child: Padding( @@ -297,10 +300,9 @@ class _AddEquipmentScreenState extends State { ); }).toList(), onChanged: (String? newValue) { - setState(() { - _selectedType = newValue; - }); - onChanged(newValue); + if (newValue != 'Escolha um...') { + onChanged(newValue); + } }, hint: Padding( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart index 77258da6..b6499792 100644 --- a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart @@ -18,6 +18,7 @@ class ViewEquipmentScreen extends StatefulWidget { class _ViewEquipmentScreenState extends State { String? _selectedEquipment; List equipmentList = []; + void navigateToEquipmentScreen() { Navigator.of(context).push(MaterialPageRoute( builder: (context) => EquipmentScreen( @@ -31,6 +32,10 @@ class _ViewEquipmentScreenState extends State { void initState() { super.initState(); equipmentList = EquipmentManager.getEquipmentList(widget.categoryNumber); + + if (equipmentList.isNotEmpty) { + _selectedEquipment = _selectedEquipment ?? equipmentList.first; + } } @override @@ -239,10 +244,10 @@ class _ViewEquipmentScreenState extends State { return DropdownMenuItem( value: equipment, child: Text(equipment, - style: const TextStyle(color: Colors.black)), + style: TextStyle(color: Colors.black)), ); }).toList(), - dropdownColor: Colors.grey[300], + dropdownColor: Colors.grey[200], ), ), ), @@ -261,6 +266,11 @@ class _ViewEquipmentScreenState extends State { if (localSelectedEquipment != null) { setState(() { equipmentList.remove(localSelectedEquipment); + if (_selectedEquipment == localSelectedEquipment) { + _selectedEquipment = equipmentList.isNotEmpty + ? equipmentList.first + : null; + } }); Navigator.of(context).pop(); } diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index 314d4eaf..303b62a5 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -30,166 +30,240 @@ class _ProfilePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text( - 'Editar Perfil', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: AppColors.sigeIeBlue, ), - centerTitle: true, - automaticallyImplyLeading: false, - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextField( - decoration: const InputDecoration(labelText: 'Email'), - controller: TextEditingController(text: userResponseModel.email), - onChanged: (value) => userResponseModel.email = value, + body: SingleChildScrollView( + child: + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), - const SizedBox(height: 10), - TextField( - decoration: const InputDecoration(labelText: 'Nome'), - controller: - TextEditingController(text: userResponseModel.firstname), - onChanged: (value) => userResponseModel.firstname = value, - ), - const SizedBox(height: 10), - TextField( - decoration: const InputDecoration(labelText: 'Username'), - controller: - TextEditingController(text: userResponseModel.username), - enabled: false, + child: const Center( + child: Column( + children: [ + Text( + 'Editar perfil', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + SizedBox(height: 10), // Espaço adicional abaixo do texto + ], + ), ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - InkWell( - onTap: () {}, - child: const Text( - 'Mudar senha', - style: TextStyle(color: Colors.blue), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Email', + style: TextStyle( + color: Colors.grey[600], fontWeight: FontWeight.bold), + ), + SizedBox(height: 5), + TextField( + decoration: InputDecoration( + filled: true, + fillColor: Colors.grey[300], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + borderSide: BorderSide.none, + ), + contentPadding: EdgeInsets.symmetric( + vertical: 15.0, horizontal: 10.0), + isDense: true, + ), + controller: + TextEditingController(text: userResponseModel.email), + onChanged: (value) => userResponseModel.email = value, ), - ), - InkWell( - onTap: () {}, - child: const Text( - 'Mudar username', - style: TextStyle(color: Colors.blue), + SizedBox(height: 15), + Text( + 'Nome', + style: TextStyle( + color: Colors.grey[600], fontWeight: FontWeight.bold), ), - ), - ], - ), - const SizedBox(height: 80), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SizedBox( - width: 150, - height: 50, - child: ElevatedButton( - onPressed: () async { - await userService.update(userResponseModel.id, - userResponseModel.firstname, userResponseModel.email); - }, - child: const Text('Salvar', - style: TextStyle( - color: AppColors.dartText, - fontSize: 15, - fontWeight: FontWeight.w900)), - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 224, 221, 221), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + SizedBox(height: 5), + TextField( + decoration: InputDecoration( + filled: true, + fillColor: Colors.grey[300], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + borderSide: BorderSide.none, ), - minimumSize: const Size(140, 50), + contentPadding: EdgeInsets.symmetric( + vertical: 15.0, horizontal: 10.0), + isDense: true, ), + controller: TextEditingController( + text: userResponseModel.firstname), + onChanged: (value) => userResponseModel.firstname = value, + ), + SizedBox(height: 15), + Text( + 'Username', + style: TextStyle( + color: Colors.grey[600], fontWeight: FontWeight.bold), ), - ), - SizedBox( - width: 150, - height: 50, - child: ElevatedButton( - onPressed: () async { - await authService.logout(); - Navigator.pushReplacementNamed(context, '/loginScreen'); - }, - child: const Text('Sair da Conta', - style: TextStyle( - color: AppColors.dartText, - fontSize: 15, - fontWeight: FontWeight.w900)), - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 153, 163, 168), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + SizedBox(height: 5), + TextField( + decoration: InputDecoration( + filled: true, + fillColor: Colors.grey[200], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + borderSide: BorderSide.none, ), - minimumSize: const Size(140, 50), + contentPadding: EdgeInsets.symmetric( + vertical: 15.0, horizontal: 10.0), + isDense: true, ), + controller: + TextEditingController(text: userResponseModel.username), + enabled: false, ), - ), - ], - ), - const SizedBox(height: 80), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 150, - height: 50, - child: ElevatedButton( - onPressed: () async { - // Mostrar o diálogo de confirmação - bool deleteConfirmed = await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('EXCLUIR'), - content: const Text( - 'Tem certeza que deseja excluir sua conta?'), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop( - false); // Retorna falso para indicar que a exclusão não foi confirmada - }, - child: const Text('Cancelar'), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () {}, + child: const Text( + 'Mudar senha', + style: TextStyle(color: Colors.blue), + ), + ), + InkWell( + onTap: () {}, + child: const Text( + 'Mudar username', + style: TextStyle(color: Colors.blue), + ), + ), + ], + ), + const SizedBox(height: 60), + Center( + child: Column( + children: [ + SizedBox( + width: 150, + height: 50, + child: ElevatedButton( + onPressed: () async { + await userService.update( + userResponseModel.id, + userResponseModel.firstname, + userResponseModel.email); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.sigeIeYellow, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + minimumSize: const Size(150, 50), + ), + child: const Text('Salvar', + style: TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 15, + fontWeight: FontWeight.w900)), + ), + ), + const SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox( + width: 150, + height: 50, + child: ElevatedButton( + onPressed: () async { + await authService.logout(); + Navigator.pushReplacementNamed( + context, '/loginScreen'); + }, + style: ElevatedButton.styleFrom( + backgroundColor: + const Color.fromARGB(207, 231, 27, 27), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - TextButton( - onPressed: () { - Navigator.of(context).pop( - true); // Retorna verdadeiro para indicar que a exclusão foi confirmada + minimumSize: const Size(150, 50), + ), + child: const Text('Sair da Conta', + style: TextStyle( + color: AppColors.lightText, + fontSize: 15, + fontWeight: FontWeight.w900)), + ), + ), + const SizedBox(width: 20), + SizedBox( + width: 150, + height: 50, + child: ElevatedButton( + onPressed: () async { + bool deleteConfirmed = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('EXCLUIR'), + content: const Text( + 'Tem certeza que deseja excluir sua conta?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context) + .pop( + false), // Retorna falso para indicar que a exclusão não foi confirmada + child: const Text('Cancelar'), + ), + TextButton( + onPressed: () => Navigator.of(context) + .pop( + true), // Retorna verdadeiro para indicar que a exclusão foi confirmada + child: const Text('Confirmar'), + ), + ], + ); }, - child: const Text('Confirmar'), - ), - ], - ); - }, - ); + ); - // Se a exclusão for confirmada, exclua a conta - if (deleteConfirmed) { - await userService.delete(userResponseModel.id); - Navigator.pushReplacementNamed( - context, '/loginScreen'); - } - }, - child: const Text('Excluir Conta', - style: TextStyle( - color: AppColors.lightText, - fontSize: 15, - fontWeight: FontWeight.w900)), - style: AppButtonStyles.warnButton.copyWith( - minimumSize: - MaterialStateProperty.all(const Size(140, 50)))), - ), - ], - ) - ], - ), - ), - ); + // Se a exclusão for confirmada, exclua a conta + if (deleteConfirmed) { + await userService + .delete(userResponseModel.id); + Navigator.pushReplacementNamed( + context, '/loginScreen'); + } + }, + style: AppButtonStyles.warnButton.copyWith( + minimumSize: MaterialStateProperty.all( + const Size(150, 50))), + child: const Text('Excluir Conta', + style: TextStyle( + color: AppColors.lightText, + fontSize: 15, + fontWeight: FontWeight.w900)), + ), + ), + ], + ), + ], + )) + ], + )), + ]))); } } From b0e101c6d2e08f69013aebbe9ddaeb22d59f949f Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 8 May 2024 17:26:54 -0300 Subject: [PATCH 098/351] =?UTF-8?q?Reorganiza=C3=A7=C3=A3o=20das=20p=C3=A1?= =?UTF-8?q?ginas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/core/feature/login/login.dart | 2 +- frontend/sige_ie/lib/core/feature/register/register.dart | 4 ++-- .../lib/core/{data => services}/auth_interceptor.dart | 0 .../sige_ie/lib/core/{data => services}/auth_service.dart | 2 +- frontend/sige_ie/lib/home/ui/home.dart | 4 ++-- frontend/sige_ie/lib/places/feature/register/new_place.dart | 4 ++-- .../sige_ie/lib/places/feature/register/room_state.dart | 4 ++-- .../lib/places/{data => models}/place_request_model.dart | 0 .../lib/places/{data => services}/place_service.dart | 4 ++-- frontend/sige_ie/lib/users/feature/profile.dart | 6 +++--- .../lib/users/{data => models}/user_request_model.dart | 0 .../lib/users/{data => models}/user_response_model.dart | 0 .../sige_ie/lib/users/{data => services}/user_service.dart | 6 +++--- 13 files changed, 18 insertions(+), 18 deletions(-) rename frontend/sige_ie/lib/core/{data => services}/auth_interceptor.dart (100%) rename frontend/sige_ie/lib/core/{data => services}/auth_service.dart (98%) rename frontend/sige_ie/lib/places/{data => models}/place_request_model.dart (100%) rename frontend/sige_ie/lib/places/{data => services}/place_service.dart (81%) rename frontend/sige_ie/lib/users/{data => models}/user_request_model.dart (100%) rename frontend/sige_ie/lib/users/{data => models}/user_response_model.dart (100%) rename frontend/sige_ie/lib/users/{data => services}/user_service.dart (92%) diff --git a/frontend/sige_ie/lib/core/feature/login/login.dart b/frontend/sige_ie/lib/core/feature/login/login.dart index e41cfd47..62b934cc 100644 --- a/frontend/sige_ie/lib/core/feature/login/login.dart +++ b/frontend/sige_ie/lib/core/feature/login/login.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sige_ie/core/data/auth_service.dart'; +import 'package:sige_ie/core/services/auth_service.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({Key? key}) : super(key: key); diff --git a/frontend/sige_ie/lib/core/feature/register/register.dart b/frontend/sige_ie/lib/core/feature/register/register.dart index 344abb74..6b89b686 100644 --- a/frontend/sige_ie/lib/core/feature/register/register.dart +++ b/frontend/sige_ie/lib/core/feature/register/register.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:sige_ie/users/data/user_request_model.dart'; -import 'package:sige_ie/users/data/user_service.dart'; +import 'package:sige_ie/users/models/user_request_model.dart'; +import 'package:sige_ie/users/services/user_service.dart'; class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); diff --git a/frontend/sige_ie/lib/core/data/auth_interceptor.dart b/frontend/sige_ie/lib/core/services/auth_interceptor.dart similarity index 100% rename from frontend/sige_ie/lib/core/data/auth_interceptor.dart rename to frontend/sige_ie/lib/core/services/auth_interceptor.dart diff --git a/frontend/sige_ie/lib/core/data/auth_service.dart b/frontend/sige_ie/lib/core/services/auth_service.dart similarity index 98% rename from frontend/sige_ie/lib/core/data/auth_service.dart rename to frontend/sige_ie/lib/core/services/auth_service.dart index b645f393..3e2717f7 100644 --- a/frontend/sige_ie/lib/core/data/auth_service.dart +++ b/frontend/sige_ie/lib/core/services/auth_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/core/services/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; class AuthService { diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 71bfc473..4a4eb9e4 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -3,8 +3,8 @@ import 'package:sige_ie/config/app_styles.dart'; import '../../users/feature/profile.dart'; import '../../core/ui/facilities.dart'; import '../../maps/feature/maps.dart'; -import 'package:sige_ie/users/data/user_response_model.dart'; -import 'package:sige_ie/users/data/user_service.dart'; +import 'package:sige_ie/users/models/user_response_model.dart'; +import 'package:sige_ie/users/services/user_service.dart'; class HomePage extends StatefulWidget { @override diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index 1a3225b1..cd69a1d6 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/data/place_request_model.dart'; -import 'package:sige_ie/places/data/place_service.dart'; +import 'package:sige_ie/places/models/place_request_model.dart'; +import 'package:sige_ie/places/services/place_service.dart'; import 'position.dart'; class NewPlace extends StatefulWidget { diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart index 30d12b25..e2c0e40e 100644 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ b/frontend/sige_ie/lib/places/feature/register/room_state.dart @@ -13,11 +13,11 @@ class _RoomLocationState extends State { String? selectedFloor; final TextEditingController roomController = TextEditingController(); final List floors = [ + 'Térreo', '1° Andar', '2° Andar', '3° Andar', - '4° Andar', - '5° Andar' + '4° Andar' ]; @override diff --git a/frontend/sige_ie/lib/places/data/place_request_model.dart b/frontend/sige_ie/lib/places/models/place_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/places/data/place_request_model.dart rename to frontend/sige_ie/lib/places/models/place_request_model.dart diff --git a/frontend/sige_ie/lib/places/data/place_service.dart b/frontend/sige_ie/lib/places/services/place_service.dart similarity index 81% rename from frontend/sige_ie/lib/places/data/place_service.dart rename to frontend/sige_ie/lib/places/services/place_service.dart index a02e95f7..1f6bc8e3 100644 --- a/frontend/sige_ie/lib/places/data/place_service.dart +++ b/frontend/sige_ie/lib/places/services/place_service.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/core/services/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; -import 'package:sige_ie/places/data/place_request_model.dart'; +import 'package:sige_ie/places/models/place_request_model.dart'; class PlaceService { Future register(PlaceRequestModel placeRequestModel) async { diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index 303b62a5..34481128 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:sige_ie/core/data/auth_service.dart'; -import 'package:sige_ie/users/data/user_response_model.dart'; -import 'package:sige_ie/users/data/user_service.dart'; +import 'package:sige_ie/core/services/auth_service.dart'; +import 'package:sige_ie/users/models/user_response_model.dart'; +import 'package:sige_ie/users/services/user_service.dart'; import 'package:sige_ie/config/app_styles.dart'; class ProfilePage extends StatefulWidget { diff --git a/frontend/sige_ie/lib/users/data/user_request_model.dart b/frontend/sige_ie/lib/users/models/user_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/users/data/user_request_model.dart rename to frontend/sige_ie/lib/users/models/user_request_model.dart diff --git a/frontend/sige_ie/lib/users/data/user_response_model.dart b/frontend/sige_ie/lib/users/models/user_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/users/data/user_response_model.dart rename to frontend/sige_ie/lib/users/models/user_response_model.dart diff --git a/frontend/sige_ie/lib/users/data/user_service.dart b/frontend/sige_ie/lib/users/services/user_service.dart similarity index 92% rename from frontend/sige_ie/lib/users/data/user_service.dart rename to frontend/sige_ie/lib/users/services/user_service.dart index 4f77b25e..581d0c69 100644 --- a/frontend/sige_ie/lib/users/data/user_service.dart +++ b/frontend/sige_ie/lib/users/services/user_service.dart @@ -1,11 +1,11 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/core/services/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; -import 'package:sige_ie/users/data/user_request_model.dart'; -import 'package:sige_ie/users/data/user_response_model.dart'; +import 'package:sige_ie/users/models/user_request_model.dart'; +import 'package:sige_ie/users/models/user_response_model.dart'; class UserService { Future register(UserRequestModel userRequestModel) async { From 25e331854084359e157f0f7e83bb6e05d523fe9c Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 10 May 2024 10:42:23 -0300 Subject: [PATCH 099/351] =?UTF-8?q?backend:=20fix=20valida=C3=A7=C3=A3o=20?= =?UTF-8?q?de=20username=20com=20regex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/users/serializers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/users/serializers.py b/api/users/serializers.py index 6ab44b23..abdd69b5 100644 --- a/api/users/serializers.py +++ b/api/users/serializers.py @@ -2,6 +2,7 @@ from rest_framework import serializers, response from django.contrib.auth.models import User from .models import PlaceOwner +import re class UserSerializer(serializers.ModelSerializer): username = serializers.CharField(min_length=6, max_length=23, required=True) @@ -9,6 +10,11 @@ class UserSerializer(serializers.ModelSerializer): first_name = serializers.CharField(required=True) email = serializers.EmailField(required=True) + def validate_username(self, value): + if not re.match("^[a-zA-Z0-9]+$", value): + raise serializers.ValidationError("The username must contain only letters and numbers.") + return value + class Meta: model = User fields = ['id', 'password', 'username', 'first_name', 'email', 'is_active', 'date_joined', 'groups'] @@ -34,7 +40,6 @@ class UserLoginSerializer(serializers.Serializer): username = serializers.CharField(min_length=6, max_length=23, required=True) password = serializers.CharField(min_length=6, max_length=200, required=True) - class UserUpdateSerializer(serializers.Serializer): first_name = serializers.CharField(required=True) email = serializers.EmailField(required=True) From 0c01e0b59bbac9b6e1bd9e92f1ca4f09526f16aa Mon Sep 17 00:00:00 2001 From: EngDann Date: Fri, 10 May 2024 19:26:00 -0300 Subject: [PATCH 100/351] =?UTF-8?q?Front:=20Conex=C3=A3o=20front-back=20pl?= =?UTF-8?q?ace=20e=20area,=20reorganizando=20rotas=20e=20par=C3=A2metros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/main.dart | 105 +++++-- .../feature/manage/EquipmentScreen.dart | 17 +- .../feature/manage/addEquipmentScreen.dart | 19 +- .../lib/places/feature/manage/lowVoltage.dart | 14 +- ...Screen.dart => manageEquipmentScreen.dart} | 10 +- .../feature/manage/systemConfiguration.dart | 46 ++- .../lib/places/feature/register/new_area.dart | 273 ++++++++++++++++++ .../places/feature/register/new_place.dart | 14 +- .../places/feature/register/room_state.dart | 198 ------------- .../lib/places/models/area_request_model.dart | 23 ++ .../places/models/place_request_model.dart | 8 + .../lib/places/services/area_service.dart | 64 ++++ .../lib/places/services/place_service.dart | 63 +++- 13 files changed, 595 insertions(+), 259 deletions(-) rename frontend/sige_ie/lib/places/feature/manage/{viewEquipmentScreen.dart => manageEquipmentScreen.dart} (97%) create mode 100644 frontend/sige_ie/lib/places/feature/register/new_area.dart delete mode 100644 frontend/sige_ie/lib/places/feature/register/room_state.dart create mode 100644 frontend/sige_ie/lib/places/models/area_request_model.dart create mode 100644 frontend/sige_ie/lib/places/services/area_service.dart diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index f794a359..c7cbe4df 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -9,7 +9,7 @@ import 'package:sige_ie/places/feature/manage/EquipmentScreen.dart'; import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; import 'package:sige_ie/places/feature/manage/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; -import 'package:sige_ie/places/feature/register/room_state.dart'; +import 'package:sige_ie/places/feature/register/new_area.dart'; import 'core/feature/login/login.dart'; void main() { @@ -43,38 +43,93 @@ class MyApp extends StatelessWidget { case '/newLocation': return MaterialPageRoute(builder: (context) => NewPlace()); case '/roomlocation': - if (settings.arguments is String) { - final localName = settings.arguments as String; - return MaterialPageRoute( - builder: (context) => RoomLocation(localName: localName), - ); + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? localName = args['placeName']?.toString(); + final int? localId = args['placeId'] as int?; + + if (localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => + RoomLocation(localName: localName, localId: localId), + ); + } else { + throw Exception( + 'Invalid arguments: localName or localId is null in /roomlocation.'); + } } throw Exception( - 'Invalid route: Expected string argument for /roomlocation.'); + 'Invalid route: Expected Map arguments for /roomlocation.'); case '/systemLocation': - if (settings.arguments is String) { - final roomName = settings.arguments as String; - return MaterialPageRoute( - builder: (context) => SystemConfiguration( - roomName: roomName, - categoryNumber: 0, - )); + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? roomName = args['roomName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + if (roomName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => SystemConfiguration( + roomName: roomName, + localName: localName, + localId: localId, + categoryNumber: 0, + )); + } else { + throw Exception( + 'Invalid arguments: One of roomName, localName, or localId is null in /systemLocation.'); + } } throw Exception( - 'Invalid route: Expected string argument for /systemLocation.'); + 'Invalid route: Expected Map arguments for /systemLocation.'); case '/lowVoltage': - return MaterialPageRoute( - builder: (context) => const LowVoltageScreen( - roomName: '', - categoryNumber: 0, - )); + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? roomName = args['roomName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (roomName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => LowVoltageScreen( + roomName: roomName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of roomName, localName, or localId is null in /lowVoltage.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /lowVoltage.'); + case '/equipamentScreen': - return MaterialPageRoute( - builder: (context) => EquipmentScreen( - roomName: '', - categoryNumber: 0, - )); + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? roomName = args['roomName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (roomName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => EquipmentScreen( + roomName: roomName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of roomName, localName, or localId is null in /equipamentScreen.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /equipamentScreen.'); + default: return MaterialPageRoute( builder: (context) => UndefinedView(name: settings.name)); diff --git a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart index 2589b7ee..127ad4ee 100644 --- a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart @@ -1,16 +1,20 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/places/feature/manage/addEquipmentScreen.dart'; -import 'package:sige_ie/places/feature/manage/viewEquipmentScreen.dart'; +import 'package:sige_ie/places/feature/manage/manageEquipmentScreen.dart'; class EquipmentScreen extends StatelessWidget { final String roomName; + final String localName; final int categoryNumber; + final int localId; EquipmentScreen({ Key? key, required this.roomName, required this.categoryNumber, + required this.localName, + required this.localId, }) : super(key: key); void navigateToAddEquipment(BuildContext context) { @@ -20,6 +24,8 @@ class EquipmentScreen extends StatelessWidget { builder: (context) => AddEquipmentScreen( roomName: roomName, categoryNumber: categoryNumber, + localName: localName, + localId: localId, ), ), ); @@ -32,6 +38,8 @@ class EquipmentScreen extends StatelessWidget { builder: (context) => ViewEquipmentScreen( roomName: roomName, categoryNumber: categoryNumber, + localName: localName, + localId: localId, ), ), ); @@ -48,7 +56,12 @@ class EquipmentScreen extends StatelessWidget { Navigator.pushReplacementNamed( context, '/systemLocation', - arguments: roomName, + arguments: { + 'roomName': roomName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, ); }, ), diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart index d1639adf..71250858 100644 --- a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart @@ -17,11 +17,17 @@ Map> categoryImagesMap = {}; class AddEquipmentScreen extends StatefulWidget { final String roomName; + final String localName; + final int localId; final int categoryNumber; - AddEquipmentScreen( - {Key? key, required this.roomName, required this.categoryNumber}) - : super(key: key); + AddEquipmentScreen({ + Key? key, + required this.roomName, + required this.categoryNumber, + required this.localName, + required this.localId, + }) : super(key: key); @override _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); @@ -253,7 +259,12 @@ class _AddEquipmentScreenState extends State { ))), onPressed: () { Navigator.pushNamed(context, '/systemLocation', - arguments: widget.roomName); + arguments: { + 'roomName': widget.roomName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }); }, child: const Text( 'ADICIONAR EQUIPAMENTO', diff --git a/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart b/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart index e33370a4..0311a6c2 100644 --- a/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart +++ b/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart @@ -3,11 +3,17 @@ import 'package:sige_ie/config/app_styles.dart'; class LowVoltageScreen extends StatefulWidget { final String roomName; + final int categoryNumber; + final String localName; + final int? localId; - const LowVoltageScreen( - {Key? key, required this.roomName, required int categoryNumber}) - : super(key: key); - + const LowVoltageScreen({ + Key? key, + required this.roomName, + required this.categoryNumber, + required this.localName, + this.localId, + }) : super(key: key); @override _LowVoltageScreenState createState() => _LowVoltageScreenState(); } diff --git a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart b/frontend/sige_ie/lib/places/feature/manage/manageEquipmentScreen.dart similarity index 97% rename from frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart rename to frontend/sige_ie/lib/places/feature/manage/manageEquipmentScreen.dart index b6499792..8d5682b3 100644 --- a/frontend/sige_ie/lib/places/feature/manage/viewEquipmentScreen.dart +++ b/frontend/sige_ie/lib/places/feature/manage/manageEquipmentScreen.dart @@ -5,10 +5,16 @@ import 'package:sige_ie/places/feature/manage/equipment_manager.dart'; class ViewEquipmentScreen extends StatefulWidget { final String roomName; + final String localName; + final int localId; final int categoryNumber; ViewEquipmentScreen( - {Key? key, required this.roomName, required this.categoryNumber}) + {Key? key, + required this.roomName, + required this.categoryNumber, + required this.localName, + required this.localId}) : super(key: key); @override @@ -24,6 +30,8 @@ class _ViewEquipmentScreenState extends State { builder: (context) => EquipmentScreen( roomName: widget.roomName, categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, ), )); } diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart index cc689c72..eb475864 100644 --- a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart @@ -5,18 +5,26 @@ import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; class SystemConfiguration extends StatefulWidget { final String roomName; + final String localName; + final int localId; final int categoryNumber; - SystemConfiguration( - {Key? key, required this.roomName, required this.categoryNumber}) - : super(key: key); + SystemConfiguration({ + Key? key, + required this.roomName, + required this.localName, + required this.localId, + required this.categoryNumber, + }) : super(key: key); @override _SystemConfigurationState createState() => _SystemConfigurationState(); } class _SystemConfigurationState extends State { - void navigateTo(String routeName, String roomName, [int categoryNumber = 0]) { + void navigateTo( + String routeName, String roomName, String localName, int localId, + [int categoryNumber = 0]) { Navigator.push( context, MaterialPageRoute( @@ -24,12 +32,18 @@ class _SystemConfigurationState extends State { switch (routeName) { case '/lowVoltage': return LowVoltageScreen( - roomName: roomName, categoryNumber: categoryNumber); + roomName: roomName, + localName: localName, + localId: localId, + categoryNumber: categoryNumber); case '/structuredCabling': case '/atmosphericDischarges': case '/fireAlarm': return EquipmentScreen( - roomName: roomName, categoryNumber: categoryNumber); + roomName: roomName, + localName: localName, + localId: localId, + categoryNumber: categoryNumber); default: return Scaffold( body: Center(child: Text('No route defined for $routeName')), @@ -75,18 +89,20 @@ class _SystemConfigurationState extends State { ), SystemButton( title: 'BAIXA TENSÃO', - onPressed: () => navigateTo('/lowVoltage', widget.roomName, 0)), + onPressed: () => navigateTo('/lowVoltage', widget.roomName, + widget.localName, widget.localId, 0)), SystemButton( title: 'CABEAMENTO ESTRUTURADO', - onPressed: () => - navigateTo('/structuredCabling', widget.roomName, 1)), + onPressed: () => navigateTo('/structuredCabling', + widget.roomName, widget.localName, widget.localId, 1)), SystemButton( title: 'DESCARGAS ATMOSFÉRICAS', - onPressed: () => - navigateTo('/atmosphericDischarges', widget.roomName, 2)), + onPressed: () => navigateTo('/atmosphericDischarges', + widget.roomName, widget.localName, widget.localId, 2)), SystemButton( title: 'ALARME DE INCÊNDIO', - onPressed: () => navigateTo('/fireAlarm', widget.roomName, 3)), + onPressed: () => navigateTo('/fireAlarm', widget.roomName, + widget.localName, widget.localId, 3)), SizedBox( height: 30, ), @@ -102,8 +118,10 @@ class _SystemConfigurationState extends State { )), ), onPressed: () { - Navigator.of(context) - .pushNamed('/roomlocation', arguments: widget.roomName); + Navigator.of(context).pushNamed('/roomlocation', arguments: { + 'placeName': widget.localName, + 'placeId': widget.localId + }); }, child: const Text( 'ENCERRAR', diff --git a/frontend/sige_ie/lib/places/feature/register/new_area.dart b/frontend/sige_ie/lib/places/feature/register/new_area.dart new file mode 100644 index 00000000..eb5fb2fe --- /dev/null +++ b/frontend/sige_ie/lib/places/feature/register/new_area.dart @@ -0,0 +1,273 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/places/models/area_request_model.dart'; +import 'package:sige_ie/places/services/area_service.dart'; + +class RoomLocation extends StatefulWidget { + final String localName; + final int localId; + + const RoomLocation({Key? key, required this.localName, required this.localId}) + : super(key: key); + @override + _RoomLocationState createState() => _RoomLocationState(); +} + +class _RoomLocationState extends State { + int? selectedFloor; + final TextEditingController roomController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + automaticallyImplyLeading: false, + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('${widget.localName} - Sala', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + SizedBox(height: 60), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text('Andar', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black)), + IconButton( + icon: Icon(Icons.info_outline), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Informação sobre os Andares'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('0 = Térreo', + style: TextStyle(fontSize: 16)), + Text('1 = 1° Andar', + style: TextStyle(fontSize: 16)), + Text('2 = 2° Andar', + style: TextStyle(fontSize: 16)), + ], + ), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () => + Navigator.of(context).pop(), + ), + ], + ); + }, + ); + }, + ), + ], + ), + SizedBox(height: 10), + Container( + padding: EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextFormField( + controller: TextEditingController( + text: selectedFloor?.toString()), + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: InputBorder.none, + ), + onChanged: (value) { + setState(() { + selectedFloor = int.tryParse(value); + }); + }, + ), + ), + SizedBox(height: 40), + Text('Sala', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black)), + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10)), + child: TextField( + controller: roomController, + decoration: InputDecoration( + hintText: 'Digite o nome da Sala', + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + SizedBox(height: 60), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.sigeIeBlue, + backgroundColor: AppColors.sigeIeYellow, + minimumSize: Size(150, 50), + textStyle: TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: () async { + if (selectedFloor != null && + roomController.text.isNotEmpty) { + AreaService areaService = AreaService(); + bool result = + await areaService.createRoom(RoomRequestModel( + name: roomController.text, + floor: selectedFloor, + place: widget.localId, + )); + if (result) { + print( + 'Sala Registrada: ${roomController.text} no $selectedFloor° andar'); + Navigator.pushNamed(context, '/systemLocation', + arguments: { + 'roomName': roomController.text, + 'localName': widget.localName, + 'localId': widget.localId + }); + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Erro'), + content: Text("Falha ao criar sala."), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () => + Navigator.of(context).pop(), + ), + ], + ); + }, + ); + } + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Erro'), + content: Text( + "Por favor, selecione um andar e digite o nome da sala"), + actions: [ + TextButton( + child: Text("OK"), + onPressed: () => + Navigator.of(context).pop(), + ), + ], + ); + }, + ); + } + }, + child: Text('CONTINUAR'), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.lightText, + backgroundColor: AppColors.warn, + minimumSize: Size(150, 50), + textStyle: TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/homeScreen', + ); + }, + child: Text('ENCERRAR'), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +Widget _buildDropdown({ + required List items, + required String? value, + required void Function(String?) onChanged, + VoidCallback? addNew, +}) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButtonHideUnderline( + child: DropdownButtonFormField( + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 4), + ), + value: value, + isExpanded: true, + items: items.map((String item) { + return DropdownMenuItem( + value: item, + child: Text(item), + ); + }).toList(), + onChanged: onChanged, + style: TextStyle(color: Colors.black), + dropdownColor: Colors.grey[200], + ), + ), + ); +} diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index cd69a1d6..5f285c09 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -161,17 +161,21 @@ class NewPlaceState extends State { ), onPressed: () async { if (coord && nameController.text.trim().isNotEmpty) { - final place = PlaceRequestModel( + final PlaceService placeService = PlaceService(); + final PlaceRequestModel place = PlaceRequestModel( name: nameController.text, lon: positionController.lon, lat: positionController.lat); - bool success = await placeService.register(place); - if (success) { + int? placeId = await placeService.register(place); + if (placeId != null) { print( 'Local Registrado: ${nameController.text} em latitude: ${positionController.lat} e longitude: ${positionController.lon}'); - Navigator.of(context).pushNamed('/roomlocation', - arguments: nameController.text.trim()); + Navigator.of(context) + .pushNamed('/roomlocation', arguments: { + 'placeName': nameController.text.trim(), + 'placeId': placeId, + }); } } else if (nameController.text.trim().isEmpty || !coord) { showDialog( diff --git a/frontend/sige_ie/lib/places/feature/register/room_state.dart b/frontend/sige_ie/lib/places/feature/register/room_state.dart deleted file mode 100644 index e2c0e40e..00000000 --- a/frontend/sige_ie/lib/places/feature/register/room_state.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; - -class RoomLocation extends StatefulWidget { - final String localName; - const RoomLocation({Key? key, required this.localName}) : super(key: key); - - @override - _RoomLocationState createState() => _RoomLocationState(); -} - -class _RoomLocationState extends State { - String? selectedFloor; - final TextEditingController roomController = TextEditingController(); - final List floors = [ - 'Térreo', - '1° Andar', - '2° Andar', - '3° Andar', - '4° Andar' - ]; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - automaticallyImplyLeading: false, - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - width: double.infinity, - padding: EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text('${widget.localName} - Sala', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - SizedBox(height: 60), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Andar', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black)), - SizedBox(height: 10), - _buildDropdown( - items: floors, - value: selectedFloor, - onChanged: (String? newValue) { - setState(() { - selectedFloor = newValue; - }); - }), - SizedBox(height: 40), - Text('Sala', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black)), - SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10)), - child: TextField( - controller: roomController, - decoration: InputDecoration( - hintText: 'Digite o nome da Sala', - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 10), - ), - ), - ), - SizedBox(height: 60), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: AppColors.sigeIeBlue, - backgroundColor: AppColors.sigeIeYellow, - minimumSize: Size(165, 50), - textStyle: TextStyle( - fontWeight: FontWeight.bold, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - onPressed: () { - if (selectedFloor != null && - roomController.text.isNotEmpty) { - print( - 'Sala Registrada: ${roomController.text} no ${selectedFloor}'); - Navigator.pushNamed(context, '/systemLocation', - arguments: roomController.text); - } else { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('Erro'), - content: Text( - "Por favor, selecione um andar e digite o nome da sala"), - actions: [ - TextButton( - child: Text("OK"), - onPressed: () => - Navigator.of(context).pop(), - ), - ], - ); - }, - ); - } - }, - child: Text('CONTINUAR'), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: AppColors.lightText, - backgroundColor: AppColors.warn, - minimumSize: Size(165, 50), - textStyle: TextStyle( - fontWeight: FontWeight.bold, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - onPressed: () { - Navigator.pushReplacementNamed( - context, - '/', //Rota temporária, modificar depois - ); - }, - child: Text('ENCERRAR'), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildDropdown({ - required List items, - required String? value, - required void Function(String?) onChanged, - VoidCallback? addNew, - }) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10), - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: DropdownButtonHideUnderline( - child: DropdownButtonFormField( - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 4), - ), - value: value, - isExpanded: true, - items: items.map((String item) { - return DropdownMenuItem( - value: item, - child: Text(item), - ); - }).toList(), - onChanged: onChanged, - style: TextStyle(color: Colors.black), - dropdownColor: Colors.grey[200], - ), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/places/models/area_request_model.dart b/frontend/sige_ie/lib/places/models/area_request_model.dart new file mode 100644 index 00000000..9037cfb1 --- /dev/null +++ b/frontend/sige_ie/lib/places/models/area_request_model.dart @@ -0,0 +1,23 @@ +class RoomRequestModel { + String name; + int? floor; + int? place; + + RoomRequestModel({required this.name, this.floor, this.place}); + + Map toJson() { + return { + 'name': name, + 'floor': floor, + 'place': place, + }; + } + + factory RoomRequestModel.fromJson(Map json) { + return RoomRequestModel( + name: json['name'], + floor: json['floor'], + place: json['place'], + ); + } +} diff --git a/frontend/sige_ie/lib/places/models/place_request_model.dart b/frontend/sige_ie/lib/places/models/place_request_model.dart index 1834ccee..f42ceb83 100644 --- a/frontend/sige_ie/lib/places/models/place_request_model.dart +++ b/frontend/sige_ie/lib/places/models/place_request_model.dart @@ -12,4 +12,12 @@ class PlaceRequestModel { 'lat': lat, }; } + + factory PlaceRequestModel.fromJson(Map json) { + return PlaceRequestModel( + name: json['name'], + lon: json['lon'].toDouble(), + lat: json['lat'].toDouble(), + ); + } } diff --git a/frontend/sige_ie/lib/places/services/area_service.dart b/frontend/sige_ie/lib/places/services/area_service.dart new file mode 100644 index 00000000..cded32e8 --- /dev/null +++ b/frontend/sige_ie/lib/places/services/area_service.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/services/auth_interceptor.dart'; +import 'package:sige_ie/main.dart'; +import 'package:sige_ie/places/models/area_request_model.dart'; + +class AreaService { + Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + final String baseUrl = 'http://10.0.2.2:8000/api/areas/'; + + // POST + Future createRoom(RoomRequestModel roomRequestModel) async { + var url = Uri.parse(baseUrl); + + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(roomRequestModel.toJson()), + ); + + return response.statusCode == 201; + } + + // Ainda não testado + // GET + Future fetchRoom(int roomId) async { + var url = Uri.parse('$baseUrl$roomId/'); + + var response = await client.get(url); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return RoomRequestModel.fromJson(data); + } else { + throw Exception('Failed to load room with ID $roomId'); + } + } + + // PUT + Future updateRoom(int roomId, RoomRequestModel roomRequestModel) async { + var url = Uri.parse('$baseUrl$roomId/'); + + var response = await client.put( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(roomRequestModel.toJson()), + ); + + return response.statusCode == 200; + } + + // DELETE + Future deleteRoom(int roomId) async { + var url = Uri.parse('$baseUrl$roomId/'); + + var response = await client.delete(url); + + return response.statusCode == 204; + } +} diff --git a/frontend/sige_ie/lib/places/services/place_service.dart b/frontend/sige_ie/lib/places/services/place_service.dart index 1f6bc8e3..aa200b08 100644 --- a/frontend/sige_ie/lib/places/services/place_service.dart +++ b/frontend/sige_ie/lib/places/services/place_service.dart @@ -1,21 +1,72 @@ import 'dart:convert'; +import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/services/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/places/models/place_request_model.dart'; class PlaceService { - Future register(PlaceRequestModel placeRequestModel) async { - var client = InterceptedClient.build( - interceptors: [AuthInterceptor(cookieJar)], - ); - var url = Uri.parse('http://10.0.2.2:8000/api/places/'); + final String baseUrl = 'http://10.0.2.2:8000/api/places/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + // POST + Future register(PlaceRequestModel placeRequestModel) async { + var url = Uri.parse(baseUrl); var response = await client.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode(placeRequestModel.toJson()), ); - return response.statusCode == 201; + + if (response.statusCode == 201) { + // Se a criação foi bem-sucedida, extrai o ID do lugar criado do corpo da resposta + Map responseData = jsonDecode(response.body); + int? placeId = + responseData['id']; // Assume que a resposta tem um campo 'id' + return placeId; + } else { + return null; + } + } + + // Ainda não testado + // GET + Future fetchPlace(int placeId) async { + var url = Uri.parse('$baseUrl$placeId/'); + + var response = await client.get(url); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return PlaceRequestModel.fromJson(data); + } else { + throw Exception('Failed to load place with ID $placeId'); + } + } + + // PUT + Future updatePlace( + int placeId, PlaceRequestModel placeRequestModel) async { + var url = Uri.parse('$baseUrl$placeId/'); + + var response = await client.put( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(placeRequestModel.toJson()), + ); + + return response.statusCode == 200; + } + + // DELETE + Future deletePlace(int placeId) async { + var url = Uri.parse('$baseUrl$placeId/'); + + var response = await client.delete(url); + + return response.statusCode == 204; } } From 27c93f0572a2722b2aeb5eb3b0bb3915df28f3b1 Mon Sep 17 00:00:00 2001 From: Oscar de Brito <98489703+OscarDeBrito@users.noreply.github.com> Date: Mon, 13 May 2024 11:08:22 -0300 Subject: [PATCH 101/351] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 25c02038..544c9ab3 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ Com o ambiente preparado, siga os passos abaixo: 4. **Execute o Projeto**: - **No Android Studio:** Escolha um dispositivo ou emulador na barra de ferramentas e clique em 'Run'. - - **No Visual Studio Code:** Selecione um dispositivo ou emulador na barra de status e pressione `F5` ou utilize o comando `Flutter: Run` na paleta de comandos. + - **No Visual Studio Code:** Selecione um dispositivo ou emulador na barra de status e pressione `F5` ou utilize o comando `flutter run` na paleta de comandos. Pronto, o Front end já está rodando e você pode utilizá-lo. From 914334593ca69d405feecd8838b207e825bdb4e6 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 13 May 2024 13:17:05 -0300 Subject: [PATCH 102/351] backend: filtra tipos de equipamentos pelo id do system --- api/equipments/urls.py | 4 ++-- api/equipments/views.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/equipments/urls.py b/api/equipments/urls.py index e4d131d3..c079a436 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -2,8 +2,8 @@ from django.urls import path urlpatterns = [ - path('equipment-type/', EquipmentTypeList.as_view()), - path('equipment-type//', EquipmentTypeDetail.as_view()), + path('equipment-types/by-system//', EquipmentTypeList.as_view(), name='equipment_types_by_system'), + path('equipment-types//', EquipmentTypeDetail.as_view()), path('equipments/', EquipmentDetailList.as_view()), path('equipments//', EquipmentDetailDetail.as_view()), path('atmospheric-discharges/', AtmosphericDischargeEquipmentList.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index 940780b6..76d338d4 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -12,6 +12,10 @@ class EquipmentTypeList(generics.ListAPIView): serializer_class = EquipmentTypeSerializer permission_classes = [IsAuthenticated] + def get_queryset(self): + system_id = self.kwargs['system_id'] + return EquipmentType.objects.filter(system_id=system_id) + class EquipmentTypeDetail(generics.RetrieveAPIView): queryset = EquipmentType.objects.all() serializer_class = EquipmentTypeSerializer From d471fa248692f33baa0a9fd5f774b6867ea4621b Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 13 May 2024 13:20:53 -0300 Subject: [PATCH 103/351] =?UTF-8?q?Solicita=C3=A7=C3=B5es=20do=20pull=20re?= =?UTF-8?q?quest=20resolvidas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 1 - .../data}/area_request_model.dart | 8 ++-- .../services => areas/data}/area_service.dart | 26 ++++++------ .../feature/register/new_area.dart | 26 ++++++------ .../{services => data}/auth_interceptor.dart | 0 .../core/{services => data}/auth_service.dart | 2 +- .../sige_ie/lib/core/feature/login/login.dart | 3 +- .../feature/manage/EquipmentScreen.dart | 16 ++++---- .../feature/manage/addEquipmentScreen.dart | 8 ++-- .../feature/manage/equipment_manager.dart | 0 .../feature/manage/lowVoltage.dart | 6 +-- .../feature/manage/manageEquipmentScreen.dart | 10 ++--- .../feature/manage/systemConfiguration.dart | 26 ++++++------ frontend/sige_ie/lib/main.dart | 40 +++++++++---------- .../{models => data}/place_request_model.dart | 0 .../{services => data}/place_service.dart | 4 +- .../places/feature/register/new_place.dart | 6 +-- .../sige_ie/lib/users/feature/profile.dart | 2 +- .../lib/users/services/user_service.dart | 2 +- 19 files changed, 93 insertions(+), 93 deletions(-) rename frontend/sige_ie/lib/{places/models => areas/data}/area_request_model.dart (59%) rename frontend/sige_ie/lib/{places/services => areas/data}/area_service.dart (58%) rename frontend/sige_ie/lib/{places => areas}/feature/register/new_area.dart (92%) rename frontend/sige_ie/lib/core/{services => data}/auth_interceptor.dart (100%) rename frontend/sige_ie/lib/core/{services => data}/auth_service.dart (98%) rename frontend/sige_ie/lib/{places => core}/feature/manage/EquipmentScreen.dart (91%) rename frontend/sige_ie/lib/{places => core}/feature/manage/addEquipmentScreen.dart (98%) rename frontend/sige_ie/lib/{places => core}/feature/manage/equipment_manager.dart (100%) rename frontend/sige_ie/lib/{places => core}/feature/manage/lowVoltage.dart (96%) rename frontend/sige_ie/lib/{places => core}/feature/manage/manageEquipmentScreen.dart (97%) rename frontend/sige_ie/lib/{places => core}/feature/manage/systemConfiguration.dart (90%) rename frontend/sige_ie/lib/places/{models => data}/place_request_model.dart (100%) rename frontend/sige_ie/lib/places/{services => data}/place_service.dart (93%) diff --git a/.idea/misc.xml b/.idea/misc.xml index 639900d1..6e866721 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/frontend/sige_ie/lib/places/models/area_request_model.dart b/frontend/sige_ie/lib/areas/data/area_request_model.dart similarity index 59% rename from frontend/sige_ie/lib/places/models/area_request_model.dart rename to frontend/sige_ie/lib/areas/data/area_request_model.dart index 9037cfb1..2cd56094 100644 --- a/frontend/sige_ie/lib/places/models/area_request_model.dart +++ b/frontend/sige_ie/lib/areas/data/area_request_model.dart @@ -1,9 +1,9 @@ -class RoomRequestModel { +class AreaRequestModel { String name; int? floor; int? place; - RoomRequestModel({required this.name, this.floor, this.place}); + AreaRequestModel({required this.name, this.floor, this.place}); Map toJson() { return { @@ -13,8 +13,8 @@ class RoomRequestModel { }; } - factory RoomRequestModel.fromJson(Map json) { - return RoomRequestModel( + factory AreaRequestModel.fromJson(Map json) { + return AreaRequestModel( name: json['name'], floor: json['floor'], place: json['place'], diff --git a/frontend/sige_ie/lib/places/services/area_service.dart b/frontend/sige_ie/lib/areas/data/area_service.dart similarity index 58% rename from frontend/sige_ie/lib/places/services/area_service.dart rename to frontend/sige_ie/lib/areas/data/area_service.dart index cded32e8..504104bd 100644 --- a/frontend/sige_ie/lib/places/services/area_service.dart +++ b/frontend/sige_ie/lib/areas/data/area_service.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/services/auth_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; -import 'package:sige_ie/places/models/area_request_model.dart'; +import 'package:sige_ie/areas/data/area_request_model.dart'; class AreaService { Client client = InterceptedClient.build( @@ -13,13 +13,13 @@ class AreaService { final String baseUrl = 'http://10.0.2.2:8000/api/areas/'; // POST - Future createRoom(RoomRequestModel roomRequestModel) async { + Future createArea(AreaRequestModel areaRequestModel) async { var url = Uri.parse(baseUrl); var response = await client.post( url, headers: {'Content-Type': 'application/json'}, - body: jsonEncode(roomRequestModel.toJson()), + body: jsonEncode(areaRequestModel.toJson()), ); return response.statusCode == 201; @@ -27,35 +27,35 @@ class AreaService { // Ainda não testado // GET - Future fetchRoom(int roomId) async { - var url = Uri.parse('$baseUrl$roomId/'); + Future fetchArea(int areaId) async { + var url = Uri.parse('$baseUrl$areaId/'); var response = await client.get(url); if (response.statusCode == 200) { var data = jsonDecode(response.body); - return RoomRequestModel.fromJson(data); + return AreaRequestModel.fromJson(data); } else { - throw Exception('Failed to load room with ID $roomId'); + throw Exception('Failed to load area with ID $areaId'); } } // PUT - Future updateRoom(int roomId, RoomRequestModel roomRequestModel) async { - var url = Uri.parse('$baseUrl$roomId/'); + Future updateArea(int areaId, AreaRequestModel areaRequestModel) async { + var url = Uri.parse('$baseUrl$areaId/'); var response = await client.put( url, headers: {'Content-Type': 'application/json'}, - body: jsonEncode(roomRequestModel.toJson()), + body: jsonEncode(areaRequestModel.toJson()), ); return response.statusCode == 200; } // DELETE - Future deleteRoom(int roomId) async { - var url = Uri.parse('$baseUrl$roomId/'); + Future deleteArea(int areaId) async { + var url = Uri.parse('$baseUrl$areaId/'); var response = await client.delete(url); diff --git a/frontend/sige_ie/lib/places/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart similarity index 92% rename from frontend/sige_ie/lib/places/feature/register/new_area.dart rename to frontend/sige_ie/lib/areas/feature/register/new_area.dart index eb5fb2fe..fab7c2b1 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -1,21 +1,21 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/models/area_request_model.dart'; -import 'package:sige_ie/places/services/area_service.dart'; +import 'package:sige_ie/areas/data/area_request_model.dart'; +import 'package:sige_ie/areas/data/area_service.dart'; -class RoomLocation extends StatefulWidget { +class AreaLocation extends StatefulWidget { final String localName; final int localId; - const RoomLocation({Key? key, required this.localName, required this.localId}) + const AreaLocation({Key? key, required this.localName, required this.localId}) : super(key: key); @override - _RoomLocationState createState() => _RoomLocationState(); + _AreaLocationState createState() => _AreaLocationState(); } -class _RoomLocationState extends State { +class _AreaLocationState extends State { int? selectedFloor; - final TextEditingController roomController = TextEditingController(); + final TextEditingController areaController = TextEditingController(); @override Widget build(BuildContext context) { @@ -123,7 +123,7 @@ class _RoomLocationState extends State { color: Colors.grey[300], borderRadius: BorderRadius.circular(10)), child: TextField( - controller: roomController, + controller: areaController, decoration: InputDecoration( hintText: 'Digite o nome da Sala', border: InputBorder.none, @@ -149,20 +149,20 @@ class _RoomLocationState extends State { ), onPressed: () async { if (selectedFloor != null && - roomController.text.isNotEmpty) { + areaController.text.isNotEmpty) { AreaService areaService = AreaService(); bool result = - await areaService.createRoom(RoomRequestModel( - name: roomController.text, + await areaService.createArea(AreaRequestModel( + name: areaController.text, floor: selectedFloor, place: widget.localId, )); if (result) { print( - 'Sala Registrada: ${roomController.text} no $selectedFloor° andar'); + 'Sala Registrada: ${areaController.text} no $selectedFloor° andar'); Navigator.pushNamed(context, '/systemLocation', arguments: { - 'roomName': roomController.text, + 'areaName': areaController.text, 'localName': widget.localName, 'localId': widget.localId }); diff --git a/frontend/sige_ie/lib/core/services/auth_interceptor.dart b/frontend/sige_ie/lib/core/data/auth_interceptor.dart similarity index 100% rename from frontend/sige_ie/lib/core/services/auth_interceptor.dart rename to frontend/sige_ie/lib/core/data/auth_interceptor.dart diff --git a/frontend/sige_ie/lib/core/services/auth_service.dart b/frontend/sige_ie/lib/core/data/auth_service.dart similarity index 98% rename from frontend/sige_ie/lib/core/services/auth_service.dart rename to frontend/sige_ie/lib/core/data/auth_service.dart index 3e2717f7..b645f393 100644 --- a/frontend/sige_ie/lib/core/services/auth_service.dart +++ b/frontend/sige_ie/lib/core/data/auth_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/services/auth_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; class AuthService { diff --git a/frontend/sige_ie/lib/core/feature/login/login.dart b/frontend/sige_ie/lib/core/feature/login/login.dart index 62b934cc..d7109f81 100644 --- a/frontend/sige_ie/lib/core/feature/login/login.dart +++ b/frontend/sige_ie/lib/core/feature/login/login.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sige_ie/core/services/auth_service.dart'; +import 'package:sige_ie/core/data/auth_service.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({Key? key}) : super(key: key); @@ -19,6 +19,7 @@ class _LoginScreenState extends State { return Scaffold( backgroundColor: const Color(0xff123c75), appBar: AppBar( + automaticallyImplyLeading: false, iconTheme: const IconThemeData(color: Colors.white), backgroundColor: const Color(0xff123c75), ), diff --git a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart similarity index 91% rename from frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart rename to frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart index 127ad4ee..5c921d42 100644 --- a/frontend/sige_ie/lib/places/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/feature/manage/addEquipmentScreen.dart'; -import 'package:sige_ie/places/feature/manage/manageEquipmentScreen.dart'; +import 'package:sige_ie/core/feature/manage/addEquipmentScreen.dart'; +import 'package:sige_ie/core/feature/manage/manageEquipmentScreen.dart'; class EquipmentScreen extends StatelessWidget { - final String roomName; + final String areaName; final String localName; final int categoryNumber; final int localId; EquipmentScreen({ Key? key, - required this.roomName, + required this.areaName, required this.categoryNumber, required this.localName, required this.localId, @@ -22,7 +22,7 @@ class EquipmentScreen extends StatelessWidget { context, MaterialPageRoute( builder: (context) => AddEquipmentScreen( - roomName: roomName, + areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, @@ -36,7 +36,7 @@ class EquipmentScreen extends StatelessWidget { context, MaterialPageRoute( builder: (context) => ViewEquipmentScreen( - roomName: roomName, + areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, @@ -57,7 +57,7 @@ class EquipmentScreen extends StatelessWidget { context, '/systemLocation', arguments: { - 'roomName': roomName, + 'areaName': areaName, 'localName': localName, 'localId': localId, 'categoryNumber': categoryNumber, @@ -76,7 +76,7 @@ class EquipmentScreen extends StatelessWidget { borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text(roomName, + child: Text(areaName, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, diff --git a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart similarity index 98% rename from frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart rename to frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart index 71250858..6f1cb477 100644 --- a/frontend/sige_ie/lib/places/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/feature/manage/equipment_manager.dart'; +import 'package:sige_ie/core/feature/manage/equipment_manager.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'dart:math'; @@ -16,14 +16,14 @@ List _images = []; Map> categoryImagesMap = {}; class AddEquipmentScreen extends StatefulWidget { - final String roomName; + final String areaName; final String localName; final int localId; final int categoryNumber; AddEquipmentScreen({ Key? key, - required this.roomName, + required this.areaName, required this.categoryNumber, required this.localName, required this.localId, @@ -260,7 +260,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { Navigator.pushNamed(context, '/systemLocation', arguments: { - 'roomName': widget.roomName, + 'areaName': widget.areaName, 'localName': widget.localName, 'localId': widget.localId, 'categoryNumber': widget.categoryNumber, diff --git a/frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart b/frontend/sige_ie/lib/core/feature/manage/equipment_manager.dart similarity index 100% rename from frontend/sige_ie/lib/places/feature/manage/equipment_manager.dart rename to frontend/sige_ie/lib/core/feature/manage/equipment_manager.dart diff --git a/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart b/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart similarity index 96% rename from frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart rename to frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart index 0311a6c2..ffc70647 100644 --- a/frontend/sige_ie/lib/places/feature/manage/lowVoltage.dart +++ b/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; class LowVoltageScreen extends StatefulWidget { - final String roomName; + final String areaName; final int categoryNumber; final String localName; final int? localId; const LowVoltageScreen({ Key? key, - required this.roomName, + required this.areaName, required this.categoryNumber, required this.localName, this.localId, @@ -45,7 +45,7 @@ class _LowVoltageScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('${widget.roomName} - Baixa Tensão', + child: Text('${widget.areaName} - Baixa Tensão', style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, diff --git a/frontend/sige_ie/lib/places/feature/manage/manageEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart similarity index 97% rename from frontend/sige_ie/lib/places/feature/manage/manageEquipmentScreen.dart rename to frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart index 8d5682b3..7e363da1 100644 --- a/frontend/sige_ie/lib/places/feature/manage/manageEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/feature/manage/EquipmentScreen.dart'; -import 'package:sige_ie/places/feature/manage/equipment_manager.dart'; +import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; +import 'package:sige_ie/core/feature/manage/equipment_manager.dart'; class ViewEquipmentScreen extends StatefulWidget { - final String roomName; + final String areaName; final String localName; final int localId; final int categoryNumber; ViewEquipmentScreen( {Key? key, - required this.roomName, + required this.areaName, required this.categoryNumber, required this.localName, required this.localId}) @@ -28,7 +28,7 @@ class _ViewEquipmentScreenState extends State { void navigateToEquipmentScreen() { Navigator.of(context).push(MaterialPageRoute( builder: (context) => EquipmentScreen( - roomName: widget.roomName, + areaName: widget.areaName, categoryNumber: widget.categoryNumber, localName: widget.localName, localId: widget.localId, diff --git a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart similarity index 90% rename from frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart rename to frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart index eb475864..fe2a9592 100644 --- a/frontend/sige_ie/lib/places/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/feature/manage/EquipmentScreen.dart'; -import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; +import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; +import 'package:sige_ie/core/feature/manage/lowVoltage.dart'; class SystemConfiguration extends StatefulWidget { - final String roomName; + final String areaName; final String localName; final int localId; final int categoryNumber; SystemConfiguration({ Key? key, - required this.roomName, + required this.areaName, required this.localName, required this.localId, required this.categoryNumber, @@ -23,7 +23,7 @@ class SystemConfiguration extends StatefulWidget { class _SystemConfigurationState extends State { void navigateTo( - String routeName, String roomName, String localName, int localId, + String routeName, String areaName, String localName, int localId, [int categoryNumber = 0]) { Navigator.push( context, @@ -32,7 +32,7 @@ class _SystemConfigurationState extends State { switch (routeName) { case '/lowVoltage': return LowVoltageScreen( - roomName: roomName, + areaName: areaName, localName: localName, localId: localId, categoryNumber: categoryNumber); @@ -40,7 +40,7 @@ class _SystemConfigurationState extends State { case '/atmosphericDischarges': case '/fireAlarm': return EquipmentScreen( - roomName: roomName, + areaName: areaName, localName: localName, localId: localId, categoryNumber: categoryNumber); @@ -73,7 +73,7 @@ class _SystemConfigurationState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text(widget.roomName, + child: Text(widget.areaName, style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -89,19 +89,19 @@ class _SystemConfigurationState extends State { ), SystemButton( title: 'BAIXA TENSÃO', - onPressed: () => navigateTo('/lowVoltage', widget.roomName, + onPressed: () => navigateTo('/lowVoltage', widget.areaName, widget.localName, widget.localId, 0)), SystemButton( title: 'CABEAMENTO ESTRUTURADO', onPressed: () => navigateTo('/structuredCabling', - widget.roomName, widget.localName, widget.localId, 1)), + widget.areaName, widget.localName, widget.localId, 1)), SystemButton( title: 'DESCARGAS ATMOSFÉRICAS', onPressed: () => navigateTo('/atmosphericDischarges', - widget.roomName, widget.localName, widget.localId, 2)), + widget.areaName, widget.localName, widget.localId, 2)), SystemButton( title: 'ALARME DE INCÊNDIO', - onPressed: () => navigateTo('/fireAlarm', widget.roomName, + onPressed: () => navigateTo('/fireAlarm', widget.areaName, widget.localName, widget.localId, 3)), SizedBox( height: 30, @@ -118,7 +118,7 @@ class _SystemConfigurationState extends State { )), ), onPressed: () { - Navigator.of(context).pushNamed('/roomlocation', arguments: { + Navigator.of(context).pushNamed('/arealocation', arguments: { 'placeName': widget.localName, 'placeId': widget.localId }); diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index c7cbe4df..796ab525 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -5,11 +5,11 @@ import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/places/feature/manage/EquipmentScreen.dart'; -import 'package:sige_ie/places/feature/manage/lowVoltage.dart'; -import 'package:sige_ie/places/feature/manage/systemConfiguration.dart'; +import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; +import 'package:sige_ie/core/feature/manage/lowVoltage.dart'; +import 'package:sige_ie/core/feature/manage/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; -import 'package:sige_ie/places/feature/register/new_area.dart'; +import 'package:sige_ie/areas/feature/register/new_area.dart'; import 'core/feature/login/login.dart'; void main() { @@ -42,7 +42,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute(builder: (context) => MapsPage()); case '/newLocation': return MaterialPageRoute(builder: (context) => NewPlace()); - case '/roomlocation': + case '/arealocation': if (settings.arguments is Map) { final args = settings.arguments as Map; final String? localName = args['placeName']?.toString(); @@ -51,33 +51,33 @@ class MyApp extends StatelessWidget { if (localName != null && localId != null) { return MaterialPageRoute( builder: (context) => - RoomLocation(localName: localName, localId: localId), + AreaLocation(localName: localName, localId: localId), ); } else { throw Exception( - 'Invalid arguments: localName or localId is null in /roomlocation.'); + 'Invalid arguments: localName or localId is null in /arealocation.'); } } throw Exception( - 'Invalid route: Expected Map arguments for /roomlocation.'); + 'Invalid route: Expected Map arguments for /arealocation.'); case '/systemLocation': if (settings.arguments is Map) { final args = settings.arguments as Map; - final String? roomName = args['roomName']?.toString(); + final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - if (roomName != null && localName != null && localId != null) { + if (areaName != null && localName != null && localId != null) { return MaterialPageRoute( builder: (context) => SystemConfiguration( - roomName: roomName, + areaName: areaName, localName: localName, localId: localId, categoryNumber: 0, )); } else { throw Exception( - 'Invalid arguments: One of roomName, localName, or localId is null in /systemLocation.'); + 'Invalid arguments: One of areaName, localName, or localId is null in /systemLocation.'); } } throw Exception( @@ -85,22 +85,22 @@ class MyApp extends StatelessWidget { case '/lowVoltage': if (settings.arguments is Map) { final args = settings.arguments as Map; - final String? roomName = args['roomName']?.toString(); + final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int categoryNumber = args['categoryNumber'] ?? 0; - if (roomName != null && localName != null && localId != null) { + if (areaName != null && localName != null && localId != null) { return MaterialPageRoute( builder: (context) => LowVoltageScreen( - roomName: roomName, + areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, )); } else { throw Exception( - 'Invalid arguments: One of roomName, localName, or localId is null in /lowVoltage.'); + 'Invalid arguments: One of areaName, localName, or localId is null in /lowVoltage.'); } } throw Exception( @@ -109,22 +109,22 @@ class MyApp extends StatelessWidget { case '/equipamentScreen': if (settings.arguments is Map) { final args = settings.arguments as Map; - final String? roomName = args['roomName']?.toString(); + final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int categoryNumber = args['categoryNumber'] ?? 0; - if (roomName != null && localName != null && localId != null) { + if (areaName != null && localName != null && localId != null) { return MaterialPageRoute( builder: (context) => EquipmentScreen( - roomName: roomName, + areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, )); } else { throw Exception( - 'Invalid arguments: One of roomName, localName, or localId is null in /equipamentScreen.'); + 'Invalid arguments: One of areaName, localName, or localId is null in /equipamentScreen.'); } } throw Exception( diff --git a/frontend/sige_ie/lib/places/models/place_request_model.dart b/frontend/sige_ie/lib/places/data/place_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/places/models/place_request_model.dart rename to frontend/sige_ie/lib/places/data/place_request_model.dart diff --git a/frontend/sige_ie/lib/places/services/place_service.dart b/frontend/sige_ie/lib/places/data/place_service.dart similarity index 93% rename from frontend/sige_ie/lib/places/services/place_service.dart rename to frontend/sige_ie/lib/places/data/place_service.dart index aa200b08..c51a4ebb 100644 --- a/frontend/sige_ie/lib/places/services/place_service.dart +++ b/frontend/sige_ie/lib/places/data/place_service.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/services/auth_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; -import 'package:sige_ie/places/models/place_request_model.dart'; +import 'package:sige_ie/places/data/place_request_model.dart'; class PlaceService { final String baseUrl = 'http://10.0.2.2:8000/api/places/'; diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index 5f285c09..b109ef47 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/places/models/place_request_model.dart'; -import 'package:sige_ie/places/services/place_service.dart'; +import 'package:sige_ie/places/data/place_request_model.dart'; +import 'package:sige_ie/places/data/place_service.dart'; import 'position.dart'; class NewPlace extends StatefulWidget { @@ -172,7 +172,7 @@ class NewPlaceState extends State { print( 'Local Registrado: ${nameController.text} em latitude: ${positionController.lat} e longitude: ${positionController.lon}'); Navigator.of(context) - .pushNamed('/roomlocation', arguments: { + .pushNamed('/arealocation', arguments: { 'placeName': nameController.text.trim(), 'placeId': placeId, }); diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index 34481128..bf7e86e0 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sige_ie/core/services/auth_service.dart'; +import 'package:sige_ie/core/data/auth_service.dart'; import 'package:sige_ie/users/models/user_response_model.dart'; import 'package:sige_ie/users/services/user_service.dart'; import 'package:sige_ie/config/app_styles.dart'; diff --git a/frontend/sige_ie/lib/users/services/user_service.dart b/frontend/sige_ie/lib/users/services/user_service.dart index 581d0c69..d220bc82 100644 --- a/frontend/sige_ie/lib/users/services/user_service.dart +++ b/frontend/sige_ie/lib/users/services/user_service.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/services/auth_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/users/models/user_request_model.dart'; From c6e987ad63984797f691fa49a7c0aa68ac5a16ac Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 13 May 2024 13:36:14 -0300 Subject: [PATCH 104/351] backend: cria tipos de equipamento pessoal --- .../migrations/0018_personalequipmenttype.py | 28 +++++++++++++++++++ api/equipments/models.py | 12 ++++++++ api/equipments/permissions.py | 7 +++++ api/equipments/serializers.py | 6 ++++ api/equipments/urls.py | 5 +++- api/equipments/views.py | 28 +++++++++++++++++++ 6 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 api/equipments/migrations/0018_personalequipmenttype.py diff --git a/api/equipments/migrations/0018_personalequipmenttype.py b/api/equipments/migrations/0018_personalequipmenttype.py new file mode 100644 index 00000000..28eece20 --- /dev/null +++ b/api/equipments/migrations/0018_personalequipmenttype.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2 on 2024-05-13 16:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0004_alter_placeowner_user'), + ('systems', '0008_delete_atmosphericdischargesystem'), + ('equipments', '0017_alter_distributionboardequipment_system_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='PersonalEquipmentType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('place_owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.placeowner')), + ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='systems.system')), + ], + options={ + 'db_table': 'equipments_personal_equipment_types', + }, + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index b221bf23..a59c5f4e 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -3,6 +3,18 @@ from systems.models import System from users.models import PlaceOwner +class PersonalEquipmentType(models.Model): + + name = models.CharField(max_length=50) + system = models.ForeignKey(System, on_delete=models.CASCADE) + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) + + def __str__(self): + return self.name + + class Meta: + db_table = 'equipments_personal_equipment_types' + class EquipmentType(models.Model): name = models.CharField(max_length=50) diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 82b75a4e..330559b4 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -2,6 +2,13 @@ from places.models import Area +class IsEquipmentTypeOwner(BasePermission): + def has_object_permission(self, request, view, obj): + if obj.place_owner == request.user.placeowner: + return True + else: + return False + class IsEquipmentDetailOwner(BasePermission): def has_object_permission(self, request, view, obj): if obj.place_owner == request.user.placeowner: diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 5af3c58e..8259d415 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -2,6 +2,12 @@ from .models import * from .mixins import ValidateAreaMixin +class PersonalEquipmentTypeSerializer(serializers.ModelSerializer): + + class Meta: + model = PersonalEquipmentType + fields = '__all__' + class EquipmentTypeSerializer(serializers.ModelSerializer): class Meta: diff --git a/api/equipments/urls.py b/api/equipments/urls.py index c079a436..3a7eb09c 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -2,7 +2,10 @@ from django.urls import path urlpatterns = [ - path('equipment-types/by-system//', EquipmentTypeList.as_view(), name='equipment_types_by_system'), + path('personal-equipment-types/', PersonalEquipmentTypeCreate.as_view(), name='personal_equipment_types'), + path('personal-equipment-types/by-system//', PersonalEquipmentTypeList.as_view(), name='personal_equipment_types_by_system'), + path('personal-equipment-types//', PersonalEquipmentTypeDetail.as_view()), + path('equipment-types/by-system//', EquipmentTypeList.as_view(), name='personal_equipment_types_by_system'), path('equipment-types//', EquipmentTypeDetail.as_view()), path('equipments/', EquipmentDetailList.as_view()), path('equipments//', EquipmentDetailDetail.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index 76d338d4..00e0983f 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -7,6 +7,34 @@ from .permissions import * from rest_framework import status +class PersonalEquipmentTypeCreate(generics.CreateAPIView): + queryset = PersonalEquipmentType.objects.all() + serializer_class = PersonalEquipmentTypeSerializer + permission_classes = [IsEquipmentTypeOwner, IsAuthenticated] + + def create(self, request, *args, **kwargs): + + if(IsEquipmentTypeOwner): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(place_owner=request.user.placeowner) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + +class PersonalEquipmentTypeList(generics.ListAPIView): + queryset = PersonalEquipmentType.objects.all() + serializer_class = PersonalEquipmentTypeSerializer + permission_classes = [IsAuthenticated] + + def get_queryset(self): + system_id = self.kwargs['system_id'] + return PersonalEquipmentType.objects.filter(system_id=system_id) + +class PersonalEquipmentTypeDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = PersonalEquipmentType.objects.all() + serializer_class = PersonalEquipmentTypeSerializer + permission_classes = [IsAuthenticated] + class EquipmentTypeList(generics.ListAPIView): queryset = EquipmentType.objects.all() serializer_class = EquipmentTypeSerializer From 1c43242df16a671c89975527e2e63f5893d130db Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 13 May 2024 17:21:48 -0300 Subject: [PATCH 105/351] =?UTF-8?q?Visualiza=C3=A7=C3=A3o=20das=20salas=20?= =?UTF-8?q?e=20organizando=20arquivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/core/feature/register/register.dart | 4 +- frontend/sige_ie/lib/core/ui/facilities.dart | 138 +++++++++++++++++- frontend/sige_ie/lib/home/ui/home.dart | 4 +- frontend/sige_ie/lib/maps/feature/maps.dart | 29 +++- .../lib/places/data/place_service.dart | 12 ++ .../{models => data}/user_request_model.dart | 0 .../{models => data}/user_response_model.dart | 0 .../{services => data}/user_service.dart | 4 +- .../sige_ie/lib/users/feature/profile.dart | 7 +- 9 files changed, 181 insertions(+), 17 deletions(-) rename frontend/sige_ie/lib/users/{models => data}/user_request_model.dart (100%) rename frontend/sige_ie/lib/users/{models => data}/user_response_model.dart (100%) rename frontend/sige_ie/lib/users/{services => data}/user_service.dart (95%) diff --git a/frontend/sige_ie/lib/core/feature/register/register.dart b/frontend/sige_ie/lib/core/feature/register/register.dart index 6b89b686..344abb74 100644 --- a/frontend/sige_ie/lib/core/feature/register/register.dart +++ b/frontend/sige_ie/lib/core/feature/register/register.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:sige_ie/users/models/user_request_model.dart'; -import 'package:sige_ie/users/services/user_service.dart'; +import 'package:sige_ie/users/data/user_request_model.dart'; +import 'package:sige_ie/users/data/user_service.dart'; class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index 7fdf777c..95ee9434 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/places/data/place_request_model.dart'; +import 'package:sige_ie/places/data/place_service.dart'; +import '../../config/app_styles.dart'; class FacilitiesPage extends StatefulWidget { @override @@ -6,12 +9,139 @@ class FacilitiesPage extends StatefulWidget { } class _FacilitiesPageState extends State { + late Future> _placesList; + + @override + void initState() { + super.initState(); + _placesList = PlaceService().fetchAllPlaces(); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('App de Navegação'), - automaticallyImplyLeading: false, - )); + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: const Color(0xff123c75), + elevation: 0, + ), + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 20), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Locais', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + const SizedBox( + height: 15, + ), + FutureBuilder>( + future: _placesList, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Erro: ${snapshot.error}')); + } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var place = snapshot.data![index]; + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), + ), + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + title: Text( + place.name, + style: const TextStyle( + color: AppColors.lightText, + fontWeight: FontWeight.bold), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: AppColors.lightText), + onPressed: () {}, + ), + IconButton( + icon: const Icon(Icons.delete, + color: AppColors.warn), + onPressed: () {}, + ), + IconButton( + icon: const Icon(Icons.description, + color: AppColors.lightText), + onPressed: () {}, + ), + ], + ), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(place.name), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text('Longitude: ${place.lon}'), + Text('Latitude: ${place.lat}'), + ], + ), + ), + actions: [ + TextButton( + child: Text('Fechar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, + ), + ); + }, + ); + } else { + return const Padding( + padding: EdgeInsets.all(10.0), + child: Text("Nenhum local encontrado."), + ); + } + }, + ), + const SizedBox( + height: 15, + ), + ], + ), + ), + ); } } diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 4a4eb9e4..71bfc473 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -3,8 +3,8 @@ import 'package:sige_ie/config/app_styles.dart'; import '../../users/feature/profile.dart'; import '../../core/ui/facilities.dart'; import '../../maps/feature/maps.dart'; -import 'package:sige_ie/users/models/user_response_model.dart'; -import 'package:sige_ie/users/services/user_service.dart'; +import 'package:sige_ie/users/data/user_response_model.dart'; +import 'package:sige_ie/users/data/user_service.dart'; class HomePage extends StatefulWidget { @override diff --git a/frontend/sige_ie/lib/maps/feature/maps.dart b/frontend/sige_ie/lib/maps/feature/maps.dart index efba6b49..3ec9a80c 100644 --- a/frontend/sige_ie/lib/maps/feature/maps.dart +++ b/frontend/sige_ie/lib/maps/feature/maps.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; class MapsPage extends StatefulWidget { @override @@ -10,8 +11,30 @@ class _MapsPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('App de Navegação'), - automaticallyImplyLeading: false, - )); + automaticallyImplyLeading: false, + backgroundColor: const Color(0xff123c75), + elevation: 0, + ), + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 20), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Mapa', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + ]))); } } diff --git a/frontend/sige_ie/lib/places/data/place_service.dart b/frontend/sige_ie/lib/places/data/place_service.dart index c51a4ebb..2056f2af 100644 --- a/frontend/sige_ie/lib/places/data/place_service.dart +++ b/frontend/sige_ie/lib/places/data/place_service.dart @@ -32,6 +32,18 @@ class PlaceService { } } + Future> fetchAllPlaces() async { + var url = Uri.parse(baseUrl); + var response = await client.get(url); + + if (response.statusCode == 200) { + List dataList = jsonDecode(response.body); + return dataList.map((data) => PlaceRequestModel.fromJson(data)).toList(); + } else { + throw Exception('Failed to load places'); + } + } + // Ainda não testado // GET Future fetchPlace(int placeId) async { diff --git a/frontend/sige_ie/lib/users/models/user_request_model.dart b/frontend/sige_ie/lib/users/data/user_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/users/models/user_request_model.dart rename to frontend/sige_ie/lib/users/data/user_request_model.dart diff --git a/frontend/sige_ie/lib/users/models/user_response_model.dart b/frontend/sige_ie/lib/users/data/user_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/users/models/user_response_model.dart rename to frontend/sige_ie/lib/users/data/user_response_model.dart diff --git a/frontend/sige_ie/lib/users/services/user_service.dart b/frontend/sige_ie/lib/users/data/user_service.dart similarity index 95% rename from frontend/sige_ie/lib/users/services/user_service.dart rename to frontend/sige_ie/lib/users/data/user_service.dart index d220bc82..4f77b25e 100644 --- a/frontend/sige_ie/lib/users/services/user_service.dart +++ b/frontend/sige_ie/lib/users/data/user_service.dart @@ -4,8 +4,8 @@ import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; -import 'package:sige_ie/users/models/user_request_model.dart'; -import 'package:sige_ie/users/models/user_response_model.dart'; +import 'package:sige_ie/users/data/user_request_model.dart'; +import 'package:sige_ie/users/data/user_response_model.dart'; class UserService { Future register(UserRequestModel userRequestModel) async { diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index bf7e86e0..c7be8a6f 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/data/auth_service.dart'; -import 'package:sige_ie/users/models/user_response_model.dart'; -import 'package:sige_ie/users/services/user_service.dart'; +import 'package:sige_ie/users/data/user_response_model.dart'; +import 'package:sige_ie/users/data/user_service.dart'; import 'package:sige_ie/config/app_styles.dart'; class ProfilePage extends StatefulWidget { @@ -38,7 +38,7 @@ class _ProfilePageState extends State { child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.symmetric(vertical: 10), + padding: const EdgeInsets.fromLTRB(10, 10, 10, 20), decoration: const BoxDecoration( color: AppColors.sigeIeBlue, borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), @@ -54,7 +54,6 @@ class _ProfilePageState extends State { color: Colors.white, ), ), - SizedBox(height: 10), // Espaço adicional abaixo do texto ], ), ), From af0fd47a2f9e0b3ebc09aecc9b9fb6a135777cd6 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 14 May 2024 10:33:25 -0300 Subject: [PATCH 106/351] Front: Deleta locais --- frontend/sige_ie/lib/core/ui/facilities.dart | 65 +++++++++++++++---- .../lib/places/data/place_response_model.dart | 35 ++++++++++ .../lib/places/data/place_service.dart | 16 ++--- 3 files changed, 95 insertions(+), 21 deletions(-) create mode 100644 frontend/sige_ie/lib/places/data/place_response_model.dart diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index 95ee9434..a8142b4d 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:sige_ie/places/data/place_request_model.dart'; +import 'package:sige_ie/places/data/place_response_model.dart'; import 'package:sige_ie/places/data/place_service.dart'; import '../../config/app_styles.dart'; @@ -9,12 +9,57 @@ class FacilitiesPage extends StatefulWidget { } class _FacilitiesPageState extends State { - late Future> _placesList; + late Future> _placesList; + final PlaceService _placeService = PlaceService(); @override void initState() { super.initState(); - _placesList = PlaceService().fetchAllPlaces(); + _placesList = _placeService.fetchAllPlaces(); + } + + void _confirmDelete(BuildContext context, PlaceResponseModel place) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Confirmação'), + content: + Text('Você realmente deseja excluir o local "${place.name}"?'), + actions: [ + TextButton( + child: Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('Excluir'), + onPressed: () async { + Navigator.of(context).pop(); + bool success = await _placeService.deletePlace(place.id); + if (success) { + setState(() { + _placesList = _placeService.fetchAllPlaces(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Local "${place.name}" excluído com sucesso')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Falha ao excluir o local "${place.name}"')), + ); + } + }, + ), + ], + ); + }, + ); } @override @@ -45,10 +90,8 @@ class _FacilitiesPageState extends State { color: Colors.white)), ), ), - const SizedBox( - height: 15, - ), - FutureBuilder>( + const SizedBox(height: 15), + FutureBuilder>( future: _placesList, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { @@ -89,7 +132,7 @@ class _FacilitiesPageState extends State { IconButton( icon: const Icon(Icons.delete, color: AppColors.warn), - onPressed: () {}, + onPressed: () => _confirmDelete(context, place), ), IconButton( icon: const Icon(Icons.description, @@ -114,7 +157,7 @@ class _FacilitiesPageState extends State { ), actions: [ TextButton( - child: Text('Fechar'), + child: const Text('Fechar'), onPressed: () { Navigator.of(context).pop(); }, @@ -136,9 +179,7 @@ class _FacilitiesPageState extends State { } }, ), - const SizedBox( - height: 15, - ), + const SizedBox(height: 15), ], ), ), diff --git a/frontend/sige_ie/lib/places/data/place_response_model.dart b/frontend/sige_ie/lib/places/data/place_response_model.dart new file mode 100644 index 00000000..5664d7df --- /dev/null +++ b/frontend/sige_ie/lib/places/data/place_response_model.dart @@ -0,0 +1,35 @@ +class PlaceResponseModel { + int id; + String name; + double lon; + double lat; + int placeOwner; + + PlaceResponseModel({ + required this.id, + required this.name, + required this.lon, + required this.lat, + required this.placeOwner, + }); + + factory PlaceResponseModel.fromJson(Map json) { + return PlaceResponseModel( + id: json['id'], + name: json['name'], + lon: json['lon'].toDouble(), + lat: json['lat'].toDouble(), + placeOwner: json['place_owner'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'lon': lon, + 'lat': lat, + 'place_owner': placeOwner, + }; + } +} diff --git a/frontend/sige_ie/lib/places/data/place_service.dart b/frontend/sige_ie/lib/places/data/place_service.dart index 2056f2af..e91640b3 100644 --- a/frontend/sige_ie/lib/places/data/place_service.dart +++ b/frontend/sige_ie/lib/places/data/place_service.dart @@ -4,6 +4,7 @@ import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/places/data/place_request_model.dart'; +import 'package:sige_ie/places/data/place_response_model.dart'; class PlaceService { final String baseUrl = 'http://10.0.2.2:8000/api/places/'; @@ -22,38 +23,35 @@ class PlaceService { ); if (response.statusCode == 201) { - // Se a criação foi bem-sucedida, extrai o ID do lugar criado do corpo da resposta Map responseData = jsonDecode(response.body); - int? placeId = - responseData['id']; // Assume que a resposta tem um campo 'id' - return placeId; + return responseData['id']; } else { return null; } } - Future> fetchAllPlaces() async { + // GET ALL + Future> fetchAllPlaces() async { var url = Uri.parse(baseUrl); var response = await client.get(url); if (response.statusCode == 200) { List dataList = jsonDecode(response.body); - return dataList.map((data) => PlaceRequestModel.fromJson(data)).toList(); + return dataList.map((data) => PlaceResponseModel.fromJson(data)).toList(); } else { throw Exception('Failed to load places'); } } - // Ainda não testado // GET - Future fetchPlace(int placeId) async { + Future fetchPlace(int placeId) async { var url = Uri.parse('$baseUrl$placeId/'); var response = await client.get(url); if (response.statusCode == 200) { var data = jsonDecode(response.body); - return PlaceRequestModel.fromJson(data); + return PlaceResponseModel.fromJson(data); } else { throw Exception('Failed to load place with ID $placeId'); } From 3fcec6fdc99033c41afa06c9d16fc3e618488162 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 14 May 2024 10:54:17 -0300 Subject: [PATCH 107/351] =?UTF-8?q?Aterar=20nome=20e=20localiza=C3=A7?= =?UTF-8?q?=C3=A3o=20dos=20locais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/core/ui/facilities.dart | 81 +++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index a8142b4d..62acbb5c 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/places/data/place_request_model.dart'; import 'package:sige_ie/places/data/place_response_model.dart'; import 'package:sige_ie/places/data/place_service.dart'; import '../../config/app_styles.dart'; @@ -62,6 +63,84 @@ class _FacilitiesPageState extends State { ); } + void _editPlace(BuildContext context, PlaceResponseModel place) { + final TextEditingController _nameController = + TextEditingController(text: place.name); + final TextEditingController _lonController = + TextEditingController(text: place.lon.toString()); + final TextEditingController _latController = + TextEditingController(text: place.lat.toString()); + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Editar Local'), + content: SingleChildScrollView( + child: Column( + children: [ + TextField( + controller: _nameController, + decoration: InputDecoration(labelText: 'Nome do Local'), + ), + TextField( + controller: _lonController, + decoration: InputDecoration(labelText: 'Longitude'), + keyboardType: TextInputType.number, + ), + TextField( + controller: _latController, + decoration: InputDecoration(labelText: 'Latitude'), + keyboardType: TextInputType.number, + ), + ], + ), + ), + actions: [ + TextButton( + child: Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('Salvar'), + onPressed: () async { + String newName = _nameController.text; + double? newLon = double.tryParse(_lonController.text); + double? newLat = double.tryParse(_latController.text); + Navigator.of(context).pop(); + + if (newName.isNotEmpty && newLon != null && newLat != null) { + PlaceRequestModel updatedPlace = PlaceRequestModel( + name: newName, + lon: newLon, + lat: newLat, + ); + bool success = + await _placeService.updatePlace(place.id, updatedPlace); + if (success) { + setState(() { + _placesList = _placeService.fetchAllPlaces(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Local atualizado para "$newName"')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Falha ao atualizar o local')), + ); + } + } + }, + ), + ], + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -127,7 +206,7 @@ class _FacilitiesPageState extends State { IconButton( icon: const Icon(Icons.edit, color: AppColors.lightText), - onPressed: () {}, + onPressed: () => _editPlace(context, place), ), IconButton( icon: const Icon(Icons.delete, From eff96037c6e65f89284e7fea0c5efe6c439d38df Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 14 May 2024 13:01:34 -0300 Subject: [PATCH 108/351] =?UTF-8?q?Visualizar=20=C3=A1reas=20em=20cada=20p?= =?UTF-8?q?lace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/areas/data/area_response_model.dart | 31 ++++++ .../sige_ie/lib/areas/data/area_service.dart | 14 +++ frontend/sige_ie/lib/core/ui/facilities.dart | 95 ++++++++++--------- 3 files changed, 93 insertions(+), 47 deletions(-) create mode 100644 frontend/sige_ie/lib/areas/data/area_response_model.dart diff --git a/frontend/sige_ie/lib/areas/data/area_response_model.dart b/frontend/sige_ie/lib/areas/data/area_response_model.dart new file mode 100644 index 00000000..28adcb04 --- /dev/null +++ b/frontend/sige_ie/lib/areas/data/area_response_model.dart @@ -0,0 +1,31 @@ +class AreaResponseModel { + int id; + String name; + int? floor; + int place; + + AreaResponseModel({ + required this.id, + required this.name, + this.floor, + required this.place, + }); + + factory AreaResponseModel.fromJson(Map json) { + return AreaResponseModel( + id: json['id'], + name: json['name'], + floor: json['floor'], + place: json['place'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'floor': floor, + 'place': place, + }; + } +} diff --git a/frontend/sige_ie/lib/areas/data/area_service.dart b/frontend/sige_ie/lib/areas/data/area_service.dart index 504104bd..d4126716 100644 --- a/frontend/sige_ie/lib/areas/data/area_service.dart +++ b/frontend/sige_ie/lib/areas/data/area_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/areas/data/area_response_model.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/areas/data/area_request_model.dart'; @@ -25,6 +26,19 @@ class AreaService { return response.statusCode == 201; } + // Fetch all areas for a specific place + Future> fetchAreasByPlaceId(int placeId) async { + var url = Uri.parse('http://10.0.2.2:8000/api/places/$placeId/areas/'); + var response = await client.get(url); + + if (response.statusCode == 200) { + List dataList = jsonDecode(response.body); + return dataList.map((data) => AreaResponseModel.fromJson(data)).toList(); + } else { + throw Exception('Failed to load areas for place $placeId'); + } + } + // Ainda não testado // GET Future fetchArea(int areaId) async { diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index 62acbb5c..0bf140f0 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/areas/data/area_response_model.dart'; import 'package:sige_ie/places/data/place_request_model.dart'; import 'package:sige_ie/places/data/place_response_model.dart'; import 'package:sige_ie/places/data/place_service.dart'; import '../../config/app_styles.dart'; +import '../../areas/data/area_service.dart'; class FacilitiesPage extends StatefulWidget { @override @@ -12,6 +14,7 @@ class FacilitiesPage extends StatefulWidget { class _FacilitiesPageState extends State { late Future> _placesList; final PlaceService _placeService = PlaceService(); + final AreaService _areaService = AreaService(); @override void initState() { @@ -19,6 +22,15 @@ class _FacilitiesPageState extends State { _placesList = _placeService.fetchAllPlaces(); } + Future> _loadAreasForPlace(int placeId) async { + try { + return await _areaService.fetchAreasByPlaceId(placeId); + } catch (e) { + print('Erro ao carregar áreas: $e'); + return []; // Retorna uma lista vazia em caso de erro + } + } + void _confirmDelete(BuildContext context, PlaceResponseModel place) { showDialog( context: context, @@ -35,28 +47,27 @@ class _FacilitiesPageState extends State { }, ), TextButton( - child: Text('Excluir'), - onPressed: () async { - Navigator.of(context).pop(); - bool success = await _placeService.deletePlace(place.id); - if (success) { - setState(() { - _placesList = _placeService.fetchAllPlaces(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Local "${place.name}" excluído com sucesso')), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Falha ao excluir o local "${place.name}"')), - ); - } - }, - ), + child: Text('Excluir'), + onPressed: () async { + Navigator.of(context).pop(); + bool success = await _placeService.deletePlace(place.id); + if (success && mounted) { + setState(() { + _placesList = _placeService.fetchAllPlaces(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Local "${place.name}" excluído com sucesso')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Falha ao excluir o local "${place.name}"')), + ); + } + }), ], ); }, @@ -220,31 +231,21 @@ class _FacilitiesPageState extends State { ), ], ), - onTap: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text(place.name), - content: SingleChildScrollView( - child: ListBody( - children: [ - Text('Longitude: ${place.lon}'), - Text('Latitude: ${place.lat}'), - ], - ), - ), - actions: [ - TextButton( - child: const Text('Fechar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); + onTap: () async { + List areasForPlace = + await _loadAreasForPlace(place.id); + showModalBottomSheet( + context: context, + builder: (context) { + return ListView.builder( + itemCount: areasForPlace.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(areasForPlace[index].name), + ); + }, + ); + }); }, ), ); From 6568eec523aa231c16605657a9b8e26e1872f0ae Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 14 May 2024 15:20:11 -0300 Subject: [PATCH 109/351] =?UTF-8?q?Segmenta=C3=A7=C3=A3o=20de=20=C3=A1reas?= =?UTF-8?q?=20por=20andares,=20edi=C3=A7=C3=A3o=20e=20exclus=C3=A3o=20de?= =?UTF-8?q?=20=C3=A1reas=20visualmente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/core/ui/facilities.dart | 159 ++++++++++++++----- 1 file changed, 118 insertions(+), 41 deletions(-) diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index 0bf140f0..0c1322b8 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -31,6 +31,37 @@ class _FacilitiesPageState extends State { } } + Map> _groupAreasByFloor( + List areas) { + Map> groupedAreas = {}; + for (var area in areas) { + if (groupedAreas[area.floor ?? 0] == null) { + groupedAreas[area.floor ?? 0] = []; + } + groupedAreas[area.floor ?? 0]!.add(area); + } + return groupedAreas; + } + + Future _showAreasForPlace(BuildContext context, int placeId) async { + List areasForPlace = await _loadAreasForPlace(placeId); + Map> groupedAreas = + _groupAreasByFloor(areasForPlace); + + showModalBottomSheet( + context: context, + builder: (context) { + return FloorAreaWidget( + groupedAreas: groupedAreas, + onAddFloor: () {}, + onEditArea: (int areaId) {}, + onDeleteArea: (int areaId) {}, + onTapArea: (int areaId) {}, + ); + }, + ); + } + void _confirmDelete(BuildContext context, PlaceResponseModel place) { showDialog( context: context, @@ -47,27 +78,28 @@ class _FacilitiesPageState extends State { }, ), TextButton( - child: Text('Excluir'), - onPressed: () async { - Navigator.of(context).pop(); - bool success = await _placeService.deletePlace(place.id); - if (success && mounted) { - setState(() { - _placesList = _placeService.fetchAllPlaces(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Local "${place.name}" excluído com sucesso')), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Falha ao excluir o local "${place.name}"')), - ); - } - }), + child: Text('Excluir'), + onPressed: () async { + Navigator.of(context).pop(); + bool success = await _placeService.deletePlace(place.id); + if (success && mounted) { + setState(() { + _placesList = _placeService.fetchAllPlaces(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Local "${place.name}" excluído com sucesso')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Falha ao excluir o local "${place.name}"')), + ); + } + }, + ), ], ); }, @@ -215,13 +247,13 @@ class _FacilitiesPageState extends State { mainAxisSize: MainAxisSize.min, children: [ IconButton( - icon: const Icon(Icons.edit, - color: AppColors.lightText), + icon: + const Icon(Icons.edit, color: Colors.blue), onPressed: () => _editPlace(context, place), ), IconButton( - icon: const Icon(Icons.delete, - color: AppColors.warn), + icon: + const Icon(Icons.delete, color: Colors.red), onPressed: () => _confirmDelete(context, place), ), IconButton( @@ -231,22 +263,7 @@ class _FacilitiesPageState extends State { ), ], ), - onTap: () async { - List areasForPlace = - await _loadAreasForPlace(place.id); - showModalBottomSheet( - context: context, - builder: (context) { - return ListView.builder( - itemCount: areasForPlace.length, - itemBuilder: (context, index) { - return ListTile( - title: Text(areasForPlace[index].name), - ); - }, - ); - }); - }, + onTap: () => _showAreasForPlace(context, place.id), ), ); }, @@ -266,3 +283,63 @@ class _FacilitiesPageState extends State { ); } } + +class FloorAreaWidget extends StatelessWidget { + final Map> groupedAreas; + final VoidCallback onAddFloor; + final Function(int) onEditArea; + final Function(int) onDeleteArea; + final Function(int) onTapArea; + + FloorAreaWidget({ + required this.groupedAreas, + required this.onAddFloor, + required this.onEditArea, + required this.onDeleteArea, + required this.onTapArea, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: ListView( + children: groupedAreas.entries.map((entry) { + int floor = entry.key; + List areas = entry.value; + String floorName = floor == 0 ? "Térreo" : "$floor° andar"; + return ExpansionTile( + title: Text(floorName), + children: areas.map((area) { + return ListTile( + title: Text(area.name), + onTap: () => onTapArea(area.id), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.edit, color: Colors.blue), + onPressed: () => onEditArea(area.id), + ), + IconButton( + icon: Icon(Icons.delete, color: Colors.red), + onPressed: () => onDeleteArea(area.id), + ), + ], + ), + ); + }).toList(), + ); + }).toList(), + ), + ), + ListTile( + leading: Icon(Icons.add), + title: Text('Adicionar andar'), + onTap: onAddFloor, + ), + ], + ); + } +} From 8c3c543174932d3c6bdf8488dea03ee200c1fe0e Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 14 May 2024 17:33:41 -0300 Subject: [PATCH 110/351] =?UTF-8?q?Editar=20e=20excluir=20=C3=A1reas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sige_ie/lib/areas/data/area_service.dart | 11 +- frontend/sige_ie/lib/core/ui/facilities.dart | 208 ++++++++++++++++-- 2 files changed, 192 insertions(+), 27 deletions(-) diff --git a/frontend/sige_ie/lib/areas/data/area_service.dart b/frontend/sige_ie/lib/areas/data/area_service.dart index d4126716..27ca5049 100644 --- a/frontend/sige_ie/lib/areas/data/area_service.dart +++ b/frontend/sige_ie/lib/areas/data/area_service.dart @@ -41,14 +41,13 @@ class AreaService { // Ainda não testado // GET - Future fetchArea(int areaId) async { + Future fetchArea(int areaId) async { var url = Uri.parse('$baseUrl$areaId/'); - var response = await client.get(url); if (response.statusCode == 200) { var data = jsonDecode(response.body); - return AreaRequestModel.fromJson(data); + return AreaResponseModel.fromJson(data); } else { throw Exception('Failed to load area with ID $areaId'); } @@ -58,12 +57,18 @@ class AreaService { Future updateArea(int areaId, AreaRequestModel areaRequestModel) async { var url = Uri.parse('$baseUrl$areaId/'); + print('Sending PUT request to $url'); + print('Request body: ${jsonEncode(areaRequestModel.toJson())}'); + var response = await client.put( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode(areaRequestModel.toJson()), ); + print('Response status: ${response.statusCode}'); + print('Response body: ${response.body}'); + return response.statusCode == 200; } diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index 0c1322b8..d2902572 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/areas/data/area_request_model.dart'; import 'package:sige_ie/areas/data/area_response_model.dart'; import 'package:sige_ie/places/data/place_request_model.dart'; import 'package:sige_ie/places/data/place_response_model.dart'; @@ -44,22 +45,181 @@ class _FacilitiesPageState extends State { } Future _showAreasForPlace(BuildContext context, int placeId) async { - List areasForPlace = await _loadAreasForPlace(placeId); - Map> groupedAreas = - _groupAreasByFloor(areasForPlace); + try { + List areasForPlace = await _loadAreasForPlace(placeId); + Map> groupedAreas = + _groupAreasByFloor(areasForPlace); - showModalBottomSheet( - context: context, - builder: (context) { - return FloorAreaWidget( - groupedAreas: groupedAreas, - onAddFloor: () {}, - onEditArea: (int areaId) {}, - onDeleteArea: (int areaId) {}, - onTapArea: (int areaId) {}, + showModalBottomSheet( + context: context, + builder: (context) { + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return FloorAreaWidget( + groupedAreas: groupedAreas, + onAddFloor: () {}, + onEditArea: (int areaId) async { + try { + AreaResponseModel area = + await _areaService.fetchArea(areaId); + TextEditingController nameController = + TextEditingController(text: area.name); + + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Editar Área'), + content: TextField( + controller: nameController, + decoration: const InputDecoration( + labelText: 'Nome da Área'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () async { + Navigator.of(context).pop(); + if (area.floor != null && area.place != null) { + AreaRequestModel updatedArea = + AreaRequestModel( + name: nameController.text, + floor: area.floor!, + place: area.place!, + ); + + bool success = await _areaService.updateArea( + areaId, updatedArea); + if (success) { + if (context.mounted) { + setState(() { + groupedAreas[area.floor ?? -1] = + groupedAreas[area.floor ?? -1]! + .map((a) => a.id == areaId + ? AreaResponseModel( + id: area.id, + name: updatedArea.name, + floor: + updatedArea.floor ?? + -1, + place: + updatedArea.place ?? + -1, + ) + : a) + .toList(); + }); + if (context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Área atualizada com sucesso')), + ); + } + } + } else { + if (context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Falha ao atualizar a área')), + ); + } + } + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Dados inválidos para a área')), + ); + } + } + }, + ), + ], + ); + }, + ); + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Erro ao editar a área: $e')), + ); + } + } + }, + onDeleteArea: (int areaId) async { + bool confirmDelete = await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: const Text('Confirmar Exclusão'), + content: + const Text('Realmente deseja excluir esta área?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(dialogContext).pop(false); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + Navigator.of(dialogContext).pop(true); + }, + ), + ], + ); + }, + ); + + if (confirmDelete) { + bool success = await _areaService.deleteArea(areaId); + if (success) { + if (context.mounted) { + setState(() { + groupedAreas.forEach((key, value) { + value.removeWhere((a) => a.id == areaId); + }); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Área excluída com sucesso')), + ); + } + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Erro ao excluir a área')), + ); + } + } + } + }, + onTapArea: (int areaId) {}, + ); + }, + ); + }, + ); + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Erro ao carregar as áreas: $e')), ); - }, - ); + } + } } void _confirmDelete(BuildContext context, PlaceResponseModel place) { @@ -67,18 +227,18 @@ class _FacilitiesPageState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Confirmação'), + title: const Text('Confirmação'), content: Text('Você realmente deseja excluir o local "${place.name}"?'), actions: [ TextButton( - child: Text('Cancelar'), + child: const Text('Cancelar'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( - child: Text('Excluir'), + child: const Text('Excluir'), onPressed: () async { Navigator.of(context).pop(); bool success = await _placeService.deletePlace(place.id); @@ -118,22 +278,22 @@ class _FacilitiesPageState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Editar Local'), + title: const Text('Editar Local'), content: SingleChildScrollView( child: Column( children: [ TextField( controller: _nameController, - decoration: InputDecoration(labelText: 'Nome do Local'), + decoration: const InputDecoration(labelText: 'Nome do Local'), ), TextField( controller: _lonController, - decoration: InputDecoration(labelText: 'Longitude'), + decoration: const InputDecoration(labelText: 'Longitude'), keyboardType: TextInputType.number, ), TextField( controller: _latController, - decoration: InputDecoration(labelText: 'Latitude'), + decoration: const InputDecoration(labelText: 'Latitude'), keyboardType: TextInputType.number, ), ], @@ -141,13 +301,13 @@ class _FacilitiesPageState extends State { ), actions: [ TextButton( - child: Text('Cancelar'), + child: const Text('Cancelar'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( - child: Text('Salvar'), + child: const Text('Salvar'), onPressed: () async { String newName = _nameController.text; double? newLon = double.tryParse(_lonController.text); @@ -336,7 +496,7 @@ class FloorAreaWidget extends StatelessWidget { ), ListTile( leading: Icon(Icons.add), - title: Text('Adicionar andar'), + title: Text('Adicionar andar ou sala'), onTap: onAddFloor, ), ], From 1ebdee00ef2ed185065287c07745ffce29b4538d Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 14 May 2024 17:42:40 -0300 Subject: [PATCH 111/351] =?UTF-8?q?Arruma=20bot=C3=B5es=20de=20entrada=20e?= =?UTF-8?q?=20saida=20da=20p=C3=A1gina?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires Rocha Co-authored-by: Kauan Jose --- .../lib/areas/feature/register/new_area.dart | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/frontend/sige_ie/lib/areas/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart index fab7c2b1..adc089ed 100644 --- a/frontend/sige_ie/lib/areas/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -135,6 +135,26 @@ class _AreaLocationState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.lightText, + backgroundColor: AppColors.warn, + minimumSize: Size(150, 50), + textStyle: TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/homeScreen', + ); + }, + child: Text('VOLTAR'), + ), ElevatedButton( style: ElevatedButton.styleFrom( foregroundColor: AppColors.sigeIeBlue, @@ -206,26 +226,6 @@ class _AreaLocationState extends State { }, child: Text('CONTINUAR'), ), - ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: AppColors.lightText, - backgroundColor: AppColors.warn, - minimumSize: Size(150, 50), - textStyle: TextStyle( - fontWeight: FontWeight.bold, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - onPressed: () { - Navigator.pushReplacementNamed( - context, - '/homeScreen', - ); - }, - child: Text('ENCERRAR'), - ), ], ), ], From c0f6ade7cc62961ac93b37b59e325b4ef22b0407 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 14 May 2024 17:55:43 -0300 Subject: [PATCH 112/351] =?UTF-8?q?Adiciona=20confirma=C3=A7=C3=A3o=20dos?= =?UTF-8?q?=20campos=20inseridos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires Rocha Co-authored-by: Kauan Jose --- .../feature/manage/addEquipmentScreen.dart | 435 ++++++++++-------- 1 file changed, 249 insertions(+), 186 deletions(-) diff --git a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart index 6f1cb477..6d9d5812 100644 --- a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/core/feature/manage/equipment_manager.dart'; import 'dart:io'; @@ -68,6 +69,72 @@ class _AddEquipmentScreenState extends State { } } + void _showSavedOptionsDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Opções Salvas'), + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Detalhes do Equipamento:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + if (_selectedType != null) Text('Tipo: $_selectedType'), + if (_equipmentNameController.text.isNotEmpty) + Text('Nome: ${_equipmentNameController.text}'), + if (_equipmentQuantityController.text.isNotEmpty) + Text('Quantidade: ${_equipmentQuantityController.text}'), + if (_selectedLocation != null) + Text('Localização: $_selectedLocation'), + const SizedBox(height: 10), + const Text( + 'Imagens:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.pushNamed(context, '/systemLocation', arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }); + }, + ), + ], + ); + }, + ); + } + @override void initState() { super.initState(); @@ -78,208 +145,204 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { return WillPopScope( - onWillPop: () async { - _equipmentNameController.clear(); - _equipmentQuantityController.clear(); - categoryImagesMap[widget.categoryNumber]?.clear(); - return true; - }, - child: Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - foregroundColor: Colors.white, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - _equipmentNameController.clear(); - _equipmentQuantityController.clear(); - categoryImagesMap[widget.categoryNumber]?.clear(); - Navigator.of(context).pop(); - }, - ), + onWillPop: () async { + _equipmentNameController.clear(); + _equipmentQuantityController.clear(); + categoryImagesMap[widget.categoryNumber]?.clear(); + return true; + }, + child: Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + _equipmentNameController.clear(); + _equipmentQuantityController.clear(); + categoryImagesMap[widget.categoryNumber]?.clear(); + Navigator.of(context).pop(); + }, ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: const Center( - child: Text('Equipamentos na sala', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), ), - Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Categoria: ', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), - ), - Expanded( - child: Text( - EquipmentManager - .categoryMap[widget.categoryNumber]!, - style: const TextStyle( - fontSize: 18, - color: AppColors.sigeIeBlue, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - const SizedBox(height: 20), - const Text('Equipamentos', - style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildDropdown( - items: equipmentTypes, - value: _selectedType, - onChanged: (newValue) { - setState(() { - _selectedType = newValue; - }); - }, - ), - const SizedBox(height: 30), - const Text('Nome do equipamento', + child: const Center( + child: Text('Equipamentos na sala', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Categoria: ', style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), + fontSize: 18, fontWeight: FontWeight.bold), ), - child: TextField( - controller: _equipmentNameController, - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10, vertical: 15), + Expanded( + child: Text( + EquipmentManager + .categoryMap[widget.categoryNumber]!, + style: const TextStyle( + fontSize: 18, + color: AppColors.sigeIeBlue, + fontWeight: FontWeight.w500), ), ), + ], + ), + const SizedBox(height: 20), + const Text('Tipo do Equipamento', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + }); + }, + ), + const SizedBox(height: 30), + const Text('Nome do equipamento', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), ), - const SizedBox(height: 30), - const Text('Quantidade', - style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: TextField( - controller: _equipmentQuantityController, - keyboardType: TextInputType.number, - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 10, vertical: 15), - ), + child: TextField( + controller: _equipmentNameController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, vertical: 15), ), ), - const SizedBox(height: 30), - const Text('Localização (Interno ou Externo)', - style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildDropdown( - items: const ['Interno', 'Externo'], - value: _selectedLocation, - onChanged: (newValue) { - setState(() { - _selectedLocation = newValue; - }); - }, + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), ), - const SizedBox(height: 15), - IconButton( - icon: const Icon(Icons.camera_alt), - onPressed: _pickImage, + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, vertical: 15), + ), ), - Wrap( - children: _images.map((imageData) { - return Stack( - alignment: Alignment.topRight, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - ), - IconButton( - icon: const Icon(Icons.remove_circle, - color: AppColors.warn), - onPressed: () { - setState(() { - _images.removeWhere((element) => - element.id == imageData.id); - }); - }, + ), + const SizedBox(height: 30), + const Text('Localização (Interno ou Externo)', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildDropdown( + items: const ['Interno', 'Externo'], + value: _selectedLocation, + onChanged: (newValue) { + setState(() { + _selectedLocation = newValue; + }); + }, + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, ), - ], - ); - }).toList(), - ), - const SizedBox(height: 15), - Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all( - AppColors.sigeIeBlue), - minimumSize: MaterialStateProperty.all( - const Size(185, 55)), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ))), - onPressed: () { - Navigator.pushNamed(context, '/systemLocation', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); }); - }, - child: const Text( - 'ADICIONAR EQUIPAMENTO', - style: TextStyle( - fontSize: 17, fontWeight: FontWeight.bold), - ), + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showSavedOptionsDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), ), - ) - ], - ), + ), + ) + ], ), - ], - ), + ), + ], ), - )); + ), + ), + ); } Widget _buildDropdown({ From 2a9b198a440a28114a922016bcc366c8a271581d Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 15 May 2024 12:44:00 -0300 Subject: [PATCH 113/351] =?UTF-8?q?Ordenar=20andares=20em=20ordem=20cresce?= =?UTF-8?q?nte,=20atualiza=C3=A7=C3=A3o=20imediata=20quando=20muda=20o=20n?= =?UTF-8?q?ome=20da=20=C3=A1rea=20e=20nome=20da=20place?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/core/ui/facilities.dart | 101 ++++++++++--------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index d2902572..70a8801b 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -54,7 +54,7 @@ class _FacilitiesPageState extends State { context: context, builder: (context) { return StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { + builder: (BuildContext context, StateSetter modalSetState) { return FloorAreaWidget( groupedAreas: groupedAreas, onAddFloor: () {}, @@ -86,43 +86,41 @@ class _FacilitiesPageState extends State { child: const Text('Salvar'), onPressed: () async { Navigator.of(context).pop(); - if (area.floor != null && area.place != null) { + if (area.floor != null) { AreaRequestModel updatedArea = AreaRequestModel( name: nameController.text, floor: area.floor!, - place: area.place!, + place: area.place, ); bool success = await _areaService.updateArea( areaId, updatedArea); if (success) { + modalSetState(() { + groupedAreas[area.floor ?? -1] = + groupedAreas[area.floor ?? -1]! + .map((a) => a.id == areaId + ? AreaResponseModel( + id: area.id, + name: updatedArea.name, + floor: + updatedArea.floor ?? + -1, + place: + updatedArea.place ?? + -1, + ) + : a) + .toList(); + }); if (context.mounted) { - setState(() { - groupedAreas[area.floor ?? -1] = - groupedAreas[area.floor ?? -1]! - .map((a) => a.id == areaId - ? AreaResponseModel( - id: area.id, - name: updatedArea.name, - floor: - updatedArea.floor ?? - -1, - place: - updatedArea.place ?? - -1, - ) - : a) - .toList(); - }); - if (context.mounted) { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Área atualizada com sucesso')), - ); - } + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Área atualizada com sucesso')), + ); } } else { if (context.mounted) { @@ -186,17 +184,15 @@ class _FacilitiesPageState extends State { if (confirmDelete) { bool success = await _areaService.deleteArea(areaId); if (success) { - if (context.mounted) { - setState(() { - groupedAreas.forEach((key, value) { - value.removeWhere((a) => a.id == areaId); - }); + modalSetState(() { + groupedAreas.forEach((key, value) { + value.removeWhere((a) => a.id == areaId); }); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Área excluída com sucesso')), - ); - } + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Área excluída com sucesso')), + ); } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -322,18 +318,23 @@ class _FacilitiesPageState extends State { ); bool success = await _placeService.updatePlace(place.id, updatedPlace); - if (success) { + if (success && mounted) { setState(() { _placesList = _placeService.fetchAllPlaces(); }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Local atualizado para "$newName"')), - ); + if (mounted) { + // Verifique novamente antes de usar o contexto + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Local atualizado para "$newName"')), + ); + } } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Falha ao atualizar o local')), - ); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Falha ao atualizar o local')), + ); + } } } }, @@ -461,13 +462,15 @@ class FloorAreaWidget extends StatelessWidget { @override Widget build(BuildContext context) { + var sortedKeys = groupedAreas.keys.toList() + ..sort(); // Ordena os andares em ordem crescente + return Column( children: [ Expanded( child: ListView( - children: groupedAreas.entries.map((entry) { - int floor = entry.key; - List areas = entry.value; + children: sortedKeys.map((floor) { + List areas = groupedAreas[floor]!; String floorName = floor == 0 ? "Térreo" : "$floor° andar"; return ExpansionTile( title: Text(floorName), From 9c13698324aacdb22bcfac897d3cfcd0d695e0f0 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 15 May 2024 13:49:50 -0300 Subject: [PATCH 114/351] Adicionar sala em um place, editar as salas e exclui-las --- .../sige_ie/lib/areas/data/area_service.dart | 13 +- .../lib/areas/feature/register/new_area.dart | 28 +- frontend/sige_ie/lib/core/ui/facilities.dart | 280 ++++++++++-------- 3 files changed, 175 insertions(+), 146 deletions(-) diff --git a/frontend/sige_ie/lib/areas/data/area_service.dart b/frontend/sige_ie/lib/areas/data/area_service.dart index 27ca5049..ade0b98d 100644 --- a/frontend/sige_ie/lib/areas/data/area_service.dart +++ b/frontend/sige_ie/lib/areas/data/area_service.dart @@ -14,16 +14,23 @@ class AreaService { final String baseUrl = 'http://10.0.2.2:8000/api/areas/'; // POST - Future createArea(AreaRequestModel areaRequestModel) async { +// POST + Future createArea( + AreaRequestModel areaRequestModel) async { var url = Uri.parse(baseUrl); - var response = await client.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode(areaRequestModel.toJson()), ); - return response.statusCode == 201; + if (response.statusCode == 201) { + // Assumindo que a resposta inclua o id da área criada. + var data = jsonDecode(response.body); + return AreaResponseModel.fromJson(data); + } else { + throw Exception('Failed to create area'); + } } // Fetch all areas for a specific place diff --git a/frontend/sige_ie/lib/areas/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart index adc089ed..72045b6a 100644 --- a/frontend/sige_ie/lib/areas/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/areas/data/area_response_model.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/areas/data/area_request_model.dart'; import 'package:sige_ie/areas/data/area_service.dart'; @@ -153,7 +154,7 @@ class _AreaLocationState extends State { '/homeScreen', ); }, - child: Text('VOLTAR'), + child: Text('ENCERRAR'), ), ElevatedButton( style: ElevatedButton.styleFrom( @@ -171,28 +172,29 @@ class _AreaLocationState extends State { if (selectedFloor != null && areaController.text.isNotEmpty) { AreaService areaService = AreaService(); - bool result = - await areaService.createArea(AreaRequestModel( - name: areaController.text, - floor: selectedFloor, - place: widget.localId, - )); - if (result) { + try { + AreaResponseModel newArea = + await areaService.createArea(AreaRequestModel( + name: areaController.text, + floor: selectedFloor, + place: widget.localId, + )); print( - 'Sala Registrada: ${areaController.text} no $selectedFloor° andar'); + 'Sala Registrada: ${newArea.name} no ${newArea.floor}° andar'); Navigator.pushNamed(context, '/systemLocation', arguments: { - 'areaName': areaController.text, + 'areaName': newArea.name, 'localName': widget.localName, - 'localId': widget.localId + 'localId': widget.localId, + 'areaId': newArea.id, }); - } else { + } catch (e) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('Erro'), - content: Text("Falha ao criar sala."), + content: Text("Falha ao criar sala: $e"), actions: [ TextButton( child: Text("OK"), diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index 70a8801b..720c6bd3 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -44,9 +44,11 @@ class _FacilitiesPageState extends State { return groupedAreas; } - Future _showAreasForPlace(BuildContext context, int placeId) async { + Future _showAreasForPlace( + BuildContext context, PlaceResponseModel place) async { try { - List areasForPlace = await _loadAreasForPlace(placeId); + List areasForPlace = + await _areaService.fetchAreasByPlaceId(place.id); Map> groupedAreas = _groupAreasByFloor(areasForPlace); @@ -56,71 +58,89 @@ class _FacilitiesPageState extends State { return StatefulBuilder( builder: (BuildContext context, StateSetter modalSetState) { return FloorAreaWidget( - groupedAreas: groupedAreas, - onAddFloor: () {}, - onEditArea: (int areaId) async { - try { - AreaResponseModel area = - await _areaService.fetchArea(areaId); - TextEditingController nameController = - TextEditingController(text: area.name); + groupedAreas: groupedAreas, + placeName: place.name, + placeId: place.id, + onAddFloor: () { + Navigator.of(context) + .pushNamed('/arealocation', arguments: { + 'placeName': place.name, + 'placeId': place.id, + }); + }, + onEditArea: (int areaId) async { + try { + AreaResponseModel area = + await _areaService.fetchArea(areaId); + TextEditingController nameController = + TextEditingController(text: area.name); - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('Editar Área'), - content: TextField( - controller: nameController, - decoration: const InputDecoration( - labelText: 'Nome da Área'), - ), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Editar Área'), + content: TextField( + controller: nameController, + decoration: const InputDecoration( + labelText: 'Nome da Área'), ), - TextButton( - child: const Text('Salvar'), - onPressed: () async { - Navigator.of(context).pop(); - if (area.floor != null) { - AreaRequestModel updatedArea = - AreaRequestModel( - name: nameController.text, - floor: area.floor!, - place: area.place, - ); + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () async { + Navigator.of(context).pop(); + if (area.floor != null) { + AreaRequestModel updatedArea = + AreaRequestModel( + name: nameController.text, + floor: area.floor!, + place: area.place, + ); - bool success = await _areaService.updateArea( - areaId, updatedArea); - if (success) { - modalSetState(() { - groupedAreas[area.floor ?? -1] = - groupedAreas[area.floor ?? -1]! - .map((a) => a.id == areaId - ? AreaResponseModel( - id: area.id, - name: updatedArea.name, - floor: - updatedArea.floor ?? - -1, - place: - updatedArea.place ?? - -1, - ) - : a) - .toList(); - }); - if (context.mounted) { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Área atualizada com sucesso')), - ); + bool success = await _areaService + .updateArea(areaId, updatedArea); + if (success) { + modalSetState(() { + groupedAreas[area.floor ?? -1] = + groupedAreas[area.floor ?? -1]! + .map((a) => a.id == areaId + ? AreaResponseModel( + id: area.id, + name: updatedArea.name, + floor: + updatedArea.floor ?? + -1, + place: + updatedArea.place ?? + -1, + ) + : a) + .toList(); + }); + if (context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Área atualizada com sucesso')), + ); + } + } else { + if (context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Falha ao atualizar a área')), + ); + } } } else { if (context.mounted) { @@ -128,83 +148,82 @@ class _FacilitiesPageState extends State { .showSnackBar( const SnackBar( content: Text( - 'Falha ao atualizar a área')), + 'Dados inválidos para a área')), ); } } - } else { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Dados inválidos para a área')), - ); - } - } + }, + ), + ], + ); + }, + ); + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Erro ao editar a área: $e')), + ); + } + } + }, + onDeleteArea: (int areaId) async { + bool confirmDelete = await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: const Text('Confirmar Exclusão'), + content: + const Text('Realmente deseja excluir esta área?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(dialogContext).pop(false); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + Navigator.of(dialogContext).pop(true); }, ), ], ); }, ); - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erro ao editar a área: $e')), - ); - } - } - }, - onDeleteArea: (int areaId) async { - bool confirmDelete = await showDialog( - context: context, - builder: (BuildContext dialogContext) { - return AlertDialog( - title: const Text('Confirmar Exclusão'), - content: - const Text('Realmente deseja excluir esta área?'), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(dialogContext).pop(false); - }, - ), - TextButton( - child: const Text('Excluir'), - onPressed: () { - Navigator.of(dialogContext).pop(true); - }, - ), - ], - ); - }, - ); - if (confirmDelete) { - bool success = await _areaService.deleteArea(areaId); - if (success) { - modalSetState(() { - groupedAreas.forEach((key, value) { - value.removeWhere((a) => a.id == areaId); + if (confirmDelete) { + bool success = await _areaService.deleteArea(areaId); + if (success) { + modalSetState(() { + groupedAreas.forEach((key, value) { + value.removeWhere((a) => a.id == areaId); + }); }); - }); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Área excluída com sucesso')), - ); - } else { - if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Erro ao excluir a área')), + content: Text('Área excluída com sucesso')), ); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Erro ao excluir a área')), + ); + } } } - } - }, - onTapArea: (int areaId) {}, - ); + }, + onTapArea: (int areaId) async { + AreaResponseModel area = + await _areaService.fetchArea(areaId); + Navigator.pushNamed(context, '/systemLocation', arguments: { + 'areaName': area.name, + 'localName': place.name, + 'localId': place.id, + 'areaId': area.id, + }); + }); }, ); }, @@ -323,7 +342,6 @@ class _FacilitiesPageState extends State { _placesList = _placeService.fetchAllPlaces(); }); if (mounted) { - // Verifique novamente antes de usar o contexto ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Local atualizado para "$newName"')), @@ -424,7 +442,7 @@ class _FacilitiesPageState extends State { ), ], ), - onTap: () => _showAreasForPlace(context, place.id), + onTap: () => _showAreasForPlace(context, place), ), ); }, @@ -447,6 +465,8 @@ class _FacilitiesPageState extends State { class FloorAreaWidget extends StatelessWidget { final Map> groupedAreas; + final String placeName; + final int placeId; final VoidCallback onAddFloor; final Function(int) onEditArea; final Function(int) onDeleteArea; @@ -454,6 +474,8 @@ class FloorAreaWidget extends StatelessWidget { FloorAreaWidget({ required this.groupedAreas, + required this.placeName, + required this.placeId, required this.onAddFloor, required this.onEditArea, required this.onDeleteArea, @@ -462,9 +484,7 @@ class FloorAreaWidget extends StatelessWidget { @override Widget build(BuildContext context) { - var sortedKeys = groupedAreas.keys.toList() - ..sort(); // Ordena os andares em ordem crescente - + var sortedKeys = groupedAreas.keys.toList()..sort(); return Column( children: [ Expanded( From 70560a344d53e9241e18d220cae5f72d5957475f Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 15 May 2024 14:55:50 -0300 Subject: [PATCH 115/351] =?UTF-8?q?Corre=C3=A7=C3=A3o=20na=20l=C3=B3gica?= =?UTF-8?q?=20de=20baixa=20tens=C3=A3o,=20l=C3=B3gica=20de=20equipamentos?= =?UTF-8?q?=20melhorada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/manage/addEquipmentScreen.dart | 26 +---------- .../feature/manage/equipment_manager.dart | 32 -------------- .../lib/core/feature/manage/lowVoltage.dart | 43 +++++++++++++------ .../feature/manage/manageEquipmentScreen.dart | 34 +++++---------- .../feature/manage/systemConfiguration.dart | 24 +++++------ frontend/sige_ie/lib/home/ui/home.dart | 3 +- frontend/sige_ie/lib/main.dart | 4 +- 7 files changed, 58 insertions(+), 108 deletions(-) delete mode 100644 frontend/sige_ie/lib/core/feature/manage/equipment_manager.dart diff --git a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart index 6d9d5812..38e82665 100644 --- a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/manage/equipment_manager.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'dart:math'; @@ -39,7 +38,8 @@ class _AddEquipmentScreenState extends State { final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedLocation; - List equipmentTypes = []; + + List equipmentTypes = ['Eletroduto', 'Eletrocalha', 'Dimensão']; @override void dispose() { @@ -138,7 +138,6 @@ class _AddEquipmentScreenState extends State { @override void initState() { super.initState(); - equipmentTypes = EquipmentManager.getEquipmentList(widget.categoryNumber); _images = categoryImagesMap[widget.categoryNumber] ?? []; } @@ -190,27 +189,6 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Categoria: ', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), - ), - Expanded( - child: Text( - EquipmentManager - .categoryMap[widget.categoryNumber]!, - style: const TextStyle( - fontSize: 18, - color: AppColors.sigeIeBlue, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - const SizedBox(height: 20), const Text('Tipo do Equipamento', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14)), diff --git a/frontend/sige_ie/lib/core/feature/manage/equipment_manager.dart b/frontend/sige_ie/lib/core/feature/manage/equipment_manager.dart deleted file mode 100644 index b123183e..00000000 --- a/frontend/sige_ie/lib/core/feature/manage/equipment_manager.dart +++ /dev/null @@ -1,32 +0,0 @@ -class EquipmentManager { - static const Map categoryMap = { - 1: 'Cabeamento Estruturado', - 2: 'Descargas Atmosféricas', - 3: 'Alarme de Incêndio' - }; - - static List getCategoryList() { - return categoryMap.values.toList(); - } - - static List getEquipmentList(int categoryId) { - switch (categoryId) { - case 1: - return ['Eletroduto', 'Eletrocalha', 'Dimensão']; - case 2: - return ['Para-raios', 'Captação', 'Subsistemas']; - case 3: - return [ - 'Alarme de incêndio', - 'Sensor de fumaça', - 'Sensor de temperatura', - 'Acionadores', - 'Central de alarme de incêndio' - ]; - default: - return []; - } - } - - static void deleteEquipment(int categoryNumber, String s) {} -} diff --git a/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart b/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart index ffc70647..00e911f5 100644 --- a/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart +++ b/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart @@ -1,26 +1,40 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; class LowVoltageScreen extends StatefulWidget { final String areaName; - final int categoryNumber; + final List categoryNumbers; final String localName; final int? localId; const LowVoltageScreen({ Key? key, required this.areaName, - required this.categoryNumber, + required this.categoryNumbers, required this.localName, this.localId, }) : super(key: key); + @override _LowVoltageScreenState createState() => _LowVoltageScreenState(); } class _LowVoltageScreenState extends State { - void navigateTo(String routeName) { - Navigator.pushNamed(context, routeName); + void navigateToEquipmentScreen(int categoryNumber) { + if (widget.localId != null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EquipmentScreen( + areaName: widget.areaName, + localName: widget.localName, + localId: widget.localId!, + categoryNumber: categoryNumber, + ), + ), + ); + } } @override @@ -52,22 +66,27 @@ class _LowVoltageScreenState extends State { color: Colors.white)), ), ), - Padding( - padding: const EdgeInsets.all(15.0), - ), + Padding(padding: const EdgeInsets.all(15.0)), OptionButton( - title: 'ILUMINAÇÃO', onPressed: () => navigateTo('/option1')), + title: 'ILUMINAÇÃO', + onPressed: () => + navigateToEquipmentScreen(widget.categoryNumbers[0])), OptionButton( title: 'CARGAS ELÉTRICAS', - onPressed: () => navigateTo('/option2')), + onPressed: () => + navigateToEquipmentScreen(widget.categoryNumbers[1])), OptionButton( title: 'LINHAS ELÉTRICAS', - onPressed: () => navigateTo('/option3')), + onPressed: () => + navigateToEquipmentScreen(widget.categoryNumbers[2])), OptionButton( - title: 'CIRCUITOS', onPressed: () => navigateTo('/option4')), + title: 'CIRCUITOS', + onPressed: () => + navigateToEquipmentScreen(widget.categoryNumbers[3])), OptionButton( title: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/option5')), + onPressed: () => + navigateToEquipmentScreen(widget.categoryNumbers[4])), ], ), ), diff --git a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart index 7e363da1..a4ce1aac 100644 --- a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; -import 'package:sige_ie/core/feature/manage/equipment_manager.dart'; class ViewEquipmentScreen extends StatefulWidget { final String areaName; @@ -23,7 +22,16 @@ class ViewEquipmentScreen extends StatefulWidget { class _ViewEquipmentScreenState extends State { String? _selectedEquipment; - List equipmentList = []; + List equipmentList = [ + 'Eletroduto', + 'Eletrocalha', + 'Dimensão', + 'Para-raios', + 'Captação', + 'Subsistemas', + 'Alarme de incêndio', + 'Sensor de fumaça' + ]; void navigateToEquipmentScreen() { Navigator.of(context).push(MaterialPageRoute( @@ -39,8 +47,8 @@ class _ViewEquipmentScreenState extends State { @override void initState() { super.initState(); - equipmentList = EquipmentManager.getEquipmentList(widget.categoryNumber); + // Seleciona o primeiro equipamento por padrão, se disponível if (equipmentList.isNotEmpty) { _selectedEquipment = _selectedEquipment ?? equipmentList.first; } @@ -81,26 +89,6 @@ class _ViewEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Categoria: ', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), - ), - Expanded( - child: Text( - EquipmentManager.categoryMap[widget.categoryNumber]!, - style: const TextStyle( - fontSize: 18, - color: AppColors.sigeIeBlue, - fontWeight: FontWeight.w500), - ), - ), - ], - ), - const SizedBox(height: 30), const Text('Equipamentos', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), diff --git a/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart index fe2a9592..5cbe44d8 100644 --- a/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart @@ -22,9 +22,8 @@ class SystemConfiguration extends StatefulWidget { } class _SystemConfigurationState extends State { - void navigateTo( - String routeName, String areaName, String localName, int localId, - [int categoryNumber = 0]) { + void navigateTo(String routeName, String areaName, String localName, + int localId, dynamic category) { Navigator.push( context, MaterialPageRoute( @@ -32,10 +31,11 @@ class _SystemConfigurationState extends State { switch (routeName) { case '/lowVoltage': return LowVoltageScreen( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: categoryNumber); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumbers: category, + ); case '/structuredCabling': case '/atmosphericDischarges': case '/fireAlarm': @@ -43,7 +43,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: categoryNumber); + categoryNumber: category); default: return Scaffold( body: Center(child: Text('No route defined for $routeName')), @@ -90,19 +90,19 @@ class _SystemConfigurationState extends State { SystemButton( title: 'BAIXA TENSÃO', onPressed: () => navigateTo('/lowVoltage', widget.areaName, - widget.localName, widget.localId, 0)), + widget.localName, widget.localId, [1, 2, 3, 4, 5])), SystemButton( title: 'CABEAMENTO ESTRUTURADO', onPressed: () => navigateTo('/structuredCabling', - widget.areaName, widget.localName, widget.localId, 1)), + widget.areaName, widget.localName, widget.localId, 6)), SystemButton( title: 'DESCARGAS ATMOSFÉRICAS', onPressed: () => navigateTo('/atmosphericDischarges', - widget.areaName, widget.localName, widget.localId, 2)), + widget.areaName, widget.localName, widget.localId, 7)), SystemButton( title: 'ALARME DE INCÊNDIO', onPressed: () => navigateTo('/fireAlarm', widget.areaName, - widget.localName, widget.localId, 3)), + widget.localName, widget.localId, 8)), SizedBox( height: 30, ), diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 71bfc473..9a3767d7 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -127,8 +127,7 @@ class _HomePageState extends State { context, 'Registrar novo local', 'Registrar', () { Navigator.of(context).pushNamed('/newLocation'); }), - buildSmallRectangle( - context, 'Gerenciar locais', 'Gerenciar', () { + buildSmallRectangle(context, 'Comunidade', 'Gerenciar', () { // Código aqui. }), const Spacer(), diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 796ab525..2b0bae2b 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -88,15 +88,13 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - final int categoryNumber = args['categoryNumber'] ?? 0; - if (areaName != null && localName != null && localId != null) { return MaterialPageRoute( builder: (context) => LowVoltageScreen( areaName: areaName, - categoryNumber: categoryNumber, localName: localName, localId: localId, + categoryNumbers: const [], )); } else { throw Exception( From e91702f47e45352bf80089eba89d9ee9b012db6c Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 15 May 2024 17:53:43 -0300 Subject: [PATCH 116/351] Visualizar mapa --- frontend/sige_ie/android/app/build.gradle | 21 +- .../android/app/src/main/AndroidManifest.xml | 63 +++--- frontend/sige_ie/lib/maps/feature/maps.dart | 80 ++++--- .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + frontend/sige_ie/pubspec.lock | 200 ++++++++++++++++-- frontend/sige_ie/pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 6 + .../windows/flutter/generated_plugins.cmake | 2 + 10 files changed, 290 insertions(+), 90 deletions(-) diff --git a/frontend/sige_ie/android/app/build.gradle b/frontend/sige_ie/android/app/build.gradle index 6462f3c4..696b9554 100644 --- a/frontend/sige_ie/android/app/build.gradle +++ b/frontend/sige_ie/android/app/build.gradle @@ -40,17 +40,16 @@ android { main.java.srcDirs += 'src/main/kotlin' } - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.sige_ie" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion // Multidex requer no mínimo Android 4.1 (API level 16) - targetSdkVersion 31 - multiDexEnabled true - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } + defaultConfig { + applicationId "com.example.sige_ie" + // Defina os valores a seguir de acordo com as necessidades do seu aplicativo. + minSdkVersion 21 // Definido como 21 conforme sua especificação + targetSdkVersion 31 // Atualizado para apontar para a versão 31, que é a mais recente no momento + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + multiDexEnabled true // Ativado para suportar múltiplos DEX +} + buildTypes { release { diff --git a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml index 5abd1764..a2711997 100644 --- a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml +++ b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml @@ -1,39 +1,40 @@ + + + - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/frontend/sige_ie/assets/lighting.png b/frontend/sige_ie/assets/lighting.png new file mode 100644 index 0000000000000000000000000000000000000000..c257c615c8b7956879a969b86c753846abe952eb GIT binary patch literal 808 zcmV+@1K0eCP)<$qlWVZK)5Vry7)tpycsJ?)NPBmTE9TWGfNmMKV#i6iy^a24WOSL4l zu2sloM0v&Xe60>B$N6ZwUhl3|*S-VF>#^1X010t)e`HzPdV$EvIQ}Z>K202bHQq1) zl38o5*2`L{b2eHl04d}$0QTAfC^vD39TVRh1_1iq*5*m4sE_4uS?g^?fwJF!Nw|?O zUO23dVKCP_2cWsqT=*DL>pyVJ^coE4B~t1#+a}*tt8JpprI5=Ayo%-t5rb@ej5Dv7Qb9WUgmezp}tm0$!|?XTp^9 zjy-4M){A!|D)b!y(6(Vht3X`S!`4TC|0_U1Z>Un9u5;HplAo?L7Zvtbvazg@jQ#{5 z9nO1;-`+T#`GcD=2jX-ftxlDj19+RzdQ)G@*Z`t1SeE)l#eUn1M_m9iliLa5>2QwZ zF!w#5t;Za&_#FcPls^Dq{bNLV%;wPt^_l~=9U#JIf1UxaG{W2pKxVpI0Xe)f3YD0000N literal 0 HcmV?d00001 diff --git a/frontend/sige_ie/lib/areas/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart index 72045b6a..e3ef26df 100644 --- a/frontend/sige_ie/lib/areas/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -8,8 +8,8 @@ class AreaLocation extends StatefulWidget { final String localName; final int localId; - const AreaLocation({Key? key, required this.localName, required this.localId}) - : super(key: key); + const AreaLocation( + {super.key, required this.localName, required this.localId}); @override _AreaLocationState createState() => _AreaLocationState(); } @@ -30,21 +30,21 @@ class _AreaLocationState extends State { children: [ Container( width: double.infinity, - padding: EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( color: AppColors.sigeIeBlue, borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( child: Text('${widget.localName} - Sala', - style: TextStyle( + style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, color: Colors.white)), ), ), - SizedBox(height: 60), + const SizedBox(height: 60), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( @@ -52,20 +52,21 @@ class _AreaLocationState extends State { children: [ Row( children: [ - Text('Andar', + const Text('Andar', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), IconButton( - icon: Icon(Icons.info_outline), + icon: const Icon(Icons.info_outline), onPressed: () { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Informação sobre os Andares'), - content: Column( + title: + const Text('Informação sobre os Andares'), + content: const Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -79,7 +80,7 @@ class _AreaLocationState extends State { ), actions: [ TextButton( - child: Text('OK'), + child: const Text('OK'), onPressed: () => Navigator.of(context).pop(), ), @@ -91,9 +92,9 @@ class _AreaLocationState extends State { ), ], ), - SizedBox(height: 10), + const SizedBox(height: 10), Container( - padding: EdgeInsets.symmetric(horizontal: 10), + padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(10), @@ -102,7 +103,7 @@ class _AreaLocationState extends State { controller: TextEditingController( text: selectedFloor?.toString()), keyboardType: TextInputType.number, - decoration: InputDecoration( + decoration: const InputDecoration( border: InputBorder.none, ), onChanged: (value) { @@ -112,27 +113,27 @@ class _AreaLocationState extends State { }, ), ), - SizedBox(height: 40), - Text('Sala', + const SizedBox(height: 40), + const Text('Sala', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), - SizedBox(height: 10), + const SizedBox(height: 10), Container( decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(10)), child: TextField( controller: areaController, - decoration: InputDecoration( + decoration: const InputDecoration( hintText: 'Digite o nome da Sala', border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), - SizedBox(height: 60), + const SizedBox(height: 60), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -140,8 +141,8 @@ class _AreaLocationState extends State { style: ElevatedButton.styleFrom( foregroundColor: AppColors.lightText, backgroundColor: AppColors.warn, - minimumSize: Size(150, 50), - textStyle: TextStyle( + minimumSize: const Size(150, 50), + textStyle: const TextStyle( fontWeight: FontWeight.bold, ), shape: RoundedRectangleBorder( @@ -154,14 +155,14 @@ class _AreaLocationState extends State { '/homeScreen', ); }, - child: Text('ENCERRAR'), + child: const Text('ENCERRAR'), ), ElevatedButton( style: ElevatedButton.styleFrom( foregroundColor: AppColors.sigeIeBlue, backgroundColor: AppColors.sigeIeYellow, - minimumSize: Size(150, 50), - textStyle: TextStyle( + minimumSize: const Size(150, 50), + textStyle: const TextStyle( fontWeight: FontWeight.bold, ), shape: RoundedRectangleBorder( @@ -193,11 +194,11 @@ class _AreaLocationState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Erro'), + title: const Text('Erro'), content: Text("Falha ao criar sala: $e"), actions: [ TextButton( - child: Text("OK"), + child: const Text("OK"), onPressed: () => Navigator.of(context).pop(), ), @@ -211,12 +212,12 @@ class _AreaLocationState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Erro'), - content: Text( + title: const Text('Erro'), + content: const Text( "Por favor, selecione um andar e digite o nome da sala"), actions: [ TextButton( - child: Text("OK"), + child: const Text("OK"), onPressed: () => Navigator.of(context).pop(), ), @@ -226,7 +227,7 @@ class _AreaLocationState extends State { ); } }, - child: Text('CONTINUAR'), + child: const Text('CONTINUAR'), ), ], ), @@ -254,7 +255,7 @@ Widget _buildDropdown({ ), child: DropdownButtonHideUnderline( child: DropdownButtonFormField( - decoration: InputDecoration( + decoration: const InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 4), ), @@ -267,7 +268,7 @@ Widget _buildDropdown({ ); }).toList(), onChanged: onChanged, - style: TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), dropdownColor: Colors.grey[200], ), ), diff --git a/frontend/sige_ie/lib/core/feature/login/login.dart b/frontend/sige_ie/lib/core/feature/login/login.dart index d7109f81..7a82efbd 100644 --- a/frontend/sige_ie/lib/core/feature/login/login.dart +++ b/frontend/sige_ie/lib/core/feature/login/login.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/data/auth_service.dart'; class LoginScreen extends StatefulWidget { - const LoginScreen({Key? key}) : super(key: key); + const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); } @@ -193,11 +193,6 @@ class _LoginScreenState extends State { ); } }, - child: const Text( - 'Login', - style: TextStyle( - fontSize: 20, fontWeight: FontWeight.bold), - ), style: ElevatedButton.styleFrom( elevation: 6, backgroundColor: @@ -206,6 +201,11 @@ class _LoginScreenState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), )), + child: const Text( + 'Login', + style: TextStyle( + fontSize: 20, fontWeight: FontWeight.bold), + ), ), ), const SizedBox(height: 30), diff --git a/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart index 5c921d42..01b6919b 100644 --- a/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart @@ -9,13 +9,13 @@ class EquipmentScreen extends StatelessWidget { final int categoryNumber; final int localId; - EquipmentScreen({ - Key? key, + const EquipmentScreen({ + super.key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, - }) : super(key: key); + }); void navigateToAddEquipment(BuildContext context) { Navigator.push( @@ -71,7 +71,7 @@ class EquipmentScreen extends StatelessWidget { children: [ Container( padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( + decoration: const BoxDecoration( color: AppColors.sigeIeBlue, borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), @@ -83,7 +83,7 @@ class EquipmentScreen extends StatelessWidget { color: AppColors.lightText)), ), ), - SizedBox(height: 150), + const SizedBox(height: 150), EquipmentButton( title: 'EQUIPAMENTOS NA SALA', onPressed: () => navigateToAddEquipment(context), @@ -102,19 +102,14 @@ class EquipmentButton extends StatelessWidget { final String title; final VoidCallback onPressed; - EquipmentButton({Key? key, required this.title, required this.onPressed}) - : super(key: key); + const EquipmentButton( + {super.key, required this.title, required this.onPressed}); @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), child: ElevatedButton( - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), style: ButtonStyle( backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), padding: MaterialStateProperty.all( @@ -124,6 +119,11 @@ class EquipmentButton extends StatelessWidget { )), ), onPressed: onPressed, + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.w900)), ), ); } diff --git a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart index 34ca0a2e..6c80f1d7 100644 --- a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart @@ -21,13 +21,13 @@ class AddEquipmentScreen extends StatefulWidget { final int localId; final int categoryNumber; - AddEquipmentScreen({ - Key? key, + const AddEquipmentScreen({ + super.key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, - }) : super(key: key); + }); @override _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); @@ -75,21 +75,21 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Adicionar novo tipo de equipamento'), + title: const Text('Adicionar novo tipo de equipamento'), content: TextField( controller: typeController, - decoration: - InputDecoration(hintText: 'Digite o novo tipo de equipamento'), + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), ), actions: [ TextButton( - child: Text('Cancelar'), + child: const Text('Cancelar'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( - child: Text('Adicionar'), + child: const Text('Adicionar'), onPressed: () { if (typeController.text.isNotEmpty) { setState(() { @@ -110,7 +110,7 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Excluir tipo de equipamento'), + title: const Text('Excluir tipo de equipamento'), content: DropdownButton( value: _selectedType, onChanged: (String? newValue) { @@ -130,7 +130,7 @@ class _AddEquipmentScreenState extends State { ), actions: [ TextButton( - child: Text('Cancelar'), + child: const Text('Cancelar'), onPressed: () { Navigator.of(context).pop(); }, diff --git a/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart b/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart index 00e911f5..1d3e7637 100644 --- a/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart +++ b/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart @@ -9,12 +9,12 @@ class LowVoltageScreen extends StatefulWidget { final int? localId; const LowVoltageScreen({ - Key? key, + super.key, required this.areaName, required this.categoryNumbers, required this.localName, this.localId, - }) : super(key: key); + }); @override _LowVoltageScreenState createState() => _LowVoltageScreenState(); @@ -53,7 +53,7 @@ class _LowVoltageScreenState extends State { Container( width: double.infinity, padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( + decoration: const BoxDecoration( color: AppColors.sigeIeBlue, borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), @@ -66,7 +66,7 @@ class _LowVoltageScreenState extends State { color: Colors.white)), ), ), - Padding(padding: const EdgeInsets.all(15.0)), + const Padding(padding: EdgeInsets.all(15.0)), OptionButton( title: 'ILUMINAÇÃO', onPressed: () => @@ -99,10 +99,10 @@ class OptionButton extends StatelessWidget { final VoidCallback onPressed; const OptionButton({ - Key? key, + super.key, required this.title, required this.onPressed, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart index 288e7f82..8874b331 100644 --- a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart @@ -8,13 +8,12 @@ class ViewEquipmentScreen extends StatefulWidget { final int localId; final int categoryNumber; - ViewEquipmentScreen( - {Key? key, + const ViewEquipmentScreen( + {super.key, required this.areaName, required this.categoryNumber, required this.localName, - required this.localId}) - : super(key: key); + required this.localId}); @override _ViewEquipmentScreenState createState() => _ViewEquipmentScreenState(); @@ -96,7 +95,8 @@ class _ViewEquipmentScreenState extends State { color: Colors.grey[300], borderRadius: BorderRadius.circular(10.0), ), - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: DropdownButtonHideUnderline( child: DropdownButton( value: _selectedEquipment, diff --git a/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart index 5cbe44d8..99ab0bc1 100644 --- a/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart @@ -9,13 +9,13 @@ class SystemConfiguration extends StatefulWidget { final int localId; final int categoryNumber; - SystemConfiguration({ - Key? key, + const SystemConfiguration({ + super.key, required this.areaName, required this.localName, required this.localId, required this.categoryNumber, - }) : super(key: key); + }); @override _SystemConfigurationState createState() => _SystemConfigurationState(); @@ -66,23 +66,23 @@ class _SystemConfigurationState extends State { children: [ Container( width: double.infinity, - padding: EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: BoxDecoration( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( color: AppColors.sigeIeBlue, borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( child: Text(widget.areaName, - style: TextStyle( + style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, color: Colors.white)), ), ), - Padding( - padding: const EdgeInsets.all(30.0), - child: const Text( + const Padding( + padding: EdgeInsets.all(30.0), + child: Text( 'Quais sistemas deseja configurar?', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), @@ -103,7 +103,7 @@ class _SystemConfigurationState extends State { title: 'ALARME DE INCÊNDIO', onPressed: () => navigateTo('/fireAlarm', widget.areaName, widget.localName, widget.localId, 8)), - SizedBox( + const SizedBox( height: 30, ), Center( @@ -141,10 +141,10 @@ class SystemButton extends StatelessWidget { final VoidCallback onPressed; const SystemButton({ - Key? key, + super.key, required this.title, required this.onPressed, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -152,11 +152,6 @@ class SystemButton extends StatelessWidget { margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), width: double.infinity, child: ElevatedButton( - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), style: ButtonStyle( backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), @@ -167,6 +162,11 @@ class SystemButton extends StatelessWidget { )), ), onPressed: onPressed, + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.w900)), ), ); } diff --git a/frontend/sige_ie/lib/core/feature/register/register.dart b/frontend/sige_ie/lib/core/feature/register/register.dart index 344abb74..50be23f9 100644 --- a/frontend/sige_ie/lib/core/feature/register/register.dart +++ b/frontend/sige_ie/lib/core/feature/register/register.dart @@ -283,11 +283,6 @@ class _RegisterScreenState extends State { } } }, - child: const Text( - 'Registro', - style: TextStyle( - fontSize: 20, fontWeight: FontWeight.bold), - ), style: ElevatedButton.styleFrom( elevation: 6, backgroundColor: @@ -296,6 +291,11 @@ class _RegisterScreenState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), )), + child: const Text( + 'Registro', + style: TextStyle( + fontSize: 20, fontWeight: FontWeight.bold), + ), ), ), const SizedBox(height: 30), diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index 720c6bd3..e18fc7dc 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -8,6 +8,8 @@ import '../../config/app_styles.dart'; import '../../areas/data/area_service.dart'; class FacilitiesPage extends StatefulWidget { + const FacilitiesPage({super.key}); + @override _FacilitiesPageState createState() => _FacilitiesPageState(); } @@ -350,7 +352,8 @@ class _FacilitiesPageState extends State { } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Falha ao atualizar o local')), + const SnackBar( + content: Text('Falha ao atualizar o local')), ); } } @@ -472,7 +475,8 @@ class FloorAreaWidget extends StatelessWidget { final Function(int) onDeleteArea; final Function(int) onTapArea; - FloorAreaWidget({ + const FloorAreaWidget({ + super.key, required this.groupedAreas, required this.placeName, required this.placeId, @@ -502,11 +506,11 @@ class FloorAreaWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ IconButton( - icon: Icon(Icons.edit, color: Colors.blue), + icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () => onEditArea(area.id), ), IconButton( - icon: Icon(Icons.delete, color: Colors.red), + icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => onDeleteArea(area.id), ), ], @@ -518,8 +522,8 @@ class FloorAreaWidget extends StatelessWidget { ), ), ListTile( - leading: Icon(Icons.add), - title: Text('Adicionar andar ou sala'), + leading: const Icon(Icons.add), + title: const Text('Adicionar andar ou sala'), onTap: onAddFloor, ), ], diff --git a/frontend/sige_ie/lib/core/ui/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_scren.dart index 8affbef2..e2469d6d 100644 --- a/frontend/sige_ie/lib/core/ui/first_scren.dart +++ b/frontend/sige_ie/lib/core/ui/first_scren.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; class FirstScreen extends StatelessWidget { + const FirstScreen({super.key}); + @override Widget build(BuildContext context) { return Scaffold( @@ -14,10 +16,6 @@ class FirstScreen extends StatelessWidget { onPressed: () { Navigator.pushNamed(context, '/loginScreen'); }, - child: const Text( - "Login", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), style: ElevatedButton.styleFrom( elevation: 6, minimumSize: const Size(200, 50), @@ -26,7 +24,11 @@ class FirstScreen extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 12), // Arredondamento dos cantos do botão - ))), + )), + child: const Text( + "Login", + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + )), const SizedBox( height: 15, // Espaço entre os botões ), @@ -34,10 +36,6 @@ class FirstScreen extends StatelessWidget { onPressed: () { Navigator.pushNamed(context, '/registerScreen'); }, - child: const Text( - "Registro", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), style: ElevatedButton.styleFrom( elevation: 6, minimumSize: const Size(200, 50), @@ -46,6 +44,10 @@ class FirstScreen extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), )), + child: const Text( + "Registro", + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), ), ], ), diff --git a/frontend/sige_ie/lib/core/ui/splash_screen.dart b/frontend/sige_ie/lib/core/ui/splash_screen.dart index 199db34f..519d9d9f 100644 --- a/frontend/sige_ie/lib/core/ui/splash_screen.dart +++ b/frontend/sige_ie/lib/core/ui/splash_screen.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; class SplashScreen extends StatefulWidget { + const SplashScreen({super.key}); + @override _SplashScreenState createState() => _SplashScreenState(); } diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 9a3767d7..30f39bbb 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -7,6 +7,8 @@ import 'package:sige_ie/users/data/user_response_model.dart'; import 'package:sige_ie/users/data/user_service.dart'; class HomePage extends StatefulWidget { + const HomePage({super.key}); + @override _HomePageState createState() => _HomePageState(); } @@ -53,8 +55,8 @@ class _HomePageState extends State { }, children: [ buildHomePage(context), - FacilitiesPage(), - MapsPage(), + const FacilitiesPage(), + const MapsPage(), ProfilePage() ], ), @@ -67,9 +69,9 @@ class _HomePageState extends State { future: userService.fetchProfileData(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return Center(child: CircularProgressIndicator()); + return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { - return Center(child: Text('Erro ao carregar os dados')); + return const Center(child: Text('Erro ao carregar os dados')); } else if (snapshot.hasData) { var user = snapshot.data!; return Column( @@ -107,7 +109,7 @@ class _HomePageState extends State { padding: const EdgeInsets.only(left: 20), child: Text( 'Olá, ${user.firstname}', - style: TextStyle( + style: const TextStyle( color: AppColors.sigeIeYellow, fontSize: 20, ), @@ -137,7 +139,7 @@ class _HomePageState extends State { ], ); } else { - return Center(child: Text('Estado desconhecido')); + return const Center(child: Text('Estado desconhecido')); } }, ); @@ -149,8 +151,8 @@ class _HomePageState extends State { decoration: BoxDecoration( color: const Color(0xff123c75), borderRadius: BorderRadius.circular(20), - boxShadow: [ - const BoxShadow(color: Colors.black, spreadRadius: 0, blurRadius: 10), + boxShadow: const [ + BoxShadow(color: Colors.black, spreadRadius: 0, blurRadius: 10), ], ), height: 135, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 2b0bae2b..2cdbee78 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -28,18 +28,19 @@ class MyApp extends StatelessWidget { onGenerateRoute: (settings) { switch (settings.name) { case '/': - return MaterialPageRoute(builder: (context) => SplashScreen()); + return MaterialPageRoute( + builder: (context) => const SplashScreen()); case '/loginScreen': return MaterialPageRoute(builder: (context) => const LoginScreen()); case '/first': - return MaterialPageRoute(builder: (context) => FirstScreen()); + return MaterialPageRoute(builder: (context) => const FirstScreen()); case '/registerScreen': return MaterialPageRoute( builder: (context) => const RegisterScreen()); case '/homeScreen': - return MaterialPageRoute(builder: (context) => HomePage()); + return MaterialPageRoute(builder: (context) => const HomePage()); case '/MapsPage': - return MaterialPageRoute(builder: (context) => MapsPage()); + return MaterialPageRoute(builder: (context) => const MapsPage()); case '/newLocation': return MaterialPageRoute(builder: (context) => NewPlace()); case '/arealocation': @@ -139,7 +140,7 @@ class MyApp extends StatelessWidget { class UndefinedView extends StatelessWidget { final String? name; - const UndefinedView({Key? key, this.name}) : super(key: key); + const UndefinedView({super.key, this.name}); @override Widget build(BuildContext context) { diff --git a/frontend/sige_ie/lib/maps/controller/maps_controller.dart b/frontend/sige_ie/lib/maps/controller/maps_controller.dart new file mode 100644 index 00000000..5c9b5ad6 --- /dev/null +++ b/frontend/sige_ie/lib/maps/controller/maps_controller.dart @@ -0,0 +1,72 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:get/get.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import '../../places/data/place_response_model.dart'; +import '../../places/data/place_service.dart'; + +class MapsController extends GetxController { + final latitude = 0.0.obs; + final longitude = 0.0.obs; + final markers = {}.obs; + + late GoogleMapController _mapsController; + final PlaceService _placeService = PlaceService(); + + @override + void onInit() { + super.onInit(); + fetchPlaces(); + } + + void onMapCreated(GoogleMapController controller) { + _mapsController = controller; + } + + Future getBytesFromAsset(String path, int width) async { + try { + ByteData data = await rootBundle.load(path); + ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), + targetWidth: width); + ui.FrameInfo fi = await codec.getNextFrame(); + return (await fi.image.toByteData(format: ui.ImageByteFormat.png)) + ?.buffer + .asUint8List(); + } catch (e) { + Get.snackbar('Erro', 'Falha ao carregar ícone: $e'); + return null; + } + } + + Future fetchPlaces() async { + try { + final List places = + await _placeService.fetchAllPlaces(); + for (var place in places) { + await addMarker(place); + } + } catch (e) { + Get.snackbar('Erro', 'Falha ao carregar locais: $e'); + } + } + + Future addMarker(PlaceResponseModel place) async { + final Uint8List? icon = await getBytesFromAsset('assets/lighting.png', 64); + if (icon != null) { + markers.add( + Marker( + markerId: MarkerId(place.id.toString()), + position: LatLng(place.lat, place.lon), + infoWindow: InfoWindow(title: place.name), + icon: BitmapDescriptor.fromBytes(icon), + ), + ); + } else { + Get.snackbar('Erro', 'Falha ao carregar ícone do marcador'); + } + } +} diff --git a/frontend/sige_ie/lib/maps/feature/maps.dart b/frontend/sige_ie/lib/maps/feature/maps.dart index 2e3f94b3..8c07fcc0 100644 --- a/frontend/sige_ie/lib/maps/feature/maps.dart +++ b/frontend/sige_ie/lib/maps/feature/maps.dart @@ -1,64 +1,38 @@ import 'package:flutter/material.dart'; -import 'package:flutter_osm_plugin/flutter_osm_plugin.dart'; +import 'package:get/get.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import '../controller/maps_controller.dart'; -class MapsPage extends StatefulWidget { - const MapsPage({super.key}); +class MapsPage extends StatelessWidget { + const MapsPage({Key? key}) : super(key: key); - @override - State createState() => _MapsPageState(); -} - -class _MapsPageState extends State { - MapController controller = MapController( - initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324), - areaLimit: BoundingBox( - east: 10.4922941, - north: 47.8084648, - south: 45.817995, - west: 5.9559113, - ), - ); @override Widget build(BuildContext context) { - return OSMFlutter( - controller: controller, - osmOption: OSMOption( - userTrackingOption: const UserTrackingOption( - enableTracking: true, - unFollowUser: false, - ), - zoomOption: const ZoomOption( - initZoom: 8, - minZoomLevel: 3, - maxZoomLevel: 19, - stepZoom: 1.0, - ), - userLocationMarker: UserLocationMaker( - personMarker: const MarkerIcon( - icon: Icon( - Icons.location_history_rounded, - color: Colors.red, - size: 48, - ), - ), - directionArrowMarker: const MarkerIcon( - icon: Icon( - Icons.double_arrow, - size: 48, - ), - ), - ), - roadConfiguration: const RoadOption( - roadColor: Colors.yellowAccent, + final MapsController _controller = Get.put(MapsController()); + + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: const Color(0xff123c75), + elevation: 0, + title: const Text('Google Maps', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + body: Obx(() { + return GoogleMap( + onMapCreated: _controller.onMapCreated, + initialCameraPosition: CameraPosition( + target: LatLng(-23.571505, -46.689104), // Posição inicial + zoom: 12, ), - markerOption: MarkerOption( - defaultMarker: const MarkerIcon( - icon: Icon( - Icons.person_pin_circle, - color: Colors.blue, - size: 56, - ), - )), - )); + markers: Set.of(_controller.markers), + myLocationEnabled: true, + myLocationButtonEnabled: true, + ); + }), + ); } } diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index b109ef47..f5f0afe3 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -5,6 +5,8 @@ import 'package:sige_ie/places/data/place_service.dart'; import 'position.dart'; class NewPlace extends StatefulWidget { + const NewPlace({super.key}); + @override NewPlaceState createState() => NewPlaceState(); } diff --git a/frontend/sige_ie/lib/users/feature/profile.dart b/frontend/sige_ie/lib/users/feature/profile.dart index c7be8a6f..e20fc75f 100644 --- a/frontend/sige_ie/lib/users/feature/profile.dart +++ b/frontend/sige_ie/lib/users/feature/profile.dart @@ -5,6 +5,8 @@ import 'package:sige_ie/users/data/user_service.dart'; import 'package:sige_ie/config/app_styles.dart'; class ProfilePage extends StatefulWidget { + const ProfilePage({super.key}); + @override _ProfilePageState createState() => _ProfilePageState(); } @@ -69,7 +71,7 @@ class _ProfilePageState extends State { style: TextStyle( color: Colors.grey[600], fontWeight: FontWeight.bold), ), - SizedBox(height: 5), + const SizedBox(height: 5), TextField( decoration: InputDecoration( filled: true, @@ -78,7 +80,7 @@ class _ProfilePageState extends State { borderRadius: BorderRadius.circular(8.0), borderSide: BorderSide.none, ), - contentPadding: EdgeInsets.symmetric( + contentPadding: const EdgeInsets.symmetric( vertical: 15.0, horizontal: 10.0), isDense: true, ), @@ -86,13 +88,13 @@ class _ProfilePageState extends State { TextEditingController(text: userResponseModel.email), onChanged: (value) => userResponseModel.email = value, ), - SizedBox(height: 15), + const SizedBox(height: 15), Text( 'Nome', style: TextStyle( color: Colors.grey[600], fontWeight: FontWeight.bold), ), - SizedBox(height: 5), + const SizedBox(height: 5), TextField( decoration: InputDecoration( filled: true, @@ -101,7 +103,7 @@ class _ProfilePageState extends State { borderRadius: BorderRadius.circular(8.0), borderSide: BorderSide.none, ), - contentPadding: EdgeInsets.symmetric( + contentPadding: const EdgeInsets.symmetric( vertical: 15.0, horizontal: 10.0), isDense: true, ), @@ -109,13 +111,13 @@ class _ProfilePageState extends State { text: userResponseModel.firstname), onChanged: (value) => userResponseModel.firstname = value, ), - SizedBox(height: 15), + const SizedBox(height: 15), Text( 'Username', style: TextStyle( color: Colors.grey[600], fontWeight: FontWeight.bold), ), - SizedBox(height: 5), + const SizedBox(height: 5), TextField( decoration: InputDecoration( filled: true, @@ -124,7 +126,7 @@ class _ProfilePageState extends State { borderRadius: BorderRadius.circular(8.0), borderSide: BorderSide.none, ), - contentPadding: EdgeInsets.symmetric( + contentPadding: const EdgeInsets.symmetric( vertical: 15.0, horizontal: 10.0), isDense: true, ), diff --git a/frontend/sige_ie/linux/flutter/generated_plugin_registrant.cc b/frontend/sige_ie/linux/flutter/generated_plugin_registrant.cc index 7299b5cf..64a0ecea 100644 --- a/frontend/sige_ie/linux/flutter/generated_plugin_registrant.cc +++ b/frontend/sige_ie/linux/flutter/generated_plugin_registrant.cc @@ -7,13 +7,9 @@ #include "generated_plugin_registrant.h" #include -#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); - g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); - url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/frontend/sige_ie/linux/flutter/generated_plugins.cmake b/frontend/sige_ie/linux/flutter/generated_plugins.cmake index 786ff5c2..2db3c22a 100644 --- a/frontend/sige_ie/linux/flutter/generated_plugins.cmake +++ b/frontend/sige_ie/linux/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux - url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift index 170142dc..af3162f6 100644 --- a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,13 +8,11 @@ import Foundation import file_selector_macos import geolocator_apple import shared_preferences_foundation -import url_launcher_macos import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) - UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 51bb19f2..4a01ae40 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -81,14 +81,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" - dio: - dependency: transitive - description: - name: dio - sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" - url: "https://pub.dev" - source: hosted - version: "5.4.3+1" fake_async: dependency: transitive description: @@ -158,30 +150,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" - flutter_osm_interface: - dependency: transitive - description: - name: flutter_osm_interface - sha256: acf22cf06c3550a565b0d4f63474275f24a1aaad8f32e127d6ea32957a791ef8 - url: "https://pub.dev" - source: hosted - version: "0.6.1" - flutter_osm_plugin: - dependency: "direct main" - description: - name: flutter_osm_plugin - sha256: "813b06bc7bd2d205a247edfbf72026cb7b9adfdd29e9ee588cc88e8b75bbf3ff" - url: "https://pub.dev" - source: hosted - version: "0.60.5" - flutter_osm_web: - dependency: transitive - description: - name: flutter_osm_web - sha256: "9346d3ffd5e79416894ca46257361e2f34e67e483c13fd17d2e27431986c180a" - url: "https://pub.dev" - source: hosted - version: "0.5.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -248,14 +216,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3" - google_polyline_algorithm: + get: + dependency: "direct main" + description: + name: get + sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e + url: "https://pub.dev" + source: hosted + version: "4.6.6" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "47eef3836b49bb030d5cb3afc60b8451408bf34cf753e571b645d6529eb4251a" + url: "https://pub.dev" + source: hosted + version: "7.1.0" + google_maps_flutter: + dependency: "direct main" + description: + name: google_maps_flutter + sha256: c1972cbad779bc5346c49045f26ae45550a0958b1cbca5b524dd3c8954995d28 + url: "https://pub.dev" + source: hosted + version: "2.6.1" + google_maps_flutter_android: + dependency: transitive + description: + name: google_maps_flutter_android + sha256: "0bcadb80eba39afda77dede89a6caafd3b68f2786b90491eceea4a01c3db181c" + url: "https://pub.dev" + source: hosted + version: "2.8.0" + google_maps_flutter_ios: + dependency: transitive + description: + name: google_maps_flutter_ios + sha256: e5132d17f051600d90d79d9f574b177c24231da702453a036db2490f9ced4646 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + google_maps_flutter_platform_interface: + dependency: transitive + description: + name: google_maps_flutter_platform_interface + sha256: "167af879da4d004cd58771f1469b91dcc3b9b0a2c5334cc6bf71fd41d4b35403" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + google_maps_flutter_web: dependency: transitive description: - name: google_polyline_algorithm - sha256: "357874f00d3f93c3ba1bf4b4d9a154aa9ee87147c068238c1e8392012b686a03" + name: google_maps_flutter_web + sha256: "0c0d5c723d94b295cf86dd1c45ff91d2ac1fff7c05ddca4f01bef9fa0a014690" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "0.5.7" html: dependency: transitive description: @@ -352,14 +368,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1+1" - intl: - dependency: transitive - description: - name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" - url: "https://pub.dev" - source: hosted - version: "0.18.1" js: dependency: transitive description: @@ -368,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + js_wrapping: + dependency: transitive + description: + name: js_wrapping + sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c + url: "https://pub.dev" + source: hosted + version: "0.7.4" leak_tracker: dependency: transitive description: @@ -400,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + logger: + dependency: "direct main" + description: + name: logger + sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" + url: "https://pub.dev" + source: hosted + version: "1.4.0" matcher: dependency: transitive description: @@ -464,46 +488,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1" - permission_handler: - dependency: transitive - description: - name: permission_handler - sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5 - url: "https://pub.dev" - source: hosted - version: "10.4.5" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47" - url: "https://pub.dev" - source: hosted - version: "10.3.6" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" - url: "https://pub.dev" - source: hosted - version: "9.1.4" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" - url: "https://pub.dev" - source: hosted - version: "3.12.0" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 - url: "https://pub.dev" - source: hosted - version: "0.1.3" platform: dependency: transitive description: @@ -520,14 +504,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - routing_client_dart: + sanitize_html: dependency: transitive description: - name: routing_client_dart - sha256: "00089e5009dd00b137d6c47c398ce29ce1b53dd3bba5a721499ce185c9301de9" + name: sanitize_html + sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989" url: "https://pub.dev" source: hosted - version: "0.4.3" + version: "2.1.0" shared_preferences: dependency: "direct main" description: @@ -661,70 +645,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" - url_launcher: - dependency: transitive - description: - name: url_launcher - sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" - url: "https://pub.dev" - source: hosted - version: "6.2.6" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" - url: "https://pub.dev" - source: hosted - version: "6.3.1" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89" - url: "https://pub.dev" - source: hosted - version: "6.3.0" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 - url: "https://pub.dev" - source: hosted - version: "3.1.1" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" - url: "https://pub.dev" - source: hosted - version: "3.2.0" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 - url: "https://pub.dev" - source: hosted - version: "3.1.1" uuid: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 881da5ea..8fe7b0a0 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -30,17 +30,19 @@ environment: dependencies: flutter: sdk: flutter - geolocator: ^9.0.2 shared_preferences: ^2.0.15 video_player: ^2.2.14 http: ^0.13.3 cookie_jar: ^4.0.8 http_interceptor: ^1.0.1 - image_picker: ^0.8.5 - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + image_picker: ^0.8.5 cupertino_icons: ^1.0.6 - flutter_osm_plugin: ^0.60.4 + get: ^4.6.5 + google_maps_flutter: ^2.0.6 + geolocator: ^9.0.2 + logger: ^1.0.0 + + dev_dependencies: flutter_test: @@ -61,6 +63,7 @@ flutter: assets: - assets/Loading.mp4 - assets/1000x1000.png + - assets/lighting.png # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in diff --git a/frontend/sige_ie/test/widget_test.dart b/frontend/sige_ie/test/widget_test.dart index 27cee208..6282fd7c 100644 --- a/frontend/sige_ie/test/widget_test.dart +++ b/frontend/sige_ie/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:sige_ie/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); diff --git a/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc b/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc index b2cbd25e..f35b3a66 100644 --- a/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc +++ b/frontend/sige_ie/windows/flutter/generated_plugin_registrant.cc @@ -8,16 +8,10 @@ #include #include -#include -#include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); - PermissionHandlerWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); - UrlLauncherWindowsRegisterWithRegistrar( - registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/frontend/sige_ie/windows/flutter/generated_plugins.cmake b/frontend/sige_ie/windows/flutter/generated_plugins.cmake index 92c9a0d3..389222ba 100644 --- a/frontend/sige_ie/windows/flutter/generated_plugins.cmake +++ b/frontend/sige_ie/windows/flutter/generated_plugins.cmake @@ -5,8 +5,6 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows geolocator_windows - permission_handler_windows - url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From 216b23be8479ce2d865c30ec17e42df83a827ab5 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 20 May 2024 17:03:41 -0300 Subject: [PATCH 120/351] Introduzindo tratamento de erros --- frontend/sige_ie/lib/core/ui/facilities.dart | 289 +++++++++--------- .../lib/maps/controller/maps_controller.dart | 53 +++- frontend/sige_ie/lib/maps/feature/maps.dart | 52 +++- .../sige_ie/lib/users/data/user_service.dart | 26 +- 4 files changed, 244 insertions(+), 176 deletions(-) diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/core/ui/facilities.dart index e18fc7dc..29151004 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/core/ui/facilities.dart @@ -18,6 +18,7 @@ class _FacilitiesPageState extends State { late Future> _placesList; final PlaceService _placeService = PlaceService(); final AreaService _areaService = AreaService(); + late BuildContext _scaffoldContext; @override void initState() { @@ -25,6 +26,12 @@ class _FacilitiesPageState extends State { _placesList = _placeService.fetchAllPlaces(); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _scaffoldContext = context; + } + Future> _loadAreasForPlace(int placeId) async { try { return await _areaService.fetchAreasByPlaceId(placeId); @@ -126,33 +133,27 @@ class _FacilitiesPageState extends State { : a) .toList(); }); - if (context.mounted) { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Área atualizada com sucesso')), - ); - } + ScaffoldMessenger.of(_scaffoldContext) + .showSnackBar( + const SnackBar( + content: Text( + 'Área atualizada com sucesso')), + ); } else { - if (context.mounted) { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Falha ao atualizar a área')), - ); - } - } - } else { - if (context.mounted) { - ScaffoldMessenger.of(context) + ScaffoldMessenger.of(_scaffoldContext) .showSnackBar( const SnackBar( content: Text( - 'Dados inválidos para a área')), + 'Falha ao atualizar a área')), ); } + } else { + ScaffoldMessenger.of(_scaffoldContext) + .showSnackBar( + const SnackBar( + content: Text( + 'Dados inválidos para a área')), + ); } }, ), @@ -161,11 +162,9 @@ class _FacilitiesPageState extends State { }, ); } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erro ao editar a área: $e')), - ); - } + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( + SnackBar(content: Text('Erro ao editar a área: $e')), + ); } }, onDeleteArea: (int areaId) async { @@ -202,17 +201,15 @@ class _FacilitiesPageState extends State { value.removeWhere((a) => a.id == areaId); }); }); - ScaffoldMessenger.of(context).showSnackBar( + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( const SnackBar( content: Text('Área excluída com sucesso')), ); } else { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Erro ao excluir a área')), - ); - } + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( + const SnackBar( + content: Text('Erro ao excluir a área')), + ); } } }, @@ -231,11 +228,9 @@ class _FacilitiesPageState extends State { }, ); } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erro ao carregar as áreas: $e')), - ); - } + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( + SnackBar(content: Text('Erro ao carregar as áreas: $e')), + ); } } @@ -259,21 +254,25 @@ class _FacilitiesPageState extends State { onPressed: () async { Navigator.of(context).pop(); bool success = await _placeService.deletePlace(place.id); - if (success && mounted) { - setState(() { - _placesList = _placeService.fetchAllPlaces(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Local "${place.name}" excluído com sucesso')), - ); + if (success) { + if (mounted) { + setState(() { + _placesList = _placeService.fetchAllPlaces(); + }); + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( + SnackBar( + content: Text( + 'Local "${place.name}" excluído com sucesso')), + ); + } } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Falha ao excluir o local "${place.name}"')), - ); + if (mounted) { + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( + SnackBar( + content: + Text('Falha ao excluir o local "${place.name}"')), + ); + } } }, ), @@ -339,19 +338,19 @@ class _FacilitiesPageState extends State { ); bool success = await _placeService.updatePlace(place.id, updatedPlace); - if (success && mounted) { - setState(() { - _placesList = _placeService.fetchAllPlaces(); - }); + if (success) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( + setState(() { + _placesList = _placeService.fetchAllPlaces(); + }); + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( SnackBar( content: Text('Local atualizado para "$newName"')), ); } } else { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( const SnackBar( content: Text('Falha ao atualizar o local')), ); @@ -374,93 +373,101 @@ class _FacilitiesPageState extends State { backgroundColor: const Color(0xff123c75), elevation: 0, ), - body: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 20), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: const Center( - child: Text('Locais', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - const SizedBox(height: 15), - FutureBuilder>( - future: _placesList, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Erro: ${snapshot.error}')); - } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { - return ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: snapshot.data!.length, - itemBuilder: (context, index) { - var place = snapshot.data![index]; - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.circular(10), - ), - child: ListTile( - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 10), - title: Text( - place.name, - style: const TextStyle( - color: AppColors.lightText, - fontWeight: FontWeight.bold), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: - const Icon(Icons.edit, color: Colors.blue), - onPressed: () => _editPlace(context, place), - ), - IconButton( - icon: - const Icon(Icons.delete, color: Colors.red), - onPressed: () => _confirmDelete(context, place), + body: Builder( + builder: (BuildContext context) { + _scaffoldContext = context; + return SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 20), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Locais', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + const SizedBox(height: 15), + FutureBuilder>( + future: _placesList, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Erro: ${snapshot.error}')); + } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var place = snapshot.data![index]; + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), + ), + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + title: Text( + place.name, + style: const TextStyle( + color: AppColors.lightText, + fontWeight: FontWeight.bold), ), - IconButton( - icon: const Icon(Icons.description, - color: AppColors.lightText), - onPressed: () {}, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => + _editPlace(_scaffoldContext, place), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => + _confirmDelete(_scaffoldContext, place), + ), + IconButton( + icon: const Icon(Icons.description, + color: AppColors.lightText), + onPressed: () {}, + ), + ], ), - ], - ), - onTap: () => _showAreasForPlace(context, place), - ), + onTap: () => + _showAreasForPlace(_scaffoldContext, place), + ), + ); + }, ); - }, - ); - } else { - return const Padding( - padding: EdgeInsets.all(10.0), - child: Text("Nenhum local encontrado."), - ); - } - }, + } else { + return const Padding( + padding: EdgeInsets.all(10.0), + child: Text("Nenhum local encontrado."), + ); + } + }, + ), + const SizedBox(height: 15), + ], ), - const SizedBox(height: 15), - ], - ), + ); + }, ), ); } diff --git a/frontend/sige_ie/lib/maps/controller/maps_controller.dart b/frontend/sige_ie/lib/maps/controller/maps_controller.dart index 5c9b5ad6..5c0ccf2c 100644 --- a/frontend/sige_ie/lib/maps/controller/maps_controller.dart +++ b/frontend/sige_ie/lib/maps/controller/maps_controller.dart @@ -1,11 +1,9 @@ import 'dart:async'; -import 'dart:typed_data'; import 'dart:ui' as ui; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:geolocator/geolocator.dart'; import '../../places/data/place_response_model.dart'; import '../../places/data/place_service.dart'; @@ -13,18 +11,59 @@ class MapsController extends GetxController { final latitude = 0.0.obs; final longitude = 0.0.obs; final markers = {}.obs; + final isLoading = true.obs; - late GoogleMapController _mapsController; final PlaceService _placeService = PlaceService(); + late CameraPosition initialCameraPosition; @override void onInit() { super.onInit(); + _getCurrentLocation(); fetchPlaces(); } - void onMapCreated(GoogleMapController controller) { - _mapsController = controller; + void onMapCreated(GoogleMapController controller) {} + + Future _getCurrentLocation() async { + bool serviceEnabled; + LocationPermission permission; + + // Verifique se o serviço de localização está habilitado + serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + Get.snackbar('Erro', 'Serviço de localização está desabilitado.'); + return; + } + + // Verifique se a permissão de localização é concedida + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + Get.snackbar('Erro', 'Permissão de localização negada.'); + return; + } + } + + if (permission == LocationPermission.deniedForever) { + Get.snackbar('Erro', 'Permissão de localização negada permanentemente.'); + return; + } + + // Obtenha a localização atual + final Position position = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high); + + latitude.value = position.latitude; + longitude.value = position.longitude; + + initialCameraPosition = CameraPosition( + target: LatLng(position.latitude, position.longitude), + zoom: 12, + ); + + isLoading.value = false; } Future getBytesFromAsset(String path, int width) async { @@ -55,7 +94,7 @@ class MapsController extends GetxController { } Future addMarker(PlaceResponseModel place) async { - final Uint8List? icon = await getBytesFromAsset('assets/lighting.png', 64); + final Uint8List? icon = await getBytesFromAsset('assets/lighting.png', 80); if (icon != null) { markers.add( Marker( diff --git a/frontend/sige_ie/lib/maps/feature/maps.dart b/frontend/sige_ie/lib/maps/feature/maps.dart index 8c07fcc0..94700d01 100644 --- a/frontend/sige_ie/lib/maps/feature/maps.dart +++ b/frontend/sige_ie/lib/maps/feature/maps.dart @@ -15,22 +15,46 @@ class MapsPage extends StatelessWidget { automaticallyImplyLeading: false, backgroundColor: const Color(0xff123c75), elevation: 0, - title: const Text('Google Maps', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), ), body: Obx(() { - return GoogleMap( - onMapCreated: _controller.onMapCreated, - initialCameraPosition: CameraPosition( - target: LatLng(-23.571505, -46.689104), // Posição inicial - zoom: 12, - ), - markers: Set.of(_controller.markers), - myLocationEnabled: true, - myLocationButtonEnabled: true, + if (_controller.isLoading.value) { + return Center(child: CircularProgressIndicator()); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 20), + decoration: const BoxDecoration( + color: Color(0xff123c75), + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Column( + children: [ + Text( + 'Mapa', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ), + Expanded( + child: GoogleMap( + onMapCreated: _controller.onMapCreated, + initialCameraPosition: _controller.initialCameraPosition, + markers: Set.of(_controller.markers), + myLocationEnabled: true, + myLocationButtonEnabled: true, + ), + ), + ], ); }), ); diff --git a/frontend/sige_ie/lib/users/data/user_service.dart b/frontend/sige_ie/lib/users/data/user_service.dart index 4f77b25e..263dab0f 100644 --- a/frontend/sige_ie/lib/users/data/user_service.dart +++ b/frontend/sige_ie/lib/users/data/user_service.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; - import 'package:sige_ie/main.dart'; import 'package:sige_ie/users/data/user_request_model.dart'; import 'package:sige_ie/users/data/user_response_model.dart'; @@ -33,14 +32,11 @@ class UserService { 'email': email, })); if (response.statusCode == 200 || response.statusCode == 201) { - //print("Atualizado com sucesso: $data"); return true; } else { - //print("Falha: ${response.body}"); return false; } } catch (e) { - //print("Erro ao tentar registrar: $e"); return false; } } @@ -55,14 +51,11 @@ class UserService { var response = await client .delete(url, headers: {'Content-Type': 'application/json'}); if (response.statusCode == 204) { - //print("Excluido com sucesso: $data"); return true; } else { - //print("Falha: ${response.body}"); return false; } } catch (e) { - //print("Erro ao tentar excluir: $e"); return false; } } @@ -72,14 +65,19 @@ class UserService { interceptors: [AuthInterceptor(cookieJar)], ); - final response = - await client.get(Uri.parse('http://10.0.2.2:8000/api/userauth')); + try { + final response = + await client.get(Uri.parse('http://10.0.2.2:8000/api/userauth')); - if (response.statusCode == 200) { - var data = jsonDecode(response.body); - return UserResponseModel.fromJson(data); - } else { - throw Exception('Falha ao carregar dados do perfil'); + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return UserResponseModel.fromJson(data); + } else { + throw Exception('Falha ao carregar dados do perfil'); + } + } catch (e) { + print('Erro ao carregar dados do perfil: $e'); + rethrow; } } } From be4d4f056e468cca4aabd0c69090bf04a7b2a3f2 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 20 May 2024 17:45:12 -0300 Subject: [PATCH 121/351] =?UTF-8?q?Tratamento=20de=20erros=20e=20altera?= =?UTF-8?q?=C3=A7=C3=B5es=20visuais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/manage/addEquipmentScreen.dart | 91 +++++++++++++------ .../feature/manage/manageEquipmentScreen.dart | 15 ++- .../lib/places/data/place_service.dart | 82 +++++++++++------ 3 files changed, 128 insertions(+), 60 deletions(-) diff --git a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart index 6c80f1d7..872ea7e9 100644 --- a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart @@ -39,7 +39,12 @@ class _AddEquipmentScreenState extends State { String? _selectedType; String? _selectedLocation; - List equipmentTypes = ['Eletroduto', 'Eletrocalha', 'Dimensão']; + List equipmentTypes = [ + 'Selecione um equipamento', + 'Eletroduto', + 'Eletrocalha', + 'Dimensão' + ]; @override void dispose() { @@ -179,26 +184,40 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const Text('Tipos de equipamentos', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), Row( children: [ Expanded( - child: _buildDropdown( + flex: 4, + child: _buildStyledDropdown( items: equipmentTypes, value: _selectedType, onChanged: (newValue) { - setState(() { - _selectedType = newValue; - }); + if (newValue != 'Selecione um equipamento') { + setState(() { + _selectedType = newValue; + }); + } }, ), ), - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: _deleteEquipmentType, + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: _deleteEquipmentType, + ), + ], + ), ), ], ), @@ -249,13 +268,19 @@ class _AddEquipmentScreenState extends State { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - _buildDropdown( - items: const ['Interno', 'Externo'], + _buildStyledDropdown( + items: const [ + 'Selecione a localização', + 'Interno', + 'Externo' + ], value: _selectedLocation, onChanged: (newValue) { - setState(() { - _selectedLocation = newValue; - }); + if (newValue != 'Selecione a localização') { + setState(() { + _selectedLocation = newValue; + }); + } }, ), const SizedBox(height: 15), @@ -324,21 +349,31 @@ class _AddEquipmentScreenState extends State { ); } - Widget _buildDropdown({ + Widget _buildStyledDropdown({ required List items, String? value, required Function(String?) onChanged, }) { - String dropdownValue = value ?? items.first; - return DropdownButton( - value: dropdownValue, - onChanged: onChanged, - items: items.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), + return Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: onChanged, + items: items.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + enabled: value != items.first, + ); + }).toList(), + ), ); } } diff --git a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart index 8874b331..da5501cf 100644 --- a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart @@ -22,6 +22,7 @@ class ViewEquipmentScreen extends StatefulWidget { class _ViewEquipmentScreenState extends State { String? _selectedEquipment; List equipmentList = [ + 'Selecione um equipamento', 'Eletroduto', 'Eletrocalha', 'Dimensão', @@ -86,7 +87,7 @@ class _ViewEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Equipamentos', + const Text('Tipos de equipamentos', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -99,18 +100,22 @@ class _ViewEquipmentScreenState extends State { const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: DropdownButtonHideUnderline( child: DropdownButton( + hint: const Text('Selecione um equipamento'), value: _selectedEquipment, isExpanded: true, onChanged: (newValue) { - setState(() { - _selectedEquipment = newValue; - }); + if (newValue != 'Selecione um equipamento') { + setState(() { + _selectedEquipment = newValue; + }); + } }, items: equipmentList.map((String equipment) { return DropdownMenuItem( value: equipment, child: Text(equipment, - style: const TextStyle(color: Colors.black)), + style: const TextStyle(color: Colors.black54)), + enabled: equipment != 'Selecione um equipamento', ); }).toList(), dropdownColor: Colors.grey[300], diff --git a/frontend/sige_ie/lib/places/data/place_service.dart b/frontend/sige_ie/lib/places/data/place_service.dart index e91640b3..91231e91 100644 --- a/frontend/sige_ie/lib/places/data/place_service.dart +++ b/frontend/sige_ie/lib/places/data/place_service.dart @@ -16,16 +16,22 @@ class PlaceService { Future register(PlaceRequestModel placeRequestModel) async { var url = Uri.parse(baseUrl); - var response = await client.post( - url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(placeRequestModel.toJson()), - ); + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(placeRequestModel.toJson()), + ); - if (response.statusCode == 201) { - Map responseData = jsonDecode(response.body); - return responseData['id']; - } else { + if (response.statusCode == 201) { + Map responseData = jsonDecode(response.body); + return responseData['id']; + } else { + print('Failed to register place: ${response.statusCode}'); + return null; + } + } catch (e) { + print('Error during register: $e'); return null; } } @@ -33,12 +39,19 @@ class PlaceService { // GET ALL Future> fetchAllPlaces() async { var url = Uri.parse(baseUrl); - var response = await client.get(url); + try { + var response = await client.get(url); - if (response.statusCode == 200) { - List dataList = jsonDecode(response.body); - return dataList.map((data) => PlaceResponseModel.fromJson(data)).toList(); - } else { + if (response.statusCode == 200) { + List dataList = jsonDecode(response.body); + return dataList + .map((data) => PlaceResponseModel.fromJson(data)) + .toList(); + } else { + throw Exception('Failed to load places'); + } + } catch (e) { + print('Error during fetchAllPlaces: $e'); throw Exception('Failed to load places'); } } @@ -47,12 +60,17 @@ class PlaceService { Future fetchPlace(int placeId) async { var url = Uri.parse('$baseUrl$placeId/'); - var response = await client.get(url); + try { + var response = await client.get(url); - if (response.statusCode == 200) { - var data = jsonDecode(response.body); - return PlaceResponseModel.fromJson(data); - } else { + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + return PlaceResponseModel.fromJson(data); + } else { + throw Exception('Failed to load place with ID $placeId'); + } + } catch (e) { + print('Error during fetchPlace: $e'); throw Exception('Failed to load place with ID $placeId'); } } @@ -62,21 +80,31 @@ class PlaceService { int placeId, PlaceRequestModel placeRequestModel) async { var url = Uri.parse('$baseUrl$placeId/'); - var response = await client.put( - url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(placeRequestModel.toJson()), - ); + try { + var response = await client.put( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(placeRequestModel.toJson()), + ); - return response.statusCode == 200; + return response.statusCode == 200; + } catch (e) { + print('Error during updatePlace: $e'); + return false; + } } // DELETE Future deletePlace(int placeId) async { var url = Uri.parse('$baseUrl$placeId/'); - var response = await client.delete(url); + try { + var response = await client.delete(url); - return response.statusCode == 204; + return response.statusCode == 204; + } catch (e) { + print('Error during deletePlace: $e'); + return false; + } } } From 458d03b2f7b436921a18b62f92e8bed347d41e64 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 20 May 2024 17:51:16 -0300 Subject: [PATCH 122/351] =?UTF-8?q?Altera=C3=A7=C3=A3o=20no=20gerenciament?= =?UTF-8?q?o=20de=20equipamentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/manage/manageEquipmentScreen.dart | 71 +++++-------------- 1 file changed, 18 insertions(+), 53 deletions(-) diff --git a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart index da5501cf..b9a88b03 100644 --- a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart @@ -20,17 +20,8 @@ class ViewEquipmentScreen extends StatefulWidget { } class _ViewEquipmentScreenState extends State { - String? _selectedEquipment; List equipmentList = [ - 'Selecione um equipamento', - 'Eletroduto', - 'Eletrocalha', - 'Dimensão', - 'Para-raios', - 'Captação', - 'Subsistemas', - 'Alarme de incêndio', - 'Sensor de fumaça' + //Vazio para simular nenhum equipamento ]; void navigateToEquipmentScreen() { @@ -44,14 +35,6 @@ class _ViewEquipmentScreenState extends State { )); } - @override - void initState() { - super.initState(); - if (equipmentList.isNotEmpty) { - _selectedEquipment = _selectedEquipment ?? equipmentList.first; - } - } - @override Widget build(BuildContext context) { return Scaffold( @@ -87,41 +70,23 @@ class _ViewEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de equipamentos', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: DropdownButtonHideUnderline( - child: DropdownButton( - hint: const Text('Selecione um equipamento'), - value: _selectedEquipment, - isExpanded: true, - onChanged: (newValue) { - if (newValue != 'Selecione um equipamento') { - setState(() { - _selectedEquipment = newValue; - }); - } - }, - items: equipmentList.map((String equipment) { - return DropdownMenuItem( - value: equipment, - child: Text(equipment, - style: const TextStyle(color: Colors.black54)), - enabled: equipment != 'Selecione um equipamento', - ); - }).toList(), - dropdownColor: Colors.grey[300], - ), - ), - ), + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), const SizedBox(height: 40), Center( child: ElevatedButton( From 466cb272786346bc25213db6d3aa02f773936ba1 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 21 May 2024 12:57:41 -0300 Subject: [PATCH 123/351] =?UTF-8?q?Tratamento=20de=20erro=20em=20login=20e?= =?UTF-8?q?=20registro,=20atualiza=C3=A7=C3=A3o=20em=20adicionar=20equipam?= =?UTF-8?q?entos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sige_ie/lib/core/feature/login/login.dart | 247 ++++++++++-------- .../feature/manage/addEquipmentScreen.dart | 205 +++++++++++++-- .../lib/core/feature/register/register.dart | 214 ++++++++------- .../{core => facilities}/ui/facilities.dart | 8 +- frontend/sige_ie/lib/home/ui/home.dart | 2 +- frontend/sige_ie/lib/main.dart | 6 +- 6 files changed, 447 insertions(+), 235 deletions(-) rename frontend/sige_ie/lib/{core => facilities}/ui/facilities.dart (98%) diff --git a/frontend/sige_ie/lib/core/feature/login/login.dart b/frontend/sige_ie/lib/core/feature/login/login.dart index 7a82efbd..eb260119 100644 --- a/frontend/sige_ie/lib/core/feature/login/login.dart +++ b/frontend/sige_ie/lib/core/feature/login/login.dart @@ -13,6 +13,52 @@ class _LoginScreenState extends State { final _loginScreen = GlobalKey(); final TextEditingController usernameController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); + bool isLoading = false; + + @override + void dispose() { + usernameController.dispose(); + passwordController.dispose(); + super.dispose(); + } + + Future _login() async { + if (!_loginScreen.currentState!.validate()) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Por favor, preencha todos os campos')), + ); + return; + } + + setState(() { + isLoading = true; + }); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Processando dados')), + ); + + bool success = await authService.login( + usernameController.text, + passwordController.text, + ); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + setState(() { + isLoading = false; + }); + + if (success) { + if (!mounted) return; + Navigator.of(context).pushReplacementNamed('/homeScreen'); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Login falhou, verifique suas credenciais')), + ); + } + } @override Widget build(BuildContext context) { @@ -38,15 +84,16 @@ class _LoginScreenState extends State { ), ), Expanded( - flex: 6, - child: Container( - padding: const EdgeInsets.fromLTRB(25.0, 50.0, 25.0, 20.0), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: - BorderRadius.only(topLeft: Radius.circular(50.0))), - child: SingleChildScrollView( - child: Form( + flex: 6, + child: Container( + padding: const EdgeInsets.fromLTRB(25.0, 50.0, 25.0, 20.0), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.only(topLeft: Radius.circular(50.0)), + ), + child: SingleChildScrollView( + child: Form( key: _loginScreen, child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -54,39 +101,41 @@ class _LoginScreenState extends State { const Text( 'Login', style: TextStyle( - fontSize: 30.0, - fontWeight: FontWeight.w900, - color: Colors.black), + fontSize: 30.0, + fontWeight: FontWeight.w900, + color: Colors.black, + ), ), const SizedBox(height: 35), TextFormField( - controller: usernameController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Por favor, insira um username válido'; - } - return null; - }, - decoration: InputDecoration( - label: const Text('Username'), - labelStyle: const TextStyle(color: Colors.black), - hintText: 'Insira o seu username', - hintStyle: const TextStyle( - color: Colors.black, - ), - border: OutlineInputBorder( - borderSide: const BorderSide( - color: Color.fromARGB(255, 39, 38, 38), - ), - borderRadius: BorderRadius.circular(10), + controller: usernameController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Por favor, insira um username válido'; + } + return null; + }, + decoration: InputDecoration( + label: const Text('Username'), + labelStyle: const TextStyle(color: Colors.black), + hintText: 'Insira o seu username', + hintStyle: const TextStyle( + color: Colors.black, + ), + border: OutlineInputBorder( + borderSide: const BorderSide( + color: Color.fromARGB(255, 39, 38, 38), ), - enabledBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Color.fromARGB(255, 0, 0, 0), - ), - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(10), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Color.fromARGB(255, 0, 0, 0), ), - )), + borderRadius: BorderRadius.circular(10), + ), + ), + ), const SizedBox(height: 20), TextFormField( controller: passwordController, @@ -126,14 +175,15 @@ class _LoginScreenState extends State { Row( children: [ Checkbox( - value: rememberMe, - onChanged: (bool? value) { - setState(() { - rememberMe = value!; - }); - }, - activeColor: - const Color.fromARGB(255, 12, 78, 170)), + value: rememberMe, + onChanged: (bool? value) { + setState(() { + rememberMe = value!; + }); + }, + activeColor: + const Color.fromARGB(255, 12, 78, 170), + ), const Text( 'Manter conectado', style: TextStyle( @@ -158,83 +208,58 @@ class _LoginScreenState extends State { width: 200, height: 50, child: ElevatedButton( - onPressed: () async { - if (_loginScreen.currentState!.validate()) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Processando dados')), - ); - - bool success = await authService.login( - usernameController.text, - passwordController.text); - - //bool isAuth = await authService.checkAuthenticated(); - //print(isAuth); - - ScaffoldMessenger.of(context) - .hideCurrentSnackBar(); - - if (success) { - Navigator.of(context) - .pushReplacementNamed('/homeScreen'); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Login falhou, verifique suas credenciais')), - ); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Por favor, preencha todos os campos')), - ); - } - }, + onPressed: isLoading ? null : _login, style: ElevatedButton.styleFrom( - elevation: 6, - backgroundColor: - const Color.fromARGB(255, 244, 248, 0), - foregroundColor: const Color(0xff123c75), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - )), - child: const Text( - 'Login', - style: TextStyle( - fontSize: 20, fontWeight: FontWeight.bold), + elevation: 6, + backgroundColor: + const Color.fromARGB(255, 244, 248, 0), + foregroundColor: const Color(0xff123c75), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), + child: isLoading + ? const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Color(0xff123c75)), + ) + : const Text( + 'Login', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold), + ), ), ), const SizedBox(height: 30), Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Não tem uma conta? ', + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Não tem uma conta? ', + style: TextStyle( + color: Color.fromARGB(255, 0, 0, 0)), + ), + GestureDetector( + onTap: () { + Navigator.pushNamed(context, '/registerScreen'); + }, + child: const Text( + 'Registre-se', style: TextStyle( - color: Color.fromARGB(255, 0, 0, 0)), - ), - GestureDetector( - onTap: () { - Navigator.pushNamed( - context, '/registerScreen'); - }, - child: const Text( - 'Registre-se', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Color(0xff123c75), - ), + fontWeight: FontWeight.bold, + color: Color(0xff123c75), ), ), - ]) + ), + ], + ), ], ), - )), - )) + ), + ), + ), + ), ], ), ), diff --git a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart index 872ea7e9..e2738057 100644 --- a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart @@ -38,6 +38,7 @@ class _AddEquipmentScreenState extends State { final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedLocation; + String? _selectedTypeToDelete; List equipmentTypes = [ 'Selecione um equipamento', @@ -111,28 +112,24 @@ class _AddEquipmentScreenState extends State { } void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de equipamento'), - content: DropdownButton( - value: _selectedType, - onChanged: (String? newValue) { - setState(() { - equipmentTypes.remove(newValue); - _selectedType = - equipmentTypes.isNotEmpty ? equipmentTypes.first : null; - }); - Navigator.of(context).pop(); - }, - items: equipmentTypes.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -140,12 +137,111 @@ class _AddEquipmentScreenState extends State { Navigator.of(context).pop(); }, ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentNameController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + _selectedType == null || + _selectedLocation == null || + _selectedType == 'Selecione um equipamento' || + _selectedLocation == 'Selecione a localização') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Nome:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentNameController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? ''), + const SizedBox(height: 10), + const Text('Localização:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedLocation ?? ''), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), ], ); }, ); } + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/equipmentScreen', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -214,7 +310,12 @@ class _AddEquipmentScreenState extends State { ), IconButton( icon: const Icon(Icons.delete), - onPressed: _deleteEquipmentType, + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, ), ], ), @@ -330,9 +431,7 @@ class _AddEquipmentScreenState extends State { MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ))), - onPressed: () { - // Implementar a lógica de adicionar equipamento - }, + onPressed: _showConfirmationDialog, child: const Text( 'ADICIONAR EQUIPAMENTO', style: TextStyle( @@ -349,6 +448,63 @@ class _AddEquipmentScreenState extends State { ); } + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + Widget _buildStyledDropdown({ required List items, String? value, @@ -369,7 +525,10 @@ class _AddEquipmentScreenState extends State { items: items.map>((String value) { return DropdownMenuItem( value: value, - child: Text(value), + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), enabled: value != items.first, ); }).toList(), diff --git a/frontend/sige_ie/lib/core/feature/register/register.dart b/frontend/sige_ie/lib/core/feature/register/register.dart index 50be23f9..34da9010 100644 --- a/frontend/sige_ie/lib/core/feature/register/register.dart +++ b/frontend/sige_ie/lib/core/feature/register/register.dart @@ -11,12 +11,71 @@ class RegisterScreen extends StatefulWidget { class _RegisterScreenState extends State { UserService userService = UserService(); bool terms = true; + bool isLoading = false; final _registerScreen = GlobalKey(); final usernameController = TextEditingController(); final nameController = TextEditingController(); final passwordController = TextEditingController(); final emailController = TextEditingController(); + Future _register() async { + if (!_registerScreen.currentState!.validate()) { + return; + } + + if (!terms) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Por Favor, concorde com o processamento de dados pessoais'), + ), + ); + return; + } + + setState(() { + isLoading = true; + }); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Processando Dados')), + ); + + final user = UserRequestModel( + username: usernameController.text, + firstname: nameController.text, + password: passwordController.text, + email: emailController.text, + ); + + bool success = await userService.register(user); + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + + setState(() { + isLoading = false; + }); + + if (success) { + FocusScope.of(context).unfocus(); + Navigator.of(context).pushReplacementNamed('/loginScreen'); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Registro falhou, por favor tente novamente.'), + ), + ); + } + } + + @override + void dispose() { + usernameController.dispose(); + nameController.dispose(); + passwordController.dispose(); + emailController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -89,8 +148,6 @@ class _RegisterScreenState extends State { }, ), const SizedBox(height: 20), - - // Campo de nome e decoração da borda de inserção dos dados TextFormField( controller: nameController, decoration: InputDecoration( @@ -112,7 +169,6 @@ class _RegisterScreenState extends State { ), borderRadius: BorderRadius.circular(10), ), - // Outras propriedades... ), validator: (value) { if (value == null || value.isEmpty) { @@ -123,34 +179,34 @@ class _RegisterScreenState extends State { ), const SizedBox(height: 20), TextFormField( - controller: emailController, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Insira um email valido'; - } - return null; - }, - decoration: InputDecoration( - label: const Text('Email'), - labelStyle: - const TextStyle(color: Colors.black), - hintText: 'Insira o Email', - hintStyle: const TextStyle( - color: Colors.black, - ), - border: OutlineInputBorder( - borderSide: const BorderSide( - color: Color.fromARGB(255, 39, 38, 38), - ), - borderRadius: BorderRadius.circular(10), + controller: emailController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Insira um email valido'; + } + return null; + }, + decoration: InputDecoration( + label: const Text('Email'), + labelStyle: const TextStyle(color: Colors.black), + hintText: 'Insira o Email', + hintStyle: const TextStyle( + color: Colors.black, + ), + border: OutlineInputBorder( + borderSide: const BorderSide( + color: Color.fromARGB(255, 39, 38, 38), ), - enabledBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Color.fromARGB(255, 0, 0, 0), - ), - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(10), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Color.fromARGB(255, 0, 0, 0), ), - )), + borderRadius: BorderRadius.circular(10), + ), + ), + ), const SizedBox(height: 20), TextFormField( controller: passwordController, @@ -176,10 +232,11 @@ class _RegisterScreenState extends State { borderRadius: BorderRadius.circular(10), ), enabledBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Color.fromARGB(255, 0, 0, 0), - ), - borderRadius: BorderRadius.circular(10)), + borderSide: const BorderSide( + color: Color.fromARGB(255, 0, 0, 0), + ), + borderRadius: BorderRadius.circular(10), + ), ), ), const SizedBox(height: 20), @@ -216,14 +273,15 @@ class _RegisterScreenState extends State { Row( children: [ Checkbox( - value: terms, - onChanged: (bool? value) { - setState(() { - terms = value!; - }); - }, - activeColor: const Color.fromARGB( - 255, 12, 78, 170)), + value: terms, + onChanged: (bool? value) { + setState(() { + terms = value!; + }); + }, + activeColor: + const Color.fromARGB(255, 12, 78, 170), + ), const Text( 'Aceite os Termos', style: TextStyle( @@ -239,63 +297,27 @@ class _RegisterScreenState extends State { width: 200, height: 50, child: ElevatedButton( - onPressed: () async { - if (_registerScreen.currentState!.validate()) { - if (terms) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Processando Dados'), - ), - ); - - final user = UserRequestModel( - username: usernameController.text, - firstname: nameController.text, - password: passwordController.text, - email: emailController.text, - ); - - bool success = - await userService.register(user); - ScaffoldMessenger.of(context) - .hideCurrentSnackBar(); - - if (success) { - FocusScope.of(context).unfocus(); - Navigator.of(context) - .pushReplacementNamed('/loginScreen'); - } else { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Registro falhou, por favor tente novamente.'), - ), - ); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Por Favor, concorde com o processamento de dados pessoais'), - ), - ); - } - } - }, + onPressed: isLoading ? null : _register, style: ElevatedButton.styleFrom( - elevation: 6, - backgroundColor: - const Color.fromARGB(255, 244, 248, 0), - foregroundColor: const Color(0xff123c75), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - )), - child: const Text( - 'Registro', - style: TextStyle( - fontSize: 20, fontWeight: FontWeight.bold), + elevation: 6, + backgroundColor: + const Color.fromARGB(255, 244, 248, 0), + foregroundColor: const Color(0xff123c75), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), + child: isLoading + ? const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Color(0xff123c75)), + ) + : const Text( + 'Registro', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold), + ), ), ), const SizedBox(height: 30), diff --git a/frontend/sige_ie/lib/core/ui/facilities.dart b/frontend/sige_ie/lib/facilities/ui/facilities.dart similarity index 98% rename from frontend/sige_ie/lib/core/ui/facilities.dart rename to frontend/sige_ie/lib/facilities/ui/facilities.dart index 29151004..0b40d93a 100644 --- a/frontend/sige_ie/lib/core/ui/facilities.dart +++ b/frontend/sige_ie/lib/facilities/ui/facilities.dart @@ -458,7 +458,13 @@ class _FacilitiesPageState extends State { } else { return const Padding( padding: EdgeInsets.all(10.0), - child: Text("Nenhum local encontrado."), + child: Text( + 'Nenhum local encontrado.', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), ); } }, diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 30f39bbb..6300859b 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import '../../users/feature/profile.dart'; -import '../../core/ui/facilities.dart'; +import '../../facilities/ui/facilities.dart'; import '../../maps/feature/maps.dart'; import 'package:sige_ie/users/data/user_response_model.dart'; import 'package:sige_ie/users/data/user_service.dart'; diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 2cdbee78..19ea4317 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -105,7 +105,7 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /lowVoltage.'); - case '/equipamentScreen': + case '/equipmentScreen': if (settings.arguments is Map) { final args = settings.arguments as Map; final String? areaName = args['areaName']?.toString(); @@ -123,11 +123,11 @@ class MyApp extends StatelessWidget { )); } else { throw Exception( - 'Invalid arguments: One of areaName, localName, or localId is null in /equipamentScreen.'); + 'Invalid arguments: One of areaName, localName, or localId is null in /equipmentScreen.'); } } throw Exception( - 'Invalid route: Expected Map arguments for /equipamentScreen.'); + 'Invalid route: Expected Map arguments for /equipmentScreen.'); default: return MaterialPageRoute( From b76220a45e84316d47992c4958a6338ee9c40796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Wed, 22 May 2024 14:58:26 -0300 Subject: [PATCH 124/351] Backend: recupera senha --- api/sigeie/settings.py | 8 ++- api/users/templates/password_reset_email.html | 12 ++++ api/users/urls.py | 4 +- api/users/views.py | 56 ++++++++++++++++++- 4 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 api/users/templates/password_reset_email.html diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index 4cd19ec4..ce84ca99 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -39,7 +39,7 @@ TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [BASE_DIR / 'users.templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -131,3 +131,9 @@ "x-csrftoken", "x-requested-with", ) + +EMAIL_HOST = 'sandbox.smtp.mailtrap.io' +EMAIL_HOST_USER = '1ae45caf98a722' +EMAIL_HOST_PASSWORD = 'c6aa9de24a4720' +EMAIL_PORT = '465' + diff --git a/api/users/templates/password_reset_email.html b/api/users/templates/password_reset_email.html new file mode 100644 index 00000000..ed81c32a --- /dev/null +++ b/api/users/templates/password_reset_email.html @@ -0,0 +1,12 @@ +{% autoescape off %} +Olá {{ user.username }}, + +Você solicitou a redefinição de senha para sua conta. Insira o código abaixo para redefinir sua senha: + +{{ token }} + +Se você não solicitou a redefinição de senha, ignore este e-mail. + +Atenciosamente, +Equipe do seu site +{% endautoescape %} diff --git a/api/users/urls.py b/api/users/urls.py index 3663dd7c..e90b8918 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -1,7 +1,7 @@ # urls.py from django.urls import path from rest_framework.routers import DefaultRouter -from .views import GetCSRFToken, GetSessionCookie, CheckAuthenticatedView, UserCreateView, AuthenticatedUserView, UserDetailView, LoginView, LogoutView +from .views import GetCSRFToken, GetSessionCookie, CheckAuthenticatedView, UserCreateView, AuthenticatedUserView, UserDetailView, LoginView, LogoutView, Email, PasswordResetConfirmView from django.urls import include urlpatterns = [ @@ -13,4 +13,6 @@ path('users//', UserDetailView.as_view(), name='user_detail'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), + path('recover/', Email.as_view()), + path('reset/', PasswordResetConfirmView.as_view()) ] diff --git a/api/users/views.py b/api/users/views.py index a78591c9..57038cff 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -9,10 +9,20 @@ from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect, csrf_exempt from .permissions import IsOwner from .serializers import UserSerializer, UserLoginSerializer, UserUpdateSerializer - from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout from django.utils.decorators import method_decorator +from django.contrib.auth.tokens import default_token_generator +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode +from django.template.loader import render_to_string +from django.utils.encoding import force_bytes +from django.core.mail import EmailMessage +from django.contrib.sites.shortcuts import get_current_site +from django.utils.http import urlsafe_base64_decode +from django.contrib.auth import get_user_model +from django.shortcuts import render, redirect +from django.contrib import messages + @method_decorator(ensure_csrf_cookie, name='dispatch') class GetCSRFToken(APIView): @@ -76,3 +86,47 @@ class LogoutView(APIView): def post(self, request, format=None): logout(request) return Response({'message': 'Logout successful'}, status=status.HTTP_200_OK) + +class Email(APIView): + permission_classes = [] + + def post(self, request, *args, **kwargs): + email = request.data.get('email') + user = User.objects.filter(email=email).first() + if user is not None: + token = default_token_generator.make_token(user) + mail_subject = 'Redefinição de senha' + message = render_to_string('password_reset_email.html', { + 'user': user, + 'token': token, + }) + email = EmailMessage(mail_subject, message, to=[email]) + email.send() + return Response({'message': 'Email de redefinição de senha enviado'}, status=status.HTTP_200_OK) + else: + return Response({'message': 'Usuário não encontrado'}, status=status.HTTP_404_NOT_FOUND) + + + +class PasswordResetConfirmView(APIView): + permission_classes = [] + def post(self, request): + try: + email = request.data.get("email") + token = request.data.get("token") + + user = get_user_model().objects.get(email=email) + except (TypeError, ValueError, OverflowError, get_user_model().DoesNotExist): + user = None + + if user is not None and default_token_generator.check_token(user, token): + new_password = request.data.get('new_password') + confirm_password = request.data.get('confirm_password') + if new_password == confirm_password: + user.set_password(new_password) + user.save() + return Response({'message': 'Senha redefinida com sucesso'}, status=status.HTTP_200_OK) + else: + return Response({'message': 'As senhas não correspondem'}, status=status.HTTP_400_BAD_REQUEST) + else: + return Response({'message': 'O link de redefinição de senha é inválido'}, status=status.HTTP_400_BAD_REQUEST) From 7d1927c80c98e73beed7e81bc440b920ad785ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Wed, 22 May 2024 15:03:23 -0300 Subject: [PATCH 125/351] backend: ajuste --- api/users/urls.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/users/urls.py b/api/users/urls.py index e90b8918..d636ccc2 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -1,6 +1,5 @@ # urls.py from django.urls import path -from rest_framework.routers import DefaultRouter from .views import GetCSRFToken, GetSessionCookie, CheckAuthenticatedView, UserCreateView, AuthenticatedUserView, UserDetailView, LoginView, LogoutView, Email, PasswordResetConfirmView from django.urls import include From 90bb207ec7ad171b9c173a0cd24c3e75a53e7374 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 22 May 2024 20:39:40 -0300 Subject: [PATCH 126/351] =?UTF-8?q?Melhoramento=20na=20jornada=20do=20usu?= =?UTF-8?q?=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/areas/feature/register/new_area.dart | 9 +- .../core/feature/manage/EquipmentScreen.dart | 157 ++++++++++-------- .../feature/manage/addEquipmentScreen.dart | 2 +- .../lib/core/feature/manage/lowVoltage.dart | 131 --------------- .../feature/manage/manageEquipmentScreen.dart | 122 -------------- .../feature/manage/systemConfiguration.dart | 121 ++++++++++---- .../sige_ie/lib/facilities/ui/facilities.dart | 4 +- frontend/sige_ie/lib/home/ui/home.dart | 13 +- frontend/sige_ie/lib/main.dart | 33 ++-- .../sige_ie/lib/users/data/user_service.dart | 11 +- 10 files changed, 212 insertions(+), 391 deletions(-) delete mode 100644 frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart delete mode 100644 frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart diff --git a/frontend/sige_ie/lib/areas/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart index e3ef26df..6b26ca98 100644 --- a/frontend/sige_ie/lib/areas/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -37,7 +37,7 @@ class _AreaLocationState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('${widget.localName} - Sala', + child: Text('${widget.localName} - Área', style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -150,17 +150,20 @@ class _AreaLocationState extends State { ), ), onPressed: () { + Navigator.of(context) + .popUntil((route) => route.isFirst); Navigator.pushReplacementNamed( context, '/homeScreen', + arguments: {'initialPage': 1}, ); }, child: const Text('ENCERRAR'), ), ElevatedButton( style: ElevatedButton.styleFrom( - foregroundColor: AppColors.sigeIeBlue, - backgroundColor: AppColors.sigeIeYellow, + foregroundColor: AppColors.lightText, + backgroundColor: AppColors.sigeIeBlue, minimumSize: const Size(150, 50), textStyle: const TextStyle( fontWeight: FontWeight.bold, diff --git a/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart index 01b6919b..61d723e9 100644 --- a/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/core/feature/manage/addEquipmentScreen.dart'; -import 'package:sige_ie/core/feature/manage/manageEquipmentScreen.dart'; class EquipmentScreen extends StatelessWidget { final String areaName; @@ -31,22 +30,45 @@ class EquipmentScreen extends StatelessWidget { ); } - void navigateToViewEquipment(BuildContext context) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ViewEquipmentScreen( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, - ), - ), - ); - } - @override Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle; + switch (categoryNumber) { + case 1: + systemTitle = 'ILUMINAÇÃO'; + break; + case 2: + systemTitle = 'CARGAS ELÉTRICAS'; + break; + case 3: + systemTitle = 'LINHAS ELÉTRICAS'; + break; + case 4: + systemTitle = 'CIRCUITOS'; + break; + case 5: + systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; + break; + case 6: + systemTitle = 'CABEAMENTO ESTRUTURADO'; + break; + case 7: + systemTitle = 'DESCARGAS ATMOSFÉRICAS'; + break; + case 8: + systemTitle = 'ALARME DE INCÊNDIO'; + break; + case 9: + systemTitle = 'REFRIGERAÇÃO'; + break; + default: + systemTitle = 'SISTEMA DESCONHECIDO'; + } + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -66,64 +88,59 @@ class EquipmentScreen extends StatelessWidget { }, ), ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), ), - child: Center( - child: Text(areaName, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), ), - ), - const SizedBox(height: 150), - EquipmentButton( - title: 'EQUIPAMENTOS NA SALA', - onPressed: () => navigateToAddEquipment(context), - ), - EquipmentButton( - title: 'GERENCIAR EQUIPAMENTOS', - onPressed: () => navigateToViewEquipment(context), - ), - ], - ), - ); - } -} - -class EquipmentButton extends StatelessWidget { - final String title; - final VoidCallback onPressed; - - const EquipmentButton( - {super.key, required this.title, required this.onPressed}); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 25)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), + ], ), - onPressed: onPressed, - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), ), ); } diff --git a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart index e2738057..f4ff2d1b 100644 --- a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart @@ -268,7 +268,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Equipamentos na sala', + child: Text('Adicionar equipamentos ', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, diff --git a/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart b/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart deleted file mode 100644 index 1d3e7637..00000000 --- a/frontend/sige_ie/lib/core/feature/manage/lowVoltage.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; - -class LowVoltageScreen extends StatefulWidget { - final String areaName; - final List categoryNumbers; - final String localName; - final int? localId; - - const LowVoltageScreen({ - super.key, - required this.areaName, - required this.categoryNumbers, - required this.localName, - this.localId, - }); - - @override - _LowVoltageScreenState createState() => _LowVoltageScreenState(); -} - -class _LowVoltageScreenState extends State { - void navigateToEquipmentScreen(int categoryNumber) { - if (widget.localId != null) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => EquipmentScreen( - areaName: widget.areaName, - localName: widget.localName, - localId: widget.localId!, - categoryNumber: categoryNumber, - ), - ), - ); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text('${widget.areaName} - Baixa Tensão', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - const Padding(padding: EdgeInsets.all(15.0)), - OptionButton( - title: 'ILUMINAÇÃO', - onPressed: () => - navigateToEquipmentScreen(widget.categoryNumbers[0])), - OptionButton( - title: 'CARGAS ELÉTRICAS', - onPressed: () => - navigateToEquipmentScreen(widget.categoryNumbers[1])), - OptionButton( - title: 'LINHAS ELÉTRICAS', - onPressed: () => - navigateToEquipmentScreen(widget.categoryNumbers[2])), - OptionButton( - title: 'CIRCUITOS', - onPressed: () => - navigateToEquipmentScreen(widget.categoryNumbers[3])), - OptionButton( - title: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => - navigateToEquipmentScreen(widget.categoryNumbers[4])), - ], - ), - ), - ); - } -} - -class OptionButton extends StatelessWidget { - final String title; - final VoidCallback onPressed; - - const OptionButton({ - super.key, - required this.title, - required this.onPressed, - }); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), - width: double.infinity, - child: ElevatedButton( - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 25)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: onPressed, - ), - ); - } -} diff --git a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart deleted file mode 100644 index b9a88b03..00000000 --- a/frontend/sige_ie/lib/core/feature/manage/manageEquipmentScreen.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; - -class ViewEquipmentScreen extends StatefulWidget { - final String areaName; - final String localName; - final int localId; - final int categoryNumber; - - const ViewEquipmentScreen( - {super.key, - required this.areaName, - required this.categoryNumber, - required this.localName, - required this.localId}); - - @override - _ViewEquipmentScreenState createState() => _ViewEquipmentScreenState(); -} - -class _ViewEquipmentScreenState extends State { - List equipmentList = [ - //Vazio para simular nenhum equipamento - ]; - - void navigateToEquipmentScreen() { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => EquipmentScreen( - areaName: widget.areaName, - categoryNumber: widget.categoryNumber, - localName: widget.localName, - localId: widget.localId, - ), - )); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - foregroundColor: Colors.white, - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: const Center( - child: Text( - 'Gerenciar equipamentos', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white), - ), - ), - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), - ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), - const SizedBox(height: 40), - Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: - MaterialStateProperty.all(const Size(175, 55)), - textStyle: MaterialStateProperty.all(const TextStyle( - fontSize: 15, fontWeight: FontWeight.bold)), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8))), - ), - onPressed: navigateToEquipmentScreen, - child: const Text( - 'SALVAR', - style: TextStyle( - fontSize: 16, fontWeight: FontWeight.bold), - )), - ), - const SizedBox(height: 50), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart index 99ab0bc1..30df88be 100644 --- a/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart +++ b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; -import 'package:sige_ie/core/feature/manage/lowVoltage.dart'; class SystemConfiguration extends StatefulWidget { final String areaName; @@ -29,16 +28,15 @@ class _SystemConfigurationState extends State { MaterialPageRoute( builder: (context) { switch (routeName) { - case '/lowVoltage': - return LowVoltageScreen( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumbers: category, - ); case '/structuredCabling': case '/atmosphericDischarges': case '/fireAlarm': + case '/lighting': + case '/electricLoads': + case '/electricLines': + case '/circuits': + case '/distributionBoard': + case '/cooling': return EquipmentScreen( areaName: areaName, localName: localName, @@ -88,47 +86,102 @@ class _SystemConfigurationState extends State { ), ), SystemButton( - title: 'BAIXA TENSÃO', - onPressed: () => navigateTo('/lowVoltage', widget.areaName, - widget.localName, widget.localId, [1, 2, 3, 4, 5])), + title: 'ALARME DE INCÊNDIO', + onPressed: () => navigateTo('/fireAlarm', widget.areaName, + widget.localName, widget.localId, 8)), SystemButton( title: 'CABEAMENTO ESTRUTURADO', onPressed: () => navigateTo('/structuredCabling', widget.areaName, widget.localName, widget.localId, 6)), + SystemButton( + title: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLoads', widget.areaName, + widget.localName, widget.localId, 2)), + SystemButton( + title: 'CIRCUITOS', + onPressed: () => navigateTo('/circuits', widget.areaName, + widget.localName, widget.localId, 4)), SystemButton( title: 'DESCARGAS ATMOSFÉRICAS', onPressed: () => navigateTo('/atmosphericDischarges', widget.areaName, widget.localName, widget.localId, 7)), SystemButton( - title: 'ALARME DE INCÊNDIO', - onPressed: () => navigateTo('/fireAlarm', widget.areaName, - widget.localName, widget.localId, 8)), + title: 'ILUMINAÇÃO', + onPressed: () => navigateTo('/lighting', widget.areaName, + widget.localName, widget.localId, 1)), + SystemButton( + title: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLines', widget.areaName, + widget.localName, widget.localId, 3)), + SystemButton( + title: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/distributionBoard', + widget.areaName, widget.localName, widget.localId, 5)), + SystemButton( + title: 'REFRIGERAÇÃO', + onPressed: () => navigateTo('/cooling', widget.areaName, + widget.localName, widget.localId, 9)), const SizedBox( height: 30, ), Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.warn), - foregroundColor: - MaterialStateProperty.all(AppColors.lightText), - minimumSize: MaterialStateProperty.all(const Size(175, 55)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: () { - Navigator.of(context).pushNamed('/arealocation', arguments: { - 'placeName': widget.localName, - 'placeId': widget.localId - }); - }, - child: const Text( - 'ENCERRAR', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.lightText, + backgroundColor: AppColors.warn, + minimumSize: const Size(150, 50), + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: () { + Navigator.of(context).popUntil((route) => route.isFirst); + Navigator.pushReplacementNamed( + context, + '/homeScreen', + arguments: {'initialPage': 1}, + ); + }, + child: const Text('ENCERRAR'), + ), + const SizedBox(width: 10), + ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + foregroundColor: + MaterialStateProperty.all(AppColors.lightText), + minimumSize: + MaterialStateProperty.all(const Size(150, 50)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: () { + Navigator.of(context).pushNamed('/arealocation', + arguments: { + 'placeName': widget.localName, + 'placeId': widget.localId + }); + }, + child: const Text( + 'SALAS', + style: + TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ], ), ), + const SizedBox( + height: 30, + ), ], ), ), diff --git a/frontend/sige_ie/lib/facilities/ui/facilities.dart b/frontend/sige_ie/lib/facilities/ui/facilities.dart index 0b40d93a..008b0436 100644 --- a/frontend/sige_ie/lib/facilities/ui/facilities.dart +++ b/frontend/sige_ie/lib/facilities/ui/facilities.dart @@ -389,7 +389,7 @@ class _FacilitiesPageState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Locais', + child: Text('Edificações', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -536,7 +536,7 @@ class FloorAreaWidget extends StatelessWidget { ), ListTile( leading: const Icon(Icons.add), - title: const Text('Adicionar andar ou sala'), + title: const Text('Adicionar sala'), onTap: onAddFloor, ), ], diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 6300859b..5e97f212 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -7,22 +7,25 @@ import 'package:sige_ie/users/data/user_response_model.dart'; import 'package:sige_ie/users/data/user_service.dart'; class HomePage extends StatefulWidget { - const HomePage({super.key}); + final int initialPage; + + const HomePage({super.key, this.initialPage = 0}); @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { - int _selectedIndex = 0; - PageController _pageController = PageController(); + late int _selectedIndex; + late PageController _pageController; UserService userService = UserService(); late UserResponseModel user; @override void initState() { super.initState(); - _pageController = PageController(); + _selectedIndex = widget.initialPage; + _pageController = PageController(initialPage: widget.initialPage); userService.fetchProfileData().then((fetchedUser) { setState(() { user = fetchedUser; @@ -129,7 +132,7 @@ class _HomePageState extends State { context, 'Registrar novo local', 'Registrar', () { Navigator.of(context).pushNamed('/newLocation'); }), - buildSmallRectangle(context, 'Comunidade', 'Gerenciar', () { + buildSmallRectangle(context, 'Equipes', 'Gerenciar', () { // Código aqui. }), const Spacer(), diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 19ea4317..dd73898b 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; +import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; -import 'package:sige_ie/core/feature/manage/lowVoltage.dart'; import 'package:sige_ie/core/feature/manage/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; @@ -34,10 +34,20 @@ class MyApp extends StatelessWidget { return MaterialPageRoute(builder: (context) => const LoginScreen()); case '/first': return MaterialPageRoute(builder: (context) => const FirstScreen()); + case '/facilities': + return MaterialPageRoute( + builder: (context) => const FacilitiesPage()); case '/registerScreen': return MaterialPageRoute( builder: (context) => const RegisterScreen()); case '/homeScreen': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final int initialPage = args['initialPage'] ?? 0; + return MaterialPageRoute( + builder: (context) => HomePage(initialPage: initialPage), + ); + } return MaterialPageRoute(builder: (context) => const HomePage()); case '/MapsPage': return MaterialPageRoute(builder: (context) => const MapsPage()); @@ -83,27 +93,6 @@ class MyApp extends StatelessWidget { } throw Exception( 'Invalid route: Expected Map arguments for /systemLocation.'); - case '/lowVoltage': - if (settings.arguments is Map) { - final args = settings.arguments as Map; - final String? areaName = args['areaName']?.toString(); - final String? localName = args['localName']?.toString(); - final int? localId = args['localId']; - if (areaName != null && localName != null && localId != null) { - return MaterialPageRoute( - builder: (context) => LowVoltageScreen( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumbers: const [], - )); - } else { - throw Exception( - 'Invalid arguments: One of areaName, localName, or localId is null in /lowVoltage.'); - } - } - throw Exception( - 'Invalid route: Expected Map arguments for /lowVoltage.'); case '/equipmentScreen': if (settings.arguments is Map) { diff --git a/frontend/sige_ie/lib/users/data/user_service.dart b/frontend/sige_ie/lib/users/data/user_service.dart index 263dab0f..7a17470b 100644 --- a/frontend/sige_ie/lib/users/data/user_service.dart +++ b/frontend/sige_ie/lib/users/data/user_service.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; @@ -75,9 +76,17 @@ class UserService { } else { throw Exception('Falha ao carregar dados do perfil'); } + } on http.ClientException catch (e) { + print('Erro ao carregar dados do perfil (ClientException): $e'); + throw Exception( + 'Erro na conexão com o servidor. Por favor, tente novamente mais tarde.'); + } on SocketException catch (e) { + print('Erro ao carregar dados do perfil (SocketException): $e'); + throw Exception('Erro de rede. Verifique sua conexão com a internet.'); } catch (e) { print('Erro ao carregar dados do perfil: $e'); - rethrow; + throw Exception( + 'Ocorreu um erro inesperado. Por favor, tente novamente.'); } } } From b61cf832be1d76e8b467b5796c30f122e16a0ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 23 May 2024 09:00:49 -0300 Subject: [PATCH 127/351] =?UTF-8?q?backend:=20cria=20refrigera=C3=A7=C3=A3?= =?UTF-8?q?o=20e=20foto=20de=20local?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/models.py | 8 ++++++++ api/equipments/serializers.py | 6 ++++++ api/equipments/urls.py | 4 +++- api/equipments/views.py | 23 +++++++++++++++++++++++ api/places/models.py | 1 + 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/api/equipments/models.py b/api/equipments/models.py index a59c5f4e..15a1ceaf 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -103,3 +103,11 @@ class IluminationEquipment(models.Model): class Meta: db_table = 'equipments_ilumination_equipments' + +class RefrigerationEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + equipment_detail = models.OneToOneField(EquipmentDetail, on_delete=models.CASCADE, null=True) + system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) + + class Meta: + db_table = 'refrigeration_equipments' \ No newline at end of file diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 8259d415..36fe33e4 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -67,3 +67,9 @@ class IluminationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSeriali class Meta: model = IluminationEquipment fields = '__all__' + +class RefrigerationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): + + class Meta: + model = RefrigerationEquipment + fields = '__all__' diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 3a7eb09c..beb1d19d 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -24,6 +24,8 @@ path('electrical-loads/', ElectricalLoadEquipmentList.as_view()), path('electrical-loads//', ElectricalLoadEquipmentDetail.as_view()), path('iluminations/', IluminationEquipmentList.as_view()), - path('iluminations//', IluminationEquipmentDetail.as_view()) + path('iluminations//', IluminationEquipmentDetail.as_view()), + path('refrigeration/', EquipmentDetailList.as_view()), + path('refrigeration//', EquipmentDetailDetail.as_view()) ] diff --git a/api/equipments/views.py b/api/equipments/views.py index 00e0983f..c738d095 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -255,4 +255,27 @@ def create(self, request, *args, **kwargs): class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + +class RefrigerationEquipmentList(generics.ListCreateAPIView): + queryset = RefrigerationEquipment.objects.all() + serializer_class = RefrigerationEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return RefrigerationEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + data = request.data.copy() + data["system"] = 1 + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + +class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = RefrigerationEquipment.objects.all() + serializer_class = RefrigerationEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] \ No newline at end of file diff --git a/api/places/models.py b/api/places/models.py index 84e28390..0dd7feef 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -8,6 +8,7 @@ class Place(models.Model): place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) lon = models.FloatField(null=True) lat = models.FloatField(null=True) + photo = models.ImageField(null=True, upload_to='place_photos/') def __str__(self): return self.name From b285c7091fa276b35aed12508a21fdafc85dcd46 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 23 May 2024 11:21:40 -0300 Subject: [PATCH 128/351] backend: cria photos para equipamentos --- ...ve_equipmentdetail_description_and_more.py | 45 +++++++++++++++++++ api/equipments/models.py | 12 +++-- api/equipments/permissions.py | 4 +- api/equipments/serializers.py | 6 +++ api/equipments/urls.py | 2 + api/equipments/views.py | 23 ++++++++-- api/places/migrations/0028_place_photo.py | 18 ++++++++ 7 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 api/equipments/migrations/0019_remove_equipmentdetail_description_and_more.py create mode 100644 api/places/migrations/0028_place_photo.py diff --git a/api/equipments/migrations/0019_remove_equipmentdetail_description_and_more.py b/api/equipments/migrations/0019_remove_equipmentdetail_description_and_more.py new file mode 100644 index 00000000..29fded19 --- /dev/null +++ b/api/equipments/migrations/0019_remove_equipmentdetail_description_and_more.py @@ -0,0 +1,45 @@ +# Generated by Django 4.2 on 2024-05-23 14:16 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('systems', '0008_delete_atmosphericdischargesystem'), + ('places', '0028_place_photo'), + ('equipments', '0018_personalequipmenttype'), + ] + + operations = [ + migrations.RemoveField( + model_name='equipmentdetail', + name='description', + ), + migrations.RemoveField( + model_name='equipmentdetail', + name='photo', + ), + migrations.CreateModel( + name='RefrigerationEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('area', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area')), + ('equipment_detail', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ('system', models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, to='systems.system')), + ], + options={ + 'db_table': 'refrigeration_equipments', + }, + ), + migrations.CreateModel( + name='EquipmentPhoto', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('photo', models.ImageField(null=True, upload_to='equipment_photos/')), + ('description', models.CharField(max_length=50)), + ('equipment_detail', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmentdetail')), + ], + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index 15a1ceaf..d3e34c6a 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -29,16 +29,20 @@ class Meta: class EquipmentDetail(models.Model): place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) + equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) + + class Meta: + db_table = 'equipments_equipment_details' + +class EquipmentPhoto(models.Model): + photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50) - equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) + equipment_detail = models.ForeignKey(EquipmentDetail, on_delete=models.CASCADE, null=True) def __str__(self): return self.description - class Meta: - db_table = 'equipments_equipment_details' - class FireAlarmEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 330559b4..94715900 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -2,7 +2,7 @@ from places.models import Area -class IsEquipmentTypeOwner(BasePermission): +class IsOwner(BasePermission): def has_object_permission(self, request, view, obj): if obj.place_owner == request.user.placeowner: return True @@ -11,7 +11,7 @@ def has_object_permission(self, request, view, obj): class IsEquipmentDetailOwner(BasePermission): def has_object_permission(self, request, view, obj): - if obj.place_owner == request.user.placeowner: + if obj.equipment_detail.place_owner == request.user.placeowner: return True else: return False diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 36fe33e4..ff7a96e0 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -20,6 +20,12 @@ class Meta: model = EquipmentDetail fields = '__all__' +class EquipmentPhotoSerializer(serializers.ModelSerializer): + + class Meta: + model = EquipmentPhoto + fields = '__all__' + class FireAlarmEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: diff --git a/api/equipments/urls.py b/api/equipments/urls.py index beb1d19d..967ce8db 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -9,6 +9,8 @@ path('equipment-types//', EquipmentTypeDetail.as_view()), path('equipments/', EquipmentDetailList.as_view()), path('equipments//', EquipmentDetailDetail.as_view()), + path('equipment-photos/', EquipmentPhotoList.as_view()), + path('equipment-photos//', EquipmentPhotoDetail.as_view()), path('atmospheric-discharges/', AtmosphericDischargeEquipmentList.as_view()), path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()), path('fire-alarms/', FireAlarmEquipmentList.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index c738d095..54ef92e8 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -10,11 +10,11 @@ class PersonalEquipmentTypeCreate(generics.CreateAPIView): queryset = PersonalEquipmentType.objects.all() serializer_class = PersonalEquipmentTypeSerializer - permission_classes = [IsEquipmentTypeOwner, IsAuthenticated] + permission_classes = [IsOwner, IsAuthenticated] def create(self, request, *args, **kwargs): - if(IsEquipmentTypeOwner): + if(IsOwner): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(place_owner=request.user.placeowner) @@ -52,7 +52,7 @@ class EquipmentTypeDetail(generics.RetrieveAPIView): class EquipmentDetailList(generics.ListCreateAPIView): queryset = EquipmentDetail.objects.all() serializer_class = EquipmentDetailSerializer - permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] + permission_classes = [IsOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -61,7 +61,7 @@ def get_queryset(self): def create(self, request, *args, **kwargs): - if(IsEquipmentDetailOwner): + if(IsOwner): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(place_owner=request.user.placeowner) @@ -71,6 +71,21 @@ def create(self, request, *args, **kwargs): class EquipmentDetailDetail(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentDetail.objects.all() serializer_class = EquipmentDetailSerializer + permission_classes = [IsOwner, IsAuthenticated] + +class EquipmentPhotoList(generics.ListCreateAPIView): + queryset = EquipmentPhoto.objects.all() + serializer_class = EquipmentPhotoSerializer + permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + queryset = super().get_queryset() + return queryset.filter(equipment_detail__place_owner__user=user) + +class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = EquipmentPhoto.objects.all() + serializer_class = EquipmentPhotoSerializer permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] class FireAlarmEquipmentList(generics.ListCreateAPIView): diff --git a/api/places/migrations/0028_place_photo.py b/api/places/migrations/0028_place_photo.py new file mode 100644 index 00000000..52a0e5ac --- /dev/null +++ b/api/places/migrations/0028_place_photo.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2024-05-23 14:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0027_alter_area_floor'), + ] + + operations = [ + migrations.AddField( + model_name='place', + name='photo', + field=models.ImageField(null=True, upload_to='place_photos/'), + ), + ] From f01b5719f831ffe5a6807fe0c4f02e49d7270fc0 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 23 May 2024 11:36:51 -0300 Subject: [PATCH 129/351] =?UTF-8?q?backend:=20cria=20listagem=20de=20equip?= =?UTF-8?q?amento=20de=20alarme,=20descargas=20e=20cabeamento=20pelo=20id?= =?UTF-8?q?=20da=20=C3=A1rea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/urls.py | 7 +++++-- api/equipments/views.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 967ce8db..36513de2 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -11,11 +11,14 @@ path('equipments//', EquipmentDetailDetail.as_view()), path('equipment-photos/', EquipmentPhotoList.as_view()), path('equipment-photos//', EquipmentPhotoDetail.as_view()), - path('atmospheric-discharges/', AtmosphericDischargeEquipmentList.as_view()), - path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()), path('fire-alarms/', FireAlarmEquipmentList.as_view()), + path('fire-alarms/by-area//', FireAlarmEquipmentByAreaList.as_view()), path('fire-alarms//', FireAlarmEquipmentDetail.as_view()), + path('atmospheric-discharges/', AtmosphericDischargeEquipmentList.as_view()), + path('atmospheric-discharges/by-area//', AtmosphericDischargeEquipmentByAreaList.as_view()), + path('atmospheric-discharges//', AtmosphericDischargeEquipmentDetail.as_view()), path('structured-cabling/', StructuredCablingEquipmentList.as_view()), + path('structured-cabling/by-area//', StructuredCablingEquipmentByAreaList.as_view()), path('structured-cabling//', StructuredCablingEquipmentDetail.as_view()), path('distribution-boards/', DistributionBoardEquipmentList.as_view()), path('distribution-boards//', DistributionBoardEquipmentDetail.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index 54ef92e8..015cef96 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -106,6 +106,14 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) +class FireAlarmEquipmentByAreaList(generics.ListAPIView): + serializer_class = FireAlarmEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + area_id = self.kwargs['area_id'] + return FireAlarmEquipment.objects.filter(area_id=area_id) + class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer @@ -129,6 +137,14 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) +class AtmosphericDischargeEquipmentByAreaList(generics.ListAPIView): + serializer_class = AtmosphericDischargeEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + area_id = self.kwargs['area_id'] + return AtmosphericDischargeEquipment.objects.filter(area_id=area_id) + class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer @@ -152,6 +168,14 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) +class StructuredCablingEquipmentByAreaList(generics.ListAPIView): + serializer_class = StructuredCablingEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + area_id = self.kwargs['area_id'] + return StructuredCablingEquipment.objects.filter(area_id=area_id) + class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer From a6a1bdc7d5158205a86037148a7047d8dc8c24cf Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 23 May 2024 11:38:43 -0300 Subject: [PATCH 130/351] Mdifica equipamentos --- .../feature/equipment/EquipmentScreen.dart | 147 +++++ .../feature/equipment/addEquipmentScreen.dart | 538 ++++++++++++++++++ .../equipment/systemConfiguration.dart | 226 ++++++++ frontend/sige_ie/lib/main.dart | 4 +- 4 files changed, 913 insertions(+), 2 deletions(-) create mode 100644 frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart create mode 100644 frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart create mode 100644 frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart diff --git a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart new file mode 100644 index 00000000..270d0680 --- /dev/null +++ b/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/core/feature/equipment/addEquipmentScreen.dart'; + +class EquipmentScreen extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const EquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddEquipmentScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle; + switch (categoryNumber) { + case 1: + systemTitle = 'ILUMINAÇÃO'; + break; + case 2: + systemTitle = 'CARGAS ELÉTRICAS'; + break; + case 3: + systemTitle = 'LINHAS ELÉTRICAS'; + break; + case 4: + systemTitle = 'CIRCUITOS'; + break; + case 5: + systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; + break; + case 6: + systemTitle = 'CABEAMENTO ESTRUTURADO'; + break; + case 7: + systemTitle = 'DESCARGAS ATMOSFÉRICAS'; + break; + case 8: + systemTitle = 'ALARME DE INCÊNDIO'; + break; + case 9: + systemTitle = 'REFRIGERAÇÃO'; + break; + default: + systemTitle = 'SISTEMA DESCONHECIDO'; + } + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart new file mode 100644 index 00000000..f4ff2d1b --- /dev/null +++ b/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart @@ -0,0 +1,538 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; + +class ImageData { + File imageFile; + int id; + + ImageData(this.imageFile) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddEquipmentScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddEquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentNameController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedLocation; + String? _selectedTypeToDelete; + + List equipmentTypes = [ + 'Selecione um equipamento', + 'Eletroduto', + 'Eletrocalha', + 'Dimensão' + ]; + + @override + void dispose() { + _equipmentNameController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + setState(() { + final imageData = ImageData(File(pickedFile.path)); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + }); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentNameController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + _selectedType == null || + _selectedLocation == null || + _selectedType == 'Selecione um equipamento' || + _selectedLocation == 'Selecione a localização') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Nome:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentNameController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? ''), + const SizedBox(height: 10), + const Text('Localização:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedLocation ?? ''), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/equipmentScreen', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de equipamentos', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + if (newValue != 'Selecione um equipamento') { + setState(() { + _selectedType = newValue; + }); + } + }, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 30), + const Text('Nome do equipamento', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentNameController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Localização (Interno ou Externo)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: const [ + 'Selecione a localização', + 'Interno', + 'Externo' + ], + value: _selectedLocation, + onChanged: (newValue) { + if (newValue != 'Selecione a localização') { + setState(() { + _selectedLocation = newValue; + }); + } + }, + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + }) { + return Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: onChanged, + items: items.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + enabled: value != items.first, + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart new file mode 100644 index 00000000..f7de023d --- /dev/null +++ b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart @@ -0,0 +1,226 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/core/feature/equipment/EquipmentScreen.dart'; + +class SystemConfiguration extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const SystemConfiguration({ + super.key, + required this.areaName, + required this.localName, + required this.localId, + required this.categoryNumber, + }); + + @override + _SystemConfigurationState createState() => _SystemConfigurationState(); +} + +class _SystemConfigurationState extends State { + void navigateTo(String routeName, String areaName, String localName, + int localId, dynamic category) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + switch (routeName) { + case '/structuredCabling': + case '/atmosphericDischarges': + case '/fireAlarm': + case '/lighting': + case '/electricLoads': + case '/electricLines': + case '/circuits': + case '/distributionBoard': + case '/cooling': + return EquipmentScreen( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + default: + return Scaffold( + body: Center(child: Text('No route defined for $routeName')), + ); + } + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: AppColors.sigeIeBlue, + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text(widget.areaName, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + const Padding( + padding: EdgeInsets.all(30.0), + child: Text( + 'Quais sistemas deseja configurar?', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + SystemButton( + title: 'ALARME DE INCÊNDIO', + onPressed: () => navigateTo('/fireAlarm', widget.areaName, + widget.localName, widget.localId, 8)), + SystemButton( + title: 'CABEAMENTO ESTRUTURADO', + onPressed: () => navigateTo('/structuredCabling', + widget.areaName, widget.localName, widget.localId, 6)), + SystemButton( + title: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLoads', widget.areaName, + widget.localName, widget.localId, 2)), + SystemButton( + title: 'CIRCUITOS', + onPressed: () => navigateTo('/circuits', widget.areaName, + widget.localName, widget.localId, 4)), + SystemButton( + title: 'DESCARGAS ATMOSFÉRICAS', + onPressed: () => navigateTo('/atmosphericDischarges', + widget.areaName, widget.localName, widget.localId, 7)), + SystemButton( + title: 'ILUMINAÇÃO', + onPressed: () => navigateTo('/lighting', widget.areaName, + widget.localName, widget.localId, 1)), + SystemButton( + title: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLines', widget.areaName, + widget.localName, widget.localId, 3)), + SystemButton( + title: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/distributionBoard', + widget.areaName, widget.localName, widget.localId, 5)), + SystemButton( + title: 'REFRIGERAÇÃO', + onPressed: () => navigateTo('/cooling', widget.areaName, + widget.localName, widget.localId, 9)), + const SizedBox( + height: 30, + ), + Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.lightText, + backgroundColor: AppColors.warn, + minimumSize: const Size(150, 50), + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: () { + Navigator.of(context).popUntil((route) => route.isFirst); + Navigator.pushReplacementNamed( + context, + '/homeScreen', + arguments: {'initialPage': 1}, + ); + }, + child: const Text('SAIR DA SALA'), + ), + const SizedBox(width: 10), + ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + foregroundColor: + MaterialStateProperty.all(AppColors.lightText), + minimumSize: + MaterialStateProperty.all(const Size(150, 50)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: () { + Navigator.of(context).pushNamed('/arealocation', + arguments: { + 'placeName': widget.localName, + 'placeId': widget.localId + }); + }, + child: const Text( + 'CRIAR NOVA SALA', + style: + TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + const SizedBox( + height: 30, + ), + ], + ), + ), + ); + } +} + +class SystemButton extends StatelessWidget { + final String title; + final VoidCallback onPressed; + + const SystemButton({ + super.key, + required this.title, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + width: double.infinity, + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 25)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: onPressed, + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.w900)), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index dd73898b..de652cfd 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -6,8 +6,8 @@ import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; -import 'package:sige_ie/core/feature/manage/systemConfiguration.dart'; +import 'package:sige_ie/core/feature/equipment/EquipmentScreen.dart'; +import 'package:sige_ie/core/feature/equipment/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; import 'core/feature/login/login.dart'; From f4d84d11834b4516a7cdccee6b4c05cfbe18d52e Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 23 May 2024 11:44:19 -0300 Subject: [PATCH 131/351] Modifica textos de equipamentos --- .../lib/areas/feature/register/new_area.dart | 19 +- .../core/feature/manage/EquipmentScreen.dart | 147 ----- .../feature/manage/addEquipmentScreen.dart | 538 ------------------ .../feature/manage/systemConfiguration.dart | 226 -------- 4 files changed, 16 insertions(+), 914 deletions(-) delete mode 100644 frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart delete mode 100644 frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart delete mode 100644 frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart diff --git a/frontend/sige_ie/lib/areas/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart index 6b26ca98..d50ca7b8 100644 --- a/frontend/sige_ie/lib/areas/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -17,6 +17,20 @@ class AreaLocation extends StatefulWidget { class _AreaLocationState extends State { int? selectedFloor; final TextEditingController areaController = TextEditingController(); + final TextEditingController floorController = TextEditingController(); + + @override + void initState() { + super.initState(); + floorController.text = selectedFloor?.toString() ?? ''; + } + + @override + void dispose() { + areaController.dispose(); + floorController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -100,8 +114,7 @@ class _AreaLocationState extends State { borderRadius: BorderRadius.circular(10), ), child: TextFormField( - controller: TextEditingController( - text: selectedFloor?.toString()), + controller: floorController, keyboardType: TextInputType.number, decoration: const InputDecoration( border: InputBorder.none, @@ -114,7 +127,7 @@ class _AreaLocationState extends State { ), ), const SizedBox(height: 40), - const Text('Sala', + const Text('Área', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, diff --git a/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart deleted file mode 100644 index 61d723e9..00000000 --- a/frontend/sige_ie/lib/core/feature/manage/EquipmentScreen.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/manage/addEquipmentScreen.dart'; - -class EquipmentScreen extends StatelessWidget { - final String areaName; - final String localName; - final int categoryNumber; - final int localId; - - const EquipmentScreen({ - super.key, - required this.areaName, - required this.categoryNumber, - required this.localName, - required this.localId, - }); - - void navigateToAddEquipment(BuildContext context) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AddEquipmentScreen( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - - String systemTitle; - switch (categoryNumber) { - case 1: - systemTitle = 'ILUMINAÇÃO'; - break; - case 2: - systemTitle = 'CARGAS ELÉTRICAS'; - break; - case 3: - systemTitle = 'LINHAS ELÉTRICAS'; - break; - case 4: - systemTitle = 'CIRCUITOS'; - break; - case 5: - systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; - break; - case 6: - systemTitle = 'CABEAMENTO ESTRUTURADO'; - break; - case 7: - systemTitle = 'DESCARGAS ATMOSFÉRICAS'; - break; - case 8: - systemTitle = 'ALARME DE INCÊNDIO'; - break; - case 9: - systemTitle = 'REFRIGERAÇÃO'; - break; - default: - systemTitle = 'SISTEMA DESCONHECIDO'; - } - - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.pushReplacementNamed( - context, - '/systemLocation', - arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'categoryNumber': categoryNumber, - }, - ); - }, - ), - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text('$areaName - $systemTitle', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), - ), - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), - ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), - const SizedBox(height: 40), - ], - ), - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () => navigateToAddEquipment(context), - backgroundColor: AppColors.sigeIeYellow, - child: const Icon(Icons.add, color: AppColors.sigeIeBlue), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart deleted file mode 100644 index f4ff2d1b..00000000 --- a/frontend/sige_ie/lib/core/feature/manage/addEquipmentScreen.dart +++ /dev/null @@ -1,538 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'dart:io'; -import 'package:image_picker/image_picker.dart'; - -class ImageData { - File imageFile; - int id; - - ImageData(this.imageFile) : id = Random().nextInt(1000000); -} - -List _images = []; -Map> categoryImagesMap = {}; - -class AddEquipmentScreen extends StatefulWidget { - final String areaName; - final String localName; - final int localId; - final int categoryNumber; - - const AddEquipmentScreen({ - super.key, - required this.areaName, - required this.categoryNumber, - required this.localName, - required this.localId, - }); - - @override - _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); -} - -class _AddEquipmentScreenState extends State { - final _equipmentNameController = TextEditingController(); - final _equipmentQuantityController = TextEditingController(); - String? _selectedType; - String? _selectedLocation; - String? _selectedTypeToDelete; - - List equipmentTypes = [ - 'Selecione um equipamento', - 'Eletroduto', - 'Eletrocalha', - 'Dimensão' - ]; - - @override - void dispose() { - _equipmentNameController.dispose(); - _equipmentQuantityController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); - super.dispose(); - } - - Future _pickImage() async { - final picker = ImagePicker(); - try { - final pickedFile = await picker.pickImage(source: ImageSource.camera); - if (pickedFile != null) { - setState(() { - final imageData = ImageData(File(pickedFile.path)); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; - }); - } - } catch (e) { - print('Erro ao capturar a imagem: $e'); - } - } - - void _addNewEquipmentType() { - TextEditingController typeController = TextEditingController(); - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Adicionar novo tipo de equipamento'), - content: TextField( - controller: typeController, - decoration: const InputDecoration( - hintText: 'Digite o novo tipo de equipamento'), - ), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Adicionar'), - onPressed: () { - if (typeController.text.isNotEmpty) { - setState(() { - equipmentTypes.add(typeController.text); - }); - Navigator.of(context).pop(); - } - }, - ), - ], - ); - }, - ); - } - - void _deleteEquipmentType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: - Text('Selecione um tipo de equipamento válido para excluir.'), - ), - ); - return; - } - - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Excluir'), - onPressed: () { - setState(() { - equipmentTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } - - void _showConfirmationDialog() { - if (_equipmentNameController.text.isEmpty || - _equipmentQuantityController.text.isEmpty || - _selectedType == null || - _selectedLocation == null || - _selectedType == 'Selecione um equipamento' || - _selectedLocation == 'Selecione a localização') { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Por favor, preencha todos os campos.'), - ), - ); - return; - } - - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Confirmar Dados do Equipamento'), - content: SingleChildScrollView( - child: ListBody( - children: [ - const Text('Nome:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentNameController.text), - const SizedBox(height: 10), - const Text('Quantidade:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentQuantityController.text), - const SizedBox(height: 10), - const Text('Tipo:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? ''), - const SizedBox(height: 10), - const Text('Localização:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedLocation ?? ''), - const SizedBox(height: 10), - const Text('Imagens:', - style: TextStyle(fontWeight: FontWeight.bold)), - Wrap( - children: _images.map((imageData) { - return Padding( - padding: const EdgeInsets.all(4.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - ); - }).toList(), - ), - ], - ), - ), - actions: [ - TextButton( - child: const Text('Editar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('OK'), - onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); - }, - ), - ], - ); - }, - ); - } - - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/equipmentScreen', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - foregroundColor: Colors.white, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: const Center( - child: Text('Adicionar equipamentos ', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Tipos de equipamentos', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Row( - children: [ - Expanded( - flex: 4, - child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, - onChanged: (newValue) { - if (newValue != 'Selecione um equipamento') { - setState(() { - _selectedType = newValue; - }); - } - }, - ), - ), - Expanded( - flex: 0, - child: Row( - children: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); - }, - ), - ], - ), - ), - ], - ), - const SizedBox(height: 30), - const Text('Nome do equipamento', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: TextField( - controller: _equipmentNameController, - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 15), - ), - ), - ), - const SizedBox(height: 30), - const Text('Quantidade', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: TextField( - controller: _equipmentQuantityController, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 15), - ), - ), - ), - const SizedBox(height: 30), - const Text('Localização (Interno ou Externo)', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildStyledDropdown( - items: const [ - 'Selecione a localização', - 'Interno', - 'Externo' - ], - value: _selectedLocation, - onChanged: (newValue) { - if (newValue != 'Selecione a localização') { - setState(() { - _selectedLocation = newValue; - }); - } - }, - ), - const SizedBox(height: 15), - IconButton( - icon: const Icon(Icons.camera_alt), - onPressed: _pickImage, - ), - Wrap( - children: _images.map((imageData) { - return Stack( - alignment: Alignment.topRight, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - ), - IconButton( - icon: const Icon(Icons.remove_circle, - color: AppColors.warn), - onPressed: () { - setState(() { - _images.removeWhere( - (element) => element.id == imageData.id); - }); - }, - ), - ], - ); - }).toList(), - ), - const SizedBox(height: 15), - Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: - MaterialStateProperty.all(const Size(185, 55)), - shape: - MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ))), - onPressed: _showConfirmationDialog, - child: const Text( - 'ADICIONAR EQUIPAMENTO', - style: TextStyle( - fontSize: 17, fontWeight: FontWeight.bold), - ), - ), - ) - ], - ), - ), - ], - ), - ), - ); - } - - void _showDeleteDialog() { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Excluir tipo de equipamento'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'Selecione um equipamento para excluir:', - textAlign: TextAlign.center, - ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), - ); - }).toList(), - ), - ], - ), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Excluir'), - onPressed: () { - if (_selectedTypeToDelete != null) { - Navigator.of(context).pop(); - _deleteEquipmentType(); - } - }, - ), - ], - ); - }, - ); - } - - Widget _buildStyledDropdown({ - required List items, - String? value, - required Function(String?) onChanged, - }) { - return Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - padding: const EdgeInsets.symmetric(horizontal: 10), - child: DropdownButton( - hint: Text(items.first), - value: value, - isExpanded: true, - underline: Container(), - onChanged: onChanged, - items: items.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), - enabled: value != items.first, - ); - }).toList(), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart deleted file mode 100644 index 30df88be..00000000 --- a/frontend/sige_ie/lib/core/feature/manage/systemConfiguration.dart +++ /dev/null @@ -1,226 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/manage/EquipmentScreen.dart'; - -class SystemConfiguration extends StatefulWidget { - final String areaName; - final String localName; - final int localId; - final int categoryNumber; - - const SystemConfiguration({ - super.key, - required this.areaName, - required this.localName, - required this.localId, - required this.categoryNumber, - }); - - @override - _SystemConfigurationState createState() => _SystemConfigurationState(); -} - -class _SystemConfigurationState extends State { - void navigateTo(String routeName, String areaName, String localName, - int localId, dynamic category) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - switch (routeName) { - case '/structuredCabling': - case '/atmosphericDischarges': - case '/fireAlarm': - case '/lighting': - case '/electricLoads': - case '/electricLines': - case '/circuits': - case '/distributionBoard': - case '/cooling': - return EquipmentScreen( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); - default: - return Scaffold( - body: Center(child: Text('No route defined for $routeName')), - ); - } - }, - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - backgroundColor: AppColors.sigeIeBlue, - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text(widget.areaName, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - const Padding( - padding: EdgeInsets.all(30.0), - child: Text( - 'Quais sistemas deseja configurar?', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - ), - SystemButton( - title: 'ALARME DE INCÊNDIO', - onPressed: () => navigateTo('/fireAlarm', widget.areaName, - widget.localName, widget.localId, 8)), - SystemButton( - title: 'CABEAMENTO ESTRUTURADO', - onPressed: () => navigateTo('/structuredCabling', - widget.areaName, widget.localName, widget.localId, 6)), - SystemButton( - title: 'CARGAS ELÉTRICAS', - onPressed: () => navigateTo('/electricLoads', widget.areaName, - widget.localName, widget.localId, 2)), - SystemButton( - title: 'CIRCUITOS', - onPressed: () => navigateTo('/circuits', widget.areaName, - widget.localName, widget.localId, 4)), - SystemButton( - title: 'DESCARGAS ATMOSFÉRICAS', - onPressed: () => navigateTo('/atmosphericDischarges', - widget.areaName, widget.localName, widget.localId, 7)), - SystemButton( - title: 'ILUMINAÇÃO', - onPressed: () => navigateTo('/lighting', widget.areaName, - widget.localName, widget.localId, 1)), - SystemButton( - title: 'LINHAS ELÉTRICAS', - onPressed: () => navigateTo('/electricLines', widget.areaName, - widget.localName, widget.localId, 3)), - SystemButton( - title: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/distributionBoard', - widget.areaName, widget.localName, widget.localId, 5)), - SystemButton( - title: 'REFRIGERAÇÃO', - onPressed: () => navigateTo('/cooling', widget.areaName, - widget.localName, widget.localId, 9)), - const SizedBox( - height: 30, - ), - Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: AppColors.lightText, - backgroundColor: AppColors.warn, - minimumSize: const Size(150, 50), - textStyle: const TextStyle( - fontWeight: FontWeight.bold, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - onPressed: () { - Navigator.of(context).popUntil((route) => route.isFirst); - Navigator.pushReplacementNamed( - context, - '/homeScreen', - arguments: {'initialPage': 1}, - ); - }, - child: const Text('ENCERRAR'), - ), - const SizedBox(width: 10), - ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - foregroundColor: - MaterialStateProperty.all(AppColors.lightText), - minimumSize: - MaterialStateProperty.all(const Size(150, 50)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: () { - Navigator.of(context).pushNamed('/arealocation', - arguments: { - 'placeName': widget.localName, - 'placeId': widget.localId - }); - }, - child: const Text( - 'SALAS', - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - ), - ], - ), - ), - const SizedBox( - height: 30, - ), - ], - ), - ), - ); - } -} - -class SystemButton extends StatelessWidget { - final String title; - final VoidCallback onPressed; - - const SystemButton({ - super.key, - required this.title, - required this.onPressed, - }); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), - width: double.infinity, - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 25)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: onPressed, - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), - ), - ); - } -} From 2363e1dd585ab3ec5bac8f671f1b1932426525ff Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 23 May 2024 12:39:46 -0300 Subject: [PATCH 132/351] backend: cria listagem de equipamento de quadros e circuitos --- api/equipments/views.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/equipments/views.py b/api/equipments/views.py index 015cef96..62718da1 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -199,6 +199,14 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) +class DistributionBoardEquipmentByAreaList(generics.ListAPIView): + serializer_class = DistributionBoardEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + area_id = self.kwargs['area_id'] + return DistributionBoardEquipment.objects.filter(area_id=area_id) + class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer @@ -222,6 +230,14 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) +class ElectricalCircuitEquipmentByAreaList(generics.ListAPIView): + serializer_class = ElectricalCircuitEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + area_id = self.kwargs['area_id'] + return ElectricalCircuitEquipment.objects.filter(area_id=area_id) + class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer From 32494239f3192757493fb7cbdd2acca3707f07a4 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 23 May 2024 12:47:36 -0300 Subject: [PATCH 133/351] =?UTF-8?q?backend:=20cria=20listagem=20de=20equip?= =?UTF-8?q?amento=20de=20linhas,=20cargas=20e=20ilumina=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/urls.py | 13 +++++--- api/equipments/views.py | 74 +++++++++++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 36513de2..1fea8e71 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -11,6 +11,9 @@ path('equipments//', EquipmentDetailDetail.as_view()), path('equipment-photos/', EquipmentPhotoList.as_view()), path('equipment-photos//', EquipmentPhotoDetail.as_view()), + path('refrigerations/', RefrigerationEquipmentList.as_view()), + path('refrigeration/by-area//', RefrigerationEquipmentByAreaList.as_view()), + path('refrigerations//', RefrigerationEquipmentDetail.as_view()), path('fire-alarms/', FireAlarmEquipmentList.as_view()), path('fire-alarms/by-area//', FireAlarmEquipmentByAreaList.as_view()), path('fire-alarms//', FireAlarmEquipmentDetail.as_view()), @@ -21,16 +24,18 @@ path('structured-cabling/by-area//', StructuredCablingEquipmentByAreaList.as_view()), path('structured-cabling//', StructuredCablingEquipmentDetail.as_view()), path('distribution-boards/', DistributionBoardEquipmentList.as_view()), + path('distribution-boards/by-area//', DistributionBoardEquipmentByAreaList.as_view()), path('distribution-boards//', DistributionBoardEquipmentDetail.as_view()), path('electrical-circuits/', ElectricalCircuitEquipmentList.as_view()), + path('electrical-circuits/by-area//', ElectricalCircuitEquipmentByAreaList.as_view()), path('electrical-circuits//', ElectricalCircuitEquipmentDetail.as_view()), path('electrical-lines/', ElectricalLineEquipmentList.as_view()), + path('electrical-lines/by-area//', ElectricalLineEquipmentByAreaList.as_view()), path('electrical-lines//', ElectricalLineEquipmentDetail.as_view()), path('electrical-loads/', ElectricalLoadEquipmentList.as_view()), + path('electrical-loads/by-area//', ElectricalLoadEquipmentByAreaList.as_view()), path('electrical-loads//', ElectricalLoadEquipmentDetail.as_view()), path('iluminations/', IluminationEquipmentList.as_view()), - path('iluminations//', IluminationEquipmentDetail.as_view()), - path('refrigeration/', EquipmentDetailList.as_view()), - path('refrigeration//', EquipmentDetailDetail.as_view()) - + path('iluminations/by-area//', IluminationEquipmentByAreaList.as_view()), + path('iluminations//', IluminationEquipmentDetail.as_view()) ] diff --git a/api/equipments/views.py b/api/equipments/views.py index 62718da1..fb6bded0 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -88,6 +88,37 @@ class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): serializer_class = EquipmentPhotoSerializer permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] +class RefrigerationEquipmentList(generics.ListCreateAPIView): + queryset = RefrigerationEquipment.objects.all() + serializer_class = RefrigerationEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return RefrigerationEquipment.objects.filter(area__place__place_owner=user.placeowner) + + def create(self, request, *args, **kwargs): + data = request.data.copy() + data["system"] = 9 + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + serializer.save() + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + +class RefrigerationEquipmentByAreaList(generics.ListAPIView): + serializer_class = RefrigerationEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + area_id = self.kwargs['area_id'] + return RefrigerationEquipment.objects.filter(area_id=area_id) + +class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = RefrigerationEquipment.objects.all() + serializer_class = RefrigerationEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + class FireAlarmEquipmentList(generics.ListCreateAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer @@ -261,6 +292,14 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) +class ElectricalLineEquipmentByAreaList(generics.ListAPIView): + serializer_class = ElectricalLineEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + area_id = self.kwargs['area_id'] + return ElectricalLineEquipment.objects.filter(area_id=area_id) + class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer @@ -284,6 +323,14 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) +class ElectricalLoadEquipmentByAreaList(generics.ListAPIView): + serializer_class = ElectricalLoadEquipmentSerializer + permission_classes = [IsPlaceOwner, IsAuthenticated] + + def get_queryset(self): + area_id = self.kwargs['area_id'] + return ElectricalLoadEquipment.objects.filter(area_id=area_id) + class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer @@ -307,30 +354,15 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) -class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = IluminationEquipment.objects.all() +class IluminationEquipmentByAreaList(generics.ListAPIView): serializer_class = IluminationEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] -class RefrigerationEquipmentList(generics.ListCreateAPIView): - queryset = RefrigerationEquipment.objects.all() - serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] - def get_queryset(self): - user = self.request.user - return RefrigerationEquipment.objects.filter(area__place__place_owner=user.placeowner) - - def create(self, request, *args, **kwargs): - data = request.data.copy() - data["system"] = 1 - serializer = self.get_serializer(data=data) - serializer.is_valid(raise_exception=True) - serializer.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + area_id = self.kwargs['area_id'] + return IluminationEquipment.objects.filter(area_id=area_id) -class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = RefrigerationEquipment.objects.all() - serializer_class = RefrigerationEquipmentSerializer +class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = IluminationEquipment.objects.all() + serializer_class = IluminationEquipmentSerializer permission_classes = [IsPlaceOwner, IsAuthenticated] \ No newline at end of file From e6039376293c6b9b641cd4985501205792051e7c Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 24 May 2024 09:55:18 -0300 Subject: [PATCH 134/351] =?UTF-8?q?backend:=20simplifica=20cria=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20equipment=20detail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/mixins.py | 7 ++-- api/equipments/serializers.py | 68 +++++++++++++++++++++++++++++++---- api/equipments/urls.py | 5 +-- api/equipments/views.py | 19 +++++----- 4 files changed, 81 insertions(+), 18 deletions(-) diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index 21e75774..c487f06f 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -13,13 +13,16 @@ def validate(self, data): if equipment_type_system != data['system']: raise serializers.ValidationError("The equipment type's system must match the equipment's system.") + return data + + def validate_equipment_detail(self, value): """ Garante que o equipment detail pertence ao place owner. """ user = self.context['request'].user - if equipment_detail.place_owner != user.placeowner: + if value.equipment_detail.place_owner != user.placeowner: raise serializers.ValidationError("You are not the owner of the equipment detail's place") - return data + return value def validate_area(self, value): """ diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index ff7a96e0..e74cb077 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers from .models import * +from .serializers import * from .mixins import ValidateAreaMixin class PersonalEquipmentTypeSerializer(serializers.ModelSerializer): @@ -14,12 +15,6 @@ class Meta: model = EquipmentType fields = '__all__' -class EquipmentDetailSerializer(serializers.ModelSerializer): - - class Meta: - model = EquipmentDetail - fields = '__all__' - class EquipmentPhotoSerializer(serializers.ModelSerializer): class Meta: @@ -79,3 +74,64 @@ class RefrigerationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSeria class Meta: model = RefrigerationEquipment fields = '__all__' + +class EquipmentDetailSerializer(serializers.ModelSerializer): + + photos = EquipmentPhotoSerializer(many=True, required=False) + + fire_alarm_equipment = FireAlarmEquipmentSerializer(required=False) + atmospheric_discharge_equipment = AtmosphericDischargeEquipmentSerializer(required=False) + structured_cabling_equipment = StructuredCablingEquipmentSerializer(required=False) + distribution_board_equipment = DistributionBoardEquipmentSerializer(required=False) + electrical_circuit_equipment = ElectricalCircuitEquipmentSerializer(required=False) + electrical_line_equipment = ElectricalLineEquipmentSerializer(required=False) + electrical_load_equipment = ElectricalLoadEquipmentSerializer(required=False) + ilumination_equipment = IluminationEquipmentSerializer(required=False) + refrigeration_equipment = RefrigerationEquipmentSerializer(required=False) + + class Meta: + model = EquipmentDetail + fields = '__all__' + extra_kwargs = {'place_owner': {'read_only': True}} + + def create(self, validated_data): + request = self.context.get('request') + validated_data['place_owner'] = request.user.placeowner + + photos_data = validated_data.pop('photos', []) + + fire_alarm_data = validated_data.pop('fire_alarm_equipment', None) + atmospheric_discharge_data = validated_data.pop('atmospheric_discharge_equipment', None) + structured_cabling_data = validated_data.pop('structured_cabling_equipment', None) + distribution_board_data = validated_data.pop('distribution_board_equipment', None) + electrical_circuit_data = validated_data.pop('electrical_circuit_equipment', None) + electrical_line_data = validated_data.pop('electrical_line_equipment', None) + electrical_load_data = validated_data.pop('electrical_load_equipment', None) + ilumination_equipment_data = validated_data.pop('ilumination_equipment', None) + refrigeration_equipment_data = validated_data.pop('refrigeration_equipment', None) + + equipment_detail = EquipmentDetail.objects.create(**validated_data) + + for photo_data in photos_data: + EquipmentPhoto.objects.create(equipment_detail=equipment_detail, **photo_data) + + if fire_alarm_data: + FireAlarmEquipment.objects.create(equipment_detail=equipment_detail, **fire_alarm_data) + elif atmospheric_discharge_data: + AtmosphericDischargeEquipment.objects.create(equipment_detail=equipment_detail, **atmospheric_discharge_data) + elif structured_cabling_data: + StructuredCablingEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **structured_cabling_data) + elif distribution_board_data: + DistributionBoardEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **distribution_board_data) + elif electrical_circuit_data: + ElectricalCircuitEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **electrical_circuit_data) + elif electrical_line_data: + ElectricalLineEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **electrical_line_data) + elif electrical_load_data: + ElectricalLoadEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **electrical_load_data) + elif ilumination_equipment_data: + IluminationEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **ilumination_equipment_data) + elif refrigeration_equipment_data: + RefrigerationEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **refrigeration_equipment_data) + + return equipment_detail \ No newline at end of file diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 1fea8e71..9a1bf7e1 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -7,8 +7,9 @@ path('personal-equipment-types//', PersonalEquipmentTypeDetail.as_view()), path('equipment-types/by-system//', EquipmentTypeList.as_view(), name='personal_equipment_types_by_system'), path('equipment-types//', EquipmentTypeDetail.as_view()), - path('equipments/', EquipmentDetailList.as_view()), - path('equipments//', EquipmentDetailDetail.as_view()), + path('equipment-details/', EquipmentDetailCreate.as_view(), name='equipment-detail-create'), + path('equipment-details/', EquipmentDetailList.as_view()), + path('equipment-details//', EquipmentDetailDetail.as_view()), path('equipment-photos/', EquipmentPhotoList.as_view()), path('equipment-photos//', EquipmentPhotoDetail.as_view()), path('refrigerations/', RefrigerationEquipmentList.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index fb6bded0..017d547b 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -49,7 +49,7 @@ class EquipmentTypeDetail(generics.RetrieveAPIView): serializer_class = EquipmentTypeSerializer permission_classes = [IsAuthenticated] -class EquipmentDetailList(generics.ListCreateAPIView): +class EquipmentDetailList(generics.ListAPIView): queryset = EquipmentDetail.objects.all() serializer_class = EquipmentDetailSerializer permission_classes = [IsOwner, IsAuthenticated] @@ -59,14 +59,17 @@ def get_queryset(self): queryset = super().get_queryset() return queryset.filter(place_owner__user=user) - def create(self, request, *args, **kwargs): +class EquipmentDetailCreate(generics.CreateAPIView): + queryset = EquipmentDetail.objects.all() + serializer_class = EquipmentDetailSerializer + permission_classes = [IsOwner, IsAuthenticated] - if(IsOwner): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save(place_owner=request.user.placeowner) - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + def get_serializer_context(self): + context = super().get_serializer_context() + context.update({ + 'request': self.request + }) + return context class EquipmentDetailDetail(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentDetail.objects.all() From 17374fe8678fdcd557ef6d9f0db3539d146f5cf7 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 13:30:59 -0300 Subject: [PATCH 135/351] =?UTF-8?q?Adiciona=20deivis=C3=B5es=20dos=20equip?= =?UTF-8?q?amentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/ilumination_request_model.dart | 0 .../data/ilumination_response_model.dart | 0 .../equipments/data/ilumination_service.dart | 0 .../IluminationEquipmentList.dart} | 2 +- .../addIluminationEquipment.dart} | 79 +++++++++++++++---- .../feature}/systemConfiguration.dart | 2 +- frontend/sige_ie/lib/main.dart | 4 +- frontend/sige_ie/pubspec.lock | 72 ++++++++++------- 8 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 frontend/sige_ie/lib/equipments/data/ilumination_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/ilumination_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/ilumination_service.dart rename frontend/sige_ie/lib/{core/feature/equipment/EquipmentScreen.dart => equipments/feature/iluminations/IluminationEquipmentList.dart} (98%) rename frontend/sige_ie/lib/{core/feature/equipment/addEquipmentScreen.dart => equipments/feature/iluminations/addIluminationEquipment.dart} (88%) rename frontend/sige_ie/lib/{core/feature/equipment => equipments/feature}/systemConfiguration.dart (98%) diff --git a/frontend/sige_ie/lib/equipments/data/ilumination_request_model.dart b/frontend/sige_ie/lib/equipments/data/ilumination_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/ilumination_response_model.dart b/frontend/sige_ie/lib/equipments/data/ilumination_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/ilumination_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart similarity index 98% rename from frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart rename to frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart index 270d0680..de363b5a 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/equipment/addEquipmentScreen.dart'; +import 'package:sige_ie/equipments/feature/iluminations/addIluminationEquipment.dart'; class EquipmentScreen extends StatelessWidget { final String areaName; diff --git a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart similarity index 88% rename from frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart rename to frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart index f4ff2d1b..5ff0b3e8 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart @@ -8,8 +8,9 @@ import 'package:image_picker/image_picker.dart'; class ImageData { File imageFile; int id; + String description; - ImageData(this.imageFile) : id = Random().nextInt(1000000); + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); } List _images = []; @@ -60,21 +61,64 @@ class _AddEquipmentScreenState extends State { try { final pickedFile = await picker.pickImage(source: ImageSource.camera); if (pickedFile != null) { - setState(() { - final imageData = ImageData(File(pickedFile.path)); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; - }); + _showImageDialog(File(pickedFile.path)); } } catch (e) { print('Erro ao capturar a imagem: $e'); } } + void _showImageDialog(File imageFile) { + TextEditingController descriptionController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + void _addNewEquipmentType() { TextEditingController typeController = TextEditingController(); showDialog( @@ -198,11 +242,16 @@ class _AddEquipmentScreenState extends State { children: _images.map((imageData) { return Padding( padding: const EdgeInsets.all(4.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], ), ); }).toList(), diff --git a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart similarity index 98% rename from frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart rename to frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index f7de023d..586458ab 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/equipment/EquipmentScreen.dart'; +import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; class SystemConfiguration extends StatefulWidget { final String areaName; diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index de652cfd..c96b111d 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -6,8 +6,8 @@ import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/core/feature/equipment/EquipmentScreen.dart'; -import 'package:sige_ie/core/feature/equipment/systemConfiguration.dart'; +import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; import 'core/feature/login/login.dart'; diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 4a01ae40..72c6d0dd 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.4" file_selector_platform_interface: dependency: transitive description: @@ -137,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -180,10 +188,10 @@ packages: dependency: transitive description: name: geolocator_android - sha256: "93906636752ea4d4e778afa981fdfe7409f545b3147046300df194330044d349" + sha256: "00c7177a95823dd3ee35ef42fd8666cd27d219ae14cea472ac76a21dff43000b" url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "4.6.0" geolocator_apple: dependency: transitive description: @@ -196,10 +204,10 @@ packages: dependency: transitive description: name: geolocator_platform_interface - sha256: "009a21c4bc2761e58dccf07c24f219adaebe0ff707abdfd40b0a763d4003fab9" + sha256: c6005787efe9e27cb0f6b50230c217e6f0ef8e1e7a8b854efb4f46489e502603 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.3" geolocator_web: dependency: transitive description: @@ -316,10 +324,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "40e24f467b75cd6f4a92ee93dd13d1a7bcb4523a84fd95f00c755f01f42398c8" + sha256: "0f57fee1e8bfadf8cc41818bbcd7f72e53bb768a54d9496355d5e8a5681a19f1" url: "https://pub.dev" source: hosted - version: "0.8.11" + version: "0.8.12+1" image_picker_for_web: dependency: transitive description: @@ -332,10 +340,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: f74064bc548b5164a033ec05638e23c91be1a249c255e0f56319dddffd759794 + sha256: "4824d8c7f6f89121ef0122ff79bb00b009607faecc8545b86bca9ab5ce1e95bf" url: "https://pub.dev" source: hosted - version: "0.8.10+1" + version: "0.8.11+2" image_picker_linux: dependency: transitive description: @@ -516,26 +524,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -581,6 +589,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -649,10 +665,10 @@ packages: dependency: transitive description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.4.0" vector_math: dependency: transitive description: @@ -665,26 +681,26 @@ packages: dependency: "direct main" description: name: video_player - sha256: efa2e24042166906ddf836dd131258d0371d0009cdf0476f6a83fd992a17f5d0 + sha256: db6a72d8f4fd155d0189845678f55ad2fd54b02c10dcafd11c068dbb631286c0 url: "https://pub.dev" source: hosted - version: "2.8.5" + version: "2.8.6" video_player_android: dependency: transitive description: name: video_player_android - sha256: "4dd9b8b86d70d65eecf3dcabfcdfbb9c9115d244d022654aba49a00336d540c2" + sha256: "134e1ad410d67e18a19486ed9512c72dfc6d8ffb284d0e8f2e99e903d1ba8fa3" url: "https://pub.dev" source: hosted - version: "2.4.12" + version: "2.4.14" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" + sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.6.1" video_player_platform_interface: dependency: transitive description: @@ -721,10 +737,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.5.0" xdg_directories: dependency: transitive description: From ba264785488eb67d83124a31ba5e830e8d0375b1 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 13:34:33 -0300 Subject: [PATCH 136/351] =?UTF-8?q?Adiciona=20deivis=C3=B5es=20dos=20equip?= =?UTF-8?q?amentos-data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/{ => ilumination-data}/ilumination_request_model.dart | 0 .../data/{ => ilumination-data}/ilumination_response_model.dart | 0 .../data/{ => ilumination-data}/ilumination_service.dart | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename frontend/sige_ie/lib/equipments/data/{ => ilumination-data}/ilumination_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{ => ilumination-data}/ilumination_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{ => ilumination-data}/ilumination_service.dart (100%) diff --git a/frontend/sige_ie/lib/equipments/data/ilumination_request_model.dart b/frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/ilumination_request_model.dart rename to frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/ilumination_response_model.dart b/frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/ilumination_response_model.dart rename to frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/ilumination_service.dart rename to frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_service.dart From fc7b82f55b883b8c1868751302321c845fe6e78b Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 14:50:32 -0300 Subject: [PATCH 137/351] Adiciona equipamento discarga atmosferica --- .../addatmospheric-dischargesEquipment.dart | 0 .../atmospheric-discharges/atmospheric-dischargesList.dart | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart new file mode 100644 index 00000000..e69de29b From 64b91770060ff1972b3764b463c3eb74bba8db42 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 14:52:35 -0300 Subject: [PATCH 138/351] Adiciona equipamento discarga atmosferica --- .../addatmospheric-dischargesEquipment.dart | 604 ++++++++++++++++++ 1 file changed, 604 insertions(+) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart index e69de29b..3f254094 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart @@ -0,0 +1,604 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddEquipmentScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddEquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentNameController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedLocation; + String? _selectedTypeToDelete; + + List equipmentTypes = [ + 'Selecione um equipamento', + 'Para-raios', + 'Captação', + 'Subsistemas' + ]; + + List pararaiosequipment = [ + 'Tipo Frankiln', + 'Tipo Melsen', + 'Tipo Radioativos' + ]; + + List captacaoequipment = [ + 'Tipo Franklin', + 'Gaiola de Faraday', + ]; + + List subsistemasequipment = [ + 'Capacitação', + 'Descidas' + 'Aterramento', + ]; + + @override + void dispose() { + _equipmentNameController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile) { + TextEditingController descriptionController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentNameController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + _selectedType == null || + _selectedLocation == null || + _selectedType == 'Selecione um equipamento' || + _selectedLocation == 'Selecione a localização') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Nome:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentNameController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? ''), + const SizedBox(height: 10), + const Text('Localização:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedLocation ?? ''), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/equipmentScreen', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de equipamentos', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + if (newValue != 'Selecione um equipamento') { + setState(() { + _selectedType = newValue; + }); + } + }, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 30), + const Text('Nome do equipamento', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentNameController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Localização (Interno ou Externo)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: const [ + 'Selecione a localização', + 'Interno', + 'Externo' + ], + value: _selectedLocation, + onChanged: (newValue) { + if (newValue != 'Selecione a localização') { + setState(() { + _selectedLocation = newValue; + }); + } + }, + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + }) { + return Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: onChanged, + items: items.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + enabled: value != items.first, + ); + }).toList(), + ), + ); + } +} From a61af92635772033b0882b7044a90d8dea9713a6 Mon Sep 17 00:00:00 2001 From: Oscar de Brito <98489703+OscarDeBrito@users.noreply.github.com> Date: Tue, 28 May 2024 15:14:53 -0300 Subject: [PATCH 139/351] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 544c9ab3..29e46e65 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ Com o ambiente preparado, siga os passos abaixo: Abra a pasta clonada no Android Studio ou no Visual Studio Code. 3. **Baixe as Dependências**: - Abra um terminal no editor e execute o comando: + Abra um terminal na pasta frontend/sige_ie e execute o comando: ``` flutter pub get ``` From 2ef2046073c2987543c7efde030205d15b4e6971 Mon Sep 17 00:00:00 2001 From: ramires Date: Tue, 28 May 2024 16:43:34 -0300 Subject: [PATCH 140/351] Modifica ilumination equipment Co-authored-by:Kauan Jose Co-authored-by: Oscar de Brito --- .../iluminations/addIluminationEquipment.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart index 5ff0b3e8..307388b8 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart @@ -42,10 +42,12 @@ class _AddEquipmentScreenState extends State { String? _selectedTypeToDelete; List equipmentTypes = [ - 'Selecione um equipamento', - 'Eletroduto', - 'Eletrocalha', - 'Dimensão' + 'Selecione um tipo de Lâmpada', + 'Halogenia', + 'Fluorescente', + 'LEDs', + 'Incadescentes', + 'Lâmpadas Queimadas' ]; @override @@ -329,7 +331,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de equipamentos', + const Text('Tipos de Lâmpadas', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -372,7 +374,7 @@ class _AddEquipmentScreenState extends State { ], ), const SizedBox(height: 30), - const Text('Nome do equipamento', + const Text('Potência da Lâmpada', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -383,6 +385,7 @@ class _AddEquipmentScreenState extends State { ), child: TextField( controller: _equipmentNameController, + keyboardType: TextInputType.number, decoration: const InputDecoration( border: InputBorder.none, contentPadding: From 80dc6e641aa285e8c768a7f9372df2b85958fb13 Mon Sep 17 00:00:00 2001 From: ramires Date: Tue, 28 May 2024 16:46:30 -0300 Subject: [PATCH 141/351] Modifica ilumination equipment Co-authored-by: Kauan Jose Co-authored-by: Oscar de Brito --- .../addatmospheric-dischargesEquipment.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart index 3f254094..90bd8f9a 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart @@ -49,20 +49,23 @@ class _AddEquipmentScreenState extends State { ]; List pararaiosequipment = [ + 'Selecione um tipo de Para-raios', 'Tipo Frankiln', 'Tipo Melsen', 'Tipo Radioativos' ]; List captacaoequipment = [ + 'Selecione o tipo de Captação', 'Tipo Franklin', - 'Gaiola de Faraday', + 'Gaiola de Faraday' ]; List subsistemasequipment = [ + 'Selecione um Subsistema', 'Capacitação', 'Descidas' - 'Aterramento', + 'Aterramento' ]; @override From 5053ec1fd7b1bb2b632b9eaf89f4d4257195b328 Mon Sep 17 00:00:00 2001 From: ramires Date: Tue, 28 May 2024 16:46:30 -0300 Subject: [PATCH 142/351] Modifica atmospheric equipment Co-authored-by: Kauan Jose Co-authored-by: Oscar de Brito --- .../addatmospheric-dischargesEquipment.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart index 3f254094..90bd8f9a 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart @@ -49,20 +49,23 @@ class _AddEquipmentScreenState extends State { ]; List pararaiosequipment = [ + 'Selecione um tipo de Para-raios', 'Tipo Frankiln', 'Tipo Melsen', 'Tipo Radioativos' ]; List captacaoequipment = [ + 'Selecione o tipo de Captação', 'Tipo Franklin', - 'Gaiola de Faraday', + 'Gaiola de Faraday' ]; List subsistemasequipment = [ + 'Selecione um Subsistema', 'Capacitação', 'Descidas' - 'Aterramento', + 'Aterramento' ]; @override From 02815e54b243ff7597812d8a46a34ec140b7117b Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 16:56:31 -0300 Subject: [PATCH 143/351] Atualiza configuration system Co-authored-by: Ramires rocha --- .../iluminations/IluminationEquipmentList.dart | 6 +++--- .../equipments/feature/systemConfiguration.dart | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart index de363b5a..65c4b1b7 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/iluminations/addIluminationEquipment.dart'; -class EquipmentScreen extends StatelessWidget { +class listIluminationEquipment extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; - const EquipmentScreen({ + const listIluminationEquipment({ super.key, required this.areaName, required this.categoryNumber, @@ -20,7 +20,7 @@ class EquipmentScreen extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => AddEquipmentScreen( + builder: (context) => AddiluminationEquipmentScreen( areaName: areaName, categoryNumber: categoryNumber, localName: localName, diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 586458ab..cb1f32f9 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; class SystemConfiguration extends StatefulWidget { final String areaName; @@ -30,14 +31,24 @@ class _SystemConfigurationState extends State { switch (routeName) { case '/structuredCabling': case '/atmosphericDischarges': + return listatmosphericEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); case '/fireAlarm': - case '/lighting': case '/electricLoads': case '/electricLines': case '/circuits': case '/distributionBoard': case '/cooling': - return EquipmentScreen( + //return EquipmentScreen( + // areaName: areaName, + //localName: localName, + //localId: localId, + //categoryNumber: category); + case '/lighting': + return listIluminationEquipment( areaName: areaName, localName: localName, localId: localId, From 6ad43852226abcd06919ac61b463c47c5e7b676d Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 16:57:34 -0300 Subject: [PATCH 144/351] Atualiza caminho atmospheric add e list Co-authored-by: Ramires rocha --- .../addatmospheric-dischargesEquipment.dart | 6 +++--- .../feature/iluminations/addIluminationEquipment.dart | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart index 90bd8f9a..702d6b09 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart @@ -16,13 +16,13 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class AddEquipmentScreen extends StatefulWidget { +class AddatmosphericEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; - const AddEquipmentScreen({ + const AddatmosphericEquipmentScreen({ super.key, required this.areaName, required this.categoryNumber, @@ -34,7 +34,7 @@ class AddEquipmentScreen extends StatefulWidget { _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddEquipmentScreenState extends State { final _equipmentNameController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart index 307388b8..9010735c 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart @@ -16,13 +16,13 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class AddEquipmentScreen extends StatefulWidget { +class AddiluminationEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; - const AddEquipmentScreen({ + const AddiluminationEquipmentScreen({ super.key, required this.areaName, required this.categoryNumber, @@ -34,7 +34,7 @@ class AddEquipmentScreen extends StatefulWidget { _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddEquipmentScreenState extends State { final _equipmentNameController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; From 9ebe6ec73ed9dedafd29f97df710390f24c68a42 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 16:58:17 -0300 Subject: [PATCH 145/351] Adiciona atmospheric list Co-authored-by: Ramires rocha --- .../atmospheric-dischargesList.dart | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart index e69de29b..eb7e4ba3 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart'; + +class listatmosphericEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listatmosphericEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddatmosphericEquipmentScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'DESCARGAS ATMOSFÉRICAS'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} From 36643add0854d97e8dd1e1021bd35bd7347c1b8f Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 16:58:40 -0300 Subject: [PATCH 146/351] Atualiza caminhos main Co-authored-by: Ramires rocha --- frontend/sige_ie/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index c96b111d..7e033744 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -104,7 +104,7 @@ class MyApp extends StatelessWidget { if (areaName != null && localName != null && localId != null) { return MaterialPageRoute( - builder: (context) => EquipmentScreen( + builder: (context) => listIluminationEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 34417202f19b73b5a3b84288a5b3b4d12f04f240 Mon Sep 17 00:00:00 2001 From: ramires Date: Tue, 28 May 2024 17:44:56 -0300 Subject: [PATCH 147/351] Atualiaza system configuration Co-authored-by: Kauan Jose Co-authored-by: Oscar de Brito --- .../lib/equipments/feature/systemConfiguration.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index cb1f32f9..f1fc85e2 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; +import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; + class SystemConfiguration extends StatefulWidget { final String areaName; @@ -38,6 +40,11 @@ class _SystemConfigurationState extends State { categoryNumber: category); case '/fireAlarm': case '/electricLoads': + return listelectricalLoadEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); case '/electricLines': case '/circuits': case '/distributionBoard': From f7e89ded6a1358f0fee6d36cdb985b1a8800780f Mon Sep 17 00:00:00 2001 From: ramires Date: Tue, 28 May 2024 17:45:36 -0300 Subject: [PATCH 148/351] Adiciona addelectricalLoad Co-authored-by: Kauan Jose Co-authored-by: Oscar de Brito --- .../electrical-load/addelectricalLoad.dart | 592 ++++++++++++++++++ 1 file changed, 592 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart new file mode 100644 index 00000000..80826c24 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart @@ -0,0 +1,592 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddelectricalLoadEquipmentScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddelectricalLoadEquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentNameController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedLocation; + String? _selectedTypeToDelete; + + List equipmentTypes = [ + 'Selecione um equipamento', + 'Geladeira', + 'Ar-Condicionado', + 'Tomada(Corrente)' + ]; + + bool _shouldShowBrandAndModel() { + return _selectedType == 'Geladeira' || _selectedType == 'Ar-Condicionado'; + } + + @override + void dispose() { + _equipmentNameController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile) { + TextEditingController descriptionController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentNameController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + _selectedType == null || + _selectedLocation == null || + _selectedType == 'Selecione um equipamento' || + _selectedLocation == 'Selecione a localização') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Nome:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentNameController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? ''), + const SizedBox(height: 10), + const Text('Localização:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedLocation ?? ''), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/equipmentScreen', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de equipamentos', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + if (newValue != 'Selecione um equipamento') { + setState(() { + _selectedType = newValue; + }); + } + }, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 30), + const Text('Marca e Modelo', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentNameController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Localização (Interno ou Externo)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: const [ + 'Selecione a localização', + 'Interno', + 'Externo' + ], + value: _selectedLocation, + onChanged: (newValue) { + if (newValue != 'Selecione a localização') { + setState(() { + _selectedLocation = newValue; + }); + } + }, + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + if (_shouldShowBrandAndModel()) + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + }) { + return Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: onChanged, + items: items.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + enabled: value != items.first, + ); + }).toList(), + ), + ); + } +} From d6c5ae90b93e3299dd75ea31b1d87570947dd2ac Mon Sep 17 00:00:00 2001 From: ramires Date: Tue, 28 May 2024 17:46:04 -0300 Subject: [PATCH 149/351] Adiciona electricalLoad List Co-authored-by: Kauan Jose Co-authored-by: Oscar de Brito --- .../electrical-load/eletricalLoadList.dart | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart new file mode 100644 index 00000000..e9871019 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/electrical-load/addelectricalLoad.dart'; + +class listelectricalLoadEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listelectricalLoadEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddelectricalLoadEquipmentScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'CARGAS ELÉTRICAS'; + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} From 29937c25e8d44d0aae625076f2109936873f1be4 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 28 May 2024 17:51:59 -0300 Subject: [PATCH 150/351] Atualiza listas do ilumination equipment Co-authored-by: Ramires rocha --- .../iluminations/addIluminationEquipment.dart | 91 +++++++++++++------ 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart index 9010735c..e61a048b 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart @@ -40,9 +40,14 @@ class _AddEquipmentScreenState extends State { String? _selectedType; String? _selectedLocation; String? _selectedTypeToDelete; + String? _selectedLampType; // State variable for the new dropdown List equipmentTypes = [ 'Selecione um tipo de Lâmpada', + ]; + + List lampTypes = [ + 'Selecione o tipo de lâmpada', 'Halogenia', 'Fluorescente', 'LEDs', @@ -70,8 +75,10 @@ class _AddEquipmentScreenState extends State { } } - void _showImageDialog(File imageFile) { - TextEditingController descriptionController = TextEditingController(); + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); showDialog( context: context, builder: (BuildContext context) { @@ -100,16 +107,20 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (descriptionController.text.isNotEmpty) { setState(() { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; }); Navigator.of(context).pop(); } @@ -244,16 +255,20 @@ class _AddEquipmentScreenState extends State { children: _images.map((imageData) { return Padding( padding: const EdgeInsets.all(4.0), - child: Column( - children: [ - Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - Text(imageData.description), - ], + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), ), ); }).toList(), @@ -331,7 +346,21 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de Lâmpadas', + const Text('Tipos de Lâmpada', // New dropdown title + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: lampTypes, + value: _selectedLampType, + onChanged: (newValue) { + setState(() { + _selectedLampType = newValue; + }); + }, + ), + const SizedBox(height: 30), + const Text('Seus tipos de lâmpadas', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -446,13 +475,17 @@ class _AddEquipmentScreenState extends State { return Stack( alignment: Alignment.topRight, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), ), ), IconButton( From 66cca9ac43277cee618e8844abe3b4aa74ca4582 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 12:57:58 -0300 Subject: [PATCH 151/351] =?UTF-8?q?Atualiza=20inter=C3=A7=C3=B5es=20das=20?= =?UTF-8?q?listas=20e=20show=20dialog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iluminations/addIluminationEquipment.dart | 87 +++++++++++++------ 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart index e61a048b..1c47d80a 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart @@ -1,9 +1,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:sige_ie/config/app_styles.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; class ImageData { File imageFile; @@ -35,12 +35,12 @@ class AddiluminationEquipmentScreen extends StatefulWidget { } class _AddEquipmentScreenState extends State { - final _equipmentNameController = TextEditingController(); + final _equipmentchargeController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedLocation; String? _selectedTypeToDelete; - String? _selectedLampType; // State variable for the new dropdown + String? _selectedLampType; List equipmentTypes = [ 'Selecione um tipo de Lâmpada', @@ -51,13 +51,13 @@ class _AddEquipmentScreenState extends State { 'Halogenia', 'Fluorescente', 'LEDs', - 'Incadescentes', - 'Lâmpadas Queimadas' + 'Incandescentes', + 'Lâmpadas Queimadas', ]; @override void dispose() { - _equipmentNameController.dispose(); + _equipmentchargeController.dispose(); _equipmentQuantityController.dispose(); categoryImagesMap[widget.categoryNumber]?.clear(); super.dispose(); @@ -211,11 +211,10 @@ class _AddEquipmentScreenState extends State { } void _showConfirmationDialog() { - if (_equipmentNameController.text.isEmpty || + if (_equipmentchargeController.text.isEmpty || _equipmentQuantityController.text.isEmpty || - _selectedType == null || + (_selectedType == null && _selectedLampType == null) || _selectedLocation == null || - _selectedType == 'Selecione um equipamento' || _selectedLocation == 'Selecione a localização') { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -233,17 +232,17 @@ class _AddEquipmentScreenState extends State { content: SingleChildScrollView( child: ListBody( children: [ - const Text('Nome:', + const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentNameController.text), + Text(_selectedType ?? _selectedLampType ?? ''), const SizedBox(height: 10), - const Text('Quantidade:', + const Text('Potência da Lâmpada(KW):', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentQuantityController.text), + Text(_equipmentchargeController.text), const SizedBox(height: 10), - const Text('Tipo:', + const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? ''), + Text(_equipmentQuantityController.text), const SizedBox(height: 10), const Text('Localização:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -346,7 +345,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de Lâmpada', // New dropdown title + const Text('Tipos de Lâmpada', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -356,8 +355,24 @@ class _AddEquipmentScreenState extends State { onChanged: (newValue) { setState(() { _selectedLampType = newValue; + if (newValue == lampTypes[0]) { + _selectedLampType = null; + } + if (_selectedLampType != null) { + _selectedType = null; + } }); }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedLampType = null; + }); + }, + child: const Text('Limpar seleção'), ), const SizedBox(height: 30), const Text('Seus tipos de lâmpadas', @@ -372,12 +387,17 @@ class _AddEquipmentScreenState extends State { items: equipmentTypes, value: _selectedType, onChanged: (newValue) { - if (newValue != 'Selecione um equipamento') { - setState(() { - _selectedType = newValue; - }); - } + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedLampType = null; + } + }); }, + enabled: _selectedLampType == null, ), ), Expanded( @@ -402,8 +422,17 @@ class _AddEquipmentScreenState extends State { ), ], ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), const SizedBox(height: 30), - const Text('Potência da Lâmpada', + const Text('Potência da Lâmpada(KW)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -413,7 +442,7 @@ class _AddEquipmentScreenState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentNameController, + controller: _equipmentchargeController, keyboardType: TextInputType.number, decoration: const InputDecoration( border: InputBorder.none, @@ -594,10 +623,11 @@ class _AddEquipmentScreenState extends State { required List items, String? value, required Function(String?) onChanged, + bool enabled = true, }) { return Container( decoration: BoxDecoration( - color: Colors.grey[300], + color: enabled ? Colors.grey[300] : Colors.grey[200], borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.symmetric(horizontal: 10), @@ -606,15 +636,16 @@ class _AddEquipmentScreenState extends State { value: value, isExpanded: true, underline: Container(), - onChanged: onChanged, + onChanged: enabled ? onChanged : null, items: items.map>((String value) { return DropdownMenuItem( - value: value, + value: value.isEmpty ? null : value, child: Text( value, - style: const TextStyle(color: Colors.black), + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), ), - enabled: value != items.first, ); }).toList(), ), From 1c815b6584fed9179c03181df30789d57afc0169 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 13:49:01 -0300 Subject: [PATCH 152/351] =?UTF-8?q?Atualiza=20p=C3=A1gina=20de=20adicionar?= =?UTF-8?q?=20cargas=20el=C3=A9tricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires rocha --- .../electrical-load/addelectricalLoad.dart | 252 ++++++++++++------ 1 file changed, 170 insertions(+), 82 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart index 80826c24..0cc8fd38 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart @@ -1,9 +1,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:sige_ie/config/app_styles.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; class ImageData { File imageFile; @@ -35,27 +35,31 @@ class AddelectricalLoadEquipmentScreen extends StatefulWidget { } class _AddEquipmentScreenState extends State { - final _equipmentNameController = TextEditingController(); + final _equipmentBrandController = TextEditingController(); + final _equipmentModelController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); + final _equipmentLoadController = TextEditingController(); String? _selectedType; - String? _selectedLocation; String? _selectedTypeToDelete; + String? _selectedLampType; List equipmentTypes = [ - 'Selecione um equipamento', + 'Selecione um tipo de Carga', + ]; + + List loadTypes = [ + 'Selecione o tipo de Carga', 'Geladeira', 'Ar-Condicionado', - 'Tomada(Corrente)' + 'Tomada (Corrente)' ]; - bool _shouldShowBrandAndModel() { - return _selectedType == 'Geladeira' || _selectedType == 'Ar-Condicionado'; - } - @override void dispose() { - _equipmentNameController.dispose(); + _equipmentBrandController.dispose(); + _equipmentModelController.dispose(); _equipmentQuantityController.dispose(); + _equipmentLoadController.dispose(); categoryImagesMap[widget.categoryNumber]?.clear(); super.dispose(); } @@ -72,8 +76,10 @@ class _AddEquipmentScreenState extends State { } } - void _showImageDialog(File imageFile) { - TextEditingController descriptionController = TextEditingController(); + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); showDialog( context: context, builder: (BuildContext context) { @@ -102,16 +108,20 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (descriptionController.text.isNotEmpty) { setState(() { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; }); Navigator.of(context).pop(); } @@ -202,12 +212,11 @@ class _AddEquipmentScreenState extends State { } void _showConfirmationDialog() { - if (_equipmentNameController.text.isEmpty || + if (_equipmentBrandController.text.isEmpty || + _equipmentModelController.text.isEmpty || _equipmentQuantityController.text.isEmpty || - _selectedType == null || - _selectedLocation == null || - _selectedType == 'Selecione um equipamento' || - _selectedLocation == 'Selecione a localização') { + _equipmentLoadController.text.isEmpty || + (_selectedType == null && _selectedLampType == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -224,21 +233,25 @@ class _AddEquipmentScreenState extends State { content: SingleChildScrollView( child: ListBody( children: [ - const Text('Nome:', + const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentNameController.text), + Text(_selectedType ?? _selectedLampType ?? ''), const SizedBox(height: 10), - const Text('Quantidade:', + const Text('Marca:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentQuantityController.text), + Text(_equipmentBrandController.text), const SizedBox(height: 10), - const Text('Tipo:', + const Text('Modelo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentModelController.text), + const SizedBox(height: 10), + const Text('Carga:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? ''), + Text(_equipmentLoadController.text), const SizedBox(height: 10), - const Text('Localização:', + const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedLocation ?? ''), + Text(_equipmentQuantityController.text), const SizedBox(height: 10), const Text('Imagens:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -246,16 +259,20 @@ class _AddEquipmentScreenState extends State { children: _images.map((imageData) { return Padding( padding: const EdgeInsets.all(4.0), - child: Column( - children: [ - Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - Text(imageData.description), - ], + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), ), ); }).toList(), @@ -333,7 +350,37 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de equipamentos', + const Text('Tipos de Carga', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: loadTypes, + value: _selectedLampType, + onChanged: (newValue) { + setState(() { + _selectedLampType = newValue; + if (newValue == loadTypes[0]) { + _selectedLampType = null; + } + if (_selectedLampType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedLampType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de Cargas', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -345,12 +392,17 @@ class _AddEquipmentScreenState extends State { items: equipmentTypes, value: _selectedType, onChanged: (newValue) { - if (newValue != 'Selecione um equipamento') { - setState(() { - _selectedType = newValue; - }); - } + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedLampType = null; + } + }); }, + enabled: _selectedLampType == null, ), ), Expanded( @@ -375,8 +427,17 @@ class _AddEquipmentScreenState extends State { ), ], ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), const SizedBox(height: 30), - const Text('Marca e Modelo', + const Text('Marca', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -386,7 +447,7 @@ class _AddEquipmentScreenState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentNameController, + controller: _equipmentBrandController, decoration: const InputDecoration( border: InputBorder.none, contentPadding: @@ -395,7 +456,7 @@ class _AddEquipmentScreenState extends State { ), ), const SizedBox(height: 30), - const Text('Quantidade', + const Text('Modelo', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -405,7 +466,26 @@ class _AddEquipmentScreenState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentQuantityController, + controller: _equipmentModelController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Carga', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentLoadController, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly @@ -418,24 +498,27 @@ class _AddEquipmentScreenState extends State { ), ), const SizedBox(height: 30), - const Text('Localização (Interno ou Externo)', + const Text('Quantidade', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - _buildStyledDropdown( - items: const [ - 'Selecione a localização', - 'Interno', - 'Externo' - ], - value: _selectedLocation, - onChanged: (newValue) { - if (newValue != 'Selecione a localização') { - setState(() { - _selectedLocation = newValue; - }); - } - }, + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), ), const SizedBox(height: 15), IconButton( @@ -447,14 +530,17 @@ class _AddEquipmentScreenState extends State { return Stack( alignment: Alignment.topRight, children: [ - if (_shouldShowBrandAndModel()) - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), ), ), IconButton( @@ -563,10 +649,11 @@ class _AddEquipmentScreenState extends State { required List items, String? value, required Function(String?) onChanged, + bool enabled = true, }) { return Container( decoration: BoxDecoration( - color: Colors.grey[300], + color: enabled ? Colors.grey[300] : Colors.grey[200], borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.symmetric(horizontal: 10), @@ -575,15 +662,16 @@ class _AddEquipmentScreenState extends State { value: value, isExpanded: true, underline: Container(), - onChanged: onChanged, + onChanged: enabled ? onChanged : null, items: items.map>((String value) { return DropdownMenuItem( - value: value, + value: value.isEmpty ? null : value, child: Text( value, - style: const TextStyle(color: Colors.black), + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), ), - enabled: value != items.first, ); }).toList(), ), From 075db7181402a1a3c47d5f42156c6037bbc81e0a Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 13:52:24 -0300 Subject: [PATCH 153/351] =?UTF-8?q?Atualiza=20par=C3=82metros=20cargas=20e?= =?UTF-8?q?l=C3=A9tricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../electrical-load/addelectricalLoad.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart index 0cc8fd38..fbf20121 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart @@ -41,7 +41,7 @@ class _AddEquipmentScreenState extends State { final _equipmentLoadController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; - String? _selectedLampType; + String? _selectedLoadType; List equipmentTypes = [ 'Selecione um tipo de Carga', @@ -216,7 +216,7 @@ class _AddEquipmentScreenState extends State { _equipmentModelController.text.isEmpty || _equipmentQuantityController.text.isEmpty || _equipmentLoadController.text.isEmpty || - (_selectedType == null && _selectedLampType == null)) { + (_selectedType == null && _selectedLoadType == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -235,7 +235,7 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectedLampType ?? ''), + Text(_selectedType ?? _selectedLoadType ?? ''), const SizedBox(height: 10), const Text('Marca:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -356,14 +356,14 @@ class _AddEquipmentScreenState extends State { const SizedBox(height: 8), _buildStyledDropdown( items: loadTypes, - value: _selectedLampType, + value: _selectedLoadType, onChanged: (newValue) { setState(() { - _selectedLampType = newValue; + _selectedLoadType = newValue; if (newValue == loadTypes[0]) { - _selectedLampType = null; + _selectedLoadType = null; } - if (_selectedLampType != null) { + if (_selectedLoadType != null) { _selectedType = null; } }); @@ -374,7 +374,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedLampType = null; + _selectedLoadType = null; }); }, child: const Text('Limpar seleção'), @@ -398,11 +398,11 @@ class _AddEquipmentScreenState extends State { _selectedType = null; } if (_selectedType != null) { - _selectedLampType = null; + _selectedLoadType = null; } }); }, - enabled: _selectedLampType == null, + enabled: _selectedLoadType == null, ), ), Expanded( From a244b26ff2ba55419cdc715b8cb6adfaf9c84ccf Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 15:41:42 -0300 Subject: [PATCH 154/351] =?UTF-8?q?Atualiza=20descargas=20atmosf=C3=A9rica?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires rocha --- .../addatmospheric-dischargesEquipment.dart | 227 +++++++++--------- 1 file changed, 109 insertions(+), 118 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart index 702d6b09..4448163a 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart @@ -1,9 +1,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:sige_ie/config/app_styles.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; class ImageData { File imageFile; @@ -35,42 +35,24 @@ class AddatmosphericEquipmentScreen extends StatefulWidget { } class _AddEquipmentScreenState extends State { - final _equipmentNameController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; - String? _selectedLocation; String? _selectedTypeToDelete; + String? _selectDischargeType; List equipmentTypes = [ - 'Selecione um equipamento', - 'Para-raios', - 'Captação', - 'Subsistemas' - ]; - - List pararaiosequipment = [ - 'Selecione um tipo de Para-raios', - 'Tipo Frankiln', - 'Tipo Melsen', - 'Tipo Radioativos' + 'Selecione um tipo de Descarga atmosféfica', ]; - List captacaoequipment = [ - 'Selecione o tipo de Captação', - 'Tipo Franklin', - 'Gaiola de Faraday' - ]; - - List subsistemasequipment = [ - 'Selecione um Subsistema', - 'Capacitação', - 'Descidas' - 'Aterramento' + List dischargeType = [ + 'Selecione o tipo de Descarga Atmosféfica', + 'Para Raios', + 'Captação', + 'Subsistemas', ]; @override void dispose() { - _equipmentNameController.dispose(); _equipmentQuantityController.dispose(); categoryImagesMap[widget.categoryNumber]?.clear(); super.dispose(); @@ -88,8 +70,10 @@ class _AddEquipmentScreenState extends State { } } - void _showImageDialog(File imageFile) { - TextEditingController descriptionController = TextEditingController(); + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); showDialog( context: context, builder: (BuildContext context) { @@ -118,16 +102,20 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (descriptionController.text.isNotEmpty) { setState(() { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; }); Navigator.of(context).pop(); } @@ -218,12 +206,8 @@ class _AddEquipmentScreenState extends State { } void _showConfirmationDialog() { - if (_equipmentNameController.text.isEmpty || - _equipmentQuantityController.text.isEmpty || - _selectedType == null || - _selectedLocation == null || - _selectedType == 'Selecione um equipamento' || - _selectedLocation == 'Selecione a localização') { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectDischargeType == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -240,38 +224,34 @@ class _AddEquipmentScreenState extends State { content: SingleChildScrollView( child: ListBody( children: [ - const Text('Nome:', + const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentNameController.text), + Text(_selectedType ?? _selectDischargeType ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), Text(_equipmentQuantityController.text), const SizedBox(height: 10), - const Text('Tipo:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? ''), - const SizedBox(height: 10), - const Text('Localização:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedLocation ?? ''), - const SizedBox(height: 10), const Text('Imagens:', style: TextStyle(fontWeight: FontWeight.bold)), Wrap( children: _images.map((imageData) { return Padding( padding: const EdgeInsets.all(4.0), - child: Column( - children: [ - Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - Text(imageData.description), - ], + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), ), ); }).toList(), @@ -349,7 +329,37 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de equipamentos', + const Text('Tipos de descarga atmosférica', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: dischargeType, + value: _selectDischargeType, + onChanged: (newValue) { + setState(() { + _selectDischargeType = newValue; + if (newValue == dischargeType[0]) { + _selectDischargeType = null; + } + if (_selectDischargeType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectDischargeType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de descargas atmosféricas', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -361,12 +371,17 @@ class _AddEquipmentScreenState extends State { items: equipmentTypes, value: _selectedType, onChanged: (newValue) { - if (newValue != 'Selecione um equipamento') { - setState(() { - _selectedType = newValue; - }); - } + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectDischargeType = null; + } + }); }, + enabled: _selectDischargeType == null, ), ), Expanded( @@ -391,24 +406,14 @@ class _AddEquipmentScreenState extends State { ), ], ), - const SizedBox(height: 30), - const Text('Nome do equipamento', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: TextField( - controller: _equipmentNameController, - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 15), - ), - ), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), ), const SizedBox(height: 30), const Text('Quantidade', @@ -433,26 +438,6 @@ class _AddEquipmentScreenState extends State { ), ), ), - const SizedBox(height: 30), - const Text('Localização (Interno ou Externo)', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildStyledDropdown( - items: const [ - 'Selecione a localização', - 'Interno', - 'Externo' - ], - value: _selectedLocation, - onChanged: (newValue) { - if (newValue != 'Selecione a localização') { - setState(() { - _selectedLocation = newValue; - }); - } - }, - ), const SizedBox(height: 15), IconButton( icon: const Icon(Icons.camera_alt), @@ -463,13 +448,17 @@ class _AddEquipmentScreenState extends State { return Stack( alignment: Alignment.topRight, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), ), ), IconButton( @@ -578,10 +567,11 @@ class _AddEquipmentScreenState extends State { required List items, String? value, required Function(String?) onChanged, + bool enabled = true, }) { return Container( decoration: BoxDecoration( - color: Colors.grey[300], + color: enabled ? Colors.grey[300] : Colors.grey[200], borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.symmetric(horizontal: 10), @@ -590,15 +580,16 @@ class _AddEquipmentScreenState extends State { value: value, isExpanded: true, underline: Container(), - onChanged: onChanged, + onChanged: enabled ? onChanged : null, items: items.map>((String value) { return DropdownMenuItem( - value: value, + value: value.isEmpty ? null : value, child: Text( value, - style: const TextStyle(color: Colors.black), + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), ), - enabled: value != items.first, ); }).toList(), ), From 27522020d612971254b2370333c2a11a87347215 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 16:19:58 -0300 Subject: [PATCH 155/351] =?UTF-8?q?Altera=20caminho=20de=20equipamentos=20?= =?UTF-8?q?linhas=20el=C3=A9tricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires rocha --- .../lib/equipments/feature/systemConfiguration.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index f1fc85e2..2f87db20 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; - class SystemConfiguration extends StatefulWidget { final String areaName; final String localName; @@ -40,12 +40,17 @@ class _SystemConfigurationState extends State { categoryNumber: category); case '/fireAlarm': case '/electricLoads': - return listelectricalLoadEquipment( + return listelectricalLoadEquipment( areaName: areaName, localName: localName, localId: localId, categoryNumber: category); case '/electricLines': + return listElectricalLineEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); case '/circuits': case '/distributionBoard': case '/cooling': From 1a147eff03c3f78722f20e8180c5ffa0784e75be Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 16:20:44 -0300 Subject: [PATCH 156/351] =?UTF-8?q?Cria=20p=C3=A1gina=20de=20lista=20de=20?= =?UTF-8?q?equipamentos=20linhas=20el=C3=A9tricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires rocha --- .../electrical-line/electricaLLineLIst.dart | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart b/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart new file mode 100644 index 00000000..fb96e0ef --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/electrical-line/addElectricalLine.dart'; + +class listElectricalLineEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listElectricalLineEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddElectricalLineScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'LINHA ELÉTRICAS'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} From 2195995c76defaae3667cec83892d2787d07821a Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 16:21:06 -0300 Subject: [PATCH 157/351] =?UTF-8?q?Cria=20p=C3=A1gina=20de=20adicionar=20e?= =?UTF-8?q?quipamentos=20linhas=20el=C3=A9tricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires rocha --- .../electrical-line/addElectricalLine.dart | 598 ++++++++++++++++++ 1 file changed, 598 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart b/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart new file mode 100644 index 00000000..a2b984d3 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart @@ -0,0 +1,598 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddElectricalLineScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddElectricalLineScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectElectricalType; + + List equipmentTypes = [ + 'Selecione um tipo de Linha Elétrica', + ]; + + List ElectricalType = [ + 'Selecione o tipo de Linha Elétrica', + 'Eletrocalha', + 'Eletroduto', + 'Interuptor', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectElectricalType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectElectricalType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/equipmentScreen', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de descarga atmosférica', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: ElectricalType, + value: _selectElectricalType, + onChanged: (newValue) { + setState(() { + _selectElectricalType = newValue; + if (newValue == ElectricalType[0]) { + _selectElectricalType = null; + } + if (_selectElectricalType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectElectricalType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de descargas atmosféricas', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectElectricalType = null; + } + }); + }, + enabled: _selectElectricalType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} From a5c68241ac3b5ac1c8dd9bef1164c848324894d4 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 17:03:24 -0300 Subject: [PATCH 158/351] =?UTF-8?q?Altera=20caminho=20de=20equipamentos=20?= =?UTF-8?q?circuitos=20el=C3=A9tricos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires rocha --- .../IluminationEquipmentList.dart | 33 +------------------ .../feature/systemConfiguration.dart | 11 ++++--- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart index 65c4b1b7..aafe9786 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart @@ -36,38 +36,7 @@ class listIluminationEquipment extends StatelessWidget { // Vazio para simular nenhum equipamento ]; - String systemTitle; - switch (categoryNumber) { - case 1: - systemTitle = 'ILUMINAÇÃO'; - break; - case 2: - systemTitle = 'CARGAS ELÉTRICAS'; - break; - case 3: - systemTitle = 'LINHAS ELÉTRICAS'; - break; - case 4: - systemTitle = 'CIRCUITOS'; - break; - case 5: - systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; - break; - case 6: - systemTitle = 'CABEAMENTO ESTRUTURADO'; - break; - case 7: - systemTitle = 'DESCARGAS ATMOSFÉRICAS'; - break; - case 8: - systemTitle = 'ALARME DE INCÊNDIO'; - break; - case 9: - systemTitle = 'REFRIGERAÇÃO'; - break; - default: - systemTitle = 'SISTEMA DESCONHECIDO'; - } + String systemTitle = 'ILUMINAÇÃO'; return Scaffold( appBar: AppBar( diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 2f87db20..99c6f0b7 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; @@ -52,13 +53,13 @@ class _SystemConfigurationState extends State { localId: localId, categoryNumber: category); case '/circuits': + return listCicuitEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); case '/distributionBoard': case '/cooling': - //return EquipmentScreen( - // areaName: areaName, - //localName: localName, - //localId: localId, - //categoryNumber: category); case '/lighting': return listIluminationEquipment( areaName: areaName, From 15129632e1ff0ea9c2838fde9f8eb582494c31fa Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 17:03:52 -0300 Subject: [PATCH 159/351] =?UTF-8?q?Cria=20p=C3=A1gina=20de=20adicionar=20e?= =?UTF-8?q?quipamentos=20circuitos=20el=C3=A9tricos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires rocha --- .../addElectricalCircuit.dart | 703 ++++++++++++++++++ 1 file changed, 703 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart new file mode 100644 index 00000000..5be80e94 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart @@ -0,0 +1,703 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddElectricalCircuitEquipmentScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddElectricalCircuitEquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState + extends State { + final _equipmentQuantityController = TextEditingController(); + final _breakerLocationController = TextEditingController(); + final _breakerStateController = TextEditingController(); + final _wireTypeController = TextEditingController(); + final _dimensionController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectCircuitType; + + List equipmentTypes = [ + 'Selecione um tipo de Circuito Elétrico', + ]; + + List cicuitType = [ + 'Selecione o tipo de Circuito Elétrico', + 'Disjuntor(Local e Estado)', + 'Tipo de Fio', + 'Dimensão', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + _breakerLocationController.dispose(); + _breakerStateController.dispose(); + _wireTypeController.dispose(); + _dimensionController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectCircuitType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectCircuitType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Disjuntor(Local):', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_breakerLocationController.text), + const SizedBox(height: 10), + const Text('Disjuntor(Estado):', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_breakerStateController.text), + const SizedBox(height: 10), + const Text('Tipo de Fio:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_wireTypeController.text), + const SizedBox(height: 10), + const Text('Dimensão:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_dimensionController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/equipmentScreen', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de Lâmpada', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: cicuitType, + value: _selectCircuitType, + onChanged: (newValue) { + setState(() { + _selectCircuitType = newValue; + if (newValue == cicuitType[0]) { + _selectCircuitType = null; + } + if (_selectCircuitType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectCircuitType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de lâmpadas', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectCircuitType = null; + } + }); + }, + enabled: _selectCircuitType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Disjuntor(Local)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _breakerLocationController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Disjuntor(Estado)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _breakerStateController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Tipo de Fio', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _wireTypeController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Dimensão', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _dimensionController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} From 26b9d23c5556f24db21bcc1f6cfbcab1170b2df9 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 17:04:17 -0300 Subject: [PATCH 160/351] =?UTF-8?q?Cria=20p=C3=A1gina=20de=20lista=20de=20?= =?UTF-8?q?equipamentos=20cicuitos=20el=C3=A9tricos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramires rocha --- .../electricalCircuitList.dart | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart new file mode 100644 index 00000000..9dd5b41a --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/electrical-circuit/addElectricalCircuit.dart'; + +class listCicuitEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listCicuitEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddElectricalCircuitEquipmentScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'CIRCUITO ELÉTRICO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} From 4480f2e0c3abd58c30d0b2bf0c50725cf7ad3744 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 30 May 2024 17:26:06 -0300 Subject: [PATCH 161/351] =?UTF-8?q?Altera=C3=A7=C3=B5es=20visuais=20na=20t?= =?UTF-8?q?ela=20de=20escolha=20do=20equipamento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipment/systemConfiguration.dart | 162 +++++++++++------- 1 file changed, 104 insertions(+), 58 deletions(-) diff --git a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart index f7de023d..00f5fb03 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart +++ b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart @@ -85,42 +85,70 @@ class _SystemConfigurationState extends State { style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), - SystemButton( - title: 'ALARME DE INCÊNDIO', - onPressed: () => navigateTo('/fireAlarm', widget.areaName, - widget.localName, widget.localId, 8)), - SystemButton( - title: 'CABEAMENTO ESTRUTURADO', - onPressed: () => navigateTo('/structuredCabling', - widget.areaName, widget.localName, widget.localId, 6)), - SystemButton( - title: 'CARGAS ELÉTRICAS', - onPressed: () => navigateTo('/electricLoads', widget.areaName, - widget.localName, widget.localId, 2)), - SystemButton( - title: 'CIRCUITOS', - onPressed: () => navigateTo('/circuits', widget.areaName, - widget.localName, widget.localId, 4)), - SystemButton( - title: 'DESCARGAS ATMOSFÉRICAS', - onPressed: () => navigateTo('/atmosphericDischarges', - widget.areaName, widget.localName, widget.localId, 7)), - SystemButton( - title: 'ILUMINAÇÃO', - onPressed: () => navigateTo('/lighting', widget.areaName, - widget.localName, widget.localId, 1)), - SystemButton( - title: 'LINHAS ELÉTRICAS', - onPressed: () => navigateTo('/electricLines', widget.areaName, - widget.localName, widget.localId, 3)), - SystemButton( - title: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/distributionBoard', - widget.areaName, widget.localName, widget.localId, 5)), - SystemButton( - title: 'REFRIGERAÇÃO', - onPressed: () => navigateTo('/cooling', widget.areaName, - widget.localName, widget.localId, 9)), + GridView.count( + shrinkWrap: true, + crossAxisCount: 3, + childAspectRatio: 1.0, + padding: const EdgeInsets.all(10.0), + mainAxisSpacing: 10.0, + crossAxisSpacing: 10.0, + children: [ + SystemIcon( + icon: Icons.fire_extinguisher, + label: 'ALARME DE INCÊNDIO', + onPressed: () => navigateTo('/fireAlarm', widget.areaName, + widget.localName, widget.localId, 8), + ), + SystemIcon( + icon: Icons.cable, + label: 'CABEAMENTO ESTRUTURADO', + onPressed: () => navigateTo('/structuredCabling', + widget.areaName, widget.localName, widget.localId, 6), + ), + SystemIcon( + icon: Icons.electrical_services, + label: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLoads', widget.areaName, + widget.localName, widget.localId, 2), + ), + SystemIcon( + icon: Icons.electrical_services, + label: 'CIRCUITOS', + onPressed: () => navigateTo('/circuits', widget.areaName, + widget.localName, widget.localId, 4), + ), + SystemIcon( + icon: Icons.bolt, + label: 'DESCARGAS ATMOSFÉRICAS', + onPressed: () => navigateTo('/atmosphericDischarges', + widget.areaName, widget.localName, widget.localId, 7), + ), + SystemIcon( + icon: Icons.lightbulb, + label: 'ILUMINAÇÃO', + onPressed: () => navigateTo('/lighting', widget.areaName, + widget.localName, widget.localId, 1), + ), + SystemIcon( + icon: Icons.power, + label: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLines', widget.areaName, + widget.localName, widget.localId, 3), + ), + SystemIcon( + icon: Icons.dashboard, + label: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/distributionBoard', + widget.areaName, widget.localName, widget.localId, 5), + ), + SystemIcon( + icon: Icons.ac_unit, + label: 'REFRIGERAÇÃO', + onPressed: () => navigateTo('/cooling', widget.areaName, + widget.localName, widget.localId, 9), + ), + ], + ), const SizedBox( height: 30, ), @@ -189,37 +217,55 @@ class _SystemConfigurationState extends State { } } -class SystemButton extends StatelessWidget { - final String title; +class SystemIcon extends StatelessWidget { + final IconData icon; + final String label; final VoidCallback onPressed; - const SystemButton({ + const SystemIcon({ super.key, - required this.title, + required this.icon, + required this.label, required this.onPressed, }); @override Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), - width: double.infinity, - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 25)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: onPressed, - child: Text(title, - style: const TextStyle( + return GestureDetector( + onTap: onPressed, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.sigeIeYellow, + ), + child: Icon( + icon, + size: 40.0, + color: AppColors.sigeIeBlue, + ), + ), + const SizedBox(height: 10), + SizedBox( + width: 80, + child: Text( + label, + textAlign: TextAlign.center, + style: const TextStyle( color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), + fontSize: 9, + fontWeight: FontWeight.bold, + ), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), ); } From 64530366830ab40b8c5e357f59103de83b17be04 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 30 May 2024 17:31:05 -0300 Subject: [PATCH 162/351] Adicionando responsividade --- .../feature/equipment/EquipmentScreen.dart | 21 ++++++++++++++----- .../equipment/systemConfiguration.dart | 7 +++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart index 270d0680..0a766690 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart @@ -100,11 +100,22 @@ class EquipmentScreen extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Text( + '$areaName - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), + ), + ], + ), ), ), const SizedBox(height: 20), diff --git a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart index 00f5fb03..edbddc16 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart +++ b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart @@ -88,7 +88,7 @@ class _SystemConfigurationState extends State { GridView.count( shrinkWrap: true, crossAxisCount: 3, - childAspectRatio: 1.0, + childAspectRatio: 0.8, // Adjusted aspect ratio padding: const EdgeInsets.all(10.0), mainAxisSpacing: 10.0, crossAxisSpacing: 10.0, @@ -250,14 +250,13 @@ class SystemIcon extends StatelessWidget { ), ), const SizedBox(height: 10), - SizedBox( - width: 80, + Flexible( child: Text( label, textAlign: TextAlign.center, style: const TextStyle( color: AppColors.sigeIeBlue, - fontSize: 9, + fontSize: 12, fontWeight: FontWeight.bold, ), softWrap: true, From 3e4e7be4d05939b840364a811195b04379482402 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 17:49:24 -0300 Subject: [PATCH 163/351] =?UTF-8?q?altera=20rota=20de=20retorno=20das=20pa?= =?UTF-8?q?=C7=B5inas=20equipamentos:=20circuito,atmosf=C3=A9ricp,ilumin?= =?UTF-8?q?=C3=A7=C3=A3o,linhas=20e=20cargas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../addatmospheric-dischargesEquipment.dart | 2 +- .../feature/electrical-circuit/addElectricalCircuit.dart | 9 +++++---- .../feature/electrical-line/addElectricalLine.dart | 2 +- .../feature/electrical-load/addelectricalLoad.dart | 2 +- .../feature/iluminations/addIluminationEquipment.dart | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart index 4448163a..6abba908 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart @@ -281,7 +281,7 @@ class _AddEquipmentScreenState extends State { void navigateToEquipmentScreen() { Navigator.of(context).pushNamed( - '/equipmentScreen', + '/listatmosphericEquipment', arguments: { 'areaName': widget.areaName, 'localName': widget.localName, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart index 5be80e94..7f0f06cd 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart @@ -295,7 +295,7 @@ class _AddEquipmentScreenState child: const Text('OK'), onPressed: () { Navigator.of(context).pop(); - navigateToEquipmentScreen(); + navigateToElectricalCircuitList(); }, ), ], @@ -304,9 +304,10 @@ class _AddEquipmentScreenState ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/equipmentScreen', + void navigateToElectricalCircuitList() { + Navigator.pushReplacementNamed( + context, + '/electricalCircuitList', arguments: { 'areaName': widget.areaName, 'localName': widget.localName, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart b/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart index a2b984d3..90455f59 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart @@ -281,7 +281,7 @@ class _AddEquipmentScreenState extends State { void navigateToEquipmentScreen() { Navigator.of(context).pushNamed( - '/equipmentScreen', + '/electricalLineList', arguments: { 'areaName': widget.areaName, 'localName': widget.localName, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart index fbf20121..24dc5790 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart @@ -302,7 +302,7 @@ class _AddEquipmentScreenState extends State { void navigateToEquipmentScreen() { Navigator.of(context).pushNamed( - '/equipmentScreen', + '/listelectricalLoadEquipment', arguments: { 'areaName': widget.areaName, 'localName': widget.localName, diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart index 1c47d80a..0ca48426 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart @@ -297,7 +297,7 @@ class _AddEquipmentScreenState extends State { void navigateToEquipmentScreen() { Navigator.of(context).pushNamed( - '/equipmentScreen', + '/listIluminationEquipment', arguments: { 'areaName': widget.areaName, 'localName': widget.localName, From e33b1805848b6a02dc486d212b8c3ed9a39b21ab Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 30 May 2024 17:49:54 -0300 Subject: [PATCH 164/351] Adiciona rotas dos equipamentos na main --- frontend/sige_ie/lib/main.dart | 103 ++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 7e033744..72f60d50 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -3,6 +3,9 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; +import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; +import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; @@ -10,6 +13,8 @@ import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList import 'package:sige_ie/equipments/feature/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; +import 'package:sige_ie/equipments/feature/electrical-circuit/addElectricalCircuit.dart'; +import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; // Importe a tela de lista de circuitos elétricos import 'core/feature/login/login.dart'; void main() { @@ -94,7 +99,7 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /systemLocation.'); - case '/equipmentScreen': + case '/listIluminationEquipment': if (settings.arguments is Map) { final args = settings.arguments as Map; final String? areaName = args['areaName']?.toString(); @@ -118,6 +123,102 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /equipmentScreen.'); + case '/electricalCircuitList': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listCicuitEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /electricalCircuitList.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /electricalCircuitList.'); + + case '/electricalLineList': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listElectricalLineEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /electricalLineList.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /electricalLineList.'); + + case '/listatmosphericEquipment': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listatmosphericEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /electricalLineList.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /electricalLineList.'); + + case '/listelectricalLoadEquipment': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listelectricalLoadEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /electricalLineList.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /electricalLineList.'); + default: return MaterialPageRoute( builder: (context) => UndefinedView(name: settings.name)); From 1d6d4db7f580358cce4675114aa272a1d9cf341d Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 30 May 2024 17:57:12 -0300 Subject: [PATCH 165/351] =?UTF-8?q?Corre=C3=A7=C3=A3o=20na=20l=C3=B3gica?= =?UTF-8?q?=20e=20visualiza=C3=A7=C3=A3o=20de=20deletar=20equipamento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/equipment/addEquipmentScreen.dart | 106 +++++++++--------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart index f4ff2d1b..6accb945 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart @@ -112,8 +112,7 @@ class _AddEquipmentScreenState extends State { } void _deleteEquipmentType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + if (_selectedTypeToDelete == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: @@ -452,54 +451,62 @@ class _AddEquipmentScreenState extends State { showDialog( context: context, builder: (BuildContext context) { - return AlertDialog( - title: const Text('Excluir tipo de equipamento'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'Selecione um equipamento para excluir:', - textAlign: TextAlign.center, - ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), - ); - }).toList(), + _selectedTypeToDelete = null; // Inicialize com null + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: [...equipmentTypes] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + enabled: + value != 'Selecione um equipamento para deletar', + ); + }).toList(), + ), + ], ), - ], - ), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Excluir'), - onPressed: () { - if (_selectedTypeToDelete != null) { - Navigator.of(context).pop(); - _deleteEquipmentType(); - } - }, - ), - ], + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null && + _selectedTypeToDelete != + 'Selecione um equipamento para deletar') { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, ); }, ); @@ -529,7 +536,6 @@ class _AddEquipmentScreenState extends State { value, style: const TextStyle(color: Colors.black), ), - enabled: value != items.first, ); }).toList(), ), From ae15f085aef2ac14936c416db5c533acb14a3c6f Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 12:18:13 -0300 Subject: [PATCH 166/351] =?UTF-8?q?cria=20p=C3=A1gina=20de=20listar=20equi?= =?UTF-8?q?pamentos=20quadro=20de=20distribui=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../distribuitionBoardEquipmentList.dart | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart new file mode 100644 index 00000000..0f463f57 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/addDistribuitionBoard.dart'; + +class listDistribuitionBoard extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listDistribuitionBoard({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddDistribuitionBoard( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} From b300f5ee34d3805a1195c128b17792a31f545da3 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 12:18:28 -0300 Subject: [PATCH 167/351] =?UTF-8?q?cria=20p=C3=A1gina=20de=20adicionar=20e?= =?UTF-8?q?quipamentos=20quadro=20de=20distribui=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../addDistribuitionBoard.dart | 648 ++++++++++++++++++ 1 file changed, 648 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart new file mode 100644 index 00000000..c91459cb --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart @@ -0,0 +1,648 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddDistribuitionBoard extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddDistribuitionBoard({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentchargeController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedLocation; + String? _selectedTypeToDelete; + String? _selectedBoardType; + + List equipmentTypes = [ + 'Selecione um tipo de quadro', + ]; + + List boardType = [ + 'Selecione o tipo de quadro', + 'quadro 1', + 'quadro 2', + 'quadro 3', + ]; + + @override + void dispose() { + _equipmentchargeController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentchargeController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedBoardType == null) || + _selectedLocation == null || + _selectedLocation == 'Selecione a opção') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedBoardType ?? ''), + const SizedBox(height: 10), + const Text('Especificação:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentchargeController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Existe dispositivos de proteção dentro do quadro:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedLocation ?? ''), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listDistribuitionBoard', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de Quadro', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: boardType, + value: _selectedBoardType, + onChanged: (newValue) { + setState(() { + _selectedBoardType = newValue; + if (newValue == boardType[0]) { + _selectedBoardType = null; + } + if (_selectedBoardType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedBoardType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de Quadros', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedBoardType = null; + } + }); + }, + enabled: _selectedBoardType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Especificação', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentchargeController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text( + 'Existe dispositivos de proteção dentro do quadro:', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: const ['Selecione a opção', 'Sim', 'Não'], + value: _selectedLocation, + onChanged: (newValue) { + if (newValue != 'Selecione a opção') { + setState(() { + _selectedLocation = newValue; + }); + } + }, + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} From becbc1cac8102f0d102b8ea7be4cab396655e55d Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 12:19:10 -0300 Subject: [PATCH 168/351] =?UTF-8?q?Atualiza=20caminhos=20na=20main=20e=20n?= =?UTF-8?q?a=20systemconfiguration=20de=20equipamentos=20quadro=20de=20dis?= =?UTF-8?q?tribui=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/systemConfiguration.dart | 6 ++++ frontend/sige_ie/lib/main.dart | 28 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 99c6f0b7..ad4ed615 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; @@ -59,6 +60,11 @@ class _SystemConfigurationState extends State { localId: localId, categoryNumber: category); case '/distributionBoard': + return listDistribuitionBoard( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); case '/cooling': case '/lighting': return listIluminationEquipment( diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 72f60d50..85e53c6e 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -4,6 +4,7 @@ import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; @@ -13,8 +14,7 @@ import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList import 'package:sige_ie/equipments/feature/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; -import 'package:sige_ie/equipments/feature/electrical-circuit/addElectricalCircuit.dart'; -import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; // Importe a tela de lista de circuitos elétricos +import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'core/feature/login/login.dart'; void main() { @@ -195,6 +195,30 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /electricalLineList.'); + case '/listDistribuitionBoard': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listDistribuitionBoard( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /listDistribuitionBoard.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + case '/listelectricalLoadEquipment': if (settings.arguments is Map) { final args = settings.arguments as Map; From fada8f0462667933e638b281497668be3a10cc95 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 12:43:28 -0300 Subject: [PATCH 169/351] =?UTF-8?q?cria=20p=C3=A1gina=20de=20adicionar=20e?= =?UTF-8?q?quipamentos=20cabeamento=20estruturado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../addStruturedCabling.dart | 624 ++++++++++++++++++ 1 file changed, 624 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart b/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart new file mode 100644 index 00000000..a4a9372a --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart @@ -0,0 +1,624 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddstruturedCabling extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddstruturedCabling({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentchargeController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectedstruturedType; + + List equipmentTypes = [ + 'Selecione o tipo de cabeamento estruturado', + ]; + + List struturedType = [ + 'Selecione o tipo de cabeamento estruturado', + 'Eletroduto', + 'Eletrocalha', + ]; + + @override + void dispose() { + _equipmentchargeController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentchargeController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedstruturedType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedstruturedType ?? ''), + const SizedBox(height: 10), + const Text('Dimensão:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentchargeController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listStruturedCabling', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de cabeamento', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: struturedType, + value: _selectedstruturedType, + onChanged: (newValue) { + setState(() { + _selectedstruturedType = newValue; + if (newValue == struturedType[0]) { + _selectedstruturedType = null; + } + if (_selectedstruturedType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedstruturedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de cabeamento', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedstruturedType = null; + } + }); + }, + enabled: _selectedstruturedType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Dimensão', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentchargeController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} From c0446e87eceab9aae5111eef038a4b31cf3ceac1 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 12:43:55 -0300 Subject: [PATCH 170/351] =?UTF-8?q?cria=20p=C3=A1gina=20de=20listar=20equi?= =?UTF-8?q?pamentos=20cabeamento=20estruturado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../struturedCablingEquipmentList.dart | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart new file mode 100644 index 00000000..1f4edc99 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/structured-cabling/addStruturedCabling.dart'; + +class listStruturedCabling extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listStruturedCabling({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddstruturedCabling( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'CABEAMENTO ESTRUTURADO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} From 57d84b3fb50dde50784fc588420d764b8c2fbf74 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 12:44:24 -0300 Subject: [PATCH 171/351] Atualiza caminhos na main e na systemconfiguration de equipamentos cabeamento estruturado --- .../feature/systemConfiguration.dart | 6 +++++ frontend/sige_ie/lib/main.dart | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index ad4ed615..32a1d91f 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -6,6 +6,7 @@ import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.da import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; +import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; class SystemConfiguration extends StatefulWidget { final String areaName; @@ -34,6 +35,11 @@ class _SystemConfigurationState extends State { builder: (context) { switch (routeName) { case '/structuredCabling': + return listStruturedCabling( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); case '/atmosphericDischarges': return listatmosphericEquipment( areaName: areaName, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 85e53c6e..b4890413 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -7,6 +7,7 @@ import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-di import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; +import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; @@ -219,6 +220,30 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + case '/listStruturedCabling': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listStruturedCabling( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /listStruturedCabling.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /listStruturedCabling.'); + case '/listelectricalLoadEquipment': if (settings.arguments is Map) { final args = settings.arguments as Map; From 9bcc29b08860c0daeb0283cc7b8d2eb47e2a7023 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 13:05:39 -0300 Subject: [PATCH 172/351] Atualiza caminhos na main e na systemconfiguration de fire alarm --- .../feature/systemConfiguration.dart | 6 +++++ frontend/sige_ie/lib/main.dart | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 32a1d91f..03d84071 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -3,6 +3,7 @@ import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/fireAlarmList.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; @@ -47,6 +48,11 @@ class _SystemConfigurationState extends State { localId: localId, categoryNumber: category); case '/fireAlarm': + return listFireAlarms( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); case '/electricLoads': return listelectricalLoadEquipment( areaName: areaName, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index b4890413..a02197df 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -7,6 +7,7 @@ import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-di import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/fireAlarmList.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; @@ -220,6 +221,30 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + case '/listFireAlarms': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listFireAlarms( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /listDistribuitionBoard.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + case '/listStruturedCabling': if (settings.arguments is Map) { final args = settings.arguments as Map; From 1e426fb37cfbf5d6a999cb9ce3e5352a3b32636a Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 13:05:58 -0300 Subject: [PATCH 173/351] =?UTF-8?q?cria=20p=C3=A1gina=20de=20listar=20equi?= =?UTF-8?q?pamentos=20fire=20alarm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire-alarm/fireAlarmList.dart | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart new file mode 100644 index 00000000..aa6690a9 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/addFireAlarm.dart'; + +class listFireAlarms extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listFireAlarms({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddfireAlarm( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'ALARME DE INCÊNDIO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} From fbd4af1db396c3eaa313b11fd6ef02b1dca26843 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 13:06:16 -0300 Subject: [PATCH 174/351] =?UTF-8?q?cria=20p=C3=A1gina=20de=20adicionar=20e?= =?UTF-8?q?quipamentos=20fire=20alarm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire-alarm/addFireAlarm.dart | 598 ++++++++++++++++++ 1 file changed, 598 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart new file mode 100644 index 00000000..b9feff2f --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -0,0 +1,598 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddfireAlarm extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddfireAlarm({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectedfireAlarmType; + + List equipmentTypes = [ + 'Selecione o tipo de alarme incêndio', + ]; + + List fireAlarmType = [ + 'Selecione o tipo de alarme de incêndio', + 'Sensor de Fumaça', + 'Sensor de Temperatura', + 'Acionadores', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedfireAlarmType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedfireAlarmType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listFireAlarms', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de alarme de incêndio', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: fireAlarmType, + value: _selectedfireAlarmType, + onChanged: (newValue) { + setState(() { + _selectedfireAlarmType = newValue; + if (newValue == fireAlarmType[0]) { + _selectedfireAlarmType = null; + } + if (_selectedfireAlarmType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedfireAlarmType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de alarme de incêndio', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedfireAlarmType = null; + } + }); + }, + enabled: _selectedfireAlarmType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} From 65545898396373c738e1983b470c8c2cbe397261 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 13:20:55 -0300 Subject: [PATCH 175/351] =?UTF-8?q?Atualiza=20caminhos=20na=20main=20e=20n?= =?UTF-8?q?a=20systemconfiguration=20de=20refrigera=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/systemConfiguration.dart | 6 +++++ frontend/sige_ie/lib/main.dart | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 03d84071..2c2df52f 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; @@ -78,6 +79,11 @@ class _SystemConfigurationState extends State { localId: localId, categoryNumber: category); case '/cooling': + return listCollingEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); case '/lighting': return listIluminationEquipment( areaName: areaName, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index a02197df..f9a7b622 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -4,6 +4,7 @@ import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; +import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; @@ -245,6 +246,30 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + case '/listCollingEquipment': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listCollingEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /listDistribuitionBoard.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + case '/listStruturedCabling': if (settings.arguments is Map) { final args = settings.arguments as Map; From 3f1b7f7f23c3d052d50c6c71821b9dcac9b73e59 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 13:21:12 -0300 Subject: [PATCH 176/351] =?UTF-8?q?cria=20p=C3=A1gina=20de=20adicionar=20e?= =?UTF-8?q?quipamentos=20refriger=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/cooling/Addcooling.dart | 598 ++++++++++++++++++ 1 file changed, 598 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart b/frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart new file mode 100644 index 00000000..99d5d749 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart @@ -0,0 +1,598 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class Addcooling extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const Addcooling({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectedcollingType; + + List equipmentTypes = [ + 'Selecione o tipo de refrigeração', + ]; + + List collingType = [ + 'Selecione o tipo de refrigeração', + 'Refrigeração1', + 'Refrigeração2', + 'Refrigeração3', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedcollingType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedcollingType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listCollingEquipment', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de refigeração', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: collingType, + value: _selectedcollingType, + onChanged: (newValue) { + setState(() { + _selectedcollingType = newValue; + if (newValue == collingType[0]) { + _selectedcollingType = null; + } + if (_selectedcollingType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedcollingType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de refigeração', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedcollingType = null; + } + }); + }, + enabled: _selectedcollingType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} From 0c4323cb4a300977c7f6993409e0fc6695ce4824 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Fri, 31 May 2024 13:21:33 -0300 Subject: [PATCH 177/351] =?UTF-8?q?cria=20p=C3=A1gina=20de=20listar=20equi?= =?UTF-8?q?pamentos=20refrigera=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/cooling/coolingEquipmentList.dart | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart new file mode 100644 index 00000000..b79a00fe --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/cooling/Addcooling.dart'; + +class listCollingEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listCollingEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Addcooling( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'Refrigeração'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} From ff20fdca7d76ed30295f6f73e10d4c662687d76d Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 3 Jun 2024 14:06:25 -0300 Subject: [PATCH 178/351] Icones na tela de equipamentos --- frontend/sige_ie/android/settings.gradle | 2 +- .../feature/systemConfiguration.dart | 154 ++++++++++++++---- frontend/sige_ie/pubspec.yaml | 2 - 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/frontend/sige_ie/android/settings.gradle b/frontend/sige_ie/android/settings.gradle index 1d6d19b7..985a6e2e 100644 --- a/frontend/sige_ie/android/settings.gradle +++ b/frontend/sige_ie/android/settings.gradle @@ -20,7 +20,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "org.jetbrains.kotlin.android" version "1.9.22" apply false } include ":app" diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 2c2df52f..1eb42c48 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -133,42 +133,70 @@ class _SystemConfigurationState extends State { style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), - SystemButton( - title: 'ALARME DE INCÊNDIO', - onPressed: () => navigateTo('/fireAlarm', widget.areaName, - widget.localName, widget.localId, 8)), - SystemButton( - title: 'CABEAMENTO ESTRUTURADO', - onPressed: () => navigateTo('/structuredCabling', - widget.areaName, widget.localName, widget.localId, 6)), - SystemButton( - title: 'CARGAS ELÉTRICAS', - onPressed: () => navigateTo('/electricLoads', widget.areaName, - widget.localName, widget.localId, 2)), - SystemButton( - title: 'CIRCUITOS', - onPressed: () => navigateTo('/circuits', widget.areaName, - widget.localName, widget.localId, 4)), - SystemButton( - title: 'DESCARGAS ATMOSFÉRICAS', - onPressed: () => navigateTo('/atmosphericDischarges', - widget.areaName, widget.localName, widget.localId, 7)), - SystemButton( - title: 'ILUMINAÇÃO', - onPressed: () => navigateTo('/lighting', widget.areaName, - widget.localName, widget.localId, 1)), - SystemButton( - title: 'LINHAS ELÉTRICAS', - onPressed: () => navigateTo('/electricLines', widget.areaName, - widget.localName, widget.localId, 3)), - SystemButton( - title: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/distributionBoard', - widget.areaName, widget.localName, widget.localId, 5)), - SystemButton( - title: 'REFRIGERAÇÃO', - onPressed: () => navigateTo('/cooling', widget.areaName, - widget.localName, widget.localId, 9)), + GridView.count( + shrinkWrap: true, + crossAxisCount: 3, + childAspectRatio: 1.0, + padding: const EdgeInsets.all(10.0), + mainAxisSpacing: 10.0, + crossAxisSpacing: 10.0, + children: [ + SystemIcon( + icon: Icons.fire_extinguisher, + label: 'ALARME DE INCÊNDIO', + onPressed: () => navigateTo('/fireAlarm', widget.areaName, + widget.localName, widget.localId, 8), + ), + SystemIcon( + icon: Icons.cable, + label: 'CABEAMENTO ESTRUTURADO', + onPressed: () => navigateTo('/structuredCabling', + widget.areaName, widget.localName, widget.localId, 6), + ), + SystemIcon( + icon: Icons.electrical_services, + label: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLoads', widget.areaName, + widget.localName, widget.localId, 2), + ), + SystemIcon( + icon: Icons.electrical_services, + label: 'CIRCUITOS', + onPressed: () => navigateTo('/circuits', widget.areaName, + widget.localName, widget.localId, 4), + ), + SystemIcon( + icon: Icons.bolt, + label: 'DESCARGAS ATMOSFÉRICAS', + onPressed: () => navigateTo('/atmosphericDischarges', + widget.areaName, widget.localName, widget.localId, 7), + ), + SystemIcon( + icon: Icons.lightbulb, + label: 'ILUMINAÇÃO', + onPressed: () => navigateTo('/lighting', widget.areaName, + widget.localName, widget.localId, 1), + ), + SystemIcon( + icon: Icons.power, + label: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLines', widget.areaName, + widget.localName, widget.localId, 3), + ), + SystemIcon( + icon: Icons.dashboard, + label: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/distributionBoard', + widget.areaName, widget.localName, widget.localId, 5), + ), + SystemIcon( + icon: Icons.ac_unit, + label: 'REFRIGERAÇÃO', + onPressed: () => navigateTo('/cooling', widget.areaName, + widget.localName, widget.localId, 9), + ), + ], + ), const SizedBox( height: 30, ), @@ -237,6 +265,60 @@ class _SystemConfigurationState extends State { } } +class SystemIcon extends StatelessWidget { + final IconData icon; + final String label; + final VoidCallback onPressed; + + const SystemIcon({ + super.key, + required this.icon, + required this.label, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onPressed, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.sigeIeYellow, + ), + child: Icon( + icon, + size: 40.0, + color: AppColors.sigeIeBlue, + ), + ), + const SizedBox(height: 10), + SizedBox( + width: 80, + child: Text( + label, + textAlign: TextAlign.center, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 9, + fontWeight: FontWeight.bold, + ), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } +} + class SystemButton extends StatelessWidget { final String title; final VoidCallback onPressed; diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 8fe7b0a0..4a544c11 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -42,8 +42,6 @@ dependencies: geolocator: ^9.0.2 logger: ^1.0.0 - - dev_dependencies: flutter_test: sdk: flutter From ca14dd71124edd2796cd7fbffbba430791f27d1f Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 3 Jun 2024 15:12:50 -0300 Subject: [PATCH 179/351] =?UTF-8?q?Mudan=C3=A7a=20visual=20na=20primeira?= =?UTF-8?q?=20tela=20e=20adi=C3=A7=C3=A3o=20do=20informativo=20na=20home?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/app/src/main/AndroidManifest.xml | 2 +- frontend/sige_ie/assets/UNB.png | Bin 0 -> 88542 bytes frontend/sige_ie/lib/core/ui/first_scren.dart | 7 +- frontend/sige_ie/lib/home/ui/home.dart | 236 +++++++++++++----- frontend/sige_ie/pubspec.yaml | 1 + 5 files changed, 179 insertions(+), 67 deletions(-) create mode 100644 frontend/sige_ie/assets/UNB.png diff --git a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml index 4ae41dcb..24e1cadb 100644 --- a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml +++ b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ android:resource="@style/NormalTheme"/> + android:value="AIzaSyC4tWuLptSAqy_SqJRVbSGT0R1eDEyirw8"/> diff --git a/frontend/sige_ie/assets/UNB.png b/frontend/sige_ie/assets/UNB.png new file mode 100644 index 0000000000000000000000000000000000000000..e5bf5276d23a9490aa011a53e654ec0b92028691 GIT binary patch literal 88542 zcmeEuRa;!$(k<>T!QEYgrEzz+;1Jv)xI=Jvg1fuBySp|HA-KCAvgJGPyZ8A4=i8&L?2)I0jRor zD<`R9f~Wo^=KuU2UEg5H@-OfHrH;Zr#MkA8M~JJ7l>gF^{=^mWFB<>r;-hpB_%=Y6 z03qLhY3LhBQ1xG`B@1MHJX?JqeYnr`KWymxVUS(+e_Qnb3Rmm^Mt^eT3Fh|jUmAWG zq#f$Nm%~ZUH&7=-o)|gnzn7yoYU#gJlga+y#rWUF__weAZ)E%>%>TcR3>)}9&cXS` zoYy84AzS#YaDMQ=gOFKi2LY4J9|(*2ndr+R4KKjT3m<$Z8`sG>q;XyeB^f_M3W84&m`06r zL!QO+6Xd8kDdV@EeMt2A7bGbJ$lta8B{0#!N(mueq zLN-CqHXb74&|I+nXn1tGR5?EX8ifR!SQNq7-_A`kBgbd;J9p@KGxK1uKuLMKz5GL9 zXp^MvOLTylUU$fU0{wCf$VW0k&6Dd+-cak;5f9ity56yS++gmoOJ~J5*8c|G%Z0+~ ze=CEED`sXUflW3v+yk3bRWR%Be<_-QI+Kd0ue#U5FSD|9pxYn_X zP1C~wzY>6ZaF>7!CIXQUo4_a}07@Dd3mY*~UOgrd%L6+^lZ!@Y#+LnY<0jplT{aRE zrYiAIJSKm?GqdK$og+VKcKrj7Y3j^Q4;NCWU50K6U2r!_hK$!SM{ScuH@g_w=oCa zc00eL36bXkT)pl8F8IG4V&Rxs_UZ;NuSayrvg0?q*u56C3=M|TX%8wKE&H8mO1{cC z&$plO+_1(Hv#=6m*`HZe%zY66nQ&oqU0^OlX@MyIx1)hW6MxU~jg%kBQ7fLZYY_^< z%1h^^YB%@^%M11^x5g-|FNP98LJ2zRg17=POpubDEtH#8AxEJWG=W7SD4mreB342M zbv9*f|EGyAlYd4Px)1`(_rD9@4Z(#Oztl~Q#ym)!@ctOv(=(usyRt&LkS*JmphO~# zNe<^rc`t2#d~(zJwu?KOe^)cCq@ed59J+)?pf&zm5!Wt&YWej$i3m?v-^X+QZg?kQ zUovN>&#Jea3UnuNRD8hA`;$blj0&Yhl2`#4^T3Qa&o933U0K`|qt=Sg=bnf{u((qWrk_%PGIa}zz~>IP&k ze#(ePHOIXsywG z&eiPPj_Y1q!GSrF3Rob7Mx0O>7_qjN)=#~X$VHr#UqQ6Jq6y8+-?cwsaGzbk6R5+o zTr=eX%>FBSO#cANN^aio{W%r5D)RQve z`{|Id3f0|Mf8@S7H8xJx1<_#SkQw}hwXpfAag31-5-BEOIx9Soy^BYC;Z50JZntbH zo!v!26r3H_j&Nb9PYh8gCZvM58;NPhgY^Hj-MsZ9MWoxJ_{piSiziGD@0 zt8uX-2n;q)qgE2MWM~mdg%tr5ub@ljTBoiA?*Xgsbo2So0Vs*uYZVz_9XlEJ1rA;0CmB%6nT8@< zd_)0YrH9E%0AITZ!D%|xRh>Zn9riG!_9hv#V=T!Kp?P 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%0Db;bvCtz88&SCf%1tmNslI392GXmZFOC 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 literal 0 HcmV?d00001 diff --git a/frontend/sige_ie/lib/core/ui/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_scren.dart index e2469d6d..5ed95ff4 100644 --- a/frontend/sige_ie/lib/core/ui/first_scren.dart +++ b/frontend/sige_ie/lib/core/ui/first_scren.dart @@ -6,12 +6,15 @@ class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xff123c75), + backgroundColor: const Color(0xffe1e1e1), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - Image.asset('assets/1000x1000.png'), + Image.asset('assets/UNB.png'), + const SizedBox( + height: 50, + ), ElevatedButton( onPressed: () { Navigator.pushNamed(context, '/loginScreen'); diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 5e97f212..9b9a4137 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -49,18 +49,22 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: PageView( - controller: _pageController, - onPageChanged: (index) { - setState(() { - _selectedIndex = index; - }); - }, - children: [ - buildHomePage(context), - const FacilitiesPage(), - const MapsPage(), - ProfilePage() + body: Stack( + children: [ + PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _selectedIndex = index; + }); + }, + children: [ + buildHomePage(context), + const FacilitiesPage(), + const MapsPage(), + ProfilePage() + ], + ), ], ), bottomNavigationBar: buildBottomNavigationBar(), @@ -77,66 +81,170 @@ class _HomePageState extends State { return const Center(child: Text('Erro ao carregar os dados')); } else if (snapshot.hasData) { var user = snapshot.data!; - return Column( + return Stack( children: [ - AppBar( - automaticallyImplyLeading: false, - backgroundColor: const Color(0xff123c75), - elevation: 0, - ), - Expanded( - flex: 3, - child: Container( - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20), - ), + Column( + children: [ + AppBar( + automaticallyImplyLeading: false, + backgroundColor: const Color(0xff123c75), + elevation: 0, ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/1000x1000.png'), - fit: BoxFit.cover, - ), - ), + Expanded( + flex: 3, + child: Container( + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), ), ), - Container( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(left: 20), - child: Text( - 'Olá, ${user.firstname}', - style: const TextStyle( - color: AppColors.sigeIeYellow, - fontSize: 20, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/1000x1000.png'), + fit: BoxFit.cover, + ), + ), + ), ), - ), + Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 20), + child: Text( + 'Olá, ${user.firstname}', + style: const TextStyle( + color: AppColors.sigeIeYellow, + fontSize: 20, + ), + ), + ), + const SizedBox(height: 10), + ], ), - const SizedBox(height: 10), - ], + ), ), - ), + Expanded( + flex: 6, + child: Column( + children: [ + const Spacer(), + buildSmallRectangle( + context, 'Registrar novo local', 'Registrar', () { + Navigator.of(context).pushNamed('/newLocation'); + }), + buildSmallRectangle(context, 'Equipes', 'Gerenciar', + () { + // Código aqui. + }), + const Spacer(), + ], + ), + ), + ], ), - Expanded( - flex: 6, - child: Column( - children: [ - const Spacer(), - buildSmallRectangle( - context, 'Registrar novo local', 'Registrar', () { - Navigator.of(context).pushNamed('/newLocation'); - }), - buildSmallRectangle(context, 'Equipes', 'Gerenciar', () { - // Código aqui. - }), - const Spacer(), - ], + Positioned( + top: 20, + right: 20, + child: IconButton( + icon: const Icon(Icons.info, color: Colors.white, size: 30), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: + const Text('Informações sobre o Projeto Sigeie'), + content: SingleChildScrollView( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + 'A criação do aplicativo Sigeie foi um esforço colaborativo de uma equipe dedicada de profissionais, cada um trazendo sua expertise para garantir o sucesso do projeto. Aqui está uma descrição detalhada da participação de cada membro, conforme a organização da equipe:\n\n', + ), + TextSpan( + text: + '1. Loana Nunes Velasco - Cliente do Projeto\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Loana, docente do curso de Engenharia de Energia, atuou como cliente do projeto. Ela foi responsável por validar as entregas, garantindo que as necessidades e expectativas dos usuários finais fossem claramente comunicadas à equipe.\n\n', + ), + TextSpan( + text: + '2. Pedro Lucas - Desenvolvedor Backend (Engenharia de Software)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Pedro Lucas foi responsável por codificar o backend e configurar a infraestrutura necessária para o funcionamento do aplicativo. Ele contou com a colaboração de Kauan Jose e Oscar de Brito para garantir que o backend fosse seguro e escalável.\n\n', + ), + TextSpan( + text: + '3. Danilo de Melo Ribeiro - Desenvolvedor Frontend e UX Design (Engenharia de Software)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Danilo trabalhou no desenvolvimento do frontend do aplicativo, codificando a interface e realizando a integração com o backend. Ele projetou a interface do usuário, criou protótipos e realizou entrevistas com os clientes para garantir que o design fosse intuitivo e atendesse às necessidades dos usuários. Ele colaborou com Ramires Rocha e Pedro Lucas para construir uma interface responsiva e interativa.\n\n', + ), + TextSpan( + text: + '4. Oscar de Brito - Analista de Requisitos (Engenharia de Software)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Oscar levantou os requisitos do projeto, gerenciou a documentação e validou as especificações com o cliente. Ele contou com a colaboração de Ramires Rocha e Pedro Lucas para garantir que todos os requisitos fossem compreendidos e implementados corretamente.\n\n', + ), + TextSpan( + text: + '5. Kauan Jose - Colaborador Backend (Engenharia de Software)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Kauan colaborou no desenvolvimento do backend, fornecendo suporte essencial para Pedro Lucas. Ele ajudou a configurar a infraestrutura e garantir que o backend funcionasse de maneira eficiente e segura.\n\n', + ), + TextSpan( + text: + '6. Ramires Rocha - Colaborador Frontend (Engenharia Eletrônica)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Ramires colaborou no desenvolvimento do frontend, fornecendo suporte para Danilo de Melo Ribeiro. Ele ajudou a implementar funcionalidades e garantir que a interface fosse responsiva e interativa.\n\n', + ), + ], + ), + style: const TextStyle(fontSize: 16), + ), + ), + actions: [ + TextButton( + child: const Text('Fechar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, ), ), ], diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 4a544c11..3ee95403 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -62,6 +62,7 @@ flutter: - assets/Loading.mp4 - assets/1000x1000.png - assets/lighting.png + - assets/UNB.png # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in From bbe35f6de9bc2036be8e38348100e895cdd3a01e Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 3 Jun 2024 15:39:59 -0300 Subject: [PATCH 180/351] =?UTF-8?q?Cria=C3=A7=C3=A3o=20das=20pastas=20para?= =?UTF-8?q?=20conex=C3=A3o=20com=20back?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atmospheric_request_model.dart} | 0 .../atmospheric_response_model.dart} | 0 .../atmospheric_service.dart} | 0 .../equipments/data/cooling-data/cooling_request_model.dart | 0 .../equipments/data/cooling-data/cooling_response_model.dart | 0 .../lib/equipments/data/cooling-data/cooling_service.dart | 0 .../data/distribution-data/distribution_request_model.dart | 0 .../data/distribution-data/distribution_response_model.dart | 0 .../data/distribution-data/distribution_service.dart | 0 .../eletrical-circuit_request_model.dart | 0 .../eletrical-circuit_response_model.dart | 0 .../eletrical-circuit-data/eletrical-circuit_service.dart | 0 .../eletrical-line-data/eletrical-line_request_model.dart | 0 .../eletrical-line-data/eletrical-line_response_model.dart | 0 .../data/eletrical-line-data/eletrical-line_service.dart | 0 .../eletrical-load_request_model.dart.dart | 0 .../eletrical-load-data/eletrical-load_response_model.dart | 0 .../data/eletrical-load-data/eletrical-load_service.dart | 0 .../data/fire-alarm-data/fire-alarm_requeste_model.dart | 0 .../data/fire-alarm-data/fire-alarm_response_model.dart | 0 .../equipments/data/fire-alarm-data/fire-alarm_service.dart | 0 .../data/iluminations-data/ilumination_request_model.dart | 0 .../data/iluminations-data/ilumination_request_response.dart | 0 .../data/iluminations-data/ilumination_service.dart | 0 .../structured-cabling_request_model.dart | 0 .../structured-cabling_response_model.dart | 0 .../structured-cabling-data/structured-cabling_service.dart | 0 frontend/sige_ie/lib/home/ui/home.dart | 4 ++-- 28 files changed, 2 insertions(+), 2 deletions(-) rename frontend/sige_ie/lib/equipments/data/{ilumination-data/ilumination_request_model.dart => atmospheric-data/atmospheric_request_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{ilumination-data/ilumination_response_model.dart => atmospheric-data/atmospheric_response_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{ilumination-data/ilumination_service.dart => atmospheric-data/atmospheric_service.dart} (100%) create mode 100644 frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/cooling-data/cooling_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/distribution-data/distribution_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/distribution-data/distribution_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/distribution-data/distribution_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_request_model.dart.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_response.dart create mode 100644 frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_request_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_request_model.dart rename to frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_response_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_response_model.dart rename to frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/ilumination-data/ilumination_service.dart rename to frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_response_model.dart b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_request_model.dart b/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_response_model.dart b/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_service.dart b/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_request_model.dart.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_request_model.dart.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_response.dart b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_response.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_request_model.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_response_model.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 9b9a4137..6984a0d6 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -160,7 +160,7 @@ class _HomePageState extends State { return AlertDialog( title: const Text('Informações sobre o Projeto Sigeie'), - content: SingleChildScrollView( + content: const SingleChildScrollView( child: Text.rich( TextSpan( children: [ @@ -230,7 +230,7 @@ class _HomePageState extends State { ), ], ), - style: const TextStyle(fontSize: 16), + style: TextStyle(fontSize: 16), ), ), actions: [ From ee8011facb932ccd6940ded7bc7809b2b693bafd Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 3 Jun 2024 17:46:10 -0300 Subject: [PATCH 181/351] =?UTF-8?q?Altera=C3=A7=C3=B5es=20solicitadas=20pe?= =?UTF-8?q?la=20cliente=20e=20icones=20na=20tela=20de=20equipamentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../android/app/src/main/AndroidManifest.xml | 2 +- frontend/sige_ie/android/settings.gradle | 2 +- frontend/sige_ie/assets/UNB.png | Bin 0 -> 88542 bytes .../feature/equipment/EquipmentScreen.dart | 21 +- .../feature/equipment/addEquipmentScreen.dart | 106 +-- .../equipment/systemConfiguration.dart | 7 +- frontend/sige_ie/lib/core/ui/first_scren.dart | 7 +- .../atmospheric_request_model.dart | 0 .../atmospheric_response_model.dart | 0 .../atmospheric-data/atmospheric_service.dart | 0 .../cooling-data/cooling_request_model.dart | 0 .../cooling-data/cooling_response_model.dart | 0 .../data/cooling-data/cooling_service.dart | 0 .../distribution_request_model.dart | 0 .../distribution_response_model.dart | 0 .../distribution_service.dart | 0 .../eletrical-circuit_request_model.dart | 0 .../eletrical-circuit_response_model.dart | 0 .../eletrical-circuit_service.dart | 0 .../eletrical-line_request_model.dart | 0 .../eletrical-line_response_model.dart | 0 .../eletrical-line_service.dart | 0 .../eletrical-load_request_model.dart.dart | 0 .../eletrical-load_response_model.dart | 0 .../eletrical-load_service.dart | 0 .../fire-alarm_requeste_model.dart | 0 .../fire-alarm_response_model.dart | 0 .../fire-alarm-data/fire-alarm_service.dart | 0 .../ilumination_request_model.dart | 0 .../ilumination_request_response.dart | 0 .../ilumination_service.dart | 0 .../structured-cabling_request_model.dart | 0 .../structured-cabling_response_model.dart | 0 .../structured-cabling_service.dart | 0 .../addatmospheric-dischargesEquipment.dart | 598 +++++++++++++++ .../atmospheric-dischargesList.dart | 116 +++ .../feature/cooling/Addcooling.dart | 598 +++++++++++++++ .../feature/cooling/coolingEquipmentList.dart | 116 +++ .../addDistribuitionBoard.dart | 648 ++++++++++++++++ .../distribuitionBoardEquipmentList.dart | 116 +++ .../addElectricalCircuit.dart | 704 ++++++++++++++++++ .../electricalCircuitList.dart | 116 +++ .../electrical-line/addElectricalLine.dart | 598 +++++++++++++++ .../electrical-line/electricaLLineLIst.dart | 116 +++ .../electrical-load/addelectricalLoad.dart | 680 +++++++++++++++++ .../electrical-load/eletricalLoadList.dart | 115 +++ .../feature/fire-alarm/addFireAlarm.dart | 598 +++++++++++++++ .../feature/fire-alarm/fireAlarmList.dart | 116 +++ .../IluminationEquipmentList.dart | 116 +++ .../iluminations/addIluminationEquipment.dart | 654 ++++++++++++++++ .../addStruturedCabling.dart | 624 ++++++++++++++++ .../struturedCablingEquipmentList.dart | 116 +++ .../feature/systemConfiguration.dart | 356 +++++++++ frontend/sige_ie/lib/home/ui/home.dart | 236 ++++-- frontend/sige_ie/lib/main.dart | 208 +++++- frontend/sige_ie/pubspec.lock | 72 +- frontend/sige_ie/pubspec.yaml | 3 +- 58 files changed, 7605 insertions(+), 162 deletions(-) create mode 100644 frontend/sige_ie/assets/UNB.png create mode 100644 frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/cooling-data/cooling_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/distribution-data/distribution_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/distribution-data/distribution_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/distribution-data/distribution_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_request_model.dart.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_response.dart create mode 100644 frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_service.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart create mode 100644 frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart diff --git a/README.md b/README.md index 544c9ab3..29e46e65 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ Com o ambiente preparado, siga os passos abaixo: Abra a pasta clonada no Android Studio ou no Visual Studio Code. 3. **Baixe as Dependências**: - Abra um terminal no editor e execute o comando: + Abra um terminal na pasta frontend/sige_ie e execute o comando: ``` flutter pub get ``` diff --git a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml index 4ae41dcb..24e1cadb 100644 --- a/frontend/sige_ie/android/app/src/main/AndroidManifest.xml +++ b/frontend/sige_ie/android/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ android:resource="@style/NormalTheme"/> + android:value="AIzaSyC4tWuLptSAqy_SqJRVbSGT0R1eDEyirw8"/> diff --git a/frontend/sige_ie/android/settings.gradle b/frontend/sige_ie/android/settings.gradle index 1d6d19b7..985a6e2e 100644 --- a/frontend/sige_ie/android/settings.gradle +++ b/frontend/sige_ie/android/settings.gradle @@ -20,7 +20,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "org.jetbrains.kotlin.android" version "1.9.22" apply false } include ":app" diff --git a/frontend/sige_ie/assets/UNB.png b/frontend/sige_ie/assets/UNB.png new file mode 100644 index 0000000000000000000000000000000000000000..e5bf5276d23a9490aa011a53e654ec0b92028691 GIT binary patch literal 88542 zcmeEuRa;!$(k<>T!QEYgrEzz+;1Jv)xI=Jvg1fuBySp|HA-KCAvgJGPyZ8A4=i8&L?2)I0jRor zD<`R9f~Wo^=KuU2UEg5H@-OfHrH;Zr#MkA8M~JJ7l>gF^{=^mWFB<>r;-hpB_%=Y6 z03qLhY3LhBQ1xG`B@1MHJX?JqeYnr`KWymxVUS(+e_Qnb3Rmm^Mt^eT3Fh|jUmAWG zq#f$Nm%~ZUH&7=-o)|gnzn7yoYU#gJlga+y#rWUF__weAZ)E%>%>TcR3>)}9&cXS` zoYy84AzS#YaDMQ=gOFKi2LY4J9|(*2ndr+R4KKjT3m<$Z8`sG>q;XyeB^f_M3W84&m`06r zL!QO+6Xd8kDdV@EeMt2A7bGbJ$lta8B{0#!N(mueq zLN-CqHXb74&|I+nXn1tGR5?EX8ifR!SQNq7-_A`kBgbd;J9p@KGxK1uKuLMKz5GL9 zXp^MvOLTylUU$fU0{wCf$VW0k&6Dd+-cak;5f9ity56yS++gmoOJ~J5*8c|G%Z0+~ ze=CEED`sXUflW3v+yk3bRWR%Be<_-QI+Kd0ue#U5FSD|9pxYn_X zP1C~wzY>6ZaF>7!CIXQUo4_a}07@Dd3mY*~UOgrd%L6+^lZ!@Y#+LnY<0jplT{aRE zrYiAIJSKm?GqdK$og+VKcKrj7Y3j^Q4;NCWU50K6U2r!_hK$!SM{ScuH@g_w=oCa zc00eL36bXkT)pl8F8IG4V&Rxs_UZ;NuSayrvg0?q*u56C3=M|TX%8wKE&H8mO1{cC z&$plO+_1(Hv#=6m*`HZe%zY66nQ&oqU0^OlX@MyIx1)hW6MxU~jg%kBQ7fLZYY_^< z%1h^^YB%@^%M11^x5g-|FNP98LJ2zRg17=POpubDEtH#8AxEJWG=W7SD4mreB342M zbv9*f|EGyAlYd4Px)1`(_rD9@4Z(#Oztl~Q#ym)!@ctOv(=(usyRt&LkS*JmphO~# zNe<^rc`t2#d~(zJwu?KOe^)cCq@ed59J+)?pf&zm5!Wt&YWej$i3m?v-^X+QZg?kQ zUovN>&#Jea3UnuNRD8hA`;$blj0&Yhl2`#4^T3Qa&o933U0K`|qt=Sg=bnf{u((qWrk_%PGIa}zz~>IP&k ze#(ePHOIXsywG z&eiPPj_Y1q!GSrF3Rob7Mx0O>7_qjN)=#~X$VHr#UqQ6Jq6y8+-?cwsaGzbk6R5+o zTr=eX%>FBSO#cANN^aio{W%r5D)RQve z`{|Id3f0|Mf8@S7H8xJx1<_#SkQw}hwXpfAag31-5-BEOIx9Soy^BYC;Z50JZntbH zo!v!26r3H_j&Nb9PYh8gCZvM58;NPhgY^Hj-MsZ9MWoxJ_{piSiziGD@0 zt8uX-2n;q)qgE2MWM~mdg%tr5ub@ljTBoiA?*Xgsbo2So0Vs*uYZVz_9XlEJ1rA;0CmB%6nT8@< zd_)0YrH9E%0AITZ!D%|xRh>Zn9riG!_9hv#V=T!Kp?P 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%0Db;bvCtz88&SCf%1tmNslI392GXmZFOC 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 literal 0 HcmV?d00001 diff --git a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart index 270d0680..0a766690 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart @@ -100,11 +100,22 @@ class EquipmentScreen extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Text( + '$areaName - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), + ), + ], + ), ), ), const SizedBox(height: 20), diff --git a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart index f4ff2d1b..6accb945 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart +++ b/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart @@ -112,8 +112,7 @@ class _AddEquipmentScreenState extends State { } void _deleteEquipmentType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + if (_selectedTypeToDelete == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: @@ -452,54 +451,62 @@ class _AddEquipmentScreenState extends State { showDialog( context: context, builder: (BuildContext context) { - return AlertDialog( - title: const Text('Excluir tipo de equipamento'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'Selecione um equipamento para excluir:', - textAlign: TextAlign.center, - ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), - ); - }).toList(), + _selectedTypeToDelete = null; // Inicialize com null + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: [...equipmentTypes] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + enabled: + value != 'Selecione um equipamento para deletar', + ); + }).toList(), + ), + ], ), - ], - ), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Excluir'), - onPressed: () { - if (_selectedTypeToDelete != null) { - Navigator.of(context).pop(); - _deleteEquipmentType(); - } - }, - ), - ], + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null && + _selectedTypeToDelete != + 'Selecione um equipamento para deletar') { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, ); }, ); @@ -529,7 +536,6 @@ class _AddEquipmentScreenState extends State { value, style: const TextStyle(color: Colors.black), ), - enabled: value != items.first, ); }).toList(), ), diff --git a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart index 00f5fb03..edbddc16 100644 --- a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart +++ b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart @@ -88,7 +88,7 @@ class _SystemConfigurationState extends State { GridView.count( shrinkWrap: true, crossAxisCount: 3, - childAspectRatio: 1.0, + childAspectRatio: 0.8, // Adjusted aspect ratio padding: const EdgeInsets.all(10.0), mainAxisSpacing: 10.0, crossAxisSpacing: 10.0, @@ -250,14 +250,13 @@ class SystemIcon extends StatelessWidget { ), ), const SizedBox(height: 10), - SizedBox( - width: 80, + Flexible( child: Text( label, textAlign: TextAlign.center, style: const TextStyle( color: AppColors.sigeIeBlue, - fontSize: 9, + fontSize: 12, fontWeight: FontWeight.bold, ), softWrap: true, diff --git a/frontend/sige_ie/lib/core/ui/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_scren.dart index e2469d6d..5ed95ff4 100644 --- a/frontend/sige_ie/lib/core/ui/first_scren.dart +++ b/frontend/sige_ie/lib/core/ui/first_scren.dart @@ -6,12 +6,15 @@ class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xff123c75), + backgroundColor: const Color(0xffe1e1e1), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - Image.asset('assets/1000x1000.png'), + Image.asset('assets/UNB.png'), + const SizedBox( + height: 50, + ), ElevatedButton( onPressed: () { Navigator.pushNamed(context, '/loginScreen'); diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_response_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_response_model.dart b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_request_model.dart b/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_response_model.dart b/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_service.dart b/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_request_model.dart.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_request_model.dart.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_response.dart b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_response.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_request_model.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_request_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_response_model.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_response_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_service.dart new file mode 100644 index 00000000..e69de29b diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart new file mode 100644 index 00000000..6abba908 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart @@ -0,0 +1,598 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddatmosphericEquipmentScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddatmosphericEquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectDischargeType; + + List equipmentTypes = [ + 'Selecione um tipo de Descarga atmosféfica', + ]; + + List dischargeType = [ + 'Selecione o tipo de Descarga Atmosféfica', + 'Para Raios', + 'Captação', + 'Subsistemas', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectDischargeType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectDischargeType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listatmosphericEquipment', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de descarga atmosférica', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: dischargeType, + value: _selectDischargeType, + onChanged: (newValue) { + setState(() { + _selectDischargeType = newValue; + if (newValue == dischargeType[0]) { + _selectDischargeType = null; + } + if (_selectDischargeType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectDischargeType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de descargas atmosféricas', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectDischargeType = null; + } + }); + }, + enabled: _selectDischargeType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart new file mode 100644 index 00000000..eb7e4ba3 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart'; + +class listatmosphericEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listatmosphericEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddatmosphericEquipmentScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'DESCARGAS ATMOSFÉRICAS'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart b/frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart new file mode 100644 index 00000000..99d5d749 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart @@ -0,0 +1,598 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class Addcooling extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const Addcooling({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectedcollingType; + + List equipmentTypes = [ + 'Selecione o tipo de refrigeração', + ]; + + List collingType = [ + 'Selecione o tipo de refrigeração', + 'Refrigeração1', + 'Refrigeração2', + 'Refrigeração3', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedcollingType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedcollingType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listCollingEquipment', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de refigeração', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: collingType, + value: _selectedcollingType, + onChanged: (newValue) { + setState(() { + _selectedcollingType = newValue; + if (newValue == collingType[0]) { + _selectedcollingType = null; + } + if (_selectedcollingType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedcollingType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de refigeração', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedcollingType = null; + } + }); + }, + enabled: _selectedcollingType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart new file mode 100644 index 00000000..b79a00fe --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/cooling/Addcooling.dart'; + +class listCollingEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listCollingEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Addcooling( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'Refrigeração'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart new file mode 100644 index 00000000..c91459cb --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart @@ -0,0 +1,648 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddDistribuitionBoard extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddDistribuitionBoard({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentchargeController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedLocation; + String? _selectedTypeToDelete; + String? _selectedBoardType; + + List equipmentTypes = [ + 'Selecione um tipo de quadro', + ]; + + List boardType = [ + 'Selecione o tipo de quadro', + 'quadro 1', + 'quadro 2', + 'quadro 3', + ]; + + @override + void dispose() { + _equipmentchargeController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentchargeController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedBoardType == null) || + _selectedLocation == null || + _selectedLocation == 'Selecione a opção') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedBoardType ?? ''), + const SizedBox(height: 10), + const Text('Especificação:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentchargeController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Existe dispositivos de proteção dentro do quadro:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedLocation ?? ''), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listDistribuitionBoard', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de Quadro', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: boardType, + value: _selectedBoardType, + onChanged: (newValue) { + setState(() { + _selectedBoardType = newValue; + if (newValue == boardType[0]) { + _selectedBoardType = null; + } + if (_selectedBoardType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedBoardType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de Quadros', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedBoardType = null; + } + }); + }, + enabled: _selectedBoardType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Especificação', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentchargeController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text( + 'Existe dispositivos de proteção dentro do quadro:', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: const ['Selecione a opção', 'Sim', 'Não'], + value: _selectedLocation, + onChanged: (newValue) { + if (newValue != 'Selecione a opção') { + setState(() { + _selectedLocation = newValue; + }); + } + }, + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart new file mode 100644 index 00000000..0f463f57 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/addDistribuitionBoard.dart'; + +class listDistribuitionBoard extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listDistribuitionBoard({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddDistribuitionBoard( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart new file mode 100644 index 00000000..7f0f06cd --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart @@ -0,0 +1,704 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddElectricalCircuitEquipmentScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddElectricalCircuitEquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState + extends State { + final _equipmentQuantityController = TextEditingController(); + final _breakerLocationController = TextEditingController(); + final _breakerStateController = TextEditingController(); + final _wireTypeController = TextEditingController(); + final _dimensionController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectCircuitType; + + List equipmentTypes = [ + 'Selecione um tipo de Circuito Elétrico', + ]; + + List cicuitType = [ + 'Selecione o tipo de Circuito Elétrico', + 'Disjuntor(Local e Estado)', + 'Tipo de Fio', + 'Dimensão', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + _breakerLocationController.dispose(); + _breakerStateController.dispose(); + _wireTypeController.dispose(); + _dimensionController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectCircuitType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectCircuitType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Disjuntor(Local):', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_breakerLocationController.text), + const SizedBox(height: 10), + const Text('Disjuntor(Estado):', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_breakerStateController.text), + const SizedBox(height: 10), + const Text('Tipo de Fio:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_wireTypeController.text), + const SizedBox(height: 10), + const Text('Dimensão:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_dimensionController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToElectricalCircuitList(); + }, + ), + ], + ); + }, + ); + } + + void navigateToElectricalCircuitList() { + Navigator.pushReplacementNamed( + context, + '/electricalCircuitList', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de Lâmpada', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: cicuitType, + value: _selectCircuitType, + onChanged: (newValue) { + setState(() { + _selectCircuitType = newValue; + if (newValue == cicuitType[0]) { + _selectCircuitType = null; + } + if (_selectCircuitType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectCircuitType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de lâmpadas', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectCircuitType = null; + } + }); + }, + enabled: _selectCircuitType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Disjuntor(Local)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _breakerLocationController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Disjuntor(Estado)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _breakerStateController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Tipo de Fio', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _wireTypeController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Dimensão', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _dimensionController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart new file mode 100644 index 00000000..9dd5b41a --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/electrical-circuit/addElectricalCircuit.dart'; + +class listCicuitEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listCicuitEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddElectricalCircuitEquipmentScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'CIRCUITO ELÉTRICO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart b/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart new file mode 100644 index 00000000..90455f59 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart @@ -0,0 +1,598 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddElectricalLineScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddElectricalLineScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectElectricalType; + + List equipmentTypes = [ + 'Selecione um tipo de Linha Elétrica', + ]; + + List ElectricalType = [ + 'Selecione o tipo de Linha Elétrica', + 'Eletrocalha', + 'Eletroduto', + 'Interuptor', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectElectricalType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectElectricalType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/electricalLineList', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de descarga atmosférica', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: ElectricalType, + value: _selectElectricalType, + onChanged: (newValue) { + setState(() { + _selectElectricalType = newValue; + if (newValue == ElectricalType[0]) { + _selectElectricalType = null; + } + if (_selectElectricalType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectElectricalType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de descargas atmosféricas', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectElectricalType = null; + } + }); + }, + enabled: _selectElectricalType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart b/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart new file mode 100644 index 00000000..fb96e0ef --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/electrical-line/addElectricalLine.dart'; + +class listElectricalLineEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listElectricalLineEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddElectricalLineScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'LINHA ELÉTRICAS'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart new file mode 100644 index 00000000..24dc5790 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart @@ -0,0 +1,680 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddelectricalLoadEquipmentScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddelectricalLoadEquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentBrandController = TextEditingController(); + final _equipmentModelController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + final _equipmentLoadController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectedLoadType; + + List equipmentTypes = [ + 'Selecione um tipo de Carga', + ]; + + List loadTypes = [ + 'Selecione o tipo de Carga', + 'Geladeira', + 'Ar-Condicionado', + 'Tomada (Corrente)' + ]; + + @override + void dispose() { + _equipmentBrandController.dispose(); + _equipmentModelController.dispose(); + _equipmentQuantityController.dispose(); + _equipmentLoadController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentBrandController.text.isEmpty || + _equipmentModelController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + _equipmentLoadController.text.isEmpty || + (_selectedType == null && _selectedLoadType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedLoadType ?? ''), + const SizedBox(height: 10), + const Text('Marca:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentBrandController.text), + const SizedBox(height: 10), + const Text('Modelo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentModelController.text), + const SizedBox(height: 10), + const Text('Carga:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentLoadController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listelectricalLoadEquipment', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de Carga', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: loadTypes, + value: _selectedLoadType, + onChanged: (newValue) { + setState(() { + _selectedLoadType = newValue; + if (newValue == loadTypes[0]) { + _selectedLoadType = null; + } + if (_selectedLoadType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedLoadType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de Cargas', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedLoadType = null; + } + }); + }, + enabled: _selectedLoadType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Marca', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentBrandController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Modelo', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentModelController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Carga', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentLoadController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart new file mode 100644 index 00000000..e9871019 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/electrical-load/addelectricalLoad.dart'; + +class listelectricalLoadEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listelectricalLoadEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddelectricalLoadEquipmentScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'CARGAS ELÉTRICAS'; + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart new file mode 100644 index 00000000..b9feff2f --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -0,0 +1,598 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddfireAlarm extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddfireAlarm({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectedfireAlarmType; + + List equipmentTypes = [ + 'Selecione o tipo de alarme incêndio', + ]; + + List fireAlarmType = [ + 'Selecione o tipo de alarme de incêndio', + 'Sensor de Fumaça', + 'Sensor de Temperatura', + 'Acionadores', + ]; + + @override + void dispose() { + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedfireAlarmType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedfireAlarmType ?? ''), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listFireAlarms', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de alarme de incêndio', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: fireAlarmType, + value: _selectedfireAlarmType, + onChanged: (newValue) { + setState(() { + _selectedfireAlarmType = newValue; + if (newValue == fireAlarmType[0]) { + _selectedfireAlarmType = null; + } + if (_selectedfireAlarmType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedfireAlarmType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de alarme de incêndio', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedfireAlarmType = null; + } + }); + }, + enabled: _selectedfireAlarmType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart new file mode 100644 index 00000000..aa6690a9 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/addFireAlarm.dart'; + +class listFireAlarms extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listFireAlarms({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddfireAlarm( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'ALARME DE INCÊNDIO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart new file mode 100644 index 00000000..aafe9786 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/iluminations/addIluminationEquipment.dart'; + +class listIluminationEquipment extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listIluminationEquipment({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddiluminationEquipmentScreen( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'ILUMINAÇÃO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart new file mode 100644 index 00000000..0ca48426 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart @@ -0,0 +1,654 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddiluminationEquipmentScreen extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddiluminationEquipmentScreen({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentchargeController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedLocation; + String? _selectedTypeToDelete; + String? _selectedLampType; + + List equipmentTypes = [ + 'Selecione um tipo de Lâmpada', + ]; + + List lampTypes = [ + 'Selecione o tipo de lâmpada', + 'Halogenia', + 'Fluorescente', + 'LEDs', + 'Incandescentes', + 'Lâmpadas Queimadas', + ]; + + @override + void dispose() { + _equipmentchargeController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentchargeController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedLampType == null) || + _selectedLocation == null || + _selectedLocation == 'Selecione a localização') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedLampType ?? ''), + const SizedBox(height: 10), + const Text('Potência da Lâmpada(KW):', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentchargeController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Localização:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedLocation ?? ''), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listIluminationEquipment', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de Lâmpada', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: lampTypes, + value: _selectedLampType, + onChanged: (newValue) { + setState(() { + _selectedLampType = newValue; + if (newValue == lampTypes[0]) { + _selectedLampType = null; + } + if (_selectedLampType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedLampType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de lâmpadas', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedLampType = null; + } + }); + }, + enabled: _selectedLampType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Potência da Lâmpada(KW)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentchargeController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Localização (Interno ou Externo)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: const [ + 'Selecione a localização', + 'Interno', + 'Externo' + ], + value: _selectedLocation, + onChanged: (newValue) { + if (newValue != 'Selecione a localização') { + setState(() { + _selectedLocation = newValue; + }); + } + }, + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart b/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart new file mode 100644 index 00000000..a4a9372a --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart @@ -0,0 +1,624 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class ImageData { + File imageFile; + int id; + String description; + + ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); +} + +List _images = []; +Map> categoryImagesMap = {}; + +class AddstruturedCabling extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const AddstruturedCabling({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + @override + _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); +} + +class _AddEquipmentScreenState extends State { + final _equipmentchargeController = TextEditingController(); + final _equipmentQuantityController = TextEditingController(); + String? _selectedType; + String? _selectedTypeToDelete; + String? _selectedstruturedType; + + List equipmentTypes = [ + 'Selecione o tipo de cabeamento estruturado', + ]; + + List struturedType = [ + 'Selecione o tipo de cabeamento estruturado', + 'Eletroduto', + 'Eletrocalha', + ]; + + @override + void dispose() { + _equipmentchargeController.dispose(); + _equipmentQuantityController.dispose(); + categoryImagesMap[widget.categoryNumber]?.clear(); + super.dispose(); + } + + Future _pickImage() async { + final picker = ImagePicker(); + try { + final pickedFile = await picker.pickImage(source: ImageSource.camera); + if (pickedFile != null) { + _showImageDialog(File(pickedFile.path)); + } + } catch (e) { + print('Erro ao capturar a imagem: $e'); + } + } + + void _showImageDialog(File imageFile, {ImageData? existingImage}) { + TextEditingController descriptionController = TextEditingController( + text: existingImage?.description ?? '', + ); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar descrição da imagem'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(imageFile, width: 100, height: 100, fit: BoxFit.cover), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: 'Digite a descrição da imagem'), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Salvar'), + onPressed: () { + if (descriptionController.text.isNotEmpty) { + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; + } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _addNewEquipmentType() { + TextEditingController typeController = TextEditingController(); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Adicionar novo tipo de equipamento'), + content: TextField( + controller: typeController, + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Adicionar'), + onPressed: () { + if (typeController.text.isNotEmpty) { + setState(() { + equipmentTypes.add(typeController.text); + }); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + }, + ); + } + + void _deleteEquipmentType() { + if (_selectedTypeToDelete == null || + _selectedTypeToDelete == 'Selecione um equipamento') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Selecione um tipo de equipamento válido para excluir.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar exclusão'), + content: Text( + 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + equipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _showConfirmationDialog() { + if (_equipmentchargeController.text.isEmpty || + _equipmentQuantityController.text.isEmpty || + (_selectedType == null && _selectedstruturedType == null)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Por favor, preencha todos os campos.'), + ), + ); + return; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Dados do Equipamento'), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('Tipo:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_selectedType ?? _selectedstruturedType ?? ''), + const SizedBox(height: 10), + const Text('Dimensão:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentchargeController.text), + const SizedBox(height: 10), + const Text('Quantidade:', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentQuantityController.text), + const SizedBox(height: 10), + const Text('Imagens:', + style: TextStyle(fontWeight: FontWeight.bold)), + Wrap( + children: _images.map((imageData) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Column( + children: [ + Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + Text(imageData.description), + ], + ), + ), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Editar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + navigateToEquipmentScreen(); + }, + ), + ], + ); + }, + ); + } + + void navigateToEquipmentScreen() { + Navigator.of(context).pushNamed( + '/listStruturedCabling', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'categoryNumber': widget.categoryNumber, + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Text('Adicionar equipamentos ', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tipos de cabeamento', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + _buildStyledDropdown( + items: struturedType, + value: _selectedstruturedType, + onChanged: (newValue) { + setState(() { + _selectedstruturedType = newValue; + if (newValue == struturedType[0]) { + _selectedstruturedType = null; + } + if (_selectedstruturedType != null) { + _selectedType = null; + } + }); + }, + enabled: _selectedType == null, + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedstruturedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Seus tipos de cabeamento', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + flex: 4, + child: _buildStyledDropdown( + items: equipmentTypes, + value: _selectedType, + onChanged: (newValue) { + setState(() { + _selectedType = newValue; + if (newValue == equipmentTypes[0]) { + _selectedType = null; + } + if (_selectedType != null) { + _selectedstruturedType = null; + } + }); + }, + enabled: _selectedstruturedType == null, + ), + ), + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + setState(() { + _selectedType = null; + }); + }, + child: const Text('Limpar seleção'), + ), + const SizedBox(height: 30), + const Text('Dimensão', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentchargeController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), + const Text('Quantidade', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + IconButton( + icon: const Icon(Icons.camera_alt), + onPressed: _pickImage, + ), + Wrap( + children: _images.map((imageData) { + return Stack( + alignment: Alignment.topRight, + children: [ + GestureDetector( + onTap: () => _showImageDialog(imageData.imageFile, + existingImage: imageData), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.file( + imageData.imageFile, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle, + color: AppColors.warn), + onPressed: () { + setState(() { + _images.removeWhere( + (element) => element.id == imageData.id); + }); + }, + ), + ], + ); + }).toList(), + ), + const SizedBox(height: 15), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + minimumSize: + MaterialStateProperty.all(const Size(185, 55)), + shape: + MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + onPressed: _showConfirmationDialog, + child: const Text( + 'ADICIONAR EQUIPAMENTO', + style: TextStyle( + fontSize: 17, fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + void _showDeleteDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Excluir tipo de equipamento'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Selecione um equipamento para excluir:', + textAlign: TextAlign.center, + ), + DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .where((value) => value != 'Selecione um equipamento') + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), + ), + ], + ), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + if (_selectedTypeToDelete != null) { + Navigator.of(context).pop(); + _deleteEquipmentType(); + } + }, + ), + ], + ); + }, + ); + } + + Widget _buildStyledDropdown({ + required List items, + String? value, + required Function(String?) onChanged, + bool enabled = true, + }) { + return Container( + decoration: BoxDecoration( + color: enabled ? Colors.grey[300] : Colors.grey[200], + borderRadius: BorderRadius.circular(10), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: DropdownButton( + hint: Text(items.first), + value: value, + isExpanded: true, + underline: Container(), + onChanged: enabled ? onChanged : null, + items: items.map>((String value) { + return DropdownMenuItem( + value: value.isEmpty ? null : value, + child: Text( + value, + style: TextStyle( + color: enabled ? Colors.black : Colors.grey, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart new file mode 100644 index 00000000..1f4edc99 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/structured-cabling/addStruturedCabling.dart'; + +class listStruturedCabling extends StatelessWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + + const listStruturedCabling({ + super.key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + }); + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddstruturedCabling( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + List equipmentList = [ + // Vazio para simular nenhum equipamento + ]; + + String systemTitle = 'CABEAMENTO ESTRUTURADO'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': areaName, + 'localName': localName, + 'localId': localId, + 'categoryNumber': categoryNumber, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text('$areaName - $systemTitle', + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText)), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart new file mode 100644 index 00000000..1eb42c48 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -0,0 +1,356 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; +import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/fireAlarmList.dart'; +import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; +import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; +import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; + +class SystemConfiguration extends StatefulWidget { + final String areaName; + final String localName; + final int localId; + final int categoryNumber; + + const SystemConfiguration({ + super.key, + required this.areaName, + required this.localName, + required this.localId, + required this.categoryNumber, + }); + + @override + _SystemConfigurationState createState() => _SystemConfigurationState(); +} + +class _SystemConfigurationState extends State { + void navigateTo(String routeName, String areaName, String localName, + int localId, dynamic category) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + switch (routeName) { + case '/structuredCabling': + return listStruturedCabling( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + case '/atmosphericDischarges': + return listatmosphericEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + case '/fireAlarm': + return listFireAlarms( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + case '/electricLoads': + return listelectricalLoadEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + case '/electricLines': + return listElectricalLineEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + case '/circuits': + return listCicuitEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + case '/distributionBoard': + return listDistribuitionBoard( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + case '/cooling': + return listCollingEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + case '/lighting': + return listIluminationEquipment( + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category); + default: + return Scaffold( + body: Center(child: Text('No route defined for $routeName')), + ); + } + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: AppColors.sigeIeBlue, + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text(widget.areaName, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + ), + const Padding( + padding: EdgeInsets.all(30.0), + child: Text( + 'Quais sistemas deseja configurar?', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + GridView.count( + shrinkWrap: true, + crossAxisCount: 3, + childAspectRatio: 1.0, + padding: const EdgeInsets.all(10.0), + mainAxisSpacing: 10.0, + crossAxisSpacing: 10.0, + children: [ + SystemIcon( + icon: Icons.fire_extinguisher, + label: 'ALARME DE INCÊNDIO', + onPressed: () => navigateTo('/fireAlarm', widget.areaName, + widget.localName, widget.localId, 8), + ), + SystemIcon( + icon: Icons.cable, + label: 'CABEAMENTO ESTRUTURADO', + onPressed: () => navigateTo('/structuredCabling', + widget.areaName, widget.localName, widget.localId, 6), + ), + SystemIcon( + icon: Icons.electrical_services, + label: 'CARGAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLoads', widget.areaName, + widget.localName, widget.localId, 2), + ), + SystemIcon( + icon: Icons.electrical_services, + label: 'CIRCUITOS', + onPressed: () => navigateTo('/circuits', widget.areaName, + widget.localName, widget.localId, 4), + ), + SystemIcon( + icon: Icons.bolt, + label: 'DESCARGAS ATMOSFÉRICAS', + onPressed: () => navigateTo('/atmosphericDischarges', + widget.areaName, widget.localName, widget.localId, 7), + ), + SystemIcon( + icon: Icons.lightbulb, + label: 'ILUMINAÇÃO', + onPressed: () => navigateTo('/lighting', widget.areaName, + widget.localName, widget.localId, 1), + ), + SystemIcon( + icon: Icons.power, + label: 'LINHAS ELÉTRICAS', + onPressed: () => navigateTo('/electricLines', widget.areaName, + widget.localName, widget.localId, 3), + ), + SystemIcon( + icon: Icons.dashboard, + label: 'QUADRO DE DISTRIBUIÇÃO', + onPressed: () => navigateTo('/distributionBoard', + widget.areaName, widget.localName, widget.localId, 5), + ), + SystemIcon( + icon: Icons.ac_unit, + label: 'REFRIGERAÇÃO', + onPressed: () => navigateTo('/cooling', widget.areaName, + widget.localName, widget.localId, 9), + ), + ], + ), + const SizedBox( + height: 30, + ), + Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.lightText, + backgroundColor: AppColors.warn, + minimumSize: const Size(150, 50), + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: () { + Navigator.of(context).popUntil((route) => route.isFirst); + Navigator.pushReplacementNamed( + context, + '/homeScreen', + arguments: {'initialPage': 1}, + ); + }, + child: const Text('SAIR DA SALA'), + ), + const SizedBox(width: 10), + ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(AppColors.sigeIeBlue), + foregroundColor: + MaterialStateProperty.all(AppColors.lightText), + minimumSize: + MaterialStateProperty.all(const Size(150, 50)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: () { + Navigator.of(context).pushNamed('/arealocation', + arguments: { + 'placeName': widget.localName, + 'placeId': widget.localId + }); + }, + child: const Text( + 'CRIAR NOVA SALA', + style: + TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + const SizedBox( + height: 30, + ), + ], + ), + ), + ); + } +} + +class SystemIcon extends StatelessWidget { + final IconData icon; + final String label; + final VoidCallback onPressed; + + const SystemIcon({ + super.key, + required this.icon, + required this.label, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onPressed, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.sigeIeYellow, + ), + child: Icon( + icon, + size: 40.0, + color: AppColors.sigeIeBlue, + ), + ), + const SizedBox(height: 10), + SizedBox( + width: 80, + child: Text( + label, + textAlign: TextAlign.center, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 9, + fontWeight: FontWeight.bold, + ), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } +} + +class SystemButton extends StatelessWidget { + final String title; + final VoidCallback onPressed; + + const SystemButton({ + super.key, + required this.title, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + width: double.infinity, + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), + foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 25)), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + )), + ), + onPressed: onPressed, + child: Text(title, + style: const TextStyle( + color: AppColors.sigeIeBlue, + fontSize: 18, + fontWeight: FontWeight.w900)), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 5e97f212..6984a0d6 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -49,18 +49,22 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: PageView( - controller: _pageController, - onPageChanged: (index) { - setState(() { - _selectedIndex = index; - }); - }, - children: [ - buildHomePage(context), - const FacilitiesPage(), - const MapsPage(), - ProfilePage() + body: Stack( + children: [ + PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _selectedIndex = index; + }); + }, + children: [ + buildHomePage(context), + const FacilitiesPage(), + const MapsPage(), + ProfilePage() + ], + ), ], ), bottomNavigationBar: buildBottomNavigationBar(), @@ -77,66 +81,170 @@ class _HomePageState extends State { return const Center(child: Text('Erro ao carregar os dados')); } else if (snapshot.hasData) { var user = snapshot.data!; - return Column( + return Stack( children: [ - AppBar( - automaticallyImplyLeading: false, - backgroundColor: const Color(0xff123c75), - elevation: 0, - ), - Expanded( - flex: 3, - child: Container( - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20), - ), + Column( + children: [ + AppBar( + automaticallyImplyLeading: false, + backgroundColor: const Color(0xff123c75), + elevation: 0, ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/1000x1000.png'), - fit: BoxFit.cover, - ), - ), + Expanded( + flex: 3, + child: Container( + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), ), ), - Container( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(left: 20), - child: Text( - 'Olá, ${user.firstname}', - style: const TextStyle( - color: AppColors.sigeIeYellow, - fontSize: 20, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/1000x1000.png'), + fit: BoxFit.cover, + ), + ), + ), ), - ), + Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 20), + child: Text( + 'Olá, ${user.firstname}', + style: const TextStyle( + color: AppColors.sigeIeYellow, + fontSize: 20, + ), + ), + ), + const SizedBox(height: 10), + ], ), - const SizedBox(height: 10), - ], + ), ), - ), + Expanded( + flex: 6, + child: Column( + children: [ + const Spacer(), + buildSmallRectangle( + context, 'Registrar novo local', 'Registrar', () { + Navigator.of(context).pushNamed('/newLocation'); + }), + buildSmallRectangle(context, 'Equipes', 'Gerenciar', + () { + // Código aqui. + }), + const Spacer(), + ], + ), + ), + ], ), - Expanded( - flex: 6, - child: Column( - children: [ - const Spacer(), - buildSmallRectangle( - context, 'Registrar novo local', 'Registrar', () { - Navigator.of(context).pushNamed('/newLocation'); - }), - buildSmallRectangle(context, 'Equipes', 'Gerenciar', () { - // Código aqui. - }), - const Spacer(), - ], + Positioned( + top: 20, + right: 20, + child: IconButton( + icon: const Icon(Icons.info, color: Colors.white, size: 30), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: + const Text('Informações sobre o Projeto Sigeie'), + content: const SingleChildScrollView( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + 'A criação do aplicativo Sigeie foi um esforço colaborativo de uma equipe dedicada de profissionais, cada um trazendo sua expertise para garantir o sucesso do projeto. Aqui está uma descrição detalhada da participação de cada membro, conforme a organização da equipe:\n\n', + ), + TextSpan( + text: + '1. Loana Nunes Velasco - Cliente do Projeto\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Loana, docente do curso de Engenharia de Energia, atuou como cliente do projeto. Ela foi responsável por validar as entregas, garantindo que as necessidades e expectativas dos usuários finais fossem claramente comunicadas à equipe.\n\n', + ), + TextSpan( + text: + '2. Pedro Lucas - Desenvolvedor Backend (Engenharia de Software)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Pedro Lucas foi responsável por codificar o backend e configurar a infraestrutura necessária para o funcionamento do aplicativo. Ele contou com a colaboração de Kauan Jose e Oscar de Brito para garantir que o backend fosse seguro e escalável.\n\n', + ), + TextSpan( + text: + '3. Danilo de Melo Ribeiro - Desenvolvedor Frontend e UX Design (Engenharia de Software)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Danilo trabalhou no desenvolvimento do frontend do aplicativo, codificando a interface e realizando a integração com o backend. Ele projetou a interface do usuário, criou protótipos e realizou entrevistas com os clientes para garantir que o design fosse intuitivo e atendesse às necessidades dos usuários. Ele colaborou com Ramires Rocha e Pedro Lucas para construir uma interface responsiva e interativa.\n\n', + ), + TextSpan( + text: + '4. Oscar de Brito - Analista de Requisitos (Engenharia de Software)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Oscar levantou os requisitos do projeto, gerenciou a documentação e validou as especificações com o cliente. Ele contou com a colaboração de Ramires Rocha e Pedro Lucas para garantir que todos os requisitos fossem compreendidos e implementados corretamente.\n\n', + ), + TextSpan( + text: + '5. Kauan Jose - Colaborador Backend (Engenharia de Software)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Kauan colaborou no desenvolvimento do backend, fornecendo suporte essencial para Pedro Lucas. Ele ajudou a configurar a infraestrutura e garantir que o backend funcionasse de maneira eficiente e segura.\n\n', + ), + TextSpan( + text: + '6. Ramires Rocha - Colaborador Frontend (Engenharia Eletrônica)\n', + style: + TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: + '- Ramires colaborou no desenvolvimento do frontend, fornecendo suporte para Danilo de Melo Ribeiro. Ele ajudou a implementar funcionalidades e garantir que a interface fosse responsiva e interativa.\n\n', + ), + ], + ), + style: TextStyle(fontSize: 16), + ), + ), + actions: [ + TextButton( + child: const Text('Fechar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, ), ), ], diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index de652cfd..f9a7b622 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -3,13 +3,21 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; +import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; +import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/fireAlarmList.dart'; +import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/core/feature/equipment/EquipmentScreen.dart'; -import 'package:sige_ie/core/feature/equipment/systemConfiguration.dart'; +import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/systemConfiguration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; +import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'core/feature/login/login.dart'; void main() { @@ -94,7 +102,7 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /systemLocation.'); - case '/equipmentScreen': + case '/listIluminationEquipment': if (settings.arguments is Map) { final args = settings.arguments as Map; final String? areaName = args['areaName']?.toString(); @@ -104,7 +112,7 @@ class MyApp extends StatelessWidget { if (areaName != null && localName != null && localId != null) { return MaterialPageRoute( - builder: (context) => EquipmentScreen( + builder: (context) => listIluminationEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, @@ -118,6 +126,198 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /equipmentScreen.'); + case '/electricalCircuitList': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listCicuitEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /electricalCircuitList.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /electricalCircuitList.'); + + case '/electricalLineList': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listElectricalLineEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /electricalLineList.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /electricalLineList.'); + + case '/listatmosphericEquipment': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listatmosphericEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /electricalLineList.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /electricalLineList.'); + + case '/listDistribuitionBoard': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listDistribuitionBoard( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /listDistribuitionBoard.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + + case '/listFireAlarms': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listFireAlarms( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /listDistribuitionBoard.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + + case '/listCollingEquipment': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listCollingEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /listDistribuitionBoard.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + + case '/listStruturedCabling': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listStruturedCabling( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /listStruturedCabling.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /listStruturedCabling.'); + + case '/listelectricalLoadEquipment': + if (settings.arguments is Map) { + final args = settings.arguments as Map; + final String? areaName = args['areaName']?.toString(); + final String? localName = args['localName']?.toString(); + final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; + + if (areaName != null && localName != null && localId != null) { + return MaterialPageRoute( + builder: (context) => listelectricalLoadEquipment( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + )); + } else { + throw Exception( + 'Invalid arguments: One of areaName, localName, or localId is null in /electricalLineList.'); + } + } + throw Exception( + 'Invalid route: Expected Map arguments for /electricalLineList.'); + default: return MaterialPageRoute( builder: (context) => UndefinedView(name: settings.name)); diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 4a01ae40..72c6d0dd 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.4" file_selector_platform_interface: dependency: transitive description: @@ -137,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -180,10 +188,10 @@ packages: dependency: transitive description: name: geolocator_android - sha256: "93906636752ea4d4e778afa981fdfe7409f545b3147046300df194330044d349" + sha256: "00c7177a95823dd3ee35ef42fd8666cd27d219ae14cea472ac76a21dff43000b" url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "4.6.0" geolocator_apple: dependency: transitive description: @@ -196,10 +204,10 @@ packages: dependency: transitive description: name: geolocator_platform_interface - sha256: "009a21c4bc2761e58dccf07c24f219adaebe0ff707abdfd40b0a763d4003fab9" + sha256: c6005787efe9e27cb0f6b50230c217e6f0ef8e1e7a8b854efb4f46489e502603 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.3" geolocator_web: dependency: transitive description: @@ -316,10 +324,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "40e24f467b75cd6f4a92ee93dd13d1a7bcb4523a84fd95f00c755f01f42398c8" + sha256: "0f57fee1e8bfadf8cc41818bbcd7f72e53bb768a54d9496355d5e8a5681a19f1" url: "https://pub.dev" source: hosted - version: "0.8.11" + version: "0.8.12+1" image_picker_for_web: dependency: transitive description: @@ -332,10 +340,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: f74064bc548b5164a033ec05638e23c91be1a249c255e0f56319dddffd759794 + sha256: "4824d8c7f6f89121ef0122ff79bb00b009607faecc8545b86bca9ab5ce1e95bf" url: "https://pub.dev" source: hosted - version: "0.8.10+1" + version: "0.8.11+2" image_picker_linux: dependency: transitive description: @@ -516,26 +524,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -581,6 +589,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -649,10 +665,10 @@ packages: dependency: transitive description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.4.0" vector_math: dependency: transitive description: @@ -665,26 +681,26 @@ packages: dependency: "direct main" description: name: video_player - sha256: efa2e24042166906ddf836dd131258d0371d0009cdf0476f6a83fd992a17f5d0 + sha256: db6a72d8f4fd155d0189845678f55ad2fd54b02c10dcafd11c068dbb631286c0 url: "https://pub.dev" source: hosted - version: "2.8.5" + version: "2.8.6" video_player_android: dependency: transitive description: name: video_player_android - sha256: "4dd9b8b86d70d65eecf3dcabfcdfbb9c9115d244d022654aba49a00336d540c2" + sha256: "134e1ad410d67e18a19486ed9512c72dfc6d8ffb284d0e8f2e99e903d1ba8fa3" url: "https://pub.dev" source: hosted - version: "2.4.12" + version: "2.4.14" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" + sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.6.1" video_player_platform_interface: dependency: transitive description: @@ -721,10 +737,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.5.0" xdg_directories: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 8fe7b0a0..3ee95403 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -42,8 +42,6 @@ dependencies: geolocator: ^9.0.2 logger: ^1.0.0 - - dev_dependencies: flutter_test: sdk: flutter @@ -64,6 +62,7 @@ flutter: - assets/Loading.mp4 - assets/1000x1000.png - assets/lighting.png + - assets/UNB.png # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in From d8af43953dcfadcbf6e9f64afe39d64c4fba008d Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 4 Jun 2024 16:06:28 -0300 Subject: [PATCH 182/351] =?UTF-8?q?Melhora=20na=20experi=C3=AAncia=20do=20?= =?UTF-8?q?usu=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire-alarm/addFireAlarm.dart | 142 +++++++++--------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index b9feff2f..e5250227 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -40,12 +40,9 @@ class _AddEquipmentScreenState extends State { String? _selectedTypeToDelete; String? _selectedfireAlarmType; - List equipmentTypes = [ - 'Selecione o tipo de alarme incêndio', - ]; + List equipmentTypes = []; List fireAlarmType = [ - 'Selecione o tipo de alarme de incêndio', 'Sensor de Fumaça', 'Sensor de Temperatura', 'Acionadores', @@ -164,8 +161,7 @@ class _AddEquipmentScreenState extends State { } void _deleteEquipmentType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + if (_selectedTypeToDelete == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: @@ -175,6 +171,16 @@ class _AddEquipmentScreenState extends State { return; } + if (fireAlarmType.contains(_selectedTypeToDelete)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Não é possível excluir tipos de equipamento predefinidos.'), + ), + ); + return; + } + showDialog( context: context, builder: (BuildContext context) { @@ -293,6 +299,8 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { + List combinedTypes = fireAlarmType + equipmentTypes; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -333,55 +341,29 @@ class _AddEquipmentScreenState extends State { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - _buildStyledDropdown( - items: fireAlarmType, - value: _selectedfireAlarmType, - onChanged: (newValue) { - setState(() { - _selectedfireAlarmType = newValue; - if (newValue == fireAlarmType[0]) { - _selectedfireAlarmType = null; - } - if (_selectedfireAlarmType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectedfireAlarmType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de alarme de incêndio', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), Row( children: [ Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, + items: ['Selecione o tipo de alarme de incêndio'] + + combinedTypes, + value: _selectedfireAlarmType ?? _selectedType, onChanged: (newValue) { - setState(() { - _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { - _selectedfireAlarmType = null; - } - }); + if (newValue != + 'Selecione o tipo de alarme de incêndio') { + setState(() { + if (fireAlarmType.contains(newValue)) { + _selectedfireAlarmType = newValue; + _selectedType = null; + } else { + _selectedType = newValue; + _selectedfireAlarmType = null; + } + }); + } }, - enabled: _selectedfireAlarmType == null, + enabled: true, ), ), Expanded( @@ -395,10 +377,19 @@ class _AddEquipmentScreenState extends State { IconButton( icon: const Icon(Icons.delete), onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); + if (equipmentTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Nenhum equipamento adicionado para excluir.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } }, ), ], @@ -410,6 +401,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { + _selectedfireAlarmType = null; _selectedType = null; }); }, @@ -519,25 +511,28 @@ class _AddEquipmentScreenState extends State { 'Selecione um equipamento para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -576,7 +571,7 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first, style: TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), @@ -584,10 +579,13 @@ class _AddEquipmentScreenState extends State { items: items.map>((String value) { return DropdownMenuItem( value: value.isEmpty ? null : value, + enabled: value != 'Selecione o tipo de alarme de incêndio', child: Text( value, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value == 'Selecione o tipo de alarme de incêndio' + ? Colors.grey + : Colors.black, ), ), ); From 7eeac260f6a86cbd4ec736d4f9c4f2cfab814a23 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 08:31:39 -0300 Subject: [PATCH 183/351] =?UTF-8?q?Prot=C3=B3tipo=20tela=20de=20alarme=20d?= =?UTF-8?q?e=20inc=C3=AAndio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fire-alarm_request_model.dart | 31 +++++++++++++ .../fire-alarm_requeste_model.dart | 0 .../fire-alarm-data/fire-alarm_service.dart | 46 +++++++++++++++++++ .../feature/fire-alarm/addFireAlarm.dart | 33 +++++++++---- 4 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart delete mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart new file mode 100644 index 00000000..38b13376 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart @@ -0,0 +1,31 @@ +class FireAlarmEquipmentRequestModel { + List photos; + int area; + int system; + int equipmentType; + + FireAlarmEquipmentRequestModel({ + required this.photos, + required this.area, + required this.system, + required this.equipmentType, + }); + + Map toJson() { + return { + 'photos': photos, + 'area': area, + 'system': system, + 'equipmentType': equipmentType, + }; + } + + factory FireAlarmEquipmentRequestModel.fromJson(Map json) { + return FireAlarmEquipmentRequestModel( + photos: List.from(json['photos'] ?? []), + area: json['area'], + system: json['system'], + equipmentType: json['equipmentType'], + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_requeste_model.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart index e69de29b..4f5943b8 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart @@ -0,0 +1,46 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_request_model.dart'; +import 'package:sige_ie/main.dart'; + +class FireAlarmEquipmentService { + final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future register( + FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + print('Sending POST request to $url'); + print( + 'Request body: ${jsonEncode(fireAlarmEquipmentRequestModel.toJson())}'); + + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(fireAlarmEquipmentRequestModel.toJson()), + ); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 201) { + Map responseData = jsonDecode(response.body); + print('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + print( + 'Failed to register fire alarm equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + print('Error during register: $e'); + return null; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index e5250227..e3b68aaf 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -4,6 +4,8 @@ import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_request_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_service.dart'; class ImageData { File imageFile; @@ -276,7 +278,7 @@ class _AddEquipmentScreenState extends State { child: const Text('OK'), onPressed: () { Navigator.of(context).pop(); - navigateToEquipmentScreen(); + _registerFireAlarmEquipment(); }, ), ], @@ -285,16 +287,27 @@ class _AddEquipmentScreenState extends State { ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/listFireAlarms', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, + void _registerFireAlarmEquipment() async { + int areaId = 1; + int systemId = widget.categoryNumber; + int equipmentTypeId = 1; + + FireAlarmEquipmentRequestModel requestModel = + FireAlarmEquipmentRequestModel( + photos: _images.map((imageData) => imageData.imageFile.path).toList(), + area: areaId, + system: systemId, + equipmentType: equipmentTypeId, ); + + FireAlarmEquipmentService service = FireAlarmEquipmentService(); + int? id = await service.register(requestModel); + + if (id != null) { + print('Fire alarm equipment registered with ID: $id'); + } else { + print('Failed to register fire alarm equipment'); + } } @override From 1de49cf73a4517555a2c9d01d1a5eed9520892c4 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 08:36:33 -0300 Subject: [PATCH 184/351] =?UTF-8?q?Corre=C3=A7=C3=A3o=20na=20passagem=20de?= =?UTF-8?q?=20par=C3=A2metros=20entre=20rotas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/systemConfiguration.dart | 81 +++++++------------ frontend/sige_ie/lib/main.dart | 10 ++- 2 files changed, 37 insertions(+), 54 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 1eb42c48..ed4b09d6 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -14,14 +14,14 @@ class SystemConfiguration extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int areaId; const SystemConfiguration({ super.key, required this.areaName, required this.localName, required this.localId, - required this.categoryNumber, + required this.areaId, }); @override @@ -30,7 +30,7 @@ class SystemConfiguration extends StatefulWidget { class _SystemConfigurationState extends State { void navigateTo(String routeName, String areaName, String localName, - int localId, dynamic category) { + int localId, int areaId, int category) { Navigator.push( context, MaterialPageRoute( @@ -145,55 +145,70 @@ class _SystemConfigurationState extends State { icon: Icons.fire_extinguisher, label: 'ALARME DE INCÊNDIO', onPressed: () => navigateTo('/fireAlarm', widget.areaName, - widget.localName, widget.localId, 8), + widget.localName, widget.localId, widget.areaId, 8), ), SystemIcon( icon: Icons.cable, label: 'CABEAMENTO ESTRUTURADO', - onPressed: () => navigateTo('/structuredCabling', - widget.areaName, widget.localName, widget.localId, 6), + onPressed: () => navigateTo( + '/structuredCabling', + widget.areaName, + widget.localName, + widget.localId, + widget.areaId, + 6), ), SystemIcon( icon: Icons.electrical_services, label: 'CARGAS ELÉTRICAS', onPressed: () => navigateTo('/electricLoads', widget.areaName, - widget.localName, widget.localId, 2), + widget.localName, widget.localId, widget.areaId, 2), ), SystemIcon( icon: Icons.electrical_services, label: 'CIRCUITOS', onPressed: () => navigateTo('/circuits', widget.areaName, - widget.localName, widget.localId, 4), + widget.localName, widget.localId, widget.areaId, 4), ), SystemIcon( icon: Icons.bolt, label: 'DESCARGAS ATMOSFÉRICAS', - onPressed: () => navigateTo('/atmosphericDischarges', - widget.areaName, widget.localName, widget.localId, 7), + onPressed: () => navigateTo( + '/atmosphericDischarges', + widget.areaName, + widget.localName, + widget.localId, + widget.areaId, + 7), ), SystemIcon( icon: Icons.lightbulb, label: 'ILUMINAÇÃO', onPressed: () => navigateTo('/lighting', widget.areaName, - widget.localName, widget.localId, 1), + widget.localName, widget.localId, widget.areaId, 1), ), SystemIcon( icon: Icons.power, label: 'LINHAS ELÉTRICAS', onPressed: () => navigateTo('/electricLines', widget.areaName, - widget.localName, widget.localId, 3), + widget.localName, widget.localId, widget.areaId, 3), ), SystemIcon( icon: Icons.dashboard, label: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/distributionBoard', - widget.areaName, widget.localName, widget.localId, 5), + onPressed: () => navigateTo( + '/distributionBoard', + widget.areaName, + widget.localName, + widget.localId, + widget.areaId, + 5), ), SystemIcon( icon: Icons.ac_unit, label: 'REFRIGERAÇÃO', onPressed: () => navigateTo('/cooling', widget.areaName, - widget.localName, widget.localId, 9), + widget.localName, widget.localId, widget.areaId, 9), ), ], ), @@ -318,39 +333,3 @@ class SystemIcon extends StatelessWidget { ); } } - -class SystemButton extends StatelessWidget { - final String title; - final VoidCallback onPressed; - - const SystemButton({ - super.key, - required this.title, - required this.onPressed, - }); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), - width: double.infinity, - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: MaterialStateProperty.all(AppColors.sigeIeBlue), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(vertical: 25)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: onPressed, - child: Text(title, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 18, - fontWeight: FontWeight.w900)), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index f9a7b622..83c99a87 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -86,17 +86,21 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - if (areaName != null && localName != null && localId != null) { + final int? areaId = args['areaId']; + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => SystemConfiguration( areaName: areaName, localName: localName, localId: localId, - categoryNumber: 0, + areaId: areaId, )); } else { throw Exception( - 'Invalid arguments: One of areaName, localName, or localId is null in /systemLocation.'); + 'Invalid arguments: One of areaName, localName, localId, or areaId is null in /systemLocation.'); } } throw Exception( From ea8aeda56fbb5eb6bdb647dcfec0511fc8a0e736 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 08:59:37 -0300 Subject: [PATCH 185/351] =?UTF-8?q?L=C3=B3gica=20entre=20telas=20corrigida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire-alarm/addFireAlarm.dart | 28 +++++++++++++++++-- .../feature/fire-alarm/fireAlarmList.dart | 5 +++- .../feature/systemConfiguration.dart | 1 + frontend/sige_ie/lib/main.dart | 24 ++++++++++------ 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index e3b68aaf..d4fc9961 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -23,6 +23,7 @@ class AddfireAlarm extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const AddfireAlarm({ super.key, @@ -30,6 +31,7 @@ class AddfireAlarm extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override @@ -277,8 +279,18 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('OK'), onPressed: () { - Navigator.of(context).pop(); _registerFireAlarmEquipment(); + Navigator.pushReplacementNamed( + context, + '/listFireAlarms', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ], @@ -288,7 +300,7 @@ class _AddEquipmentScreenState extends State { } void _registerFireAlarmEquipment() async { - int areaId = 1; + int areaId = widget.areaId; int systemId = widget.categoryNumber; int equipmentTypeId = 1; @@ -321,7 +333,17 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + Navigator.pushReplacementNamed( + context, + '/listFireAlarms', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart index aa6690a9..0533c862 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart @@ -7,6 +7,7 @@ class listFireAlarms extends StatelessWidget { final String localName; final int categoryNumber; final int localId; + final int areaId; const listFireAlarms({ super.key, @@ -14,6 +15,7 @@ class listFireAlarms extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -25,6 +27,7 @@ class listFireAlarms extends StatelessWidget { categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, // Passando areaId ), ), ); @@ -51,7 +54,7 @@ class listFireAlarms extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, - 'categoryNumber': categoryNumber, + 'areaId': areaId, }, ); }, diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index ed4b09d6..27da950c 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -53,6 +53,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, + areaId: areaId, categoryNumber: category); case '/electricLoads': return listelectricalLoadEquipment( diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 83c99a87..d1258088 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -232,23 +232,29 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; + final int? areaId = args['areaId']; // Adicione esta linha final int categoryNumber = args['categoryNumber'] ?? 0; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( - builder: (context) => listFireAlarms( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, - )); + builder: (context) => listFireAlarms( + areaName: areaName, + categoryNumber: categoryNumber, + localName: localName, + localId: localId, + areaId: areaId, + ), + ); } else { throw Exception( - 'Invalid arguments: One of areaName, localName, or localId is null in /listDistribuitionBoard.'); + 'Invalid arguments: One of areaName, localName, localId, or areaId is null in /listFireAlarms.'); } } throw Exception( - 'Invalid route: Expected Map arguments for /listDistribuitionBoard.'); + 'Invalid route: Expected Map arguments for /listFireAlarms.'); case '/listCollingEquipment': if (settings.arguments is Map) { From 32569e6f048801320058707ac6f9fb9bebc9aa9e Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 09:07:27 -0300 Subject: [PATCH 186/351] =?UTF-8?q?Resolu=C3=A7=C3=A3o=20de=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/equipments/feature/fire-alarm/fireAlarmList.dart | 2 +- frontend/sige_ie/lib/main.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart index 0533c862..b948b489 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart @@ -27,7 +27,7 @@ class listFireAlarms extends StatelessWidget { categoryNumber: categoryNumber, localName: localName, localId: localId, - areaId: areaId, // Passando areaId + areaId: areaId, ), ), ); diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index d1258088..7c86902b 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -232,7 +232,7 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - final int? areaId = args['areaId']; // Adicione esta linha + final int? areaId = args['areaId']; final int categoryNumber = args['categoryNumber'] ?? 0; if (areaName != null && From 6efe9d4a16a7720ed5a8c0420e25a85771b63d8c Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 10:17:52 -0300 Subject: [PATCH 187/351] =?UTF-8?q?Rotas=20de=20descargas=20atmosf=C3=A9ri?= =?UTF-8?q?cas=20corrigidas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atmospheric_request_model.dart | 31 +++ .../atmospheric-data/atmospheric_service.dart | 46 +++++ ...> addAtmospheric-dischargesEquipment.dart} | 183 ++++++++++-------- .../atmospheric-dischargesList.dart | 6 +- .../{Addcooling.dart => addCooling.dart} | 0 .../feature/cooling/coolingEquipmentList.dart | 2 +- .../feature/systemConfiguration.dart | 10 +- frontend/sige_ie/lib/main.dart | 7 +- 8 files changed, 200 insertions(+), 85 deletions(-) rename frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/{addatmospheric-dischargesEquipment.dart => addAtmospheric-dischargesEquipment.dart} (79%) rename frontend/sige_ie/lib/equipments/feature/cooling/{Addcooling.dart => addCooling.dart} (100%) diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart index e69de29b..f1cc7c46 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart @@ -0,0 +1,31 @@ +class AtmosphericEquipmentRequestModel { + List photos; + int area; + int system; + int equipmentType; + + AtmosphericEquipmentRequestModel({ + required this.photos, + required this.area, + required this.system, + required this.equipmentType, + }); + + Map toJson() { + return { + 'photos': photos, + 'area': area, + 'system': system, + 'equipmentType': equipmentType, + }; + } + + factory AtmosphericEquipmentRequestModel.fromJson(Map json) { + return AtmosphericEquipmentRequestModel( + photos: List.from(json['photos'] ?? []), + area: json['area'], + system: json['system'], + equipmentType: json['equipmentType'], + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart index e69de29b..a9d63131 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart @@ -0,0 +1,46 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/atmospheric-data/atmospheric_request_model.dart'; +import 'package:sige_ie/main.dart'; + +class AtmosphericEquipmentService { + final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future register( + AtmosphericEquipmentRequestModel atmosphericEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + print('Sending POST request to $url'); + print( + 'Request body: ${jsonEncode(atmosphericEquipmentRequestModel.toJson())}'); + + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(atmosphericEquipmentRequestModel.toJson()), + ); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 201) { + Map responseData = jsonDecode(response.body); + print('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + print( + 'Failed to register atmospheric equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + print('Error during register: $e'); + return null; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart similarity index 79% rename from frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart rename to frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart index 6abba908..2aeb1eb1 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart @@ -4,6 +4,8 @@ import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/atmospheric-data/atmospheric_request_model.dart'; +import 'package:sige_ie/equipments/data/atmospheric-data/atmospheric_service.dart'; class ImageData { File imageFile; @@ -21,6 +23,7 @@ class AddatmosphericEquipmentScreen extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const AddatmosphericEquipmentScreen({ super.key, @@ -28,6 +31,7 @@ class AddatmosphericEquipmentScreen extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override @@ -269,8 +273,18 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('OK'), onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); + _registerAtmosphericEquipment(); + Navigator.pushReplacementNamed( + context, + '/listatmosphericEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ], @@ -279,20 +293,33 @@ class _AddEquipmentScreenState extends State { ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/listatmosphericEquipment', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, + void _registerAtmosphericEquipment() async { + int areaId = widget.areaId; + int systemId = widget.categoryNumber; + int equipmentTypeId = 1; + + AtmosphericEquipmentRequestModel requestModel = + AtmosphericEquipmentRequestModel( + photos: _images.map((imageData) => imageData.imageFile.path).toList(), + area: areaId, + system: systemId, + equipmentType: equipmentTypeId, ); + + AtmosphericEquipmentService service = AtmosphericEquipmentService(); + int? id = await service.register(requestModel); + + if (id != null) { + print('Atmospheric equipment registered with ID: $id'); + } else { + print('Failed to register atmospheric equipment'); + } } @override Widget build(BuildContext context) { + List combinedTypes = dischargeType + equipmentTypes; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -300,7 +327,17 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + Navigator.pushReplacementNamed( + context, + '/listatmosphericEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), @@ -333,55 +370,29 @@ class _AddEquipmentScreenState extends State { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - _buildStyledDropdown( - items: dischargeType, - value: _selectDischargeType, - onChanged: (newValue) { - setState(() { - _selectDischargeType = newValue; - if (newValue == dischargeType[0]) { - _selectDischargeType = null; - } - if (_selectDischargeType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectDischargeType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de descargas atmosféricas', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), Row( children: [ Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, + items: ['Selecione o tipo de descarga atmosférica'] + + combinedTypes, + value: _selectDischargeType ?? _selectedType, onChanged: (newValue) { - setState(() { - _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { - _selectDischargeType = null; - } - }); + if (newValue != + 'Selecione o tipo de descarga atmosférica') { + setState(() { + if (dischargeType.contains(newValue)) { + _selectDischargeType = newValue; + _selectedType = null; + } else { + _selectedType = newValue; + _selectDischargeType = null; + } + }); + } }, - enabled: _selectDischargeType == null, + enabled: true, ), ), Expanded( @@ -395,10 +406,19 @@ class _AddEquipmentScreenState extends State { IconButton( icon: const Icon(Icons.delete), onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); + if (equipmentTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Nenhum equipamento adicionado para excluir.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } }, ), ], @@ -410,6 +430,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { + _selectDischargeType = null; _selectedType = null; }); }, @@ -519,25 +540,28 @@ class _AddEquipmentScreenState extends State { 'Selecione um equipamento para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: equipmentTypes + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -576,7 +600,7 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first, style: TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), @@ -584,10 +608,13 @@ class _AddEquipmentScreenState extends State { items: items.map>((String value) { return DropdownMenuItem( value: value.isEmpty ? null : value, + enabled: value != 'Selecione o tipo de descarga atmosférica', child: Text( value, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value == 'Selecione o tipo de descarga atmosférica' + ? Colors.grey + : Colors.black, ), ), ); diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart index eb7e4ba3..f379d748 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/atmospheric-discharges/addatmospheric-dischargesEquipment.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart'; class listatmosphericEquipment extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; + final int areaId; const listatmosphericEquipment({ super.key, @@ -14,6 +15,7 @@ class listatmosphericEquipment extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -25,6 +27,7 @@ class listatmosphericEquipment extends StatelessWidget { categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, ), ), ); @@ -51,6 +54,7 @@ class listatmosphericEquipment extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, + 'areaId': areaId, 'categoryNumber': categoryNumber, }, ); diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart b/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/cooling/Addcooling.dart rename to frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart index b79a00fe..72814d78 100644 --- a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/cooling/Addcooling.dart'; +import 'package:sige_ie/equipments/feature/cooling/addCooling.dart'; class listCollingEquipment extends StatelessWidget { final String areaName; diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 27da950c..c85f5336 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -44,10 +44,12 @@ class _SystemConfigurationState extends State { categoryNumber: category); case '/atmosphericDischarges': return listatmosphericEquipment( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); case '/fireAlarm': return listFireAlarms( areaName: areaName, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 7c86902b..be962500 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -185,14 +185,19 @@ class MyApp extends StatelessWidget { final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int categoryNumber = args['categoryNumber'] ?? 0; + final int? areaId = args['areaId']; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => listatmosphericEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, )); } else { throw Exception( From 4dea0db170b09679c5e535e257515ec31eb827f4 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 10:28:23 -0300 Subject: [PATCH 188/351] =?UTF-8?q?Rotas=20de=20refrigera=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cooling-data/cooling_request_model.dart | 31 +++++++++++++ .../data/cooling-data/cooling_service.dart | 45 +++++++++++++++++++ .../feature/cooling/addCooling.dart | 3 ++ .../feature/cooling/coolingEquipmentList.dart | 5 ++- .../feature/systemConfiguration.dart | 10 +++-- frontend/sige_ie/lib/main.dart | 7 ++- 6 files changed, 95 insertions(+), 6 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart index e69de29b..5f9a0107 100644 --- a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart @@ -0,0 +1,31 @@ +class CoolingEquipmentRequestModel { + List photos; + int area; + int system; + int equipmentType; + + CoolingEquipmentRequestModel({ + required this.photos, + required this.area, + required this.system, + required this.equipmentType, + }); + + Map toJson() { + return { + 'photos': photos, + 'area': area, + 'system': system, + 'equipmentType': equipmentType, + }; + } + + factory CoolingEquipmentRequestModel.fromJson(Map json) { + return CoolingEquipmentRequestModel( + photos: List.from(json['photos'] ?? []), + area: json['area'], + system: json['system'], + equipmentType: json['equipmentType'], + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart index e69de29b..0d82396d 100644 --- a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart +++ b/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/cooling-data/cooling_request_model.dart'; +import 'package:sige_ie/main.dart'; + +class CoolingEquipmentService { + final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future register( + CoolingEquipmentRequestModel coolingEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + print('Sending POST request to $url'); + print( + 'Request body: ${jsonEncode(coolingEquipmentRequestModel.toJson())}'); + + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(coolingEquipmentRequestModel.toJson()), + ); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 201) { + Map responseData = jsonDecode(response.body); + print('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + print('Failed to register cooling equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + print('Error during register: $e'); + return null; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart b/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart index 99d5d749..db538d47 100644 --- a/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart +++ b/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart @@ -21,6 +21,7 @@ class Addcooling extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const Addcooling({ super.key, @@ -28,6 +29,7 @@ class Addcooling extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override @@ -287,6 +289,7 @@ class _AddEquipmentScreenState extends State { 'localName': widget.localName, 'localId': widget.localId, 'categoryNumber': widget.categoryNumber, + 'areaId': widget.areaId, }, ); } diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart index 72814d78..52b8d453 100644 --- a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart @@ -7,6 +7,7 @@ class listCollingEquipment extends StatelessWidget { final String localName; final int categoryNumber; final int localId; + final int areaId; const listCollingEquipment({ super.key, @@ -14,6 +15,7 @@ class listCollingEquipment extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -25,6 +27,7 @@ class listCollingEquipment extends StatelessWidget { categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, ), ), ); @@ -51,7 +54,7 @@ class listCollingEquipment extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, - 'categoryNumber': categoryNumber, + 'areaId': areaId, }, ); }, diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index c85f5336..480f97b0 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -83,10 +83,12 @@ class _SystemConfigurationState extends State { categoryNumber: category); case '/cooling': return listCollingEquipment( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); case '/lighting': return listIluminationEquipment( areaName: areaName, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index be962500..8ea5eaa5 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -267,15 +267,20 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; + final int? areaId = args['areaId']; final int categoryNumber = args['categoryNumber'] ?? 0; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => listCollingEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, )); } else { throw Exception( From 5553b65c65a3b62a79be10cece43c9fe4b8aeb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Wed, 5 Jun 2024 14:36:22 -0300 Subject: [PATCH 189/351] Backend: ajuste models equipments --- api/equipments/models.py | 5 +++-- frontend/sige_ie/pubspec.lock | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/api/equipments/models.py b/api/equipments/models.py index d3e34c6a..fa249389 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -37,8 +37,9 @@ class Meta: class EquipmentPhoto(models.Model): photo = models.ImageField(null=True, upload_to='equipment_photos/') - description = models.CharField(max_length=50) - equipment_detail = models.ForeignKey(EquipmentDetail, on_delete=models.CASCADE, null=True) + description = models.CharField(max_length=50, null=True) + equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE, null=True) + personalEquipmentType = models.ForeignKey(PersonalEquipmentType, on_delete=models.CASCADE, null=True) def __str__(self): return self.description diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 72c6d0dd..473c2164 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -396,26 +396,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -452,10 +452,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mime: dependency: transitive description: @@ -641,10 +641,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" typed_data: dependency: transitive description: @@ -721,10 +721,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" web: dependency: transitive description: From 3f780426b3648f23ffe9ae8a5ef6312502baf588 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 15:56:17 -0300 Subject: [PATCH 190/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20com=20o=20back?= =?UTF-8?q?=20da=20tela=20de=20alarme=20de=20inc=C3=AAndio=20parcialemnte?= =?UTF-8?q?=20concluida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fire-alarm_request_model.dart | 18 ++-- .../fire-alarm_response_model.dart | 27 ++++++ .../fire-alarm-data/fire-alarm_service.dart | 86 ++++++++++++++++++- .../feature/fire-alarm/addFireAlarm.dart | 60 ++++++++++--- .../feature/systemConfiguration.dart | 9 +- frontend/sige_ie/lib/main.dart | 3 +- 6 files changed, 170 insertions(+), 33 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart index 38b13376..53bab432 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart @@ -1,31 +1,23 @@ class FireAlarmEquipmentRequestModel { - List photos; - int area; - int system; - int equipmentType; + String name; + int? system; FireAlarmEquipmentRequestModel({ - required this.photos, - required this.area, + required this.name, required this.system, - required this.equipmentType, }); Map toJson() { return { - 'photos': photos, - 'area': area, + 'name': name, 'system': system, - 'equipmentType': equipmentType, }; } factory FireAlarmEquipmentRequestModel.fromJson(Map json) { return FireAlarmEquipmentRequestModel( - photos: List.from(json['photos'] ?? []), - area: json['area'], + name: json['name'], system: json['system'], - equipmentType: json['equipmentType'], ); } } diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart index e69de29b..68afcf64 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart @@ -0,0 +1,27 @@ +class FireAlarmEquipmentResponseModel { + int id; + String name; + int system; + + FireAlarmEquipmentResponseModel({ + required this.id, + required this.name, + required this.system, + }); + + Map toJson() { + return { + 'id': id, + 'name': name, + 'system': system, + }; + } + + factory FireAlarmEquipmentResponseModel.fromJson(Map json) { + return FireAlarmEquipmentResponseModel( + id: json['id'], + name: json['name'], + system: json['system'], + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart index 4f5943b8..a16db829 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart @@ -3,17 +3,18 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_request_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_response_model.dart'; // Import the response model import 'package:sige_ie/main.dart'; class FireAlarmEquipmentService { - final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; + final String baseUrl = 'http://10.0.2.2:8000/api/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); Future register( FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { - var url = Uri.parse(baseUrl); + var url = Uri.parse('${baseUrl}personal-equipment-types/'); try { print('Sending POST request to $url'); @@ -43,4 +44,85 @@ class FireAlarmEquipmentService { return null; } } + + Future> getAllPersonalEquipmentBySystem( + int systemId) async { + var url = + Uri.parse('${baseUrl}personal-equipment-types/by-system/$systemId/'); + + try { + print('Sending GET request to $url'); + var response = + await client.get(url, headers: {'Content-Type': 'application/json'}); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 200) { + List responseData = jsonDecode(response.body); + List equipmentList = responseData + .map((item) => FireAlarmEquipmentResponseModel.fromJson(item)) + .toList(); + print('Request successful, received ${equipmentList.length} items'); + return equipmentList; + } else { + print( + 'Failed to get personal equipment by system: ${response.statusCode}'); + return []; + } + } catch (e) { + print('Error during get all personal equipment by system: $e'); + return []; + } + } + + Future> getAllEquipmentBySystem( + int systemId) async { + var url = Uri.parse('${baseUrl}equipment-types/by-system/$systemId/'); + + try { + print('Sending GET request to $url'); + var response = + await client.get(url, headers: {'Content-Type': 'application/json'}); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 200) { + List responseData = jsonDecode(response.body); + List equipmentList = responseData + .map((item) => FireAlarmEquipmentResponseModel.fromJson(item)) + .toList(); + print('Request successful, received ${equipmentList.length} items'); + return equipmentList; + } else { + print('Failed to get equipment by system: ${response.statusCode}'); + return []; + } + } catch (e) { + print('Error during get all equipment by system: $e'); + return []; + } + } + + Future> getAllEquipment( + int systemId) async { + try { + List personalEquipmentList = + await getAllPersonalEquipmentBySystem(systemId); + List equipmentList = + await getAllEquipmentBySystem(systemId); + + List combinedList = [ + ...personalEquipmentList, + ...equipmentList, + ]; + + print('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + print('Error during get all equipment: $e'); + return []; + } + } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index d4fc9961..ce67fae3 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -6,6 +6,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_request_model.dart'; import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_service.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_response_model.dart'; class ImageData { File imageFile; @@ -43,15 +44,31 @@ class _AddEquipmentScreenState extends State { String? _selectedType; String? _selectedTypeToDelete; String? _selectedfireAlarmType; + String? _newEquipmentTypeName; List equipmentTypes = []; - List fireAlarmType = [ 'Sensor de Fumaça', 'Sensor de Temperatura', 'Acionadores', ]; + @override + void initState() { + super.initState(); + _fetchEquipmentTypes(); + } + + Future _fetchEquipmentTypes() async { + FireAlarmEquipmentService service = FireAlarmEquipmentService(); + List equipmentList = + await service.getAllEquipment(widget.categoryNumber); + + setState(() { + equipmentTypes = equipmentList.map((e) => e.name).toList(); + }); + } + @override void dispose() { _equipmentQuantityController.dispose(); @@ -152,8 +169,9 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - equipmentTypes.add(typeController.text); + _newEquipmentTypeName = typeController.text; }); + _registerFireAlarmEquipment(); Navigator.of(context).pop(); } }, @@ -217,7 +235,9 @@ class _AddEquipmentScreenState extends State { void _showConfirmationDialog() { if (_equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectedfireAlarmType == null)) { + (_selectedType == null && + _selectedfireAlarmType == null && + _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -236,7 +256,10 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectedfireAlarmType ?? ''), + Text(_selectedType ?? + _selectedfireAlarmType ?? + _newEquipmentTypeName ?? + ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -279,7 +302,6 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('OK'), onPressed: () { - _registerFireAlarmEquipment(); Navigator.pushReplacementNamed( context, '/listFireAlarms', @@ -300,25 +322,37 @@ class _AddEquipmentScreenState extends State { } void _registerFireAlarmEquipment() async { - int areaId = widget.areaId; int systemId = widget.categoryNumber; - int equipmentTypeId = 1; - FireAlarmEquipmentRequestModel requestModel = FireAlarmEquipmentRequestModel( - photos: _images.map((imageData) => imageData.imageFile.path).toList(), - area: areaId, system: systemId, - equipmentType: equipmentTypeId, + name: _newEquipmentTypeName ?? '', ); FireAlarmEquipmentService service = FireAlarmEquipmentService(); int? id = await service.register(requestModel); if (id != null) { - print('Fire alarm equipment registered with ID: $id'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Equipamento de alarme de incêndio registrado com sucesso. ID: $id'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + equipmentTypes.add(_newEquipmentTypeName!); + _newEquipmentTypeName = null; + }); } else { - print('Failed to register fire alarm equipment'); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Falha ao registrar o equipamento de alarme de incêndio.'), + backgroundColor: Colors.red, + ), + ); } } diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 480f97b0..5c0cb830 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -38,10 +38,11 @@ class _SystemConfigurationState extends State { switch (routeName) { case '/structuredCabling': return listStruturedCabling( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + ); case '/atmosphericDischarges': return listatmosphericEquipment( areaName: areaName, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 8ea5eaa5..5bac1169 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -19,6 +19,7 @@ import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'core/feature/login/login.dart'; +// void main() { runApp(const MyApp()); @@ -28,7 +29,6 @@ final cookieJar = CookieJar(); class MyApp extends StatelessWidget { const MyApp({super.key}); - @override Widget build(BuildContext context) { return MaterialApp( @@ -296,6 +296,7 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; + final int categoryNumber = args['categoryNumber'] ?? 0; if (areaName != null && localName != null && localId != null) { From ac4851cffe97675eb3558e82500c8f984639f65f Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Wed, 5 Jun 2024 15:59:01 -0300 Subject: [PATCH 191/351] Atualiza caminhos dos equipmentlists na main Co-authored-by: Danilo melo --- frontend/sige_ie/lib/main.dart | 43 +++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 8ea5eaa5..b311673f 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -113,14 +113,19 @@ class MyApp extends StatelessWidget { final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int categoryNumber = args['categoryNumber'] ?? 0; + final int? areaId = args['areaId']; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => listIluminationEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, )); } else { throw Exception( @@ -137,14 +142,19 @@ class MyApp extends StatelessWidget { final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int categoryNumber = args['categoryNumber'] ?? 0; + final int? areaId = args['areaId']; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => listCicuitEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, )); } else { throw Exception( @@ -161,14 +171,19 @@ class MyApp extends StatelessWidget { final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int categoryNumber = args['categoryNumber'] ?? 0; + final int? areaId = args['areaId']; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => listElectricalLineEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, )); } else { throw Exception( @@ -214,14 +229,19 @@ class MyApp extends StatelessWidget { final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int categoryNumber = args['categoryNumber'] ?? 0; + final int? areaId = args['areaId']; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => listDistribuitionBoard( areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, )); } else { throw Exception( @@ -296,15 +316,21 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; + final int? areaId = args['areaId']; + final int categoryNumber = args['categoryNumber'] ?? 0; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => listStruturedCabling( areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, )); } else { throw Exception( @@ -321,14 +347,19 @@ class MyApp extends StatelessWidget { final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int categoryNumber = args['categoryNumber'] ?? 0; + final int? areaId = args['areaId']; - if (areaName != null && localName != null && localId != null) { + if (areaName != null && + localName != null && + localId != null && + areaId != null) { return MaterialPageRoute( builder: (context) => listelectricalLoadEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, localId: localId, + areaId: areaId, )); } else { throw Exception( From 274b8ce27829f0f8b531f1c76e96d3095edf6217 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Wed, 5 Jun 2024 16:00:06 -0300 Subject: [PATCH 192/351] Atualiza caminhos dos equipmentlists na SystemConfig Co-authored-by: Danilo melo --- .../feature/systemConfiguration.dart | 75 +++++++++++-------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 480f97b0..8661a918 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -38,10 +38,12 @@ class _SystemConfigurationState extends State { switch (routeName) { case '/structuredCabling': return listStruturedCabling( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); case '/atmosphericDischarges': return listatmosphericEquipment( areaName: areaName, @@ -52,35 +54,44 @@ class _SystemConfigurationState extends State { ); case '/fireAlarm': return listFireAlarms( - areaName: areaName, - localName: localName, - localId: localId, - areaId: areaId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); case '/electricLoads': return listelectricalLoadEquipment( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); case '/electricLines': return listElectricalLineEquipment( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); case '/circuits': return listCicuitEquipment( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); case '/distributionBoard': return listDistribuitionBoard( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); case '/cooling': return listCollingEquipment( areaName: areaName, @@ -91,10 +102,12 @@ class _SystemConfigurationState extends State { ); case '/lighting': return listIluminationEquipment( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); + areaName: areaName, + localName: localName, + localId: localId, + categoryNumber: category, + areaId: areaId, + ); default: return Scaffold( body: Center(child: Text('No route defined for $routeName')), @@ -147,7 +160,7 @@ class _SystemConfigurationState extends State { crossAxisSpacing: 10.0, children: [ SystemIcon( - icon: Icons.fire_extinguisher, + icon: Icons.local_fire_department, label: 'ALARME DE INCÊNDIO', onPressed: () => navigateTo('/fireAlarm', widget.areaName, widget.localName, widget.localId, widget.areaId, 8), @@ -170,7 +183,7 @@ class _SystemConfigurationState extends State { widget.localName, widget.localId, widget.areaId, 2), ), SystemIcon( - icon: Icons.electrical_services, + icon: Icons.electric_meter, label: 'CIRCUITOS', onPressed: () => navigateTo('/circuits', widget.areaName, widget.localName, widget.localId, widget.areaId, 4), From e004512155a0729d82ab267ae5f2a29454c4afa9 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Wed, 5 Jun 2024 16:00:59 -0300 Subject: [PATCH 193/351] Atualiza caminhos dos equipmentsLists em todas os tipos de equipamentos Co-authored-by: Danilo melo --- .../distribuition-Board/distribuitionBoardEquipmentList.dart | 4 +++- .../feature/electrical-circuit/electricalCircuitList.dart | 4 +++- .../feature/electrical-line/electricaLLineLIst.dart | 4 +++- .../equipments/feature/electrical-load/eletricalLoadList.dart | 4 +++- .../feature/iluminations/IluminationEquipmentList.dart | 4 +++- .../structured-cabling/struturedCablingEquipmentList.dart | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart index 0f463f57..91d8e879 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart @@ -7,6 +7,7 @@ class listDistribuitionBoard extends StatelessWidget { final String localName; final int categoryNumber; final int localId; + final int areaId; const listDistribuitionBoard({ super.key, @@ -14,6 +15,7 @@ class listDistribuitionBoard extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -51,7 +53,7 @@ class listDistribuitionBoard extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, - 'categoryNumber': categoryNumber, + 'areaId': areaId, }, ); }, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart index 9dd5b41a..490b2c17 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart @@ -7,6 +7,7 @@ class listCicuitEquipment extends StatelessWidget { final String localName; final int categoryNumber; final int localId; + final int areaId; const listCicuitEquipment({ super.key, @@ -14,6 +15,7 @@ class listCicuitEquipment extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -51,7 +53,7 @@ class listCicuitEquipment extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, - 'categoryNumber': categoryNumber, + 'areaId': areaId, }, ); }, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart b/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart index fb96e0ef..1cbd30db 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart @@ -7,6 +7,7 @@ class listElectricalLineEquipment extends StatelessWidget { final String localName; final int categoryNumber; final int localId; + final int areaId; const listElectricalLineEquipment({ super.key, @@ -14,6 +15,7 @@ class listElectricalLineEquipment extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -51,7 +53,7 @@ class listElectricalLineEquipment extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, - 'categoryNumber': categoryNumber, + 'areaId': areaId, }, ); }, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart index e9871019..7e34e5ea 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart @@ -7,6 +7,7 @@ class listelectricalLoadEquipment extends StatelessWidget { final String localName; final int categoryNumber; final int localId; + final int areaId; const listelectricalLoadEquipment({ super.key, @@ -14,6 +15,7 @@ class listelectricalLoadEquipment extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -50,7 +52,7 @@ class listelectricalLoadEquipment extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, - 'categoryNumber': categoryNumber, + 'areaId': areaId, }, ); }, diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart index aafe9786..1cfd8b2a 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart @@ -7,6 +7,7 @@ class listIluminationEquipment extends StatelessWidget { final String localName; final int categoryNumber; final int localId; + final int areaId; const listIluminationEquipment({ super.key, @@ -14,6 +15,7 @@ class listIluminationEquipment extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -51,7 +53,7 @@ class listIluminationEquipment extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, - 'categoryNumber': categoryNumber, + 'areaId': areaId, }, ); }, diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart index 1f4edc99..87e120af 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart @@ -7,6 +7,7 @@ class listStruturedCabling extends StatelessWidget { final String localName; final int categoryNumber; final int localId; + final int areaId; const listStruturedCabling({ super.key, @@ -14,6 +15,7 @@ class listStruturedCabling extends StatelessWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); void navigateToAddEquipment(BuildContext context) { @@ -51,7 +53,7 @@ class listStruturedCabling extends StatelessWidget { 'areaName': areaName, 'localName': localName, 'localId': localId, - 'categoryNumber': categoryNumber, + 'areaId': areaId, }, ); }, From 6a1681b80f7bcaa3ee5648eb369fbeef50a86fd7 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 16:24:13 -0300 Subject: [PATCH 194/351] =?UTF-8?q?Integra=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: OscarDeBrito --- .../fire-alarm-data/fire-alarm_service.dart | 6 +-- .../feature/fire-alarm/addFireAlarm.dart | 54 +++++++------------ 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart index a16db829..0bff842b 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart @@ -108,14 +108,14 @@ class FireAlarmEquipmentService { Future> getAllEquipment( int systemId) async { try { - List personalEquipmentList = - await getAllPersonalEquipmentBySystem(systemId); List equipmentList = await getAllEquipmentBySystem(systemId); + List personalEquipmentList = + await getAllPersonalEquipmentBySystem(systemId); List combinedList = [ - ...personalEquipmentList, ...equipmentList, + ...personalEquipmentList, ]; print('Combined list length: ${combinedList.length}'); diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index ce67fae3..09ffbea5 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -43,15 +43,10 @@ class _AddEquipmentScreenState extends State { final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; - String? _selectedfireAlarmType; String? _newEquipmentTypeName; List equipmentTypes = []; - List fireAlarmType = [ - 'Sensor de Fumaça', - 'Sensor de Temperatura', - 'Acionadores', - ]; + List personalEquipmentTypes = []; @override void initState() { @@ -62,10 +57,14 @@ class _AddEquipmentScreenState extends State { Future _fetchEquipmentTypes() async { FireAlarmEquipmentService service = FireAlarmEquipmentService(); List equipmentList = - await service.getAllEquipment(widget.categoryNumber); + await service.getAllEquipmentBySystem(widget.categoryNumber); + List personalEquipmentList = + await service.getAllPersonalEquipmentBySystem(widget.categoryNumber); setState(() { equipmentTypes = equipmentList.map((e) => e.name).toList(); + personalEquipmentTypes = + personalEquipmentList.map((e) => e.name).toList(); }); } @@ -183,21 +182,20 @@ class _AddEquipmentScreenState extends State { } void _deleteEquipmentType() { - if (_selectedTypeToDelete == null) { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: - Text('Selecione um tipo de equipamento válido para excluir.'), + content: Text('Não existem equipamentos pessoais a serem excluídos.'), ), ); return; } - if (fireAlarmType.contains(_selectedTypeToDelete)) { + if (_selectedTypeToDelete == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Não é possível excluir tipos de equipamento predefinidos.'), + Text('Selecione um tipo de equipamento válido para excluir.'), ), ); return; @@ -221,7 +219,7 @@ class _AddEquipmentScreenState extends State { child: const Text('Excluir'), onPressed: () { setState(() { - equipmentTypes.remove(_selectedTypeToDelete); + personalEquipmentTypes.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; }); Navigator.of(context).pop(); @@ -235,9 +233,7 @@ class _AddEquipmentScreenState extends State { void _showConfirmationDialog() { if (_equipmentQuantityController.text.isEmpty || - (_selectedType == null && - _selectedfireAlarmType == null && - _newEquipmentTypeName == null)) { + (_selectedType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -256,10 +252,7 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? - _selectedfireAlarmType ?? - _newEquipmentTypeName ?? - ''), + Text(_selectedType ?? _newEquipmentTypeName ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -342,7 +335,7 @@ class _AddEquipmentScreenState extends State { ); setState(() { - equipmentTypes.add(_newEquipmentTypeName!); + personalEquipmentTypes.add(_newEquipmentTypeName!); _newEquipmentTypeName = null; }); } else { @@ -358,7 +351,7 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { - List combinedTypes = fireAlarmType + equipmentTypes; + List combinedTypes = equipmentTypes + personalEquipmentTypes; return Scaffold( appBar: AppBar( @@ -417,18 +410,12 @@ class _AddEquipmentScreenState extends State { child: _buildStyledDropdown( items: ['Selecione o tipo de alarme de incêndio'] + combinedTypes, - value: _selectedfireAlarmType ?? _selectedType, + value: _selectedType, onChanged: (newValue) { if (newValue != 'Selecione o tipo de alarme de incêndio') { setState(() { - if (fireAlarmType.contains(newValue)) { - _selectedfireAlarmType = newValue; - _selectedType = null; - } else { - _selectedType = newValue; - _selectedfireAlarmType = null; - } + _selectedType = newValue; }); } }, @@ -446,11 +433,11 @@ class _AddEquipmentScreenState extends State { IconButton( icon: const Icon(Icons.delete), onPressed: () { - if (equipmentTypes.isEmpty) { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - 'Nenhum equipamento adicionado para excluir.'), + 'Não existem equipamentos pessoais a serem excluídos.'), ), ); } else { @@ -470,7 +457,6 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedfireAlarmType = null; _selectedType = null; }); }, @@ -590,7 +576,7 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: equipmentTypes + items: personalEquipmentTypes .map>((String value) { return DropdownMenuItem( value: value, From 1c23436581002d8d4ff56bde9992c2ed8e9c07cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Wed, 5 Jun 2024 16:50:40 -0300 Subject: [PATCH 195/351] backend: ajuste models --- ...quipmentphoto_equipment_detail_and_more.py | 33 +++++++++++++++++++ ...e_equipmentphoto_equipmenttype_and_more.py | 32 ++++++++++++++++++ api/equipments/mixins.py | 5 +++ api/equipments/models.py | 7 ++-- api/equipments/serializers.py | 14 ++++---- 5 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 api/equipments/migrations/0020_remove_equipmentphoto_equipment_detail_and_more.py create mode 100644 api/equipments/migrations/0021_remove_equipmentphoto_equipmenttype_and_more.py diff --git a/api/equipments/migrations/0020_remove_equipmentphoto_equipment_detail_and_more.py b/api/equipments/migrations/0020_remove_equipmentphoto_equipment_detail_and_more.py new file mode 100644 index 00000000..22ee62c0 --- /dev/null +++ b/api/equipments/migrations/0020_remove_equipmentphoto_equipment_detail_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2 on 2024-06-05 17:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0019_remove_equipmentdetail_description_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='equipmentphoto', + name='equipment_detail', + ), + migrations.AddField( + model_name='equipmentphoto', + name='equipmentType', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmenttype'), + ), + migrations.AddField( + model_name='equipmentphoto', + name='personalEquipmentType', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.personalequipmenttype'), + ), + migrations.AlterField( + model_name='equipmentphoto', + name='description', + field=models.CharField(max_length=50, null=True), + ), + ] diff --git a/api/equipments/migrations/0021_remove_equipmentphoto_equipmenttype_and_more.py b/api/equipments/migrations/0021_remove_equipmentphoto_equipmenttype_and_more.py new file mode 100644 index 00000000..673f7782 --- /dev/null +++ b/api/equipments/migrations/0021_remove_equipmentphoto_equipmenttype_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2 on 2024-06-05 18:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0020_remove_equipmentphoto_equipment_detail_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='equipmentphoto', + name='equipmentType', + ), + migrations.RemoveField( + model_name='equipmentphoto', + name='personalEquipmentType', + ), + migrations.AddField( + model_name='equipmentdetail', + name='personalEquipmentType', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.personalequipmenttype'), + ), + migrations.AlterField( + model_name='equipmentdetail', + name='equipmentType', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='equipments.equipmenttype'), + ), + ] diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index c487f06f..621a0e72 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -10,8 +10,13 @@ def validate(self, data): equipment_detail = data.get('equipment_detail') if equipment_detail: equipment_type_system = equipment_detail.equipmentType.system + personal_equipment_type_system = equipment_detail.personalEquipmentType.system + if equipment_detail.equipmentType and equipment_detail.personalEquipmentType: + raise serializers.ValidationError("Only one equipment type must exist") if equipment_type_system != data['system']: raise serializers.ValidationError("The equipment type's system must match the equipment's system.") + elif personal_equipment_type_system != data['system']: + raise serializers.ValidationError("The personal equipment type's system must match the equipment's system.") return data diff --git a/api/equipments/models.py b/api/equipments/models.py index fa249389..ae61d292 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -29,7 +29,9 @@ class Meta: class EquipmentDetail(models.Model): place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) - equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE) + equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE, null=True) + personalEquipmentType = models.ForeignKey(PersonalEquipmentType, on_delete=models.CASCADE, null=True) + class Meta: db_table = 'equipments_equipment_details' @@ -38,8 +40,7 @@ class EquipmentPhoto(models.Model): photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50, null=True) - equipmentType = models.ForeignKey(EquipmentType, on_delete=models.CASCADE, null=True) - personalEquipmentType = models.ForeignKey(PersonalEquipmentType, on_delete=models.CASCADE, null=True) + def __str__(self): return self.description diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index e74cb077..2788a28c 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -120,18 +120,18 @@ def create(self, validated_data): elif atmospheric_discharge_data: AtmosphericDischargeEquipment.objects.create(equipment_detail=equipment_detail, **atmospheric_discharge_data) elif structured_cabling_data: - StructuredCablingEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **structured_cabling_data) + StructuredCablingEquipment.objects.create(equipment_detail=equipment_detail, **structured_cabling_data) elif distribution_board_data: - DistributionBoardEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **distribution_board_data) + DistributionBoardEquipment.objects.create(equipment_detail=equipment_detail, **distribution_board_data) elif electrical_circuit_data: - ElectricalCircuitEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **electrical_circuit_data) + ElectricalCircuitEquipment.objects.create(equipment_detail=equipment_detail, **electrical_circuit_data) elif electrical_line_data: - ElectricalLineEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **electrical_line_data) + ElectricalLineEquipment.objects.create(equipment_detail=equipment_detail, **electrical_line_data) elif electrical_load_data: - ElectricalLoadEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **electrical_load_data) + ElectricalLoadEquipment.objects.create(equipment_detail=equipment_detail, **electrical_load_data) elif ilumination_equipment_data: - IluminationEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **ilumination_equipment_data) + IluminationEquipment.objects.create(equipment_detail=equipment_detail, **ilumination_equipment_data) elif refrigeration_equipment_data: - RefrigerationEquipmentSerializer.objects.create(equipment_detail=equipment_detail, **refrigeration_equipment_data) + RefrigerationEquipment.objects.create(equipment_detail=equipment_detail, **refrigeration_equipment_data) return equipment_detail \ No newline at end of file From f4fc5b5ab186ac27accfadb6d2878da0954a69cd Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 17:09:55 -0300 Subject: [PATCH 196/351] Adicionar equipamento parcialmente finalizado Co-authored-by: OscarDeBrito --- .../fire-alarm-data/fire-alarm_service.dart | 51 ++++++- .../feature/fire-alarm/addFireAlarm.dart | 129 ++++++++++++++---- 2 files changed, 149 insertions(+), 31 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart index 0bff842b..703ca365 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart @@ -3,7 +3,7 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_response_model.dart'; // Import the response model +import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_response_model.dart'; import 'package:sige_ie/main.dart'; class FireAlarmEquipmentService { @@ -125,4 +125,53 @@ class FireAlarmEquipmentService { return []; } } + + Future deleteEquipment(int id) async { + var url = Uri.parse('${baseUrl}personal-equipment-types/$id/'); + + try { + print('Sending DELETE request to $url'); + var response = await client + .delete(url, headers: {'Content-Type': 'application/json'}); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + return response.statusCode == 204; + } catch (e) { + print('Error during delete equipment: $e'); + return false; + } + } + + Future registerEquipmentDetail( + Map equipmentDetail) async { + var url = Uri.parse('${baseUrl}equipment-details/'); + + try { + print('Sending POST request to $url'); + print('Request body: ${jsonEncode(equipmentDetail)}'); + + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(equipmentDetail), + ); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 201) { + Map responseData = jsonDecode(response.body); + print('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + print('Failed to register equipment detail: ${response.statusCode}'); + return null; + } + } catch (e) { + print('Error during register equipment detail: $e'); + return null; + } + } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index 09ffbea5..7489aeab 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -47,6 +47,8 @@ class _AddEquipmentScreenState extends State { List equipmentTypes = []; List personalEquipmentTypes = []; + Map personalEquipmentMap = + {}; // Map to store equipment name and ID @override void initState() { @@ -65,6 +67,10 @@ class _AddEquipmentScreenState extends State { equipmentTypes = equipmentList.map((e) => e.name).toList(); personalEquipmentTypes = personalEquipmentList.map((e) => e.name).toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentList) + equipment.name: equipment.id + }; }); } @@ -181,7 +187,7 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteEquipmentType() { + void _deleteEquipmentType() async { if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -201,34 +207,30 @@ class _AddEquipmentScreenState extends State { return; } - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Excluir'), - onPressed: () { - setState(() { - personalEquipmentTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + + FireAlarmEquipmentService service = FireAlarmEquipmentService(); + bool success = await service.deleteEquipment(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } } void _showConfirmationDialog() { @@ -336,6 +338,7 @@ class _AddEquipmentScreenState extends State { setState(() { personalEquipmentTypes.add(_newEquipmentTypeName!); + personalEquipmentMap[_newEquipmentTypeName!] = id; _newEquipmentTypeName = null; }); } else { @@ -349,6 +352,72 @@ class _AddEquipmentScreenState extends State { } } + Future _registerEquipmentDetail() async { + int systemId = widget.categoryNumber; + String categoryKey; + + switch (systemId) { + case 1: + categoryKey = 'ilumination_equipment'; + break; + case 2: + categoryKey = 'electrical_load_equipment'; + break; + case 3: + categoryKey = 'electrical_line_equipment'; + break; + case 4: + categoryKey = 'electrical_circuit_equipment'; + break; + case 5: + categoryKey = 'distribution_board_equipment'; + break; + case 6: + categoryKey = 'structured_cabling_equipment'; + break; + case 7: + categoryKey = 'atmospheric_discharge_equipment'; + break; + case 8: + categoryKey = 'fire_alarm_equipment'; + break; + case 9: + categoryKey = 'refrigeration_equipment'; + break; + default: + categoryKey = 'unknown'; + } + + Map equipmentDetail = { + "photos": [], + categoryKey: { + "area": widget.areaId, + "system": systemId, + }, + "equipmentType": systemId, + }; + + FireAlarmEquipmentService service = FireAlarmEquipmentService(); + int? id = await service.registerEquipmentDetail(equipmentDetail); + + if (id != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Detalhe do equipamento registrado com sucesso. ID: $id'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + @override Widget build(BuildContext context) { List combinedTypes = equipmentTypes + personalEquipmentTypes; @@ -536,7 +605,7 @@ class _AddEquipmentScreenState extends State { MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ))), - onPressed: _showConfirmationDialog, + onPressed: _registerEquipmentDetail, child: const Text( 'ADICIONAR EQUIPAMENTO', style: TextStyle( From 257a01f2e0c119bdbd792fef03e32888e591e713 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 5 Jun 2024 17:42:39 -0300 Subject: [PATCH 197/351] backend: corrie mixin --- api/equipments/mixins.py | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index 621a0e72..5de64727 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -1,25 +1,9 @@ -from .models import EquipmentDetail +from .models import EquipmentDetail, EquipmentType from rest_framework import serializers +from django.core.exceptions import ObjectDoesNotExist class ValidateAreaMixin: - def validate(self, data): - """ - Garante que o equipment detail pertence ao system. - """ - equipment_detail = data.get('equipment_detail') - if equipment_detail: - equipment_type_system = equipment_detail.equipmentType.system - personal_equipment_type_system = equipment_detail.personalEquipmentType.system - if equipment_detail.equipmentType and equipment_detail.personalEquipmentType: - raise serializers.ValidationError("Only one equipment type must exist") - if equipment_type_system != data['system']: - raise serializers.ValidationError("The equipment type's system must match the equipment's system.") - elif personal_equipment_type_system != data['system']: - raise serializers.ValidationError("The personal equipment type's system must match the equipment's system.") - - return data - def validate_equipment_detail(self, value): """ Garante que o equipment detail pertence ao place owner. @@ -36,4 +20,4 @@ def validate_area(self, value): user = self.context['request'].user if value.place.place_owner != user.placeowner: raise serializers.ValidationError("You are not the owner of this place") - return value \ No newline at end of file + return value From 3b76ea7d4717c4f48bf7095d2d95468376eb1273 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 5 Jun 2024 17:56:06 -0300 Subject: [PATCH 198/351] =?UTF-8?q?Integra=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: OscarDeBrito --- .../fire-alarm-data/fire-alarm_service.dart | 20 ++--- .../feature/fire-alarm/addFireAlarm.dart | 75 ++++++++++++------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart index 703ca365..b51dd6d2 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart @@ -144,34 +144,26 @@ class FireAlarmEquipmentService { } } - Future registerEquipmentDetail( - Map equipmentDetail) async { + Future registerEquipmentDetail( + Map requestPayload) async { var url = Uri.parse('${baseUrl}equipment-details/'); try { - print('Sending POST request to $url'); - print('Request body: ${jsonEncode(equipmentDetail)}'); - var response = await client.post( url, headers: {'Content-Type': 'application/json'}, - body: jsonEncode(equipmentDetail), + body: jsonEncode(requestPayload), ); - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); - if (response.statusCode == 201) { - Map responseData = jsonDecode(response.body); - print('Request successful, received ID: ${responseData['id']}'); - return responseData['id']; + return true; } else { print('Failed to register equipment detail: ${response.statusCode}'); - return null; + return false; } } catch (e) { print('Error during register equipment detail: $e'); - return null; + return false; } } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index 7489aeab..c405dfc2 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -44,6 +45,7 @@ class _AddEquipmentScreenState extends State { String? _selectedType; String? _selectedTypeToDelete; String? _newEquipmentTypeName; + int? _selectedTypeId; // ID of the selected type List equipmentTypes = []; List personalEquipmentTypes = []; @@ -352,66 +354,81 @@ class _AddEquipmentScreenState extends State { } } - Future _registerEquipmentDetail() async { - int systemId = widget.categoryNumber; - String categoryKey; - - switch (systemId) { + void _registerEquipmentDetail() async { + FireAlarmEquipmentService service = FireAlarmEquipmentService(); + String equipmentKey; + switch (widget.categoryNumber) { case 1: - categoryKey = 'ilumination_equipment'; + equipmentKey = 'ilumination_equipment'; break; case 2: - categoryKey = 'electrical_load_equipment'; + equipmentKey = 'electrical_load_equipment'; break; case 3: - categoryKey = 'electrical_line_equipment'; + equipmentKey = 'electrical_line_equipment'; break; case 4: - categoryKey = 'electrical_circuit_equipment'; + equipmentKey = 'electrical_circuit_equipment'; break; case 5: - categoryKey = 'distribution_board_equipment'; + equipmentKey = 'distribution_board_equipment'; break; case 6: - categoryKey = 'structured_cabling_equipment'; + equipmentKey = 'structured_cabling_equipment'; break; case 7: - categoryKey = 'atmospheric_discharge_equipment'; + equipmentKey = 'atmospheric_discharge_equipment'; break; case 8: - categoryKey = 'fire_alarm_equipment'; + equipmentKey = 'fire_alarm_equipment'; break; case 9: - categoryKey = 'refrigeration_equipment'; + equipmentKey = 'refrigeration_equipment'; break; default: - categoryKey = 'unknown'; + equipmentKey = 'unknown'; } Map equipmentDetail = { - "photos": [], - categoryKey: { - "area": widget.areaId, - "system": systemId, - }, - "equipmentType": systemId, + 'area': widget.areaId, + 'system': widget.categoryNumber, }; - FireAlarmEquipmentService service = FireAlarmEquipmentService(); - int? id = await service.registerEquipmentDetail(equipmentDetail); + Map requestPayload = { + 'photos': _images.map((imageData) { + return { + 'file': base64Encode(imageData.imageFile.readAsBytesSync()), + 'description': imageData.description, + }; + }).toList(), + equipmentKey: equipmentDetail, + 'equipmentType': _selectedTypeId, + }; - if (id != null) { + bool success = await service.registerEquipmentDetail(requestPayload); + + if (success) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Detalhe do equipamento registrado com sucesso. ID: $id'), + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), backgroundColor: Colors.green, ), ); + Navigator.pushReplacementNamed( + context, + '/listFireAlarms', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Falha ao registrar o equipamento.'), + content: Text('Falha ao registrar os detalhes do equipamento.'), backgroundColor: Colors.red, ), ); @@ -485,6 +502,8 @@ class _AddEquipmentScreenState extends State { 'Selecione o tipo de alarme de incêndio') { setState(() { _selectedType = newValue; + _selectedTypeId = + personalEquipmentMap[newValue] ?? -1; }); } }, From 85fb3cfa4fb3d5253476aea2f630ff48af14a131 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 6 Jun 2024 14:21:04 -0300 Subject: [PATCH 199/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20com=20backend=20?= =?UTF-8?q?-=20tentativa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/equipment_detail_service.dart | 43 +++++++++++++++++++ .../fire-alarm_request_model.dart | 23 ---------- ..._alarm_equipment_detail_request_model.dart | 22 ++++++++++ .../fire_alarm_request_model.dart | 16 +++++++ ...el.dart => fire_alarm_response_model.dart} | 0 ...m_service.dart => fire_alarm_service.dart} | 37 +--------------- ...nation_equipment_detail_request_model.dart | 22 ++++++++++ .../ilumination_request_model.dart | 16 +++++++ .../data/photo-data/photo_request_model.dart | 16 +++++++ .../feature/fire-alarm/addFireAlarm.dart | 6 +-- 10 files changed, 140 insertions(+), 61 deletions(-) create mode 100644 frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart delete mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_request_model.dart rename frontend/sige_ie/lib/equipments/data/fire-alarm-data/{fire-alarm_response_model.dart => fire_alarm_response_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/fire-alarm-data/{fire-alarm_service.dart => fire_alarm_service.dart} (78%) create mode 100644 frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_equipment_detail_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/photo-data/photo_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart new file mode 100644 index 00000000..aacac49d --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart'; +import 'package:sige_ie/main.dart'; + +class EquipmentDetailService { + final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future createFireAlarm( + FireAlarmEquipmentDetailRequestModel + fireAlarmEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(fireAlarmEquipmentRequestModel.toJson()), + ); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 201) { + Map responseData = jsonDecode(response.body); + print('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + print( + 'Failed to register fire alarm equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + print('Error during register: $e'); + return null; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart deleted file mode 100644 index 53bab432..00000000 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_request_model.dart +++ /dev/null @@ -1,23 +0,0 @@ -class FireAlarmEquipmentRequestModel { - String name; - int? system; - - FireAlarmEquipmentRequestModel({ - required this.name, - required this.system, - }); - - Map toJson() { - return { - 'name': name, - 'system': system, - }; - } - - factory FireAlarmEquipmentRequestModel.fromJson(Map json) { - return FireAlarmEquipmentRequestModel( - name: json['name'], - system: json['system'], - ); - } -} diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart new file mode 100644 index 00000000..95354f83 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart @@ -0,0 +1,22 @@ +import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_request_model.dart'; +import 'package:sige_ie/equipments/data/photo-data/photo_request_model.dart'; + +class FireAlarmEquipmentDetailRequestModel { + int? equipmenteType; + FireAlarmRequestModel? fireAlarm; + List? photos; + + FireAlarmEquipmentDetailRequestModel({ + required this.equipmenteType, + required this.fireAlarm, + required this.photos, + }); + + Map toJson() { + return { + 'equipmenteType': equipmenteType, + 'fire_alarm_equipment': fireAlarm?.toJson(), + 'photos': photos?.map((photo) => photo.toJson()).toList(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_request_model.dart new file mode 100644 index 00000000..7c41b07b --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_request_model.dart @@ -0,0 +1,16 @@ +class FireAlarmRequestModel { + int? area; + int? system; + + FireAlarmRequestModel({ + required this.area, + required this.system, + }); + + Map toJson() { + return { + 'name': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_response_model.dart rename to frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_service.dart similarity index 78% rename from frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart rename to frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_service.dart index b51dd6d2..10cc8d22 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire-alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_service.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_response_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_response_model.dart'; import 'package:sige_ie/main.dart'; class FireAlarmEquipmentService { @@ -12,39 +12,6 @@ class FireAlarmEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future register( - FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { - var url = Uri.parse('${baseUrl}personal-equipment-types/'); - - try { - print('Sending POST request to $url'); - print( - 'Request body: ${jsonEncode(fireAlarmEquipmentRequestModel.toJson())}'); - - var response = await client.post( - url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(fireAlarmEquipmentRequestModel.toJson()), - ); - - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); - - if (response.statusCode == 201) { - Map responseData = jsonDecode(response.body); - print('Request successful, received ID: ${responseData['id']}'); - return responseData['id']; - } else { - print( - 'Failed to register fire alarm equipment: ${response.statusCode}'); - return null; - } - } catch (e) { - print('Error during register: $e'); - return null; - } - } - Future> getAllPersonalEquipmentBySystem( int systemId) async { var url = diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_equipment_detail_request_model.dart new file mode 100644 index 00000000..cdc9c823 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_equipment_detail_request_model.dart @@ -0,0 +1,22 @@ +import 'package:sige_ie/equipments/data/iluminations-data/ilumination_request_model.dart'; +import 'package:sige_ie/equipments/data/photo-data/photo_request_model.dart'; + +class FireAlarmEquipmentDetailRequestModel { + int? equipmenteType; + IluminationRequestModel? ilumination; + List? photos; + + FireAlarmEquipmentDetailRequestModel({ + required this.equipmenteType, + required this.ilumination, + required this.photos, + }); + + Map toJson() { + return { + 'equipmenteType': equipmenteType, + 'ilumination_equipment': ilumination?.toJson(), + 'photos': photos?.map((photo) => photo.toJson()).toList(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart index e69de29b..43441b35 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart @@ -0,0 +1,16 @@ +class IluminationRequestModel { + int? area; + int? system; + + IluminationRequestModel({ + required this.area, + required this.system, + }); + + Map toJson() { + return { + 'name': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/photo-data/photo_request_model.dart b/frontend/sige_ie/lib/equipments/data/photo-data/photo_request_model.dart new file mode 100644 index 00000000..28c5a361 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/photo-data/photo_request_model.dart @@ -0,0 +1,16 @@ +class PhotoRequestModel { + String photo; + String description; + + PhotoRequestModel({ + required this.photo, + required this.description, + }); + + Map toJson() { + return { + 'photo': photo, + 'description': description, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index c405dfc2..c39659e9 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -5,9 +5,9 @@ import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_service.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire-alarm_response_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_request_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_service.dart'; +import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_response_model.dart'; class ImageData { File imageFile; From 9581912d670b28208366faef06e6d1538c19762a Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Thu, 6 Jun 2024 17:50:22 -0300 Subject: [PATCH 200/351] Refatora equipament detail Co-authored-by: Ramires rocha Co-authored-by: Pedro Lucas --- .../atmospheric_request_model.dart | 0 .../atmospheric_response_model.dart | 0 .../atmospheric_service.dart | 2 +- .../cooling_request_model.dart | 0 .../cooling_response_model.dart | 0 .../cooling_service.dart | 2 +- .../distribution_request_model.dart | 0 .../distribution_response_model.dart | 0 .../distribution_service.dart | 0 .../eletrical-circuit_request_model.dart | 0 .../eletrical-circuit_response_model.dart | 0 .../eletrical-circuit_service.dart | 0 .../eletrical-line_request_model.dart | 0 .../eletrical-line_response_model.dart | 0 .../eletrical-line_service.dart | 0 .../eletrical-load_request_model.dart.dart | 0 .../eletrical-load_response_model.dart | 0 .../eletrical-load_service.dart | 0 .../equipment_type_response_model.dart | 27 ++++ .../equipment_type_service.dart | 42 ++++++ .../data/equipment_detail_service.dart | 10 +- .../fire-alarm-data/fire_alarm_service.dart | 136 ------------------ ..._alarm_equipment_detail_request_model.dart | 4 +- .../fire_alarm_request_model.dart | 0 .../fire_alarm_response_model.dart | 22 +-- .../data/fire-alarm/fire_alarm_service.dart | 51 +++++++ ...nation_equipment_detail_request_model.dart | 4 +- .../ilumination_request_model.dart | 0 .../ilumination_request_response.dart | 0 .../ilumination_service.dart | 0 .../mix-equipment-type-service.dart | 27 ++++ ...personal_equipment_type_request_model.dart | 16 +++ ...ersonal_equipment_type_response_model.dart | 28 ++++ .../personal_equipment_type_service.dart | 58 ++++++++ .../photo_request_model.dart | 0 .../structured-cabling_request_model.dart | 0 .../structured-cabling_response_model.dart | 0 .../structured-cabling_service.dart | 0 .../addAtmospheric-dischargesEquipment.dart | 4 +- .../feature/fire-alarm/addFireAlarm.dart | 75 ++++------ 40 files changed, 301 insertions(+), 207 deletions(-) rename frontend/sige_ie/lib/equipments/data/{atmospheric-data => atmospheric}/atmospheric_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{atmospheric-data => atmospheric}/atmospheric_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{atmospheric-data => atmospheric}/atmospheric_service.dart (94%) rename frontend/sige_ie/lib/equipments/data/{cooling-data => cooling}/cooling_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{cooling-data => cooling}/cooling_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{cooling-data => cooling}/cooling_service.dart (94%) rename frontend/sige_ie/lib/equipments/data/{distribution-data => distribution}/distribution_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{distribution-data => distribution}/distribution_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{distribution-data => distribution}/distribution_service.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-circuit-data => eletrical-circuit}/eletrical-circuit_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-circuit-data => eletrical-circuit}/eletrical-circuit_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-circuit-data => eletrical-circuit}/eletrical-circuit_service.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-line-data => eletrical-line}/eletrical-line_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-line-data => eletrical-line}/eletrical-line_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-line-data => eletrical-line}/eletrical-line_service.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-load-data => eletrical-load}/eletrical-load_request_model.dart.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-load-data => eletrical-load}/eletrical-load_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-load-data => eletrical-load}/eletrical-load_service.dart (100%) create mode 100644 frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_service.dart delete mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_service.dart rename frontend/sige_ie/lib/equipments/data/{fire-alarm-data => fire-alarm}/fire_alarm_equipment_detail_request_model.dart (74%) rename frontend/sige_ie/lib/equipments/data/{fire-alarm-data => fire-alarm}/fire_alarm_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{fire-alarm-data => fire-alarm}/fire_alarm_response_model.dart (83%) create mode 100644 frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart rename frontend/sige_ie/lib/equipments/data/{iluminations-data => iluminations}/ilumination_equipment_detail_request_model.dart (74%) rename frontend/sige_ie/lib/equipments/data/{iluminations-data => iluminations}/ilumination_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{iluminations-data => iluminations}/ilumination_request_response.dart (100%) rename frontend/sige_ie/lib/equipments/data/{iluminations-data => iluminations}/ilumination_service.dart (100%) create mode 100644 frontend/sige_ie/lib/equipments/data/mix-equipment-type/mix-equipment-type-service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart rename frontend/sige_ie/lib/equipments/data/{photo-data => photo}/photo_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{structured-cabling-data => structured-cabling}/structured-cabling_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{structured-cabling-data => structured-cabling}/structured-cabling_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{structured-cabling-data => structured-cabling}/structured-cabling_service.dart (100%) diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_request_model.dart rename to frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_response_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_response_model.dart rename to frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart similarity index 94% rename from frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart rename to frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart index a9d63131..a2bb4b57 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric-data/atmospheric_service.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/atmospheric-data/atmospheric_request_model.dart'; +import 'package:sige_ie/equipments/data/atmospheric/atmospheric_request_model.dart'; import 'package:sige_ie/main.dart'; class AtmosphericEquipmentService { diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart b/frontend/sige_ie/lib/equipments/data/cooling/cooling_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/cooling-data/cooling_request_model.dart rename to frontend/sige_ie/lib/equipments/data/cooling/cooling_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_response_model.dart b/frontend/sige_ie/lib/equipments/data/cooling/cooling_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/cooling-data/cooling_response_model.dart rename to frontend/sige_ie/lib/equipments/data/cooling/cooling_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart b/frontend/sige_ie/lib/equipments/data/cooling/cooling_service.dart similarity index 94% rename from frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart rename to frontend/sige_ie/lib/equipments/data/cooling/cooling_service.dart index 0d82396d..96eeb3ae 100644 --- a/frontend/sige_ie/lib/equipments/data/cooling-data/cooling_service.dart +++ b/frontend/sige_ie/lib/equipments/data/cooling/cooling_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/cooling-data/cooling_request_model.dart'; +import 'package:sige_ie/equipments/data/cooling/cooling_request_model.dart'; import 'package:sige_ie/main.dart'; class CoolingEquipmentService { diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_request_model.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/distribution-data/distribution_request_model.dart rename to frontend/sige_ie/lib/equipments/data/distribution/distribution_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_response_model.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/distribution-data/distribution_response_model.dart rename to frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/distribution-data/distribution_service.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/distribution-data/distribution_service.dart rename to frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_request_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_response_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-circuit-data/eletrical-circuit_service.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_request_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_response_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-line-data/eletrical-line_service.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_request_model.dart.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_request_model.dart.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_request_model.dart.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_request_model.dart.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_response_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-load-data/eletrical-load_service.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_response_model.dart b/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_response_model.dart new file mode 100644 index 00000000..e2573327 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_response_model.dart @@ -0,0 +1,27 @@ +class EquipmentTypeResponseModel { + int id; + String name; + int system; + + EquipmentTypeResponseModel({ + required this.id, + required this.name, + required this.system, + }); + + factory EquipmentTypeResponseModel.fromJson(Map json) { + return EquipmentTypeResponseModel( + id: json['id'], + name: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_service.dart b/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_service.dart new file mode 100644 index 00000000..020e2b34 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_service.dart @@ -0,0 +1,42 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class EquipmentTypeService { + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipmentTypeBySystem( + int systemId) async { + var url = Uri.parse('${baseUrl}equipment-types/by-system/$systemId/'); + + try { + print('Sending GET request to $url'); + var response = + await client.get(url, headers: {'Content-Type': 'application/json'}); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 200) { + List responseData = jsonDecode(response.body); + List equipmentList = responseData + .map((item) => EquipmentTypeResponseModel.fromJson(item)) + .toList(); + print('Request successful, received ${equipmentList.length} items'); + return equipmentList; + } else { + print('Failed to get equipment by system: ${response.statusCode}'); + return []; + } + } catch (e) { + print('Error during get all equipment by system: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart index aacac49d..bd66dec1 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart'; import 'package:sige_ie/main.dart'; class EquipmentDetailService { @@ -11,7 +11,7 @@ class EquipmentDetailService { interceptors: [AuthInterceptor(cookieJar)], ); - Future createFireAlarm( + Future createFireAlarm( FireAlarmEquipmentDetailRequestModel fireAlarmEquipmentRequestModel) async { var url = Uri.parse(baseUrl); @@ -29,15 +29,15 @@ class EquipmentDetailService { if (response.statusCode == 201) { Map responseData = jsonDecode(response.body); print('Request successful, received ID: ${responseData['id']}'); - return responseData['id']; + return true; } else { print( 'Failed to register fire alarm equipment: ${response.statusCode}'); - return null; + return false; } } catch (e) { print('Error during register: $e'); - return null; + return false; } } } diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_service.dart deleted file mode 100644 index 10cc8d22..00000000 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_service.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'dart:convert'; -import 'package:http/http.dart' as http; -import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_response_model.dart'; -import 'package:sige_ie/main.dart'; - -class FireAlarmEquipmentService { - final String baseUrl = 'http://10.0.2.2:8000/api/'; - http.Client client = InterceptedClient.build( - interceptors: [AuthInterceptor(cookieJar)], - ); - - Future> getAllPersonalEquipmentBySystem( - int systemId) async { - var url = - Uri.parse('${baseUrl}personal-equipment-types/by-system/$systemId/'); - - try { - print('Sending GET request to $url'); - var response = - await client.get(url, headers: {'Content-Type': 'application/json'}); - - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); - - if (response.statusCode == 200) { - List responseData = jsonDecode(response.body); - List equipmentList = responseData - .map((item) => FireAlarmEquipmentResponseModel.fromJson(item)) - .toList(); - print('Request successful, received ${equipmentList.length} items'); - return equipmentList; - } else { - print( - 'Failed to get personal equipment by system: ${response.statusCode}'); - return []; - } - } catch (e) { - print('Error during get all personal equipment by system: $e'); - return []; - } - } - - Future> getAllEquipmentBySystem( - int systemId) async { - var url = Uri.parse('${baseUrl}equipment-types/by-system/$systemId/'); - - try { - print('Sending GET request to $url'); - var response = - await client.get(url, headers: {'Content-Type': 'application/json'}); - - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); - - if (response.statusCode == 200) { - List responseData = jsonDecode(response.body); - List equipmentList = responseData - .map((item) => FireAlarmEquipmentResponseModel.fromJson(item)) - .toList(); - print('Request successful, received ${equipmentList.length} items'); - return equipmentList; - } else { - print('Failed to get equipment by system: ${response.statusCode}'); - return []; - } - } catch (e) { - print('Error during get all equipment by system: $e'); - return []; - } - } - - Future> getAllEquipment( - int systemId) async { - try { - List equipmentList = - await getAllEquipmentBySystem(systemId); - List personalEquipmentList = - await getAllPersonalEquipmentBySystem(systemId); - - List combinedList = [ - ...equipmentList, - ...personalEquipmentList, - ]; - - print('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - print('Error during get all equipment: $e'); - return []; - } - } - - Future deleteEquipment(int id) async { - var url = Uri.parse('${baseUrl}personal-equipment-types/$id/'); - - try { - print('Sending DELETE request to $url'); - var response = await client - .delete(url, headers: {'Content-Type': 'application/json'}); - - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); - - return response.statusCode == 204; - } catch (e) { - print('Error during delete equipment: $e'); - return false; - } - } - - Future registerEquipmentDetail( - Map requestPayload) async { - var url = Uri.parse('${baseUrl}equipment-details/'); - - try { - var response = await client.post( - url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(requestPayload), - ); - - if (response.statusCode == 201) { - return true; - } else { - print('Failed to register equipment detail: ${response.statusCode}'); - return false; - } - } catch (e) { - print('Error during register equipment detail: $e'); - return false; - } - } -} diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart similarity index 74% rename from frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart rename to frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart index 95354f83..1fbfdb30 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_equipment_detail_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart @@ -1,5 +1,5 @@ -import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/photo-data/photo_request_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; +import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; class FireAlarmEquipmentDetailRequestModel { int? equipmenteType; diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_request_model.dart rename to frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_response_model.dart similarity index 83% rename from frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_response_model.dart rename to frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_response_model.dart index 68afcf64..0ad038d7 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm-data/fire_alarm_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_response_model.dart @@ -1,27 +1,27 @@ class FireAlarmEquipmentResponseModel { int id; - String name; + String area; int system; FireAlarmEquipmentResponseModel({ required this.id, - required this.name, + required this.area, required this.system, }); - Map toJson() { - return { - 'id': id, - 'name': name, - 'system': system, - }; - } - factory FireAlarmEquipmentResponseModel.fromJson(Map json) { return FireAlarmEquipmentResponseModel( id: json['id'], - name: json['name'], + area: json['name'], system: json['system'], ); } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } } diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart new file mode 100644 index 00000000..fcaeb969 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class FireAlarmEquipmentService { + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipment(int systemId, equipmentTypeList, personalEquipmentList) async { + + List combinedList = [ + ...equipmentTypeList, + ...personalEquipmentList, + ]; + + print('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + print('Error during get all equipment: $e'); + return []; + } + } + + Future registerEquipmentDetail( + Map requestPayload) async { + var url = Uri.parse('${baseUrl}equipment-details/'); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(requestPayload), + ); + + if (response.statusCode == 201) { + return true; + } else { + print('Failed to register equipment detail: ${response.statusCode}'); + return false; + } + } catch (e) { + print('Error during register equipment detail: $e'); + return false; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart similarity index 74% rename from frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_equipment_detail_request_model.dart rename to frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart index cdc9c823..5e5c9003 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_equipment_detail_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart @@ -1,5 +1,5 @@ -import 'package:sige_ie/equipments/data/iluminations-data/ilumination_request_model.dart'; -import 'package:sige_ie/equipments/data/photo-data/photo_request_model.dart'; +import 'package:sige_ie/equipments/data/iluminations/ilumination_request_model.dart'; +import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; class FireAlarmEquipmentDetailRequestModel { int? equipmenteType; diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_model.dart rename to frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_response.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_request_response.dart rename to frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart diff --git a/frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/iluminations-data/ilumination_service.dart rename to frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/mix-equipment-type/mix-equipment-type-service.dart b/frontend/sige_ie/lib/equipments/data/mix-equipment-type/mix-equipment-type-service.dart new file mode 100644 index 00000000..750f7060 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/mix-equipment-type/mix-equipment-type-service.dart @@ -0,0 +1,27 @@ +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class MixEquipmentTypeService { + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipmentBySystem( + int systemId, equipmentTypeList, personalEquipmentList) async { + try { + List combinedList = [ + ...equipmentTypeList, + ...personalEquipmentList, + ]; + print('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + print('Error during get all equipment: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart new file mode 100644 index 00000000..e1e8d4a4 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart @@ -0,0 +1,16 @@ +class PersonalEquipmentTypeRequestModel { + String name; + int? system; + + PersonalEquipmentTypeRequestModel({ + required this.name, + required this.system, + }); + + Map toJson() { + return { + 'name': name, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_response_model.dart b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_response_model.dart new file mode 100644 index 00000000..abda96f0 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_response_model.dart @@ -0,0 +1,28 @@ +class PersonalEquipmentTypeResponseModel { + int id; + String name; + int system; + + PersonalEquipmentTypeResponseModel({ + required this.id, + required this.name, + required this.system, + }); + + factory PersonalEquipmentTypeResponseModel.fromJson( + Map json) { + return PersonalEquipmentTypeResponseModel( + id: json['id'], + name: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart new file mode 100644 index 00000000..c2539e00 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class PersonalEquipmentTypeService { + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllPersonalEquipmentBySystem( + int systemId) async { + var url = + Uri.parse('${baseUrl}personal-equipment-types/by-system/$systemId/'); + + try { + var response = + await client.get(url, headers: {'Content-Type': 'application/json'}); + + if (response.statusCode == 200) { + List responseData = jsonDecode(response.body); + List equipmentList = responseData + .map((item) => EquipmentTypeResponseModel.fromJson(item)) + .toList(); + //print('Request successful, received ${equipmentList.length} items'); + return equipmentList; + } else { + print( + 'Failed to get personal equipment by system: ${response.statusCode}'); + return []; + } + } catch (e) { + print('Error during get all personal equipment by system: $e'); + return []; + } + } + + Future deletePersonalEquipmentType(int id) async { + var url = Uri.parse('${baseUrl}personal-equipment-types/$id/'); + + try { + print('Sending DELETE request to $url'); + var response = await client + .delete(url, headers: {'Content-Type': 'application/json'}); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + return response.statusCode == 204; + } catch (e) { + print('Error during delete equipment: $e'); + return false; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/photo-data/photo_request_model.dart b/frontend/sige_ie/lib/equipments/data/photo/photo_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/photo-data/photo_request_model.dart rename to frontend/sige_ie/lib/equipments/data/photo/photo_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_request_model.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_request_model.dart rename to frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_response_model.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_response_model.dart rename to frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/structured-cabling-data/structured-cabling_service.dart rename to frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_service.dart diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart index 2aeb1eb1..fd18d186 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart @@ -4,8 +4,8 @@ import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/data/atmospheric-data/atmospheric_request_model.dart'; -import 'package:sige_ie/equipments/data/atmospheric-data/atmospheric_service.dart'; +import 'package:sige_ie/equipments/data/atmospheric/atmospheric_request_model.dart'; +import 'package:sige_ie/equipments/data/atmospheric/atmospheric_service.dart'; class ImageData { File imageFile; diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index c39659e9..6b9a7c82 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -5,9 +5,12 @@ import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_service.dart'; -import 'package:sige_ie/equipments/data/fire-alarm-data/fire_alarm_response_model.dart'; +import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; +import 'package:sige_ie/equipments/data/equipment-type/equipment_type_service.dart'; +import 'package:sige_ie/equipments/data/equipment_detail_service.dart'; +import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_service.dart'; +import 'package:sige_ie/equipments/data/mix-equipment-type/mix-equipment-type-service.dart'; +import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_service.dart'; class ImageData { File imageFile; @@ -41,6 +44,11 @@ class AddfireAlarm extends StatefulWidget { } class _AddEquipmentScreenState extends State { + EquipmentDetailService equipmentDetailService = EquipmentDetailService(); + PersonalEquipmentTypeService personalEquipmentTypeService = + PersonalEquipmentTypeService(); + EquipmentTypeService equipmentTypeService = EquipmentTypeService(); + MixEquipmentTypeService mixEquipmentTypeService = MixEquipmentTypeService(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; @@ -59,11 +67,17 @@ class _AddEquipmentScreenState extends State { } Future _fetchEquipmentTypes() async { - FireAlarmEquipmentService service = FireAlarmEquipmentService(); - List equipmentList = - await service.getAllEquipmentBySystem(widget.categoryNumber); - List personalEquipmentList = - await service.getAllPersonalEquipmentBySystem(widget.categoryNumber); + List equipmentTypeList = + await equipmentTypeService + .getAllEquipmentTypeBySystem(widget.categoryNumber); + + List personalEquipmentList = + await personalEquipmentTypeService + .getAllPersonalEquipmentBySystem(widget.categoryNumber); + + List equipmentList = + await mixEquipmentTypeService.getAllEquipmentBySystem( + widget.categoryNumber, equipmentTypeList, personalEquipmentList); setState(() { equipmentTypes = equipmentList.map((e) => e.name).toList(); @@ -211,8 +225,8 @@ class _AddEquipmentScreenState extends State { int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; - FireAlarmEquipmentService service = FireAlarmEquipmentService(); - bool success = await service.deleteEquipment(equipmentId); + bool success = await personalEquipmentTypeService + .deletePersonalEquipmentType(equipmentId); if (success) { setState(() { @@ -326,8 +340,7 @@ class _AddEquipmentScreenState extends State { name: _newEquipmentTypeName ?? '', ); - FireAlarmEquipmentService service = FireAlarmEquipmentService(); - int? id = await service.register(requestModel); + int? id = await equipmentDetailService.createFireAlarm(requestModel); if (id != null) { ScaffoldMessenger.of(context).showSnackBar( @@ -356,40 +369,8 @@ class _AddEquipmentScreenState extends State { void _registerEquipmentDetail() async { FireAlarmEquipmentService service = FireAlarmEquipmentService(); - String equipmentKey; - switch (widget.categoryNumber) { - case 1: - equipmentKey = 'ilumination_equipment'; - break; - case 2: - equipmentKey = 'electrical_load_equipment'; - break; - case 3: - equipmentKey = 'electrical_line_equipment'; - break; - case 4: - equipmentKey = 'electrical_circuit_equipment'; - break; - case 5: - equipmentKey = 'distribution_board_equipment'; - break; - case 6: - equipmentKey = 'structured_cabling_equipment'; - break; - case 7: - equipmentKey = 'atmospheric_discharge_equipment'; - break; - case 8: - equipmentKey = 'fire_alarm_equipment'; - break; - case 9: - equipmentKey = 'refrigeration_equipment'; - break; - default: - equipmentKey = 'unknown'; - } - Map equipmentDetail = { + Map fireAlarmEquipment = { 'area': widget.areaId, 'system': widget.categoryNumber, }; @@ -401,11 +382,11 @@ class _AddEquipmentScreenState extends State { 'description': imageData.description, }; }).toList(), - equipmentKey: equipmentDetail, + 'fire_alarm_equipment': fireAlarmEquipment, 'equipmentType': _selectedTypeId, }; - bool success = await service.registerEquipmentDetail(requestPayload); + bool success = await equipmentDetailService.createFireAlarm(requestPayload); if (success) { ScaffoldMessenger.of(context).showSnackBar( From 5a71d8767d700549682d87b3e26a6b56435116f3 Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 7 Jun 2024 14:09:06 -0300 Subject: [PATCH 201/351] frontend: conecta frontend com backend --- ..._alarm_equipment_detail_request_model.dart | 6 +- .../data/fire-alarm/fire_alarm_service.dart | 24 +------ .../personal_equipment_type_service.dart | 31 ++++++++++ .../feature/fire-alarm/addFireAlarm.dart | 62 ++++++++++--------- 4 files changed, 67 insertions(+), 56 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart index 1fbfdb30..79634700 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart @@ -2,19 +2,19 @@ import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; class FireAlarmEquipmentDetailRequestModel { - int? equipmenteType; + int? equipmentType; FireAlarmRequestModel? fireAlarm; List? photos; FireAlarmEquipmentDetailRequestModel({ - required this.equipmenteType, + required this.equipmentType, required this.fireAlarm, required this.photos, }); Map toJson() { return { - 'equipmenteType': equipmenteType, + 'equipmentType': equipmentType, 'fire_alarm_equipment': fireAlarm?.toJson(), 'photos': photos?.map((photo) => photo.toJson()).toList(), }; diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart index fcaeb969..a5a118bd 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart @@ -17,7 +17,7 @@ class FireAlarmEquipmentService { ...equipmentTypeList, ...personalEquipmentList, ]; - + try{ print('Combined list length: ${combinedList.length}'); return combinedList; } catch (e) { @@ -26,26 +26,4 @@ class FireAlarmEquipmentService { } } - Future registerEquipmentDetail( - Map requestPayload) async { - var url = Uri.parse('${baseUrl}equipment-details/'); - - try { - var response = await client.post( - url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(requestPayload), - ); - - if (response.statusCode == 201) { - return true; - } else { - print('Failed to register equipment detail: ${response.statusCode}'); - return false; - } - } catch (e) { - print('Error during register equipment detail: $e'); - return false; - } - } } diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart index c2539e00..ffd2fa7a 100644 --- a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart +++ b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart @@ -3,6 +3,7 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; +import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart'; import 'package:sige_ie/main.dart'; class PersonalEquipmentTypeService { @@ -38,6 +39,36 @@ class PersonalEquipmentTypeService { } } + Future createPersonalEquipmentType( + PersonalEquipmentTypeRequestModel + personalEquipmentTypeRequestModel) async { + var url = Uri.parse('${baseUrl}personal-equipment-types/'); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(personalEquipmentTypeRequestModel.toJson()), + ); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 201) { + Map responseData = jsonDecode(response.body); + print('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + print( + 'Failed to register fire alarm equipment: ${response.statusCode}'); + return -1; + } + } catch (e) { + print('Error during register: $e'); + return -1; + } + } + Future deletePersonalEquipmentType(int id) async { var url = Uri.parse('${baseUrl}personal-equipment-types/$id/'); diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index 6b9a7c82..f9249914 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -8,9 +8,13 @@ import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; import 'package:sige_ie/equipments/data/equipment-type/equipment_type_service.dart'; import 'package:sige_ie/equipments/data/equipment_detail_service.dart'; +import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_service.dart'; import 'package:sige_ie/equipments/data/mix-equipment-type/mix-equipment-type-service.dart'; +import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart'; import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_service.dart'; +import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; class ImageData { File imageFile; @@ -192,7 +196,7 @@ class _AddEquipmentScreenState extends State { setState(() { _newEquipmentTypeName = typeController.text; }); - _registerFireAlarmEquipment(); + _registerPersonalEquipmentType(); Navigator.of(context).pop(); } }, @@ -332,21 +336,19 @@ class _AddEquipmentScreenState extends State { ); } - void _registerFireAlarmEquipment() async { + void _registerPersonalEquipmentType() async { int systemId = widget.categoryNumber; - FireAlarmEquipmentRequestModel requestModel = - FireAlarmEquipmentRequestModel( - system: systemId, - name: _newEquipmentTypeName ?? '', - ); + PersonalEquipmentTypeRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentTypeRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); - int? id = await equipmentDetailService.createFireAlarm(requestModel); + int id = await personalEquipmentTypeService.createPersonalEquipmentType(personalEquipmentTypeRequestModel); - if (id != null) { + if (id != -1) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( + const SnackBar( content: Text( - 'Equipamento de alarme de incêndio registrado com sucesso. ID: $id'), + 'Equipamento de alarme de incêndio registrado com sucesso.'), backgroundColor: Colors.green, ), ); @@ -368,25 +370,25 @@ class _AddEquipmentScreenState extends State { } void _registerEquipmentDetail() async { - FireAlarmEquipmentService service = FireAlarmEquipmentService(); - - Map fireAlarmEquipment = { - 'area': widget.areaId, - 'system': widget.categoryNumber, - }; - - Map requestPayload = { - 'photos': _images.map((imageData) { - return { - 'file': base64Encode(imageData.imageFile.readAsBytesSync()), - 'description': imageData.description, - }; - }).toList(), - 'fire_alarm_equipment': fireAlarmEquipment, - 'equipmentType': _selectedTypeId, - }; - - bool success = await equipmentDetailService.createFireAlarm(requestPayload); + + List photos = _images.map((imageData) { + return PhotoRequestModel( + photo: base64Encode(imageData.imageFile.readAsBytesSync()), + description: imageData.description); + }).toList(); + + final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( + area: _selectedTypeId, system: widget.categoryNumber); + + final FireAlarmEquipmentDetailRequestModel fireAlarmEquipmentDetail = + FireAlarmEquipmentDetailRequestModel( + equipmentType: _selectedTypeId, + fireAlarm: fireAlarmModel, + photos: photos, + ); + + bool success = + await equipmentDetailService.createFireAlarm(fireAlarmEquipmentDetail); if (success) { ScaffoldMessenger.of(context).showSnackBar( From 68a1b611740f035575e996387ecec95a3c75478a Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 10:14:51 -0300 Subject: [PATCH 202/351] Conserto de bug de duplicidade ao listar os equipamentos pessoais --- .../data/fire-alarm/fire_alarm_service.dart | 16 ++-- .../feature/fire-alarm/addFireAlarm.dart | 88 +++++++++++-------- 2 files changed, 57 insertions(+), 47 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart index a5a118bd..362b8534 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; @@ -11,13 +10,13 @@ class FireAlarmEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment(int systemId, equipmentTypeList, personalEquipmentList) async { - - List combinedList = [ - ...equipmentTypeList, - ...personalEquipmentList, - ]; - try{ + Future> getAllEquipment( + int systemId, equipmentTypeList, personalEquipmentList) async { + List combinedList = [ + ...equipmentTypeList, + ...personalEquipmentList, + ]; + try { print('Combined list length: ${combinedList.length}'); return combinedList; } catch (e) { @@ -25,5 +24,4 @@ class FireAlarmEquipmentService { return []; } } - } diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index f9249914..6a2bc71c 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -10,7 +10,6 @@ import 'package:sige_ie/equipments/data/equipment-type/equipment_type_service.da import 'package:sige_ie/equipments/data/equipment_detail_service.dart'; import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart'; import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_service.dart'; import 'package:sige_ie/equipments/data/mix-equipment-type/mix-equipment-type-service.dart'; import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart'; import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_service.dart'; @@ -196,7 +195,13 @@ class _AddEquipmentScreenState extends State { setState(() { _newEquipmentTypeName = typeController.text; }); - _registerPersonalEquipmentType(); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectedType = null; // Clear the selected type + _selectedTypeId = null; // Clear the selected type ID + _fetchEquipmentTypes(); // Refresh the list of equipment types + }); + }); Navigator.of(context).pop(); } }, @@ -207,6 +212,41 @@ class _AddEquipmentScreenState extends State { ); } + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentTypeRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentTypeRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentTypeService + .createPersonalEquipmentType(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Equipamento de alarme de incêndio registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes.add(_newEquipmentTypeName!); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentTypes(); // Refresh the list of equipment types + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Falhaao registrar o equipamento de alarme de incêndio.'), + backgroundColor: Colors.red, + ), + ); + } + } + void _deleteEquipmentType() async { if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( @@ -243,6 +283,7 @@ class _AddEquipmentScreenState extends State { backgroundColor: Colors.green, ), ); + _fetchEquipmentTypes(); // Refresh the list of equipment types after deletion } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -336,41 +377,7 @@ class _AddEquipmentScreenState extends State { ); } - void _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; - PersonalEquipmentTypeRequestModel personalEquipmentTypeRequestModel = - PersonalEquipmentTypeRequestModel( - name: _newEquipmentTypeName ?? '', system: systemId); - - int id = await personalEquipmentTypeService.createPersonalEquipmentType(personalEquipmentTypeRequestModel); - - if (id != -1) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Equipamento de alarme de incêndio registrado com sucesso.'), - backgroundColor: Colors.green, - ), - ); - - setState(() { - personalEquipmentTypes.add(_newEquipmentTypeName!); - personalEquipmentMap[_newEquipmentTypeName!] = id; - _newEquipmentTypeName = null; - }); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: - Text('Falha ao registrar o equipamento de alarme de incêndio.'), - backgroundColor: Colors.red, - ), - ); - } - } - void _registerEquipmentDetail() async { - List photos = _images.map((imageData) { return PhotoRequestModel( photo: base64Encode(imageData.imageFile.readAsBytesSync()), @@ -420,7 +427,12 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { - List combinedTypes = equipmentTypes + personalEquipmentTypes; + // Combine equipment types into a set to remove duplicates + Set combinedTypesSet = { + ...equipmentTypes, + ...personalEquipmentTypes + }; + List combinedTypes = combinedTypesSet.toList(); return Scaffold( appBar: AppBar( @@ -697,7 +709,7 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first, style: TextStyle(color: Colors.grey)), + hint: Text(items.first, style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), From b71540f3079b3a6df0d9ac9570bbd833196f24c8 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 10:24:23 -0300 Subject: [PATCH 203/351] =?UTF-8?q?Adiciona=20confirma=C3=A7=C3=A3o=20ante?= =?UTF-8?q?s=20de=20excluir=20equipamento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire-alarm/addFireAlarm.dart | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index 6a2bc71c..d9cfc42b 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -240,7 +240,7 @@ class _AddEquipmentScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Falhaao registrar o equipamento de alarme de incêndio.'), + Text('Falha ao registrar o equipamento de alarme de incêndio.'), backgroundColor: Colors.red, ), ); @@ -269,29 +269,53 @@ class _AddEquipmentScreenState extends State { int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; - bool success = await personalEquipmentTypeService - .deletePersonalEquipmentType(equipmentId); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () async { + Navigator.of(context).pop(); + bool success = await personalEquipmentTypeService + .deletePersonalEquipmentType(equipmentId); - if (success) { - setState(() { - personalEquipmentTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Equipamento excluído com sucesso.'), - backgroundColor: Colors.green, - ), - ); - _fetchEquipmentTypes(); // Refresh the list of equipment types after deletion - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Falha ao excluir o equipamento.'), - backgroundColor: Colors.red, - ), - ); - } + if (success) { + setState(() { + personalEquipmentTypes.remove(_selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentTypes(); // Refresh the list of equipment types after deletion + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + }, + ), + ], + ); + }, + ); } void _showConfirmationDialog() { From 3a1ca148d07302b51c8e57c15b7fc17796760c01 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 10:55:51 -0300 Subject: [PATCH 204/351] =?UTF-8?q?L=C3=B3gica=20de=20listagem=20de=20equi?= =?UTF-8?q?pamentos=20por=20=C3=A1rea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/fire-alarm/fire_alarm_service.dart | 22 ++++- ...fireAlarmList.dart => listFireAlarms.dart} | 89 ++++++++++++------- .../feature/systemConfiguration.dart | 4 +- frontend/sige_ie/lib/main.dart | 9 +- 4 files changed, 83 insertions(+), 41 deletions(-) rename frontend/sige_ie/lib/equipments/feature/fire-alarm/{fireAlarmList.dart => listFireAlarms.dart} (53%) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart index 362b8534..c17762e2 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; @@ -9,9 +10,10 @@ class FireAlarmEquipmentService { http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, equipmentTypeList, personalEquipmentList) async { + int systemId, + List equipmentTypeList, + List personalEquipmentList) async { List combinedList = [ ...equipmentTypeList, ...personalEquipmentList, @@ -24,4 +26,20 @@ class FireAlarmEquipmentService { return []; } } + + Future> getEquipmentListByArea(int areaId) async { + final url = '${baseUrl}fire-alarms/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); + } else { + throw Exception('Failed to load equipment'); + } + } catch (e) { + print('Error during get equipment list: $e'); + return []; + } + } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart similarity index 53% rename from frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart rename to frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart index b948b489..b9d6bc03 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/fireAlarmList.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart @@ -1,15 +1,16 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_service.dart'; import 'package:sige_ie/equipments/feature/fire-alarm/addFireAlarm.dart'; -class listFireAlarms extends StatelessWidget { +class ListFireAlarms extends StatefulWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listFireAlarms({ + const ListFireAlarms({ super.key, required this.areaName, required this.categoryNumber, @@ -18,16 +19,40 @@ class listFireAlarms extends StatelessWidget { required this.areaId, }); + @override + _ListFireAlarmsState createState() => _ListFireAlarmsState(); +} + +class _ListFireAlarmsState extends State { + List equipmentList = []; + bool isLoading = true; + final FireAlarmEquipmentService _service = FireAlarmEquipmentService(); + + @override + void initState() { + super.initState(); + fetchEquipmentList(); + } + + Future fetchEquipmentList() async { + final List equipmentList = + await _service.getEquipmentListByArea(widget.areaId); + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddfireAlarm( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, - areaId: areaId, + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, ), ), ); @@ -35,10 +60,6 @@ class listFireAlarms extends StatelessWidget { @override Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - String systemTitle = 'ALARME DE INCÊNDIO'; return Scaffold( @@ -51,10 +72,10 @@ class listFireAlarms extends StatelessWidget { context, '/systemLocation', arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, }, ); }, @@ -72,7 +93,7 @@ class listFireAlarms extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', + child: Text('${widget.areaName} - $systemTitle', style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -85,23 +106,27 @@ class listFireAlarms extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), + isLoading + ? const Center( + child: CircularProgressIndicator(), ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), const SizedBox(height: 40), ], ), diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 8661a918..d109b52c 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -4,11 +4,11 @@ import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; -import 'package:sige_ie/equipments/feature/fire-alarm/fireAlarmList.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/listFireAlarms.dart'; class SystemConfiguration extends StatefulWidget { final String areaName; @@ -53,7 +53,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/fireAlarm': - return listFireAlarms( + return ListFireAlarms( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index c8b42950..38cf6855 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -8,7 +8,7 @@ import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; -import 'package:sige_ie/equipments/feature/fire-alarm/fireAlarmList.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/listFireAlarms.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; @@ -19,7 +19,6 @@ import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'core/feature/login/login.dart'; -// void main() { runApp(const MyApp()); @@ -265,7 +264,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listFireAlarms( + builder: (context) => ListFireAlarms( areaName: areaName, categoryNumber: categoryNumber, localName: localName, @@ -334,7 +333,7 @@ class MyApp extends StatelessWidget { )); } else { throw Exception( - 'Invalid arguments: One of areaName, localName, or localId is null in /listStruturedCabling.'); + 'Invalid arguments: One of areaName, localName, ou localId is null in /listStruturedCabling.'); } } throw Exception( @@ -363,7 +362,7 @@ class MyApp extends StatelessWidget { )); } else { throw Exception( - 'Invalid arguments: One of areaName, localName, or localId is null in /electricalLineList.'); + 'Invalid arguments: One of areaName, localName, ou localId is null in /electricalLineList.'); } } throw Exception( From 178f707f7762fe0c40a197e3741fdeced27c9cd2 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 11:25:23 -0300 Subject: [PATCH 205/351] =?UTF-8?q?Conserta=20fluxo=20de=20confirma=C3=A7?= =?UTF-8?q?=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire-alarm/addFireAlarm.dart | 71 ++++++++----------- .../feature/fire-alarm/listFireAlarms.dart | 20 ++++-- 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index d9cfc42b..263ee295 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -56,12 +56,11 @@ class _AddEquipmentScreenState extends State { String? _selectedType; String? _selectedTypeToDelete; String? _newEquipmentTypeName; - int? _selectedTypeId; // ID of the selected type + int? _selectedTypeId; List equipmentTypes = []; List personalEquipmentTypes = []; - Map personalEquipmentMap = - {}; // Map to store equipment name and ID + Map personalEquipmentMap = {}; @override void initState() { @@ -128,7 +127,7 @@ class _AddEquipmentScreenState extends State { TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -142,25 +141,23 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -197,9 +194,9 @@ class _AddEquipmentScreenState extends State { }); _registerPersonalEquipmentType().then((_) { setState(() { - _selectedType = null; // Clear the selected type - _selectedTypeId = null; // Clear the selected type ID - _fetchEquipmentTypes(); // Refresh the list of equipment types + _selectedType = null; + _selectedTypeId = null; + _fetchEquipmentTypes(); }); }); Navigator.of(context).pop(); @@ -234,7 +231,7 @@ class _AddEquipmentScreenState extends State { personalEquipmentTypes.add(_newEquipmentTypeName!); personalEquipmentMap[_newEquipmentTypeName!] = id; _newEquipmentTypeName = null; - _fetchEquipmentTypes(); // Refresh the list of equipment types + _fetchEquipmentTypes(); }); } else { ScaffoldMessenger.of(context).showSnackBar( @@ -294,7 +291,7 @@ class _AddEquipmentScreenState extends State { setState(() { personalEquipmentTypes.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; - _fetchEquipmentTypes(); // Refresh the list of equipment types after deletion + _fetchEquipmentTypes(); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -380,19 +377,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.pushReplacementNamed( - context, - '/listFireAlarms', - arguments: { - 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, - 'localName': widget.localName, - 'localId': widget.localId, - 'areaId': widget.areaId, - }, - ); + _registerEquipmentDetail(); }, ), ], @@ -405,7 +392,8 @@ class _AddEquipmentScreenState extends State { List photos = _images.map((imageData) { return PhotoRequestModel( photo: base64Encode(imageData.imageFile.readAsBytesSync()), - description: imageData.description); + description: + imageData.description.isNotEmpty ? imageData.description : ''); }).toList(); final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( @@ -451,7 +439,6 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { - // Combine equipment types into a set to remove duplicates Set combinedTypesSet = { ...equipmentTypes, ...personalEquipmentTypes @@ -643,7 +630,7 @@ class _AddEquipmentScreenState extends State { MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ))), - onPressed: _registerEquipmentDetail, + onPressed: _showConfirmationDialog, child: const Text( 'ADICIONAR EQUIPAMENTO', style: TextStyle( diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart index b9d6bc03..fee21ae6 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart @@ -35,12 +35,20 @@ class _ListFireAlarmsState extends State { } Future fetchEquipmentList() async { - final List equipmentList = - await _service.getEquipmentListByArea(widget.areaId); - setState(() { - this.equipmentList = equipmentList; - isLoading = false; - }); + try { + final List equipmentList = + await _service.getEquipmentListByArea(widget.areaId); + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + print('DEU BOM'); + } catch (e) { + print('Error fetching equipment list: $e'); + setState(() { + isLoading = false; + }); + } } void navigateToAddEquipment(BuildContext context) { From b0caafffabf4c4d2f9b0a7d60f278f3a883c3a9c Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 14:28:00 -0300 Subject: [PATCH 206/351] Refatorando cabeamento estruturado --- .../feature/fire-alarm/listFireAlarms.dart | 1 - .../addStruturedCabling.dart | 127 +++++++----------- .../struturedCablingEquipmentList.dart | 21 +-- 3 files changed, 59 insertions(+), 90 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart index fee21ae6..358219dd 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart @@ -42,7 +42,6 @@ class _ListFireAlarmsState extends State { this.equipmentList = equipmentList; isLoading = false; }); - print('DEU BOM'); } catch (e) { print('Error fetching equipment list: $e'); setState(() { diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart b/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart index a4a9372a..671d96b8 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart @@ -41,16 +41,16 @@ class _AddEquipmentScreenState extends State { String? _selectedTypeToDelete; String? _selectedstruturedType; - List equipmentTypes = [ - 'Selecione o tipo de cabeamento estruturado', - ]; - List struturedType = [ 'Selecione o tipo de cabeamento estruturado', 'Eletroduto', 'Eletrocalha', ]; + List equipmentTypes = [ + 'Selecione o tipo de cabeamento estruturado', + ]; + @override void dispose() { _equipmentchargeController.dispose(); @@ -87,7 +87,7 @@ class _AddEquipmentScreenState extends State { TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -101,25 +101,23 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -339,76 +337,43 @@ class _AddEquipmentScreenState extends State { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - _buildStyledDropdown( - items: struturedType, - value: _selectedstruturedType, - onChanged: (newValue) { - setState(() { - _selectedstruturedType = newValue; - if (newValue == struturedType[0]) { - _selectedstruturedType = null; - } - if (_selectedstruturedType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectedstruturedType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de cabeamento', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), Row( children: [ Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, + items: struturedType, + value: _selectedstruturedType, onChanged: (newValue) { setState(() { - _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { + _selectedstruturedType = newValue; + if (newValue == struturedType[0]) { _selectedstruturedType = null; } + if (_selectedstruturedType != null) { + _selectedType = null; + } }); }, - enabled: _selectedstruturedType == null, + enabled: _selectedType == null, ), ), - Expanded( - flex: 0, - child: Row( - children: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); - }, - ), - ], - ), + Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], ), ], ), @@ -416,7 +381,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedType = null; + _selectedstruturedType = null; }); }, child: const Text('Limpar seleção'), diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart index 87e120af..0dd48fa1 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart @@ -71,11 +71,15 @@ class listStruturedCabling extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Text( + '$areaName - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), ), ), const SizedBox(height: 20), @@ -96,9 +100,10 @@ class listStruturedCabling extends StatelessWidget { child: Text( 'Você ainda não tem equipamentos', style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), ), ), const SizedBox(height: 40), From e0e9f9c7b437ec4065076af74434cdf9ba380e27 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 14:59:46 -0300 Subject: [PATCH 207/351] =?UTF-8?q?Refatorando=20cargas=20el=C3=A9tricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../electrical-load/addelectricalLoad.dart | 121 +++++++----------- .../electrical-load/eletricalLoadList.dart | 1 + .../feature/fire-alarm/listFireAlarms.dart | 1 + .../addStruturedCabling.dart | 4 +- 4 files changed, 49 insertions(+), 78 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart index 24dc5790..4cbbf9e2 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart @@ -92,7 +92,7 @@ class _AddEquipmentScreenState extends State { TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -106,25 +106,23 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -157,6 +155,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { + loadTypes.add(typeController.text); equipmentTypes.add(typeController.text); }); Navigator.of(context).pop(); @@ -199,6 +198,7 @@ class _AddEquipmentScreenState extends State { child: const Text('Excluir'), onPressed: () { setState(() { + loadTypes.remove(_selectedTypeToDelete); equipmentTypes.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; }); @@ -354,76 +354,43 @@ class _AddEquipmentScreenState extends State { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - _buildStyledDropdown( - items: loadTypes, - value: _selectedLoadType, - onChanged: (newValue) { - setState(() { - _selectedLoadType = newValue; - if (newValue == loadTypes[0]) { - _selectedLoadType = null; - } - if (_selectedLoadType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectedLoadType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de Cargas', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), Row( children: [ Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, + items: loadTypes, + value: _selectedLoadType, onChanged: (newValue) { setState(() { - _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { + _selectedLoadType = newValue; + if (newValue == loadTypes[0]) { _selectedLoadType = null; } + if (_selectedLoadType != null) { + _selectedType = null; + } }); }, - enabled: _selectedLoadType == null, + enabled: _selectedType == null, ), ), - Expanded( - flex: 0, - child: Row( - children: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); - }, - ), - ], - ), + Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], ), ], ), @@ -431,7 +398,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedType = null; + _selectedLoadType = null; }); }, child: const Text('Limpar seleção'), diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart b/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart index 7e34e5ea..45699979 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart @@ -71,6 +71,7 @@ class listelectricalLoadEquipment extends StatelessWidget { ), child: Center( child: Text('$areaName - $systemTitle', + textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart index 358219dd..0afd1fff 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart @@ -101,6 +101,7 @@ class _ListFireAlarmsState extends State { ), child: Center( child: Text('${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart b/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart index 671d96b8..067ce9bd 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart @@ -151,6 +151,7 @@ class _AddEquipmentScreenState extends State { if (typeController.text.isNotEmpty) { setState(() { equipmentTypes.add(typeController.text); + struturedType.add(typeController.text); // Adicione aqui }); Navigator.of(context).pop(); } @@ -193,6 +194,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { setState(() { equipmentTypes.remove(_selectedTypeToDelete); + struturedType.remove(_selectedTypeToDelete); // Adicione aqui _selectedTypeToDelete = null; }); Navigator.of(context).pop(); @@ -518,7 +520,7 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: equipmentTypes + items: struturedType // Use struturedType aqui .where((value) => value != 'Selecione um equipamento') .map>((String value) { return DropdownMenuItem( From 494aec3367200d169c25de031208ccabdab713e4 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 15:24:03 -0300 Subject: [PATCH 208/351] =?UTF-8?q?Refatorando=20circuitos=20el=C3=A9trico?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../addElectricalCircuit.dart | 150 +++++++----------- .../electricalCircuitList.dart | 1 + 2 files changed, 55 insertions(+), 96 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart index 7f0f06cd..14d50482 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart @@ -41,14 +41,9 @@ class _AddEquipmentScreenState final _breakerStateController = TextEditingController(); final _wireTypeController = TextEditingController(); final _dimensionController = TextEditingController(); - String? _selectedType; String? _selectedTypeToDelete; String? _selectCircuitType; - List equipmentTypes = [ - 'Selecione um tipo de Circuito Elétrico', - ]; - List cicuitType = [ 'Selecione o tipo de Circuito Elétrico', 'Disjuntor(Local e Estado)', @@ -95,7 +90,7 @@ class _AddEquipmentScreenState TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -109,25 +104,23 @@ class _AddEquipmentScreenState TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -142,11 +135,11 @@ class _AddEquipmentScreenState context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Adicionar novo tipo de equipamento'), + title: const Text('Adicionar novo tipo de Circuito Elétrico'), content: TextField( controller: typeController, decoration: const InputDecoration( - hintText: 'Digite o novo tipo de equipamento'), + hintText: 'Digite o novo tipo de Circuito Elétrico'), ), actions: [ TextButton( @@ -160,7 +153,7 @@ class _AddEquipmentScreenState onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - equipmentTypes.add(typeController.text); + cicuitType.add(typeController.text); }); Navigator.of(context).pop(); } @@ -177,8 +170,8 @@ class _AddEquipmentScreenState _selectedTypeToDelete == 'Selecione um equipamento') { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: - Text('Selecione um tipo de equipamento válido para excluir.'), + content: Text( + 'Selecione um tipo de Circuito Elétrico válido para excluir.'), ), ); return; @@ -190,7 +183,7 @@ class _AddEquipmentScreenState return AlertDialog( title: const Text('Confirmar exclusão'), content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + 'Tem certeza de que deseja excluir o tipo de Circuito Elétrico "$_selectedTypeToDelete"?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -202,7 +195,7 @@ class _AddEquipmentScreenState child: const Text('Excluir'), onPressed: () { setState(() { - equipmentTypes.remove(_selectedTypeToDelete); + cicuitType.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; }); Navigator.of(context).pop(); @@ -216,7 +209,7 @@ class _AddEquipmentScreenState void _showConfirmationDialog() { if (_equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectCircuitType == null)) { + _selectCircuitType == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -235,7 +228,7 @@ class _AddEquipmentScreenState children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectCircuitType ?? ''), + Text(_selectCircuitType ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -355,37 +348,7 @@ class _AddEquipmentScreenState child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de Lâmpada', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildStyledDropdown( - items: cicuitType, - value: _selectCircuitType, - onChanged: (newValue) { - setState(() { - _selectCircuitType = newValue; - if (newValue == cicuitType[0]) { - _selectCircuitType = null; - } - if (_selectCircuitType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectCircuitType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de lâmpadas', + const Text('Tipos de Circuito Elétrico', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -394,41 +357,35 @@ class _AddEquipmentScreenState Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, + items: cicuitType, + value: _selectCircuitType, onChanged: (newValue) { setState(() { - _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { + _selectCircuitType = newValue; + if (newValue == cicuitType[0]) { _selectCircuitType = null; } }); }, - enabled: _selectCircuitType == null, + enabled: true, ), ), - Expanded( - flex: 0, - child: Row( - children: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); - }, - ), - ], - ), + Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + }, + ), + ], ), ], ), @@ -436,7 +393,7 @@ class _AddEquipmentScreenState TextButton( onPressed: () { setState(() { - _selectedType = null; + _selectCircuitType = null; }); }, child: const Text('Limpar seleção'), @@ -617,12 +574,12 @@ class _AddEquipmentScreenState context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de equipamento'), + title: const Text('Excluir tipo de Circuito Elétrico'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Selecione um equipamento para excluir:', + 'Selecione um tipo de Circuito Elétrico para excluir:', textAlign: TextAlign.center, ), DropdownButton( @@ -633,8 +590,9 @@ class _AddEquipmentScreenState _selectedTypeToDelete = newValue; }); }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') + items: cicuitType + .where((value) => + value != 'Selecione um tipo de Circuito Elétrico') .map>((String value) { return DropdownMenuItem( value: value, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart index 490b2c17..0f00893a 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart @@ -72,6 +72,7 @@ class listCicuitEquipment extends StatelessWidget { ), child: Center( child: Text('$areaName - $systemTitle', + textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, From 9eb12535110a72eb62696400e82de1c63cf15e27 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 16:13:16 -0300 Subject: [PATCH 209/351] =?UTF-8?q?Refatorando=20descargas=20atmosf=C3=A9r?= =?UTF-8?q?icas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../addAtmospheric-dischargesEquipment.dart | 55 +++++++++---------- .../atmospheric-dischargesList.dart | 1 + 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart index fd18d186..580aea79 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart @@ -44,10 +44,6 @@ class _AddEquipmentScreenState extends State { String? _selectedTypeToDelete; String? _selectDischargeType; - List equipmentTypes = [ - 'Selecione um tipo de Descarga atmosféfica', - ]; - List dischargeType = [ 'Selecione o tipo de Descarga Atmosféfica', 'Para Raios', @@ -55,6 +51,8 @@ class _AddEquipmentScreenState extends State { 'Subsistemas', ]; + List additionalTypes = []; + @override void dispose() { _equipmentQuantityController.dispose(); @@ -90,7 +88,7 @@ class _AddEquipmentScreenState extends State { TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -104,25 +102,23 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -155,7 +151,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - equipmentTypes.add(typeController.text); + additionalTypes.add(typeController.text); }); Navigator.of(context).pop(); } @@ -197,7 +193,7 @@ class _AddEquipmentScreenState extends State { child: const Text('Excluir'), onPressed: () { setState(() { - equipmentTypes.remove(_selectedTypeToDelete); + additionalTypes.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; }); Navigator.of(context).pop(); @@ -318,7 +314,7 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { - List combinedTypes = dischargeType + equipmentTypes; + List combinedTypes = dischargeType + additionalTypes; return Scaffold( appBar: AppBar( @@ -375,8 +371,7 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: ['Selecione o tipo de descarga atmosférica'] + - combinedTypes, + items: combinedTypes, value: _selectDischargeType ?? _selectedType, onChanged: (newValue) { if (newValue != @@ -406,7 +401,7 @@ class _AddEquipmentScreenState extends State { IconButton( icon: const Icon(Icons.delete), onPressed: () { - if (equipmentTypes.isEmpty) { + if (additionalTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( @@ -550,7 +545,7 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: equipmentTypes + items: additionalTypes .map>((String value) { return DropdownMenuItem( value: value, diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart index f379d748..54d04d82 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart @@ -74,6 +74,7 @@ class listatmosphericEquipment extends StatelessWidget { ), child: Center( child: Text('$areaName - $systemTitle', + textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, From 5c7a86cba4fb62abb71db90bcd49acdfd8515c1e Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 16:19:40 -0300 Subject: [PATCH 210/351] =?UTF-8?q?Refatorando=20ilumina=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IluminationEquipmentList.dart | 1 + .../iluminations/addIluminationEquipment.dart | 184 ++++++++---------- 2 files changed, 84 insertions(+), 101 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart index 1cfd8b2a..4e49d48e 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart @@ -72,6 +72,7 @@ class listIluminationEquipment extends StatelessWidget { ), child: Center( child: Text('$areaName - $systemTitle', + textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart index 0ca48426..dcd289aa 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart @@ -42,10 +42,6 @@ class _AddEquipmentScreenState extends State { String? _selectedTypeToDelete; String? _selectedLampType; - List equipmentTypes = [ - 'Selecione um tipo de Lâmpada', - ]; - List lampTypes = [ 'Selecione o tipo de lâmpada', 'Halogenia', @@ -55,6 +51,8 @@ class _AddEquipmentScreenState extends State { 'Lâmpadas Queimadas', ]; + List additionalTypes = []; + @override void dispose() { _equipmentchargeController.dispose(); @@ -91,7 +89,7 @@ class _AddEquipmentScreenState extends State { TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -105,25 +103,23 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -132,17 +128,17 @@ class _AddEquipmentScreenState extends State { ); } - void _addNewEquipmentType() { + void _addNewLampType() { TextEditingController typeController = TextEditingController(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Adicionar novo tipo de equipamento'), + title: const Text('Adicionar novo tipo de lâmpada'), content: TextField( controller: typeController, decoration: const InputDecoration( - hintText: 'Digite o novo tipo de equipamento'), + hintText: 'Digite o novo tipo de lâmpada'), ), actions: [ TextButton( @@ -156,7 +152,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - equipmentTypes.add(typeController.text); + additionalTypes.add(typeController.text); }); Navigator.of(context).pop(); } @@ -168,13 +164,12 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteEquipmentType() { + void _deleteLampType() { if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + _selectedTypeToDelete == 'Selecione um tipo de lâmpada') { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: - Text('Selecione um tipo de equipamento válido para excluir.'), + content: Text('Selecione um tipo de lâmpada válido para excluir.'), ), ); return; @@ -186,7 +181,7 @@ class _AddEquipmentScreenState extends State { return AlertDialog( title: const Text('Confirmar exclusão'), content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + 'Tem certeza de que deseja excluir o tipo de lâmpada "$_selectedTypeToDelete"?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -198,7 +193,7 @@ class _AddEquipmentScreenState extends State { child: const Text('Excluir'), onPressed: () { setState(() { - equipmentTypes.remove(_selectedTypeToDelete); + additionalTypes.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; }); Navigator.of(context).pop(); @@ -309,6 +304,8 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { + List combinedTypes = lampTypes + additionalTypes; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -349,55 +346,27 @@ class _AddEquipmentScreenState extends State { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - _buildStyledDropdown( - items: lampTypes, - value: _selectedLampType, - onChanged: (newValue) { - setState(() { - _selectedLampType = newValue; - if (newValue == lampTypes[0]) { - _selectedLampType = null; - } - if (_selectedLampType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectedLampType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de lâmpadas', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), Row( children: [ Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, + items: combinedTypes, + value: _selectedLampType ?? _selectedType, onChanged: (newValue) { - setState(() { - _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { - _selectedLampType = null; - } - }); + if (newValue != 'Selecione o tipo de lâmpada') { + setState(() { + if (lampTypes.contains(newValue)) { + _selectedLampType = newValue; + _selectedType = null; + } else { + _selectedType = newValue; + _selectedLampType = null; + } + }); + } }, - enabled: _selectedLampType == null, + enabled: true, ), ), Expanded( @@ -406,15 +375,24 @@ class _AddEquipmentScreenState extends State { children: [ IconButton( icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, + onPressed: _addNewLampType, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); + if (additionalTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Nenhum tipo de lâmpada adicionado para excluir.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } }, ), ], @@ -426,6 +404,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { + _selectedLampType = null; _selectedType = null; }); }, @@ -567,33 +546,36 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de equipamento'), + title: const Text('Excluir tipo de lâmpada'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Selecione um equipamento para excluir:', + 'Selecione um tipo de lâmpada para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: additionalTypes + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -609,7 +591,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (_selectedTypeToDelete != null) { Navigator.of(context).pop(); - _deleteEquipmentType(); + _deleteLampType(); } }, ), From 402b0528be2dcc12585404582cf3c87970c78e98 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 16:27:00 -0300 Subject: [PATCH 211/351] =?UTF-8?q?Refatorando=20linha=20el=C3=A9trica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../electrical-line/addElectricalLine.dart | 174 ++++++++---------- .../electrical-line/electricaLLineLIst.dart | 1 + 2 files changed, 74 insertions(+), 101 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart b/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart index 90455f59..c4ac9938 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart @@ -38,11 +38,6 @@ class _AddEquipmentScreenState extends State { final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; - String? _selectElectricalType; - - List equipmentTypes = [ - 'Selecione um tipo de Linha Elétrica', - ]; List ElectricalType = [ 'Selecione o tipo de Linha Elétrica', @@ -51,6 +46,8 @@ class _AddEquipmentScreenState extends State { 'Interuptor', ]; + List additionalTypes = []; + @override void dispose() { _equipmentQuantityController.dispose(); @@ -86,7 +83,7 @@ class _AddEquipmentScreenState extends State { TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -100,25 +97,23 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -127,17 +122,17 @@ class _AddEquipmentScreenState extends State { ); } - void _addNewEquipmentType() { + void _addNewElectricalType() { TextEditingController typeController = TextEditingController(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Adicionar novo tipo de equipamento'), + title: const Text('Adicionar novo tipo de linha elétrica'), content: TextField( controller: typeController, decoration: const InputDecoration( - hintText: 'Digite o novo tipo de equipamento'), + hintText: 'Digite o novo tipo de linha elétrica'), ), actions: [ TextButton( @@ -151,7 +146,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - equipmentTypes.add(typeController.text); + additionalTypes.add(typeController.text); }); Navigator.of(context).pop(); } @@ -163,13 +158,13 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteEquipmentType() { + void _deleteElectricalType() { if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + _selectedTypeToDelete == 'Selecione um tipo de linha elétrica') { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Selecione um tipo de equipamento válido para excluir.'), + Text('Selecione um tipo de linha elétrica válido para excluir.'), ), ); return; @@ -181,7 +176,7 @@ class _AddEquipmentScreenState extends State { return AlertDialog( title: const Text('Confirmar exclusão'), content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + 'Tem certeza de que deseja excluir o tipo de linha elétrica "$_selectedTypeToDelete"?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -193,7 +188,7 @@ class _AddEquipmentScreenState extends State { child: const Text('Excluir'), onPressed: () { setState(() { - equipmentTypes.remove(_selectedTypeToDelete); + additionalTypes.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; }); Navigator.of(context).pop(); @@ -206,8 +201,7 @@ class _AddEquipmentScreenState extends State { } void _showConfirmationDialog() { - if (_equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectElectricalType == null)) { + if (_equipmentQuantityController.text.isEmpty || _selectedType == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -226,7 +220,7 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectElectricalType ?? ''), + Text(_selectedType ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -293,6 +287,8 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { + List combinedTypes = ElectricalType + additionalTypes; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -329,37 +325,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de descarga atmosférica', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildStyledDropdown( - items: ElectricalType, - value: _selectElectricalType, - onChanged: (newValue) { - setState(() { - _selectElectricalType = newValue; - if (newValue == ElectricalType[0]) { - _selectElectricalType = null; - } - if (_selectElectricalType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectElectricalType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de descargas atmosféricas', + const Text('Tipos de linha elétrica', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -368,20 +334,14 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, + items: combinedTypes, value: _selectedType, onChanged: (newValue) { setState(() { _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { - _selectElectricalType = null; - } }); }, - enabled: _selectElectricalType == null, + enabled: true, ), ), Expanded( @@ -390,15 +350,24 @@ class _AddEquipmentScreenState extends State { children: [ IconButton( icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, + onPressed: _addNewElectricalType, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); + if (additionalTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Nenhum tipo de linha elétrica adicionado para excluir.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } }, ), ], @@ -511,33 +480,36 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de equipamento'), + title: const Text('Excluir tipo de linha elétrica'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Selecione um equipamento para excluir:', + 'Selecione um tipo de linha elétrica para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: additionalTypes + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -553,7 +525,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (_selectedTypeToDelete != null) { Navigator.of(context).pop(); - _deleteEquipmentType(); + _deleteElectricalType(); } }, ), diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart b/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart index 1cbd30db..284990c0 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart @@ -72,6 +72,7 @@ class listElectricalLineEquipment extends StatelessWidget { ), child: Center( child: Text('$areaName - $systemTitle', + textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, From 43680470b14c94c78a42133bc7be4f193fbb1f5b Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 16:34:49 -0300 Subject: [PATCH 212/351] =?UTF-8?q?Refatorando=20quadro=20de=20distribui?= =?UTF-8?q?=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../addDistribuitionBoard.dart | 186 ++++++++---------- .../distribuitionBoardEquipmentList.dart | 1 + 2 files changed, 85 insertions(+), 102 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart index c91459cb..129fed2e 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart @@ -42,10 +42,6 @@ class _AddEquipmentScreenState extends State { String? _selectedTypeToDelete; String? _selectedBoardType; - List equipmentTypes = [ - 'Selecione um tipo de quadro', - ]; - List boardType = [ 'Selecione o tipo de quadro', 'quadro 1', @@ -53,6 +49,8 @@ class _AddEquipmentScreenState extends State { 'quadro 3', ]; + List additionalTypes = []; + @override void dispose() { _equipmentchargeController.dispose(); @@ -89,7 +87,7 @@ class _AddEquipmentScreenState extends State { TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -103,25 +101,23 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -130,17 +126,17 @@ class _AddEquipmentScreenState extends State { ); } - void _addNewEquipmentType() { + void _addNewBoardType() { TextEditingController typeController = TextEditingController(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Adicionar novo tipo de equipamento'), + title: const Text('Adicionar novo tipo de quadro'), content: TextField( controller: typeController, - decoration: const InputDecoration( - hintText: 'Digite o novo tipo de equipamento'), + decoration: + const InputDecoration(hintText: 'Digite o novo tipo de quadro'), ), actions: [ TextButton( @@ -154,7 +150,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - equipmentTypes.add(typeController.text); + additionalTypes.add(typeController.text); }); Navigator.of(context).pop(); } @@ -166,13 +162,12 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteEquipmentType() { + void _deleteBoardType() { if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + _selectedTypeToDelete == 'Selecione um tipo de quadro') { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: - Text('Selecione um tipo de equipamento válido para excluir.'), + content: Text('Selecione um tipo de quadro válido para excluir.'), ), ); return; @@ -184,7 +179,7 @@ class _AddEquipmentScreenState extends State { return AlertDialog( title: const Text('Confirmar exclusão'), content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + 'Tem certeza de que deseja excluir o tipo de quadro "$_selectedTypeToDelete"?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -196,7 +191,7 @@ class _AddEquipmentScreenState extends State { child: const Text('Excluir'), onPressed: () { setState(() { - equipmentTypes.remove(_selectedTypeToDelete); + additionalTypes.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; }); Navigator.of(context).pop(); @@ -307,6 +302,8 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { + List combinedTypes = boardType + additionalTypes; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -347,55 +344,27 @@ class _AddEquipmentScreenState extends State { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), - _buildStyledDropdown( - items: boardType, - value: _selectedBoardType, - onChanged: (newValue) { - setState(() { - _selectedBoardType = newValue; - if (newValue == boardType[0]) { - _selectedBoardType = null; - } - if (_selectedBoardType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectedBoardType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de Quadros', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), Row( children: [ Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, + items: ['Selecione o tipo de quadro'] + combinedTypes, + value: _selectedBoardType ?? _selectedType, onChanged: (newValue) { - setState(() { - _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { - _selectedBoardType = null; - } - }); + if (newValue != 'Selecione o tipo de quadro') { + setState(() { + if (boardType.contains(newValue)) { + _selectedBoardType = newValue; + _selectedType = null; + } else { + _selectedType = newValue; + _selectedBoardType = null; + } + }); + } }, - enabled: _selectedBoardType == null, + enabled: true, ), ), Expanded( @@ -404,15 +373,24 @@ class _AddEquipmentScreenState extends State { children: [ IconButton( icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, + onPressed: _addNewBoardType, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); + if (additionalTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Nenhum tipo de quadro adicionado para excluir.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } }, ), ], @@ -424,6 +402,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { + _selectedBoardType = null; _selectedType = null; }); }, @@ -561,33 +540,36 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de equipamento'), + title: const Text('Excluir tipo de quadro'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Selecione um equipamento para excluir:', + 'Selecione um tipo de quadro para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: additionalTypes + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -603,7 +585,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (_selectedTypeToDelete != null) { Navigator.of(context).pop(); - _deleteEquipmentType(); + _deleteBoardType(); } }, ), diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart index 91d8e879..24a62c54 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart @@ -72,6 +72,7 @@ class listDistribuitionBoard extends StatelessWidget { ), child: Center( child: Text('$areaName - $systemTitle', + textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, From f28ee4b7b0ae08a40931b8dedce09ce1bdec2629 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 16:44:29 -0300 Subject: [PATCH 213/351] =?UTF-8?q?Refatorando=20refrigera=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/cooling/addCooling.dart | 195 ++++++++---------- .../feature/cooling/coolingEquipmentList.dart | 1 + 2 files changed, 91 insertions(+), 105 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart b/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart index db538d47..183621b1 100644 --- a/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart +++ b/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart @@ -40,19 +40,17 @@ class _AddEquipmentScreenState extends State { final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; - String? _selectedcollingType; + String? _selectedCoolingType; - List equipmentTypes = [ - 'Selecione o tipo de refrigeração', - ]; - - List collingType = [ + List coolingTypes = [ 'Selecione o tipo de refrigeração', 'Refrigeração1', 'Refrigeração2', 'Refrigeração3', ]; + List additionalCoolingTypes = []; + @override void dispose() { _equipmentQuantityController.dispose(); @@ -88,7 +86,7 @@ class _AddEquipmentScreenState extends State { TextField( controller: descriptionController, decoration: const InputDecoration( - hintText: 'Digite a descrição da imagem'), + hintText: 'Digite a descrição da imagem (opcional)'), ), ], ), @@ -102,25 +100,23 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Salvar'), onPressed: () { - if (descriptionController.text.isNotEmpty) { - setState(() { - if (existingImage != null) { - existingImage.description = descriptionController.text; - } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + setState(() { + if (existingImage != null) { + existingImage.description = descriptionController.text; + } else { + final imageData = ImageData( + imageFile, + descriptionController.text, + ); + final categoryNumber = widget.categoryNumber; + if (!categoryImagesMap.containsKey(categoryNumber)) { + categoryImagesMap[categoryNumber] = []; } - }); - Navigator.of(context).pop(); - } + categoryImagesMap[categoryNumber]!.add(imageData); + _images = categoryImagesMap[categoryNumber]!; + } + }); + Navigator.of(context).pop(); }, ), ], @@ -129,17 +125,17 @@ class _AddEquipmentScreenState extends State { ); } - void _addNewEquipmentType() { + void _addNewCoolingType() { TextEditingController typeController = TextEditingController(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Adicionar novo tipo de equipamento'), + title: const Text('Adicionar novo tipo de refrigeração'), content: TextField( controller: typeController, decoration: const InputDecoration( - hintText: 'Digite o novo tipo de equipamento'), + hintText: 'Digite o novo tipo de refrigeração'), ), actions: [ TextButton( @@ -153,7 +149,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - equipmentTypes.add(typeController.text); + additionalCoolingTypes.add(typeController.text); }); Navigator.of(context).pop(); } @@ -165,13 +161,13 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteEquipmentType() { + void _deleteCoolingType() { if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + _selectedTypeToDelete == 'Selecione um tipo de refrigeração') { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Selecione um tipo de equipamento válido para excluir.'), + Text('Selecione um tipo de refrigeração válido para excluir.'), ), ); return; @@ -183,7 +179,7 @@ class _AddEquipmentScreenState extends State { return AlertDialog( title: const Text('Confirmar exclusão'), content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + 'Tem certeza de que deseja excluir o tipo de refrigeração "$_selectedTypeToDelete"?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -195,7 +191,7 @@ class _AddEquipmentScreenState extends State { child: const Text('Excluir'), onPressed: () { setState(() { - equipmentTypes.remove(_selectedTypeToDelete); + additionalCoolingTypes.remove(_selectedTypeToDelete); _selectedTypeToDelete = null; }); Navigator.of(context).pop(); @@ -209,7 +205,7 @@ class _AddEquipmentScreenState extends State { void _showConfirmationDialog() { if (_equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectedcollingType == null)) { + (_selectedType == null && _selectedCoolingType == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -228,7 +224,7 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectedcollingType ?? ''), + Text(_selectedType ?? _selectedCoolingType ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -296,6 +292,8 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { + List combinedTypes = coolingTypes + additionalCoolingTypes; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -332,37 +330,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de refigeração', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildStyledDropdown( - items: collingType, - value: _selectedcollingType, - onChanged: (newValue) { - setState(() { - _selectedcollingType = newValue; - if (newValue == collingType[0]) { - _selectedcollingType = null; - } - if (_selectedcollingType != null) { - _selectedType = null; - } - }); - }, - enabled: _selectedType == null, - ), - const SizedBox(height: 8), - TextButton( - onPressed: () { - setState(() { - _selectedcollingType = null; - }); - }, - child: const Text('Limpar seleção'), - ), - const SizedBox(height: 30), - const Text('Seus tipos de refigeração', + const Text('Tipos de refrigeração', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -371,20 +339,24 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, + items: ['Selecione o tipo de refrigeração'] + + combinedTypes, + value: _selectedCoolingType ?? _selectedType, onChanged: (newValue) { - setState(() { - _selectedType = newValue; - if (newValue == equipmentTypes[0]) { - _selectedType = null; - } - if (_selectedType != null) { - _selectedcollingType = null; - } - }); + if (newValue != + 'Selecione o tipo de refrigeração') { + setState(() { + if (coolingTypes.contains(newValue)) { + _selectedCoolingType = newValue; + _selectedType = null; + } else { + _selectedType = newValue; + _selectedCoolingType = null; + } + }); + } }, - enabled: _selectedcollingType == null, + enabled: true, ), ), Expanded( @@ -393,15 +365,24 @@ class _AddEquipmentScreenState extends State { children: [ IconButton( icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, + onPressed: _addNewCoolingType, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); + if (additionalCoolingTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Nenhum tipo de refrigeração adicionado para excluir.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } }, ), ], @@ -413,6 +394,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { + _selectedCoolingType = null; _selectedType = null; }); }, @@ -514,33 +496,36 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de equipamento'), + title: const Text('Excluir tipo de refrigeração'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Selecione um equipamento para excluir:', + 'Selecione um tipo de refrigeração para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: additionalCoolingTypes + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -556,7 +541,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (_selectedTypeToDelete != null) { Navigator.of(context).pop(); - _deleteEquipmentType(); + _deleteCoolingType(); } }, ), diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart index 52b8d453..87729983 100644 --- a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart @@ -73,6 +73,7 @@ class listCollingEquipment extends StatelessWidget { ), child: Center( child: Text('$areaName - $systemTitle', + textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, From 8ad31fd4006b033ca0fe4a153db1e11158b5bee8 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 17:38:19 -0300 Subject: [PATCH 214/351] =?UTF-8?q?Adiciona=20p=C3=A1gina=20de=20equipes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/Teams/teams.dart | 94 ++++++++++++++++++++++++++ frontend/sige_ie/lib/home/ui/home.dart | 10 +-- 2 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 frontend/sige_ie/lib/Teams/teams.dart diff --git a/frontend/sige_ie/lib/Teams/teams.dart b/frontend/sige_ie/lib/Teams/teams.dart new file mode 100644 index 00000000..a91aa9a0 --- /dev/null +++ b/frontend/sige_ie/lib/Teams/teams.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; + +class TeamsPage extends StatelessWidget { + const TeamsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + foregroundColor: Colors.white, + automaticallyImplyLeading: false, + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 20), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: const Center( + child: Column( + children: [ + Text( + 'Equipes', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Lista de Equipes', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 20), + ListView.builder( + shrinkWrap: true, + itemCount: teams.length, + itemBuilder: (context, index) { + final team = teams[index]; + return Card( + child: ListTile( + leading: + Icon(Icons.group, color: AppColors.sigeIeBlue), + title: Text(team.name), + subtitle: Text('Membros: ${team.members.length}'), + onTap: () { + // Navegação para a página de detalhes da equipe + }, + ), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +class Team { + final String name; + final List members; + + Team(this.name, this.members); +} + +final List teams = [ + Team('Equipe 1', ['Membro 1', 'Membro 2', 'Membro 3']), + Team('Equipe 2', ['Membro 4', 'Membro 5']), + Team('Equipe 3', ['Membro 6', 'Membro 7', 'Membro 8']), +]; diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 6984a0d6..6266f156 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sige_ie/Teams/teams.dart'; import 'package:sige_ie/config/app_styles.dart'; import '../../users/feature/profile.dart'; import '../../facilities/ui/facilities.dart'; @@ -61,6 +62,7 @@ class _HomePageState extends State { children: [ buildHomePage(context), const FacilitiesPage(), + const TeamsPage(), const MapsPage(), ProfilePage() ], @@ -138,10 +140,6 @@ class _HomePageState extends State { context, 'Registrar novo local', 'Registrar', () { Navigator.of(context).pushNamed('/newLocation'); }), - buildSmallRectangle(context, 'Equipes', 'Gerenciar', - () { - // Código aqui. - }), const Spacer(), ], ), @@ -334,6 +332,10 @@ class _HomePageState extends State { icon: Icon(Icons.build, size: 35), label: 'Instalações', backgroundColor: Color(0xFFF1F60E)), + BottomNavigationBarItem( + icon: Icon(Icons.group, size: 35), + label: 'Equipes', + backgroundColor: Color(0xFFF1F60E)), BottomNavigationBarItem( icon: Icon(Icons.map, size: 35), label: 'Mapa', From ed34d34877d21a2c675e25078ee9c38a3dd2b894 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 10 Jun 2024 17:40:55 -0300 Subject: [PATCH 215/351] =?UTF-8?q?Complementa=20p=C3=A1gina=20de=20equipe?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/Teams/teams.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/sige_ie/lib/Teams/teams.dart b/frontend/sige_ie/lib/Teams/teams.dart index a91aa9a0..8b81531c 100644 --- a/frontend/sige_ie/lib/Teams/teams.dart +++ b/frontend/sige_ie/lib/Teams/teams.dart @@ -44,14 +44,6 @@ class TeamsPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Lista de Equipes', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 20), ListView.builder( shrinkWrap: true, itemCount: teams.length, From 71840d08237b4fbd85ef28cdaa0972b0d4d4cecd Mon Sep 17 00:00:00 2001 From: ramires Date: Tue, 11 Jun 2024 17:17:30 -0300 Subject: [PATCH 216/351] =?UTF-8?q?frontend:=20corrige=20a=20conex=C3=A3o?= =?UTF-8?q?=20do=20equipment=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fire_alarm_equipment_detail_request_model.dart | 3 +++ .../lib/equipments/feature/fire-alarm/addFireAlarm.dart | 6 ++++-- .../lib/equipments/feature/fire-alarm/listFireAlarms.dart | 2 +- .../sige_ie/lib/equipments/feature/systemConfiguration.dart | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart index 79634700..1d097b26 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart @@ -3,11 +3,13 @@ import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; class FireAlarmEquipmentDetailRequestModel { int? equipmentType; + int? personalEquipmentType; FireAlarmRequestModel? fireAlarm; List? photos; FireAlarmEquipmentDetailRequestModel({ required this.equipmentType, + required this.personalEquipmentType, required this.fireAlarm, required this.photos, }); @@ -15,6 +17,7 @@ class FireAlarmEquipmentDetailRequestModel { Map toJson() { return { 'equipmentType': equipmentType, + 'personalEquipmentType': personalEquipmentType, 'fire_alarm_equipment': fireAlarm?.toJson(), 'photos': photos?.map((photo) => photo.toJson()).toList(), }; diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index 263ee295..a5e41236 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -57,6 +57,7 @@ class _AddEquipmentScreenState extends State { String? _selectedTypeToDelete; String? _newEquipmentTypeName; int? _selectedTypeId; + int? _selectedPersonalEquipmentTypeId; List equipmentTypes = []; List personalEquipmentTypes = []; @@ -397,11 +398,12 @@ class _AddEquipmentScreenState extends State { }).toList(); final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( - area: _selectedTypeId, system: widget.categoryNumber); + area: widget.areaId, system: widget.categoryNumber); final FireAlarmEquipmentDetailRequestModel fireAlarmEquipmentDetail = FireAlarmEquipmentDetailRequestModel( equipmentType: _selectedTypeId, + personalEquipmentType: _selectedPersonalEquipmentTypeId, fireAlarm: fireAlarmModel, photos: photos, ); @@ -508,7 +510,7 @@ class _AddEquipmentScreenState extends State { 'Selecione o tipo de alarme de incêndio') { setState(() { _selectedType = newValue; - _selectedTypeId = + _selectedPersonalEquipmentTypeId = personalEquipmentMap[newValue] ?? -1; }); } diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart index 0afd1fff..83df2377 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart @@ -100,7 +100,7 @@ class _ListFireAlarmsState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('${widget.areaName} - $systemTitle', + child: Text('${widget.areaId} - {widget.areaName} - $systemTitle', textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index d109b52c..3cb4f106 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -137,7 +137,7 @@ class _SystemConfigurationState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text(widget.areaName, + child: Text('$widget.areaId' style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, From bebf1bb53d8d0ed7af9d5904bcd8f6458b6dea8f Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 12 Jun 2024 11:10:06 -0300 Subject: [PATCH 217/351] =?UTF-8?q?Exclus=C3=A3o=20de=20arquivos=20n=C3=A3?= =?UTF-8?q?o=20utilizados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/equipment/EquipmentScreen.dart | 158 ----- .../feature/equipment/addEquipmentScreen.dart | 544 ------------------ .../equipment/systemConfiguration.dart | 271 --------- .../feature/fire-alarm/addFireAlarm.dart | 6 + .../feature/fire-alarm/listFireAlarms.dart | 3 +- .../feature/systemConfiguration.dart | 13 +- 6 files changed, 16 insertions(+), 979 deletions(-) delete mode 100644 frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart delete mode 100644 frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart delete mode 100644 frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart diff --git a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart deleted file mode 100644 index 0a766690..00000000 --- a/frontend/sige_ie/lib/core/feature/equipment/EquipmentScreen.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/equipment/addEquipmentScreen.dart'; - -class EquipmentScreen extends StatelessWidget { - final String areaName; - final String localName; - final int categoryNumber; - final int localId; - - const EquipmentScreen({ - super.key, - required this.areaName, - required this.categoryNumber, - required this.localName, - required this.localId, - }); - - void navigateToAddEquipment(BuildContext context) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AddEquipmentScreen( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - - String systemTitle; - switch (categoryNumber) { - case 1: - systemTitle = 'ILUMINAÇÃO'; - break; - case 2: - systemTitle = 'CARGAS ELÉTRICAS'; - break; - case 3: - systemTitle = 'LINHAS ELÉTRICAS'; - break; - case 4: - systemTitle = 'CIRCUITOS'; - break; - case 5: - systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; - break; - case 6: - systemTitle = 'CABEAMENTO ESTRUTURADO'; - break; - case 7: - systemTitle = 'DESCARGAS ATMOSFÉRICAS'; - break; - case 8: - systemTitle = 'ALARME DE INCÊNDIO'; - break; - case 9: - systemTitle = 'REFRIGERAÇÃO'; - break; - default: - systemTitle = 'SISTEMA DESCONHECIDO'; - } - - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.pushReplacementNamed( - context, - '/systemLocation', - arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'categoryNumber': categoryNumber, - }, - ); - }, - ), - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Text( - '$areaName - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText, - ), - ), - ), - ], - ), - ), - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), - ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), - const SizedBox(height: 40), - ], - ), - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () => navigateToAddEquipment(context), - backgroundColor: AppColors.sigeIeYellow, - child: const Icon(Icons.add, color: AppColors.sigeIeBlue), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart b/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart deleted file mode 100644 index 6accb945..00000000 --- a/frontend/sige_ie/lib/core/feature/equipment/addEquipmentScreen.dart +++ /dev/null @@ -1,544 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'dart:io'; -import 'package:image_picker/image_picker.dart'; - -class ImageData { - File imageFile; - int id; - - ImageData(this.imageFile) : id = Random().nextInt(1000000); -} - -List _images = []; -Map> categoryImagesMap = {}; - -class AddEquipmentScreen extends StatefulWidget { - final String areaName; - final String localName; - final int localId; - final int categoryNumber; - - const AddEquipmentScreen({ - super.key, - required this.areaName, - required this.categoryNumber, - required this.localName, - required this.localId, - }); - - @override - _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); -} - -class _AddEquipmentScreenState extends State { - final _equipmentNameController = TextEditingController(); - final _equipmentQuantityController = TextEditingController(); - String? _selectedType; - String? _selectedLocation; - String? _selectedTypeToDelete; - - List equipmentTypes = [ - 'Selecione um equipamento', - 'Eletroduto', - 'Eletrocalha', - 'Dimensão' - ]; - - @override - void dispose() { - _equipmentNameController.dispose(); - _equipmentQuantityController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); - super.dispose(); - } - - Future _pickImage() async { - final picker = ImagePicker(); - try { - final pickedFile = await picker.pickImage(source: ImageSource.camera); - if (pickedFile != null) { - setState(() { - final imageData = ImageData(File(pickedFile.path)); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; - } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; - }); - } - } catch (e) { - print('Erro ao capturar a imagem: $e'); - } - } - - void _addNewEquipmentType() { - TextEditingController typeController = TextEditingController(); - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Adicionar novo tipo de equipamento'), - content: TextField( - controller: typeController, - decoration: const InputDecoration( - hintText: 'Digite o novo tipo de equipamento'), - ), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Adicionar'), - onPressed: () { - if (typeController.text.isNotEmpty) { - setState(() { - equipmentTypes.add(typeController.text); - }); - Navigator.of(context).pop(); - } - }, - ), - ], - ); - }, - ); - } - - void _deleteEquipmentType() { - if (_selectedTypeToDelete == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: - Text('Selecione um tipo de equipamento válido para excluir.'), - ), - ); - return; - } - - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Excluir'), - onPressed: () { - setState(() { - equipmentTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } - - void _showConfirmationDialog() { - if (_equipmentNameController.text.isEmpty || - _equipmentQuantityController.text.isEmpty || - _selectedType == null || - _selectedLocation == null || - _selectedType == 'Selecione um equipamento' || - _selectedLocation == 'Selecione a localização') { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Por favor, preencha todos os campos.'), - ), - ); - return; - } - - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Confirmar Dados do Equipamento'), - content: SingleChildScrollView( - child: ListBody( - children: [ - const Text('Nome:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentNameController.text), - const SizedBox(height: 10), - const Text('Quantidade:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentQuantityController.text), - const SizedBox(height: 10), - const Text('Tipo:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? ''), - const SizedBox(height: 10), - const Text('Localização:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedLocation ?? ''), - const SizedBox(height: 10), - const Text('Imagens:', - style: TextStyle(fontWeight: FontWeight.bold)), - Wrap( - children: _images.map((imageData) { - return Padding( - padding: const EdgeInsets.all(4.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - ); - }).toList(), - ), - ], - ), - ), - actions: [ - TextButton( - child: const Text('Editar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('OK'), - onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); - }, - ), - ], - ); - }, - ); - } - - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/equipmentScreen', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - foregroundColor: Colors.white, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: const Center( - child: Text('Adicionar equipamentos ', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Tipos de equipamentos', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Row( - children: [ - Expanded( - flex: 4, - child: _buildStyledDropdown( - items: equipmentTypes, - value: _selectedType, - onChanged: (newValue) { - if (newValue != 'Selecione um equipamento') { - setState(() { - _selectedType = newValue; - }); - } - }, - ), - ), - Expanded( - flex: 0, - child: Row( - children: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); - }, - ), - ], - ), - ), - ], - ), - const SizedBox(height: 30), - const Text('Nome do equipamento', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: TextField( - controller: _equipmentNameController, - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 15), - ), - ), - ), - const SizedBox(height: 30), - const Text('Quantidade', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - child: TextField( - controller: _equipmentQuantityController, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 15), - ), - ), - ), - const SizedBox(height: 30), - const Text('Localização (Interno ou Externo)', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildStyledDropdown( - items: const [ - 'Selecione a localização', - 'Interno', - 'Externo' - ], - value: _selectedLocation, - onChanged: (newValue) { - if (newValue != 'Selecione a localização') { - setState(() { - _selectedLocation = newValue; - }); - } - }, - ), - const SizedBox(height: 15), - IconButton( - icon: const Icon(Icons.camera_alt), - onPressed: _pickImage, - ), - Wrap( - children: _images.map((imageData) { - return Stack( - alignment: Alignment.topRight, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), - ), - IconButton( - icon: const Icon(Icons.remove_circle, - color: AppColors.warn), - onPressed: () { - setState(() { - _images.removeWhere( - (element) => element.id == imageData.id); - }); - }, - ), - ], - ); - }).toList(), - ), - const SizedBox(height: 15), - Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeYellow), - foregroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - minimumSize: - MaterialStateProperty.all(const Size(185, 55)), - shape: - MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ))), - onPressed: _showConfirmationDialog, - child: const Text( - 'ADICIONAR EQUIPAMENTO', - style: TextStyle( - fontSize: 17, fontWeight: FontWeight.bold), - ), - ), - ) - ], - ), - ), - ], - ), - ), - ); - } - - void _showDeleteDialog() { - showDialog( - context: context, - builder: (BuildContext context) { - _selectedTypeToDelete = null; // Inicialize com null - return StatefulBuilder( - builder: (context, setState) { - return AlertDialog( - title: const Text('Excluir tipo de equipamento'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'Selecione um equipamento para excluir:', - textAlign: TextAlign.center, - ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: [...equipmentTypes] - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), - enabled: - value != 'Selecione um equipamento para deletar', - ); - }).toList(), - ), - ], - ), - actions: [ - TextButton( - child: const Text('Cancelar'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Excluir'), - onPressed: () { - if (_selectedTypeToDelete != null && - _selectedTypeToDelete != - 'Selecione um equipamento para deletar') { - Navigator.of(context).pop(); - _deleteEquipmentType(); - } - }, - ), - ], - ); - }, - ); - }, - ); - } - - Widget _buildStyledDropdown({ - required List items, - String? value, - required Function(String?) onChanged, - }) { - return Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - padding: const EdgeInsets.symmetric(horizontal: 10), - child: DropdownButton( - hint: Text(items.first), - value: value, - isExpanded: true, - underline: Container(), - onChanged: onChanged, - items: items.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), - ); - }).toList(), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart b/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart deleted file mode 100644 index edbddc16..00000000 --- a/frontend/sige_ie/lib/core/feature/equipment/systemConfiguration.dart +++ /dev/null @@ -1,271 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/core/feature/equipment/EquipmentScreen.dart'; - -class SystemConfiguration extends StatefulWidget { - final String areaName; - final String localName; - final int localId; - final int categoryNumber; - - const SystemConfiguration({ - super.key, - required this.areaName, - required this.localName, - required this.localId, - required this.categoryNumber, - }); - - @override - _SystemConfigurationState createState() => _SystemConfigurationState(); -} - -class _SystemConfigurationState extends State { - void navigateTo(String routeName, String areaName, String localName, - int localId, dynamic category) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - switch (routeName) { - case '/structuredCabling': - case '/atmosphericDischarges': - case '/fireAlarm': - case '/lighting': - case '/electricLoads': - case '/electricLines': - case '/circuits': - case '/distributionBoard': - case '/cooling': - return EquipmentScreen( - areaName: areaName, - localName: localName, - localId: localId, - categoryNumber: category); - default: - return Scaffold( - body: Center(child: Text('No route defined for $routeName')), - ); - } - }, - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - backgroundColor: AppColors.sigeIeBlue, - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text(widget.areaName, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), - ), - ), - const Padding( - padding: EdgeInsets.all(30.0), - child: Text( - 'Quais sistemas deseja configurar?', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - ), - GridView.count( - shrinkWrap: true, - crossAxisCount: 3, - childAspectRatio: 0.8, // Adjusted aspect ratio - padding: const EdgeInsets.all(10.0), - mainAxisSpacing: 10.0, - crossAxisSpacing: 10.0, - children: [ - SystemIcon( - icon: Icons.fire_extinguisher, - label: 'ALARME DE INCÊNDIO', - onPressed: () => navigateTo('/fireAlarm', widget.areaName, - widget.localName, widget.localId, 8), - ), - SystemIcon( - icon: Icons.cable, - label: 'CABEAMENTO ESTRUTURADO', - onPressed: () => navigateTo('/structuredCabling', - widget.areaName, widget.localName, widget.localId, 6), - ), - SystemIcon( - icon: Icons.electrical_services, - label: 'CARGAS ELÉTRICAS', - onPressed: () => navigateTo('/electricLoads', widget.areaName, - widget.localName, widget.localId, 2), - ), - SystemIcon( - icon: Icons.electrical_services, - label: 'CIRCUITOS', - onPressed: () => navigateTo('/circuits', widget.areaName, - widget.localName, widget.localId, 4), - ), - SystemIcon( - icon: Icons.bolt, - label: 'DESCARGAS ATMOSFÉRICAS', - onPressed: () => navigateTo('/atmosphericDischarges', - widget.areaName, widget.localName, widget.localId, 7), - ), - SystemIcon( - icon: Icons.lightbulb, - label: 'ILUMINAÇÃO', - onPressed: () => navigateTo('/lighting', widget.areaName, - widget.localName, widget.localId, 1), - ), - SystemIcon( - icon: Icons.power, - label: 'LINHAS ELÉTRICAS', - onPressed: () => navigateTo('/electricLines', widget.areaName, - widget.localName, widget.localId, 3), - ), - SystemIcon( - icon: Icons.dashboard, - label: 'QUADRO DE DISTRIBUIÇÃO', - onPressed: () => navigateTo('/distributionBoard', - widget.areaName, widget.localName, widget.localId, 5), - ), - SystemIcon( - icon: Icons.ac_unit, - label: 'REFRIGERAÇÃO', - onPressed: () => navigateTo('/cooling', widget.areaName, - widget.localName, widget.localId, 9), - ), - ], - ), - const SizedBox( - height: 30, - ), - Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: AppColors.lightText, - backgroundColor: AppColors.warn, - minimumSize: const Size(150, 50), - textStyle: const TextStyle( - fontWeight: FontWeight.bold, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - onPressed: () { - Navigator.of(context).popUntil((route) => route.isFirst); - Navigator.pushReplacementNamed( - context, - '/homeScreen', - arguments: {'initialPage': 1}, - ); - }, - child: const Text('SAIR DA SALA'), - ), - const SizedBox(width: 10), - ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.sigeIeBlue), - foregroundColor: - MaterialStateProperty.all(AppColors.lightText), - minimumSize: - MaterialStateProperty.all(const Size(150, 50)), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - )), - ), - onPressed: () { - Navigator.of(context).pushNamed('/arealocation', - arguments: { - 'placeName': widget.localName, - 'placeId': widget.localId - }); - }, - child: const Text( - 'CRIAR NOVA SALA', - style: - TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - ), - ], - ), - ), - const SizedBox( - height: 30, - ), - ], - ), - ), - ); - } -} - -class SystemIcon extends StatelessWidget { - final IconData icon; - final String label; - final VoidCallback onPressed; - - const SystemIcon({ - super.key, - required this.icon, - required this.label, - required this.onPressed, - }); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onPressed, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColors.sigeIeYellow, - ), - child: Icon( - icon, - size: 40.0, - color: AppColors.sigeIeBlue, - ), - ), - const SizedBox(height: 10), - Flexible( - child: Text( - label, - textAlign: TextAlign.center, - style: const TextStyle( - color: AppColors.sigeIeBlue, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - softWrap: true, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ); - } -} diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index a5e41236..e99eb614 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -390,6 +390,12 @@ class _AddEquipmentScreenState extends State { } void _registerEquipmentDetail() async { + print('areaId: ${widget.areaId}'); + print('categoryNumber: ${widget.categoryNumber}'); + print('_selectedType: $_selectedType'); + print( + '_selectedPersonalEquipmentTypeId: $_selectedPersonalEquipmentTypeId'); + List photos = _images.map((imageData) { return PhotoRequestModel( photo: base64Encode(imageData.imageFile.readAsBytesSync()), diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart index 83df2377..f2e65aed 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart @@ -100,7 +100,8 @@ class _ListFireAlarmsState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('${widget.areaId} - {widget.areaName} - $systemTitle', + child: Text( + '${widget.areaId} - ${widget.areaName} - $systemTitle', textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 3cb4f106..4f9c20ed 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -137,11 +137,14 @@ class _SystemConfigurationState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$widget.areaId' - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white)), + child: Text( + widget.areaName, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), ), ), const Padding( From 401883e08cf506d62811558f18f69c028e7c19e1 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 12 Jun 2024 11:40:37 -0300 Subject: [PATCH 218/351] =?UTF-8?q?Diferencia=C3=A7=C3=A3o=20entre=20lista?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire-alarm/addFireAlarm.dart | 90 ++++++++++++------- .../feature/fire-alarm/listFireAlarms.dart | 3 +- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index e99eb614..766e256c 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -58,9 +58,10 @@ class _AddEquipmentScreenState extends State { String? _newEquipmentTypeName; int? _selectedTypeId; int? _selectedPersonalEquipmentTypeId; + bool _isPersonalTypeSelected = false; - List equipmentTypes = []; - List personalEquipmentTypes = []; + List> equipmentTypes = []; + List> personalEquipmentTypes = []; Map personalEquipmentMap = {}; @override @@ -78,14 +79,13 @@ class _AddEquipmentScreenState extends State { await personalEquipmentTypeService .getAllPersonalEquipmentBySystem(widget.categoryNumber); - List equipmentList = - await mixEquipmentTypeService.getAllEquipmentBySystem( - widget.categoryNumber, equipmentTypeList, personalEquipmentList); - setState(() { - equipmentTypes = equipmentList.map((e) => e.name).toList(); - personalEquipmentTypes = - personalEquipmentList.map((e) => e.name).toList(); + equipmentTypes = equipmentTypeList + .map((e) => {'name': e.name, 'id': e.id, 'type': 0}) + .toList(); + personalEquipmentTypes = personalEquipmentList + .map((e) => {'name': e.name, 'id': e.id, 'type': 1}) + .toList(); personalEquipmentMap = { for (var equipment in personalEquipmentList) equipment.name: equipment.id @@ -229,7 +229,8 @@ class _AddEquipmentScreenState extends State { ); setState(() { - personalEquipmentTypes.add(_newEquipmentTypeName!); + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 1}); personalEquipmentMap[_newEquipmentTypeName!] = id; _newEquipmentTypeName = null; _fetchEquipmentTypes(); @@ -290,7 +291,8 @@ class _AddEquipmentScreenState extends State { if (success) { setState(() { - personalEquipmentTypes.remove(_selectedTypeToDelete); + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); _selectedTypeToDelete = null; _fetchEquipmentTypes(); }); @@ -403,13 +405,24 @@ class _AddEquipmentScreenState extends State { imageData.description.isNotEmpty ? imageData.description : ''); }).toList(); + int? equipmentType; + int? personalEquipmentType; + + if (_isPersonalTypeSelected) { + equipmentType = null; + personalEquipmentType = _selectedPersonalEquipmentTypeId; + } else { + equipmentType = _selectedTypeId; + personalEquipmentType = null; + } + final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( area: widget.areaId, system: widget.categoryNumber); final FireAlarmEquipmentDetailRequestModel fireAlarmEquipmentDetail = FireAlarmEquipmentDetailRequestModel( - equipmentType: _selectedTypeId, - personalEquipmentType: _selectedPersonalEquipmentTypeId, + equipmentType: equipmentType, + personalEquipmentType: personalEquipmentType, fireAlarm: fireAlarmModel, photos: photos, ); @@ -447,11 +460,10 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { - Set combinedTypesSet = { + List> combinedTypes = [ ...equipmentTypes, ...personalEquipmentTypes - }; - List combinedTypes = combinedTypesSet.toList(); + ]; return Scaffold( appBar: AppBar( @@ -508,7 +520,14 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: ['Selecione o tipo de alarme de incêndio'] + + items: [ + { + 'name': + 'Selecione o tipo de alarme de incêndio', + 'id': -1, + 'type': -1 + } + ] + combinedTypes, value: _selectedType, onChanged: (newValue) { @@ -516,8 +535,18 @@ class _AddEquipmentScreenState extends State { 'Selecione o tipo de alarme de incêndio') { setState(() { _selectedType = newValue; - _selectedPersonalEquipmentTypeId = - personalEquipmentMap[newValue] ?? -1; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalTypeSelected = selected['type'] == 1; + if (_isPersonalTypeSelected) { + _selectedPersonalEquipmentTypeId = + selected['id'] as int; + _selectedTypeId = null; + } else { + _selectedTypeId = selected['id'] as int; + _selectedPersonalEquipmentTypeId = null; + } }); } }, @@ -678,12 +707,12 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: personalEquipmentTypes - .map>((String value) { + items: personalEquipmentTypes.map>( + (Map value) { return DropdownMenuItem( - value: value, + value: value['name'] as String, child: Text( - value, + value['name'] as String, style: const TextStyle(color: Colors.black), ), ); @@ -716,7 +745,7 @@ class _AddEquipmentScreenState extends State { } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -728,19 +757,20 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first, style: const TextStyle(color: Colors.grey)), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, - enabled: value != 'Selecione o tipo de alarme de incêndio', + value: value['name'] as String, + enabled: value['name'] != 'Selecione o tipo de alarme de incêndio', child: Text( - value, + value['name'] as String, style: TextStyle( - color: value == 'Selecione o tipo de alarme de incêndio' + color: value['name'] == 'Selecione o tipo de alarme de incêndio' ? Colors.grey : Colors.black, ), diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart index f2e65aed..0afd1fff 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart @@ -100,8 +100,7 @@ class _ListFireAlarmsState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text( - '${widget.areaId} - ${widget.areaName} - $systemTitle', + child: Text('${widget.areaName} - $systemTitle', textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, From ebd69204ef00dfaad39cabc78d2f4d4b20355cd7 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 12 Jun 2024 13:20:31 -0300 Subject: [PATCH 219/351] =?UTF-8?q?backend:=20cria=20place=20editor=20e=20?= =?UTF-8?q?concess=C3=A3o=20de=20acesso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/.vscode/settings.json | 22 ++++++++++++++ api/places/migrations/0029_place_editors.py | 19 ++++++++++++ .../migrations/0030_place_access_code.py | 18 +++++++++++ .../0031_remove_place_access_code.py | 17 +++++++++++ api/places/models.py | 4 ++- api/places/urls.py | 5 ++-- api/places/views.py | 30 +++++++++++++++++-- api/users/migrations/0005_placeeditor.py | 23 ++++++++++++++ api/users/models.py | 9 +++++- api/users/serializers.py | 10 ++++++- api/users/views.py | 5 +--- 11 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 api/.vscode/settings.json create mode 100644 api/places/migrations/0029_place_editors.py create mode 100644 api/places/migrations/0030_place_access_code.py create mode 100644 api/places/migrations/0031_remove_place_access_code.py create mode 100644 api/users/migrations/0005_placeeditor.py diff --git a/api/.vscode/settings.json b/api/.vscode/settings.json new file mode 100644 index 00000000..79eab2c1 --- /dev/null +++ b/api/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#3399ff", + "activityBar.background": "#3399ff", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#bf0060", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#e7e7e799", + "sash.hoverBorder": "#3399ff", + "statusBar.background": "#007fff", + "statusBar.foreground": "#e7e7e7", + "statusBarItem.hoverBackground": "#3399ff", + "statusBarItem.remoteBackground": "#007fff", + "statusBarItem.remoteForeground": "#e7e7e7", + "titleBar.activeBackground": "#007fff", + "titleBar.activeForeground": "#e7e7e7", + "titleBar.inactiveBackground": "#007fff99", + "titleBar.inactiveForeground": "#e7e7e799" + }, + "peacock.color": "#007fff" +} \ No newline at end of file diff --git a/api/places/migrations/0029_place_editors.py b/api/places/migrations/0029_place_editors.py new file mode 100644 index 00000000..803f27ee --- /dev/null +++ b/api/places/migrations/0029_place_editors.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2 on 2024-06-12 13:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0005_placeeditor'), + ('places', '0028_place_photo'), + ] + + operations = [ + migrations.AddField( + model_name='place', + name='editors', + field=models.ManyToManyField(related_name='places', to='users.placeeditor'), + ), + ] diff --git a/api/places/migrations/0030_place_access_code.py b/api/places/migrations/0030_place_access_code.py new file mode 100644 index 00000000..ed49739a --- /dev/null +++ b/api/places/migrations/0030_place_access_code.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2024-06-12 14:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0029_place_editors'), + ] + + operations = [ + migrations.AddField( + model_name='place', + name='access_code', + field=models.CharField(blank=True, max_length=36, null=True, unique=True), + ), + ] diff --git a/api/places/migrations/0031_remove_place_access_code.py b/api/places/migrations/0031_remove_place_access_code.py new file mode 100644 index 00000000..0325efb7 --- /dev/null +++ b/api/places/migrations/0031_remove_place_access_code.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2 on 2024-06-12 14:32 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0030_place_access_code'), + ] + + operations = [ + migrations.RemoveField( + model_name='place', + name='access_code', + ), + ] diff --git a/api/places/models.py b/api/places/models.py index 0dd7feef..f27fdbb8 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -1,6 +1,7 @@ +import uuid from django.db import models from django.core.validators import MinValueValidator -from users.models import PlaceOwner +from users.models import PlaceOwner, PlaceEditor class Place(models.Model): @@ -9,6 +10,7 @@ class Place(models.Model): lon = models.FloatField(null=True) lat = models.FloatField(null=True) photo = models.ImageField(null=True, upload_to='place_photos/') + editors = models.ManyToManyField(PlaceEditor, related_name='places') def __str__(self): return self.name diff --git a/api/places/urls.py b/api/places/urls.py index 40f60081..659a07e3 100644 --- a/api/places/urls.py +++ b/api/places/urls.py @@ -1,7 +1,8 @@ from django.urls import path, include -from .views import PlaceViewSet, AreaViewSet +from .views import PlaceViewSet, AreaViewSet, GrantAccessViewSet from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register(r'places',PlaceViewSet) -router.register(r'areas', AreaViewSet) \ No newline at end of file +router.register(r'areas', AreaViewSet) +router.register(r'grant_access', GrantAccessViewSet, basename='grant_access') \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index 48ea92f7..de77e18a 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -1,12 +1,14 @@ from django.shortcuts import render - -from users.models import PlaceOwner +from rest_framework.views import APIView +from django.contrib.auth.models import User +from users.models import PlaceOwner, PlaceEditor from rest_framework import generics from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated from places.permissions import IsPlaceOwner from rest_framework import viewsets, status from rest_framework.decorators import action +from django.http import JsonResponse from rest_framework.response import Response from rest_framework.exceptions import NotFound @@ -150,3 +152,27 @@ def destroy(self, request, pk=None): return Response({"message": "Area deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) + +class GrantAccessViewSet(viewsets.ViewSet): + permission_classes = [IsAuthenticated, IsPlaceOwner] + + @action(detail=True, methods=['post']) + def grant_access(self, request, pk=None): + place = get_object_or_404(Place, pk=pk) + place_owner = place.place_owner + + if request.user != place_owner.user: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + + username = request.data.get('username') + + if not username: + return Response({'error': 'Username is required'}, status=status.HTTP_400_BAD_REQUEST) + + user = get_object_or_404(User, username=username) + + place_editor, created = PlaceEditor.objects.get_or_create(user=user) + + place.editors.add(place_editor) + + return Response({'message': 'Access granted successfully'}, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/api/users/migrations/0005_placeeditor.py b/api/users/migrations/0005_placeeditor.py new file mode 100644 index 00000000..5adb732c --- /dev/null +++ b/api/users/migrations/0005_placeeditor.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2 on 2024-06-12 13:26 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('users', '0004_alter_placeowner_user'), + ] + + operations = [ + migrations.CreateModel( + name='PlaceEditor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='place_editor', to=settings.AUTH_USER_MODEL, verbose_name='user')), + ], + ), + ] diff --git a/api/users/models.py b/api/users/models.py index c395489b..dffa67bc 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -6,4 +6,11 @@ class PlaceOwner(models.Model): user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE, related_name='placeowner') def __str__(self): - return self.user.first_name \ No newline at end of file + return self.user.first_name + +class PlaceEditor(models.Model): + + user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE, related_name='place_editor') + + def __str__(self): + return self.user.first_name diff --git a/api/users/serializers.py b/api/users/serializers.py index abdd69b5..581402d6 100644 --- a/api/users/serializers.py +++ b/api/users/serializers.py @@ -1,7 +1,7 @@ # serializers.py from rest_framework import serializers, response from django.contrib.auth.models import User -from .models import PlaceOwner +from .models import PlaceOwner, PlaceEditor import re class UserSerializer(serializers.ModelSerializer): @@ -40,6 +40,9 @@ class UserLoginSerializer(serializers.Serializer): username = serializers.CharField(min_length=6, max_length=23, required=True) password = serializers.CharField(min_length=6, max_length=200, required=True) +class UsernameSerializer(serializers.Serializer): + username = serializers.CharField(min_length=6, max_length=23, required=True) + class UserUpdateSerializer(serializers.Serializer): first_name = serializers.CharField(required=True) email = serializers.EmailField(required=True) @@ -54,3 +57,8 @@ class PlaceOwnerSerializer(serializers.ModelSerializer): class Meta: model = PlaceOwner fields = ['id'] + +class PlaceEditorSerializer(serializers.ModelSerializer): + class Meta: + model = PlaceEditor + fields = ['id'] diff --git a/api/users/views.py b/api/users/views.py index 57038cff..e4c4ad05 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -23,7 +23,6 @@ from django.shortcuts import render, redirect from django.contrib import messages - @method_decorator(ensure_csrf_cookie, name='dispatch') class GetCSRFToken(APIView): permission_classes = [AllowAny] @@ -86,7 +85,7 @@ class LogoutView(APIView): def post(self, request, format=None): logout(request) return Response({'message': 'Logout successful'}, status=status.HTTP_200_OK) - + class Email(APIView): permission_classes = [] @@ -105,8 +104,6 @@ def post(self, request, *args, **kwargs): return Response({'message': 'Email de redefinição de senha enviado'}, status=status.HTTP_200_OK) else: return Response({'message': 'Usuário não encontrado'}, status=status.HTTP_404_NOT_FOUND) - - class PasswordResetConfirmView(APIView): permission_classes = [] From 98c22f47524e0c1e0ac1d3e7db61c2bd11e41329 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 12 Jun 2024 14:09:32 -0300 Subject: [PATCH 220/351] =?UTF-8?q?backend:=20verifica=20permiss=C3=A3o=20?= =?UTF-8?q?de=20place=20editor=20para=20visualizar=20e=20editar=20place?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/places/views.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/api/places/views.py b/api/places/views.py index de77e18a..4755368a 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -26,6 +26,10 @@ def get_place_owner(self, user): except PlaceOwner.DoesNotExist: return PlaceOwner.objects.create(user=user) + def _has_permission(self, request, place): + place_owner = place.place_owner + return request.user == place_owner.user or place.editors.filter(user=request.user).exists() + def create(self, request, *args, **kwargs): user = request.user @@ -53,26 +57,22 @@ def list(self, request, *args, **kwargs): return Response(place_serializer.data) def retrieve(self, request, pk=None): - place_owner_id = request.user.placeowner.id - place = get_object_or_404(Place, pk=pk) - if place.place_owner.id == place_owner_id: + if place.place_owner.user == request.user or place.editors.filter(user=request.user).exists(): serializer = PlaceSerializer(place) return Response(serializer.data) else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) def update(self, request, pk=None): - place_owner_id = request.user.placeowner.id - place = get_object_or_404(Place, pk=pk) - if place.place_owner.id == place_owner_id: + if place.place_owner.user == request.user or place.editors.filter(user=request.user).exists(): serializer = PlaceSerializer(place, data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data) else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) def destroy(self, request, pk=None): place_owner_id = request.user.placeowner.id From cda2fe6df91c542480a3d35afa38c9d2bb70f498 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 12 Jun 2024 15:52:11 -0300 Subject: [PATCH 221/351] =?UTF-8?q?integra=C3=A7=C3=A3o=20de=20adicionar?= =?UTF-8?q?=20equipamento=20de=20alarme=20de=20inc=C3=AAndio=20feito?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/fire-alarm/fire_alarm_request_model.dart | 2 +- .../lib/equipments/feature/fire-alarm/addFireAlarm.dart | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_request_model.dart index 7c41b07b..52f7e86c 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_request_model.dart @@ -9,7 +9,7 @@ class FireAlarmRequestModel { Map toJson() { return { - 'name': area, + 'area': area, 'system': system, }; } diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart index 766e256c..f0b7fa07 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart @@ -81,10 +81,10 @@ class _AddEquipmentScreenState extends State { setState(() { equipmentTypes = equipmentTypeList - .map((e) => {'name': e.name, 'id': e.id, 'type': 0}) + .map((e) => {'name': e.name, 'id': e.id, 'type': 'generico'}) .toList(); personalEquipmentTypes = personalEquipmentList - .map((e) => {'name': e.name, 'id': e.id, 'type': 1}) + .map((e) => {'name': e.name, 'id': e.id, 'type': 'pessoal'}) .toList(); personalEquipmentMap = { for (var equipment in personalEquipmentList) @@ -230,7 +230,7 @@ class _AddEquipmentScreenState extends State { setState(() { personalEquipmentTypes - .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 1}); + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); personalEquipmentMap[_newEquipmentTypeName!] = id; _newEquipmentTypeName = null; _fetchEquipmentTypes(); @@ -538,7 +538,8 @@ class _AddEquipmentScreenState extends State { Map selected = combinedTypes.firstWhere((element) => element['name'] == newValue); - _isPersonalTypeSelected = selected['type'] == 1; + _isPersonalTypeSelected = + selected['type'] == 'pessoal'; if (_isPersonalTypeSelected) { _selectedPersonalEquipmentTypeId = selected['id'] as int; From a4e0fbeb2adb0f335e48e9bdd91109272c82a221 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 12 Jun 2024 17:45:44 -0300 Subject: [PATCH 222/351] =?UTF-8?q?backend:=20corrige=20associa=C3=A7?= =?UTF-8?q?=C3=A3o=20em=20equipment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipment_photos/1532550872310.png | Bin 0 -> 618365 bytes .../0022_equipmentphoto_equipment_detail.py | 19 ++++++++++++++++++ api/equipments/models.py | 3 +-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 api/equipment_photos/1532550872310.png create mode 100644 api/equipments/migrations/0022_equipmentphoto_equipment_detail.py diff --git a/api/equipment_photos/1532550872310.png b/api/equipment_photos/1532550872310.png new file mode 100644 index 0000000000000000000000000000000000000000..366b3695b144e9f120fcfd094277e7bc7f8d5f7d GIT binary patch literal 618365 zcmX_{Wl$YF7p)KO?(S9`3dP+i?(Qzd{lLMByBCMz?(XjHR@_|<&V9doXYR^mlG*v0 z*~xxZCK1Yt(kO_8hyVZpMOH>a6##(wZwi?M5BuNn%GxXk03ajVh>I)Rn3(|pG7%|S zaN25PxH)>N@UrxQ@G=NpR9$k{szt#u@zC%zVq(g5g;9vrFgQ5qI0g_Z&di`noQ`_< z)$@U%@$q);+Wb3O7&qXh@A2Ewwy@PS?|p8P>v1N8$XIH2T3YZosCY(t^kL|?y2L~^ z?Z46h2p(lvT+sAU_?=&2;Q&PP9C+t&3&k4cu)K+Sxo7%IOJ(;?f*25Q9lp>?smL-I z2I%GD@$QENz>_Hd;;A543WB51#1abvpgSW>$D5!aC?=UOmMQ2*3l9nb;>+xt*`NUJ zAwMeOlOXnIX^0(Q^t%Cjw&7ho8TKrLVDjeisceKgOajw}#_K)vcV^3V?VsHIX2=sR z35dKP*6|6GnHS-S+a8)9(gN+jA&3`$n#e>`TWPnvUSybH{tSICC1>V9G}Q=ay=hbR zMd(gqK#fS-Ky#A#thEFo01#U*Ky2Yn6qb<<`@qB19OmR#G2icrM(BieBR2gR?03)B z`Q&sQ`lMFgw5)E}D>o8>b%org?|I7MCMEe{ksSM&76Nmeq3n92&Ms6Nxfv9OXW z4@2zVLEpMK;$N`z;J5vfHB@ZeB(2yzgdQ0^i@wCe8IFB@!tRAlT7vLR9?HRHHaa&j z6m`prCW;Mm`iBMyKnmZLf#MX!{pX3=h}>&bpF!RWD;5vo)vH@g@F_2JvY}a6%H}~b0S^ysS);ceod@Qn%fJ}nWZ|^cbC+Q$M3b*B(7u6vCyBXXig&{ za1dY$&mL|qG#m`2bKFY-{T@(=6#R>HQn6o>?ax4qS4c^SV?R~DyBO;(w+uk(ugZZY z=kriMS$cyA^hxMk(7{>}Fqgy;Qip%nzm@I)t!@9OK9cr+EE|^5CehImbkhzecAWE@ z<^4AGm_`$(mK^xl_2OzREFQz1<=F@JdWh@uHzU7tY7J{yS}Ev9hsYiK^UP!>)q#~c zZmr+0Q!bxlKQq<=zBi!+z_txs zAn5*m*$oRaGa#<3RW44l9{~aAMG*XP{)ph=Lvp6a2|B@&_QF92$&w=u2AZ>xXbNB* zh)p;23e@5!gE(qo?ExvqjH?i5eTGheMo{5DY#un4U=}Bm%~0SRoFPc`1{IkKUQEmy z9%@eF0o`m6vrbwcgOwT`LE1FloEm3L1Uei)BaBIOJl^Fm=34A`@*Ya*c(qZm>a(Jo8WMHtd=FGev~T8jA_xDnwZflOmWcN?ELw{DP90HY;>Tfozu~ zLBu|W8Z{bW*KO9J*5UYq9mUa#Tu+fsH6AQ|&_0S1gA(f! zYv$dGD2ot_g5|W5VUux^6_P(v*2a;NE0W{rchpH$O-fhGB+3rTE7jMkTvQI*zR500 za4Dx$w5z*^o!KmnAV-^w-dH)|%j5GCSP=kiCAid@l$)5EIGVa_vu%UV%FklY5Y8Hw zDi-6)z#Qzw2%O0o1Nu_}y`6pU-xaO0u6edwarU2L&I!PVW}Uy|V3i8h3uX7)hv^hj zE%Pj!k94PSr`~j1Q$v@{t8{d^bgi0Thbv;h_%j4Xe4o9~{OhI7p&rps`j;f@BAqUk$}!&>gT=~~#wrbqFI zq1TN^{>LzspD24MktlzVuZUEU4Nw+PR*0I3Pzg^7B=M&>ZNImhRT^K|X4!PkJ$K&v zVyPubBw5fh)6OVx{S;S-`bng4oiR7rIdwN>J5`;5HDxf>Fx8UosgsARh?{|{fvd%& zB5x(%eTX~Z$QYE8nbKRnd{)f0w7$|l+dlW2kFr3!S-e_2baV(^h`@lgtjHt0TTzwd z5PPY((cw|HCejuOs;4(bT)h$Kn3$aT#psyW%5!$NsSUdU5A{WOtQ$*f9bw%B{+s zy{_Hy4eM1%Vj`&@&Sexr-^i!J}&L|#HT z4B-wzRdmVt9x$K5W~*(lZLwPD6y;>%r3X#O&R}XV*d?9gY~Ii{~!9{+&4XB%ER-bgQur;$=P#%YH#TF58L*r(jqFKqBi>=b4){Oy0wBR@q4 zmxU}2E^d|OuBlw;J}K*3>$B&4EpLT-!K_P|T`eG97y?q6nWBkNYD)BbMBzNj-iHC!v!&#u+x*3Re4#6cPd zdG}Nn-Ph-mf4PM6Cp7(unG*u34hFHT$E?@-yS1kGku^n|^YRwW7C08US}vM~uWMJw z3c_)faz;yS>)!5DgnNX2e~9e5R+iH$lJQc64e{!#y#?)nr>`wUmiDdMyV`Z`R2GF^ zX4l0x3RRgg+%;?OZ5$1EJ}E{TYE2%Gk!N7NRy`kEKiemVr8V8=#|7@sX1$HzmLDsB z7Bw%(ujn^#-K_ej?|WK5=WFHdwAQLw-Gkb=jX#^~ zK=b*8C+@iBfu6N@O;7bN>m~L_vx+#iSXMzHmx7bQ*|E+fs1goELC4%Vf3Vy9sht&V z?x_Hg$5Ve>o@>d>TaK`E-+ljF)D>QwzDw7l%i4>JYna-|?oPJ9@aE~}n%~f&oL!op zmL7QUYF}_$u%sGRU(l2JVg2wtVWs2O;7+T%>)F({-lpEnuj5UB_mZ3Gn=MH&JXr3U zai_*B?<4Pt?C8t-%>7F6iS3`sNa3oqCz&g0j~AuSUE8lywe^KaE1wy{oDE^&b+W~s z$B|^=SHpuYd{X)swZoaSG)q>rkHdSd5#xE|9md^?gcZge>+Uk&^XFzDz0XNs$EN4N zwY;#kf9coB>+O5Vpwi55DI*C`fx2l&1?X}J z@Vh~XPzpQ&zi* zz<&#{rt;DffUp0xysna@{~`!ZGCHmR04~k{I)tn$_04}lI5$}ZNw_^Y0&Ge$-uGk; z000%vM_t=Z)x?wB$>o=&jlBiAo41n%`TxZc?a-j@XhtK# z9fBd0GHhcw3HoYl;i}HL&r$gT%R94c-i*(1DSMLjf(H-qYU(Pz%afB z+M9+M7X)lZSSXO+C;X?c@9VxV7ru{Cel!#v;>pLI2Yq(l;AYQg;aqrb;uNdDb>5&2 zd>yZR1yJhqd=J=>i=GKnDVHjHc_DQy3DuNm)$@t1iE!Kr5ehd=ra_zU7i5c;Ge2|< zTzOg3CT|cC+Sbw@P}81=YUNDcU_;YKpq$UW2lprN9L~F@I&~V!%$Yn}9Qs1oQ)^PD z9)9>mfIOg*KK`7L68AA%r=a0?GL_?aS=L=2JMhX-A3wMjsQ!!;fcM6y^-$@Aka(Ob zafYX2!qZ{3+F`&vMu7KKB!P;reVn~t5yX-O7KpO8+!j=*f>ctrZ1 zL-O~8m!RA6Qn8zwE#{Z#*~U177Fe>HOz71OrUG*+?q=uLm{MAb&MxpUbCTL$`;|#Q z$|~FSt)n4HIAp3Ttws;jT_t({#*tK?z|UaaFz zg!obm;}ul`QhUkKTNh8PYyxC^R7{i93fGk#IL|j4``CE+hFK=XAmLM-$c*J^S2$d@-=UE&;o1I)STst&J z!1sRdtmKs)O`I~&U4x%})bp$5-&_ANdfc2eGOD8LiKZuXX#98iXVOp+G zIA8kK^`M36617RgjwgI%?-BYh{UD}DnOl&B+MuWc!IyLVSAnOjv2Ib_Z&!38aXY<_ z4*h8ZU+7cUZOyYmj6ksTI}Ol3W3uo`%g&$Z0ONu1c!zb=Z)C28xxjlj->o>GIB?)u zi07gG$KzC=;K0n({(e7N#_dGh`V7^7wntM+!5 z@>i8(niLd%LY$>p6@3WPxR57Ps?shoF5bOxbsT++dC3zp#IFgvk|TS?wSx6f<8{J*^(s)2yfnjBciZwEo5zsG&lwNH zP&BBm__j=+eN#n_bl+vE!0vAvrAfRUT@CFTfvKQ1_-a(+OR_3NXU%K~F9qQnR;$G@ z^#59nXiO-f8J`(3pYb_yj$N>5u;{jAuk`W74ab(dcUJFtFxD@Obkj=q#+M?^^i1%OC&WJPF8SKC25K)c8@l%7T$Q}JiQbu z?J2TlUpPNybvS7uOx;knlC(76+i9=-L&0~Dm1)JODrzE@^Pf`hh&*Zo>hC)-*FSRz zcYOJ;E^?5yStv&j93(buxD19gS!^H0;F~O0lbw)ok+9^7c=o7l>=oEFV2LNqgOrGA z>+zH`tzhpmj1Lw;vDv&@kx~7P#3IyY8ji%-r50}U2ETdW?;l5SRk~5nI}$Owg7_HL z`?f)BAis?tI~eKpqyFZ1r(>1IVug&~uo)~Fuy;{%>gx$toEpU}`A$Ez)jf+Y&;F%V zGgCBvqt?6n4Ln+nA^5F&ZYn|#;ReAztUsd6>B3Dt;( zU=C@htNLw$BJ;g@Q6>`>nh?XbwO~%A`O^6a&3iS8#A;^uqL%srN!ZvP{IE+h;z`Lf zn-+}73F;r+{~N{!w@Rp>^s5`LmxPE+;R`BXO<-xA_jcl77-8>N<~Yl5m<)-BX|^St z6nN9-HZYv+#uU!&*SJi{dbxQK5PXNiWv|?IyL6?v@wRE#8U}K`B~(aX?J=!S(96D8 zLn9*?JvcXtM~Rz8KZDQTtumpg96$OVuSFq;S|>y9E@sa#*jHzK%1J^kM5Y}og%A19 z+StcDoIL4|wFPDzETfE#sI4@=(x^!t?E$)}h+kON1V$-Att498OmDToMkiUxVTSS4Y7XveUnI=qEuhb!o?`5(#%4Ib%g)}-_ z7p~p4ofw6=dG_X~n%|;5Of3c>>d?)s8d|o42knLSycN>4hzNP~WXBWR%?v&j@;g68 z6~oW(OT3ei`KU=_n)HFTB)CF4v?dUxO-%L$bqk0x-LYXaUMdWN4%GLQJaF#u zu;$XG4vQ#Kgekxok(NwPnwR2Q@+iE@X6KEOi0dzJc9+_bcUh%;FcVQ<%TLnO1Y6Uf+(d^ zI2mH&EIIpWsnKAI{Um&rRx8Cvh%h7SN+6l_E;ivQ3)D7baHVye*>=+rtzvol4i+)# zkY+;ToE-lO#^J+;*-=|i5`~QGZQ|}(GWxRi%JZwsbW1%=E0TiDDg0XLd)GoJX@9V! zCKtGS8DV-BL-W#YA!wvxQefHR{GxQA$>0k7NZvixxABk`P~GNyaYb%FS6`Qd36v~U zIF{|JO-O!fhgEm?;Nnpw!C}v?!6!h{vD)8=T}&@7qi&Y7Sc@PjF$7uImDR|Ja8Z=a z8_S!~tgohzra~)XA&Z00(B`M9nB}m4iuvhZIRRq2C{x(SmZY1a{G6ESnatgY^4v_& zQVluy6FCv!b-@ama`+>UV^iVapsQe`}jBUT1R!Wws>R zk}SBaEDD3|12vISn!ZnjZ%1>+R{s6ZxYQ!oc0c0t;np6G?DI6d{r=;3({P>Zl~)zf zi0SkoL_|!4B$jK*vOt(cdc<#+gC(oqcV+)5)Bxj4ik9(1_Ucg2X^bd@L$%@3g)xPB zNlwhh9Ly2xE?m=}Otu6k8OgvzzZa@?9hp9{M7(hy6{vwtaZ|kVdzCZ7q4d#8oQ>Gz z@#P|F{5aX&3Dr2SQv7b;g}E$J442~z?e)7=5Q!9}V^vP(n4?xfU}K#* zc<6L{NBp3mpFBbDQ28#x$b=FM%L6psd&E0wxanON<&p67Ii;-Fk@bx(4MWA62`EU* zi3QNO2~>zZc62toVs5sFH|1g-VyI>3D=;uDm?$nHec@N3S@?1gc<|s@uLU} zL&2p;S$Qc;MgG%>ixo_X*`jUkiwV9zUd5YzF2}f;^B3iIj7G{~Z8FzX9BSkYm$`L| z7Pb7e-$>oD@q=Bl&i+Ja_^xOOa93POtTkF_HNahG{Tm(iywQregRuEgnCe@PuMc^c zSN2U-I_IF18LeYJ{)P~7+^KjMu7U%#f@DGj=WN;@O+>j$K?G$5+#JJU9jrI3nMoe~ z&3sW%&mk~%OseB@3l*F8)k9TNlA#L5=eSnvtv{bwU5=tc#uc-+c~cvyEC`A)9m1l* zWNFSewhGk-ov8)i)Ur`(uB4z*@z0}$`okQooD-sMh;DZ&NmTvy^AKe~v=6x%XMKgc zxeQuT5RLjf!~&h)4lX~nQp``(vg7qxtJSJy3T_cC{^j^}j|V;*EBcCIW9{~qoD7@W zK4;w5+N+=vrN7Lo*o0vV+$p(o?LP`I8N92V$wAnMfRST`r9x^EYMXf^^0)krgrWw{ zS@OoE;NRl<@u0Zx(wAoBX*v_A*m5L1X1%zDV2*T~+62)d!W)9nLSz|;-EuQC;jKvl z{(5GVryyhn-+5*dtoGbRqIc_62#m2m-g{M9RvEQC_q$;_NEXh4Q$ggkS0Q%ITepIH z;7PbAt_QqttB5pyKdEOnM}xOc?CH}U*5Rervje-wlIzykIhb4RRHRZbL~+MO9Wo~Z z1cF#`0eRGvP_H6pfgiF06idzI3Zq5)ze?TneODezYTQBx-5q%2n&8Cv7L+SHp?oMV zVp98P`nZ!DLm|bs0;_hn+dLRt@z4mx8eEiM++?zYiDyxoE`BFpmuGq<(&M15=NybC zcCQdSQQLI1`v^5Jox;@_`nN3CxG8{H~6!5C5;P43$)^2*|@gNt-0~Bbn)YV zdHe|A`+5alZNw>`)>n4A6Aj^qX{+yItFMz9^1~HR|9m zkO0&)^34}K40J=wzT?djw{Xy3e*lS}J6~U{p~2yMjDLZl<&f(@xIEh(d4Vcq2+_rE zGPsxA0@^!?2a8uc^v8zK00rEcVXig90j1=(RHo!gnCld+{M^}ou=CF==)6ZQi8o&L zlr7&HWJm~K_k!Go>ra%6rWrGzn_tFCU1QghM5rgfPe@PUq)FcWLJS|=6$Swh6xqCp z@-y@LPgaYE7K~`n1?R7o%x42Yi^d`mvafa$dQVpiMN%mU)dMvtIp-Jvq}zu8lRrjX zeI8k%W{ggw&_bB1h6q!Pxvco$!4eLcvPcUbbe#CoFxdU2aFVNggYuyj6?(JJFrYcK z;?ebRTL~H?P=s5n!mi7KV6oP~V2BG4#lv!CP`0CGIKZako08o|&itAcXU05WRZtVh zCA9vz+*+a-*W6aP8%ufSfpX58=Cjk=)Sz-X46n+ja+wPeTFoa^P|S#!SA%$dEogBa z!dxkJ68YHLL4!vD3Qxd<@{bAc%ZgVG63IiRZnn#!n@whBq65Ku|A{Zg3U3gciC#F> zA}IujDXfn$B1u2@e9e5Joq5;#LMtK+A{Rqb%m2vTzIl4Gv?+@IuE3M6ILb>^f~<%z z3B8A`sJ}tPNi{6PfOyNUB3HJfF-T+OqJe{rk*rEyl-^zO2%L-_EL-A&IB*{~cb`_V zq_=k{btg_%Wy{@h7RqJMo#Dxyq2~}k3cC0H{i=(FcBNQK<3HpWXXTBB*MWFti7E1l}-zN?%aG+iW z=`-@f<0C=+&Uv0NRaIaQjNv1-Jo*X3Bd`i??XBs#>M{4TA10c|PV~W+n8ccun7O|0 z$IJSFsh9`z2UoRw2UjoR`7>JTDMsbtY});L*HR47+YnhA?(oUBHT4p0Ij~yj931nV zOzx}z^=f2J_sy6iHZE3c#l`u1>~+{gXnM0`ADt-9YJhVgS9JbijfHzp@WmT8lDm9| zTjB1*jPjepQykFAfr?8(g&yf!U)hK2Ef?-t^Qi0^o|So2;%AWt6&7W+c5PV+L5#Xd z)XcIq_$;0tJ}(7YFs?c8bWca1i=@o3t!22L+xl_p%v8{ggj7w4AnwR4t+DOyy{T>3 z$FPp}8zi2?#=w)7=7x-!NEpDMXv@~qQ7vN59kH(Cs=i61*HQ_MWFXWGG6gzCUJ)? z$XMI3Bgw~b*rgINu1z1$!_1EPfNR!9LT?kAiMYQO(hL{#vtV3P$Je2$Dn;UURh!l-Jb^nKvqj zjSy$^%1k>duS={KB8DV0sgkU|OBU~GNw2=5na%B@}7MY5;%rg)&z)zZlan?O& z?vxCeQ=7Ze#p5=orG_`6n|mtvE-j+HoL%{vBV_5F#|&{&uKh-~gShgVAdSmo_aOec z)Oa~*G7{av08GzlI!qS&dK$WBM$_Rd9P|(PJd{R>wQoL-}X)0uPA+K=S9FQ$7<9*_x=r;ffF5?yp1OJ zJRw|%3KG#Wi2BLpn;e59@5!Ug=4phTRFzAWv!gA)PQ=}ESH=-u#21PuFM`FdU z^W$f45A+$~S#NJo$a2`Hf%e;*HPJhN(dn4z7SYM_ZXUmRU+;ff>6a0Di;~>LHNp6B z3sKRaR#bMtMN!U6##HL9q+DCJigMg$60q%YI1k|#{B>SL7!r|+2akmL_Y!6iU1hk2 zedR5qRZ7#a157g4(&K9^F$HG*506lQDWdvOmgX+xmo!VVci|YJHRe9Y0s`ozn#aux%s5E?#_8+_U#=e{vUwfCoru*A&1PBYn;+t3mx= zDfOB1b+OZ;AR{9`C|_~$`5Z34HZ1)9vjU!&7 z+^MEfsp0I2-`AM1e!Lj4&h++#*4T*e5PcE8c%uEb=aV@09h<(~w2teIP^YA-yilbA z;LTKyB*Tl}MuQ&}&uA2zrOD_HvSQvV5+KOMrjrt)*TVb1D-5G?@V|J9<#430lFA|s z5=am@RSaZts&WL)F~M`HP7579PrSvxLkaObYN=Perq~dPEtex=1_ots*ZY;)ZUrcv zbBQ$NVM7dl9<6N#Qs4?bQ8fy_Ur8g0zE$e!n2M@R|Gk@VU z5oZ^xn`@$3Q2Eo1Iz$X-N;RX6n)GA|q6bNm;^RLPkayY7%lUSV??5w456q!`Xg^#H zfLXLtqqfuE=?zs11HL^^NGR~kV{9(x>w%5%e5q$HIeJ)vI_o)@=ms>mV?yUmRe{*J zTH$QC>b(|z<K4BkK8?{B1@4v90H z3ft{;;9*)2x^xKLltbJ2L*JPo2MZ2p+$opoJ!g1L`-oGMH@q4rYZP-Fb#k|ZhF_hb zYLvIhM}&!hIxBE;^lspP=>MF7gwImsAdwU{NM!@=sx5E)bK~qN3ee~qyTMNdY=xRo z_%+!^q0f^K+|-KA7+-6L?aD8X^h7daQwbuh;Eeci^enZ*?C@TD$8H8*ULK2sB$E03 zt8IRTU!!#Vn^bT9{+1LxfnN0(@&DdFv*YXAz6fte+0ryYqxa{B6qNKY5Za2RkC%e| zP^J62Ktt$Slc~*`71%#=;R{YFLugnG=ou;p`V05y4gxGkcX~$zhkn1O8?Zt_xPg-% z@G=KYB&w?99+4~}w*^zf7I!}aZ;_#zazvV&F}Sq$K5sHgg<0d5$Xf7x<8%OgpZ+UC zY;0^d7?pH}g<5YpH-Yy9Ca|CGo&5#w|BS-0QWkNYh<#yRO`(KdY7G9cq7ns)Z|#VN zAYmj_2{vA)4c6*`CBPpyg-AbcU{)!W5k7M4p}q(U@C>bc3s{@Te7n$IdVKj7^=`J* z8)Aey2@rk0YDkn$%*GEpN0qLoq)rYQHg--!PLo9#MAuMX|I7M=(-48|_{1&wNs&MC z_hTbXGVropW*N(8e-6N{Y*XV6sL93W&-AS-5m( zk-{7O<>h*BYdaTjUuE!rpCbl`WTk~+R%HfM$0Qh)vM%He4QteGm+X8|epHB5a6XnO zsKrK`lTiZTV1<64L^wWbLqX++4`Jv@w@(GG1e@-t(iIzlb?=}*1+$+$WoxTkM$8ww z(UARMOR3(@(y^mJgyVP|=17U{@cI6A%4UrnTI+YAhY3FFh~Wi^0XV|-daHkeW4%v% zX`YxK%O&S&Re}s6P5zNVya*El{Q}hNgvl89h6;griY9AY`kHv{MHm;l(hm0-wo@q2 zt0w!ADl*74XHTU~#}{@)C5d{U7)fNvi3xPvT00DH5R_OhIc(V%NvieFYpB?~*@^~j z@Mkc^*uIE=U?^XnEyL`eg}f6u(?~|E4wR+GU#lYi#^eJ_NtY6pk>q%xz8Sg+N8s%_ z_JJBA;xVtfkI$a$9J^AD7$64&seUR#wj)rkdy)32lnDUH<2lZpu3XrTmJdZr`Q}yv zelgn|;)Xo>S7$;GTyLd6tHJveSr3G~JgQ8^{zx{PF@IL6%-td52|O`w)T$&4i5qwM zM{qKh+Y}1~ex#R^tvp(crkj%jKhGyT(U$AA8}SSjv)>dF-49h8Hl`a<6IupXYM&6` zA7(s)k9S}3SA!Mmo3DXhEeRx*vPoRSn@3h4KlX+4j-^L`5@Aj7#Pe%6iuEa`2)D)c z8&|v+{+HTLw}|}8wcVB@Kl?^RDB>?_6)7n#ZPyJ*<@>^@tkvJgwZL2nY*joNlCu0# z@2d&xp?2CC!z;L(G6xp(j_zi>qr0KGy4(G0$}jF1ic9Q0nD7v+{f31xR;;-cF4KYr0jRlSv>?kM%Pr@Q;T%9e zKWXKN&IgsSSc>u{Imsa@L$f*bqbHLfRd2gP0bDCPdvN}v_HXZ^n|Cu~BF}$90TY?J zviQ*P1?t(g?-j4DvYTgr0QBOGB33ZU#wGHcBC2SNWn9x+uAiZa1``$h71tZ&$gW4Z zF#Y?>?0VW@{KyZM1|xH{Y{F-zhxVm_o~<1qu2A)50PFRP6l;`wT&<^4JHIayF~Hr$ ziOj=N5YGe>JS==e*s3v8WvGD@$Fc`&2qh1RiZs7}zkcJ>`5!q!eRG$kw~a{+kKVio z2EE%l1tOu>9xM+&A=e9L`>{*he5#t5@9@ps0vT#P&zh;xu)jMc%vtSg8ZO%v%uG(K z{QG3T2?P0L3&zH4v9?%;eUJv6Ki=eIc^`-F3G1=$V8<;lAG(HkXp3)NJ|w>?od;hq zHl2Sq3H4;O9ZJFgn$jTIrL5lrjW=e?Zl%s0(GvuMlYx4Ou27bif1#Z$$;6?_>wz#C zlXr1R384&TKEC2|W5Y=GSqwx7%OnBf!3r6IC8TEr6#-!0WD_?AYWAXGYaK0G_( zN4_=-tu)C_zYo?u=?XXJ7lL-87#Z3RHYezVUuR>??9FwyzT4Lr*nb%(FjVPacA+V> zfRB%w!5*@POtc$vH+JwhkLmA`;R+4aWVjqRzsQH>Lmgug;?U!vgvrhDZdr242mlJy(fX9rX^d5D%ynsyrXrP^ zs4ys+-ELm8l7qbcM+DNOHuHNYv4$4h?OyP|;aouyr}{(lE6b}yrt{Yh3li-xRjn~$ znz&oIp5qseNo5#AM4+3ocR2TaqZ+1sO3KifirhJZJ&@S*`KbZ8fHo%DK{I~rHZ{wN zl@BW<#{ZwZ2rA(wAFXkNZ+lmEPonqE&kp$zTGMuFCU$x;O?H?4!JctvE6kPWo+Q6A zDHvxtC=ef9Kdy>?hdHr;0+*zB|+PwY}ar(r2j4e8kjW)}z7V$W5BdSQ`Q!B=H#G$VB zQA}P+y#9e*1A;og&?W?f!k=Z^q_WE3BjH1^zgaG6`T40mCV36~QH;S1Db4ObuWmmo zI9~=ut}4!4;Xp)SxE=aE-OeD0aB(Qx<&1@QujvCdrkb>%<8}UM|u#^65pN58zail5*l~ac035I3{zrJ!WjY;FtUYQvG zPygJV?4mgzD$1U4>$mR}GSjGbdaZ(*{Rmm}ma-3=8?E9>$5S=1=ll)vus-pI>bUQ^ zj1P>2M;tC+Ihi+JTbVCqjPl17uzWOGEWze0K3dtUZd;Z7iv}%BseEKw0GkY!7s!F2 z#4xyi%pku*9ZOM_CWZ0P%B_qDXy~c=zbo;;Q=9hyVo#V)`o=dX&_w7dZn(Xu-R|3Q z&3?nWMixkiaWBxk05}MiuJylC1+&%!a6dT0_z^?va=}{RkT=I2qmm|owZ@(P-Onb0 z&Rpfi&KER*^fJ|YP3uiUN?Tc0Nucfd6#TcvH`E#N#+2F7Pg$Z=_#1&4-Ck9;Gy*2* z<=Mk&$(+ug>-9Y)ZGF;Z{ljGS16-!jsv2}qQ5|>X7!G^50p+05BwI%VJWY2+Kq^OJ zmeoe)f4jn1m1I|N^+%5_H>NZAnnBwD1nkkbgbarW)I*&n%@s6V4nSl$qk{|ctwAst zyd#)F$3QP3g#w*<2r5Sp9K1a33@(xIAEA2~%=x0l|C#)abn`!q?48KVMcxr+CiQ|P z)rt3`O&uJA5UAy@hhfX}X=BaWPU2Rkd*Juf9sP3(9~9#s$?g+p!TP$ZJf{Aio}%e%JJzs^}OKB=3GIa)tF_B0v=zDDP)kQu>3|U%EIJ zN*Ge9^lSf4fjLsCR~8b`-pUt2&~bzO{62Krm4$VJuJL#^sM6572z`a zCDR=nqC+uUIwVk(`CiU1N_sUwIy#VM+~Eg@#C3y?$~eXH`bT%VWV0Nk$yHJD(#ra0 zkFrlFZKcsLsz}o9DP&nfdEek{Ms>f*VlUK~Ls#Y~_BMLGWSA5L++x}Pu+AnMn(8E* z8V(G7=@qoT>-;;}!&*J%JoVe*`cs8~7^Nj{C9{W^LE}7Imu}0k4fxL@t$}oYtZJM_ zrik1hn_Uy1NxB^HUR0t}W=n!pi3TQiH{dJ#&%0AC#SLeZGm$X?#iW~A(r5TOMGG$> zHYaAJ5~wzorS%W2(~a743h+mFawe+gJ@Qi<>aWq2r71)UCD&eH@q3BOQK2~WAZ6~C z$+%B&G4_%JV4%xPzCS@DV=&}>b}ScR#p+I~s$hf^c3{{wyxVE|ouEVh5{78i5ISoh z2hnSoE+4_)#!q~zm>PH3tT?$lYg6-#1I^TuVjKhr^B@kii9mUfIz>{A$Fg5nHD^EZntwH{* zGD56lTucog2jNnu(?Gj3ay+`56~3gpb{6Ts`Ch&X0>@QSoc56bwUS13$5kw##c{^1 zU6PA8S_sghfMb zBrZ$fT^VE|Oy2q-h7lT>=@utUXI@3|3B7Nww`X8>X&f=qB6?q-6#;cY#(z_7rOdPb zl{{mvefkvvEQYCJbP?Cf9?d^^`i5yJ19rcmr$Jx)~hC}E1=^Vnke{GQxG!^C4WjX->E4|^mB0Ucgv>B6Lx*V|c zbbksBnPUgR^B=eWPd6=jkwbr;oQVSx+#|fM7zhaPPiD{mws$Y=)%W9J)&*he0S1Kv zqPjZFV6Mn;weSc`Fu=IFnF||%JzRc8u*N#mpRYYpWu6R+o*{=Mi@?ziLP=G+zO#@$>_O~E2*N@E9SqhE8@#TXXq+cf*?eM&y#7t?F?fCi z%vqoAESMl@%D#tG2YY2HVp|G1a$c&#|6buNXtQ>9?1ZY&>y#)02bHjo%q8&kaw)zk zhb-m1LXc)l8f)W3W)Cv@!^0Zcw;vwtR zo(oaAhCSvw+U(YA3180|@sE$^m!_%6hGi-moODQ7r zgO=yf830r0-qPbC$)NkUju_!e%E5P}E^i_6ts?yHmOQyYYiF&!`Is17St7Ef%FCgB zCPbXyxE=C;;UQ-Y|GHEEGmWC*D2QDRfJu<9<+g-2+Ji1OH{l87C4d;6O6153=jLgscd3yR=xc{2l@*#mdQFUgaF@5)IQNR#FjVDVYj559JPKJ4;%0| zGaCH1uY+EKs{v8l)d{}Fh*=Jmg>xWlANu}5ub=4(2A94y3O0V?VyCqg8zYkTYkLZy z9iV_UTXR8iC!4~N?P%LWC=Zvjmpypke%%>ESRH}L)eVjJnX%kg{f536Ji+1Pp=Stm z=zf@f$1!PBiS&_)zm^WPkj(!5EDZi%D?XHc{}qCiib^{6NWIA>P-+;~m>>iOi@#Z- zS>z3&T9j;9+Ny$UnsR*?bobNu(U@W0&)fdCBbSf`^c+NUFre_fgR6mtFz3DL7DR~+ zI_2}v9Co2UhgmzUoP%i4w-9r}0z0-GW{GM#MzNIrJgR&cYOVeW~*OZri3n>L(JaJ5h_pM!z2>I&{EqB~-Q_k+gf7wQchee31qi8% zIKBqYCWl3zU_}>5jt;L4U5>0O8knR@nt({5fYnQo7ov(?248Fg>-76ygN9#UR3 zf#BzwEZa_xBA*!Y86`)BKuD&;)|XOVgRxNVWrv_jk)(t%tC3*W%9cL`qUu`(%V3en z@g8(x2cfi&il%$S%LJCBHeUaj7-av_ihSTy�N7O?^lQggAsnd5EMWhtEwTUznZY@WxF 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%0DuqvstU3nRV4TD`xxI7;Duln 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 Date: Fri, 14 Jun 2024 10:57:03 -0300 Subject: [PATCH 223/351] backend: refatora nomes em equipments --- api/equipment_photos/1532550872310.png | Bin 618365 -> 0 bytes ...name_equipmentdetail_equipment_and_more.py | 95 ++++++++++++++++++ api/equipments/mixins.py | 14 +-- api/equipments/models.py | 34 +++---- api/equipments/permissions.py | 8 +- api/equipments/serializers.py | 36 +++---- api/equipments/urls.py | 16 +-- api/equipments/views.py | 78 +++++++------- .../migrations/0006_alter_placeowner_user.py | 21 ++++ api/users/models.py | 2 +- 10 files changed, 210 insertions(+), 94 deletions(-) delete mode 100644 api/equipment_photos/1532550872310.png create mode 100644 api/equipments/migrations/0023_rename_equipmentdetail_equipment_and_more.py create mode 100644 api/users/migrations/0006_alter_placeowner_user.py diff --git a/api/equipment_photos/1532550872310.png b/api/equipment_photos/1532550872310.png deleted file mode 100644 index 366b3695b144e9f120fcfd094277e7bc7f8d5f7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618365 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%0AC#SLeZGm$X?#iW~A(r5TOMGG$> zHYaAJ5~wzorS%W2(~a743h+mFawe+gJ@Qi<>aWq2r71)UCD&eH@q3BOQK2~WAZ6~C z$+%B&G4_%JV4%xPzCS@DV=&}>b}ScR#p+I~s$hf^c3{{wyxVE|ouEVh5{78i5ISoh z2hnSoE+4_)#!q~zm>PH3tT?$lYg6-#1I^TuVjKhr^B@kii9mUfIz>{A$Fg5nHD^EZntwH{* zGD56lTucog2jNnu(?Gj3ay+`56~3gpb{6Ts`Ch&X0>@QSoc56bwUS13$5kw##c{^1 zU6PA8S_sghfMb zBrZ$fT^VE|Oy2q-h7lT>=@utUXI@3|3B7Nww`X8>X&f=qB6?q-6#;cY#(z_7rOdPb zl{{mvefkvvEQYCJbP?Cf9?d^^`i5yJ19rcmr$Jx)~hC}E1=^Vnke{GQxG!^C4WjX->E4|^mB0Ucgv>B6Lx*V|c zbbksBnPUgR^B=eWPd6=jkwbr;oQVSx+#|fM7zhaPPiD{mws$Y=)%W9J)&*he0S1Kv zqPjZFV6Mn;weSc`Fu=IFnF||%JzRc8u*N#mpRYYpWu6R+o*{=Mi@?ziLP=G+zO#@$>_O~E2*N@E9SqhE8@#TXXq+cf*?eM&y#7t?F?fCi z%vqoAESMl@%D#tG2YY2HVp|G1a$c&#|6buNXtQ>9?1ZY&>y#)02bHjo%q8&kaw)zk zhb-m1LXc)l8f)W3W)Cv@!^0Zcw;vwtR zo(oaAhCSvw+U(YA3180|@sE$^m!_%6hGi-moODQ7r zgO=yf830r0-qPbC$)NkUju_!e%E5P}E^i_6ts?yHmOQyYYiF&!`Is17St7Ef%FCgB zCPbXyxE=C;;UQ-Y|GHEEGmWC*D2QDRfJu<9<+g-2+Ji1OH{l87C4d;6O6153=jLgscd3yR=xc{2l@*#mdQFUgaF@5)IQNR#FjVDVYj559JPKJ4;%0| zGaCH1uY+EKs{v8l)d{}Fh*=Jmg>xWlANu}5ub=4(2A94y3O0V?VyCqg8zYkTYkLZy z9iV_UTXR8iC!4~N?P%LWC=Zvjmpypke%%>ESRH}L)eVjJnX%kg{f536Ji+1Pp=Stm z=zf@f$1!PBiS&_)zm^WPkj(!5EDZi%D?XHc{}qCiib^{6NWIA>P-+;~m>>iOi@#Z- zS>z3&T9j;9+Ny$UnsR*?bobNu(U@W0&)fdCBbSf`^c+NUFre_fgR6mtFz3DL7DR~+ zI_2}v9Co2UhgmzUoP%i4w-9r}0z0-GW{GM#MzNIrJgR&cYOVeW~*OZri3n>L(JaJ5h_pM!z2>I&{EqB~-Q_k+gf7wQchee31qi8% zIKBqYCWl3zU_}>5jt;L4U5>0O8knR@nt({5fYnQo7ov(?248Fg>-76ygN9#UR3 zf#BzwEZa_xBA*!Y86`)BKuD&;)|XOVgRxNVWrv_jk)(t%tC3*W%9cL`qUu`(%V3en z@g8(x2cfi&il%$S%LJCBHeUaj7-av_ihSTy�N7O?^lQggAsnd5EMWhtEwTUznZY@WxF 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 z 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 z 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%0DuqvstU3nRV4TD`xxI7;Duln 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/', PersonalEquipmentTypeList.as_view(), name='personal_equipment_types_by_system'), - path('personal-equipment-types//', PersonalEquipmentTypeDetail.as_view()), - path('equipment-types/by-system//', EquipmentTypeList.as_view(), name='personal_equipment_types_by_system'), - path('equipment-types//', EquipmentTypeDetail.as_view()), - path('equipment-details/', EquipmentDetailCreate.as_view(), name='equipment-detail-create'), - path('equipment-details/', EquipmentDetailList.as_view()), - path('equipment-details//', EquipmentDetailDetail.as_view()), + path('personal-equipment-types/', PersonalEquipmentCategoryCreate.as_view(), name='personal_equipment_category_create'), + path('personal-equipment-types/by-system//', PersonalEquipmentCategoryList.as_view(), name='personal_equipment_category_by_system'), + path('personal-equipment-types//', PersonalEquipmentCategoryDetail.as_view()), + path('equipment-types/by-system//', GenericEquipmentCategoryList.as_view(), name='personal_equipment_types_by_system'), + path('equipment-types//', GenericEquipmentCategoryDetail.as_view()), + path('equipment-details/', EquipmentCreate.as_view(), name='equipment-create'), + path('equipment-details/', EquipmentList.as_view()), + path('equipment-details//', EquipmentDetail.as_view()), path('equipment-photos/', EquipmentPhotoList.as_view()), path('equipment-photos//', EquipmentPhotoDetail.as_view()), path('refrigerations/', RefrigerationEquipmentList.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index 017d547b..80c2dff2 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -7,9 +7,9 @@ from .permissions import * from rest_framework import status -class PersonalEquipmentTypeCreate(generics.CreateAPIView): - queryset = PersonalEquipmentType.objects.all() - serializer_class = PersonalEquipmentTypeSerializer +class PersonalEquipmentCategoryCreate(generics.CreateAPIView): + queryset = PersonalEquipmentCategory.objects.all() + serializer_class = PersonalEquipmentCategorySerializer permission_classes = [IsOwner, IsAuthenticated] def create(self, request, *args, **kwargs): @@ -17,41 +17,41 @@ def create(self, request, *args, **kwargs): if(IsOwner): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save(place_owner=request.user.placeowner) + serializer.save(place_owner=request.user.place_owner) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) -class PersonalEquipmentTypeList(generics.ListAPIView): - queryset = PersonalEquipmentType.objects.all() - serializer_class = PersonalEquipmentTypeSerializer +class PersonalEquipmentCategoryList(generics.ListAPIView): + queryset = PersonalEquipmentCategory.objects.all() + serializer_class = PersonalEquipmentCategorySerializer permission_classes = [IsAuthenticated] def get_queryset(self): system_id = self.kwargs['system_id'] - return PersonalEquipmentType.objects.filter(system_id=system_id) + return PersonalEquipmentCategory.objects.filter(system_id=system_id) -class PersonalEquipmentTypeDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = PersonalEquipmentType.objects.all() - serializer_class = PersonalEquipmentTypeSerializer +class PersonalEquipmentCategoryDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = PersonalEquipmentCategory.objects.all() + serializer_class = PersonalEquipmentCategorySerializer permission_classes = [IsAuthenticated] -class EquipmentTypeList(generics.ListAPIView): - queryset = EquipmentType.objects.all() - serializer_class = EquipmentTypeSerializer +class GenericEquipmentCategoryList(generics.ListAPIView): + queryset = GenericEquipmentCategory.objects.all() + serializer_class = GenericEquipmentCategorySerializer permission_classes = [IsAuthenticated] def get_queryset(self): system_id = self.kwargs['system_id'] - return EquipmentType.objects.filter(system_id=system_id) + return GenericEquipmentCategory.objects.filter(system_id=system_id) -class EquipmentTypeDetail(generics.RetrieveAPIView): - queryset = EquipmentType.objects.all() - serializer_class = EquipmentTypeSerializer +class GenericEquipmentCategoryDetail(generics.RetrieveAPIView): + queryset = GenericEquipmentCategory.objects.all() + serializer_class = GenericEquipmentCategorySerializer permission_classes = [IsAuthenticated] -class EquipmentDetailList(generics.ListAPIView): - queryset = EquipmentDetail.objects.all() - serializer_class = EquipmentDetailSerializer +class EquipmentList(generics.ListAPIView): + queryset = Equipment.objects.all() + serializer_class = EquipmentSerializer permission_classes = [IsOwner, IsAuthenticated] def get_queryset(self): @@ -59,9 +59,9 @@ def get_queryset(self): queryset = super().get_queryset() return queryset.filter(place_owner__user=user) -class EquipmentDetailCreate(generics.CreateAPIView): - queryset = EquipmentDetail.objects.all() - serializer_class = EquipmentDetailSerializer +class EquipmentCreate(generics.CreateAPIView): + queryset = Equipment.objects.all() + serializer_class = EquipmentSerializer permission_classes = [IsOwner, IsAuthenticated] def get_serializer_context(self): @@ -71,25 +71,25 @@ def get_serializer_context(self): }) return context -class EquipmentDetailDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = EquipmentDetail.objects.all() - serializer_class = EquipmentDetailSerializer +class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Equipment.objects.all() + serializer_class = EquipmentSerializer permission_classes = [IsOwner, IsAuthenticated] class EquipmentPhotoList(generics.ListCreateAPIView): queryset = EquipmentPhoto.objects.all() serializer_class = EquipmentPhotoSerializer - permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] + permission_classes = [IsEquipmentOwner, IsAuthenticated] def get_queryset(self): user = self.request.user queryset = super().get_queryset() - return queryset.filter(equipment_detail__place_owner__user=user) + return queryset.filter(equipment__place_owner__user=user) class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentPhoto.objects.all() serializer_class = EquipmentPhotoSerializer - permission_classes = [IsEquipmentDetailOwner, IsAuthenticated] + permission_classes = [IsEquipmentOwner, IsAuthenticated] class RefrigerationEquipmentList(generics.ListCreateAPIView): queryset = RefrigerationEquipment.objects.all() @@ -98,7 +98,7 @@ class RefrigerationEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return RefrigerationEquipment.objects.filter(area__place__place_owner=user.placeowner) + return RefrigerationEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -129,7 +129,7 @@ class FireAlarmEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return FireAlarmEquipment.objects.filter(area__place__place_owner=user.placeowner) + return FireAlarmEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -160,7 +160,7 @@ class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=user.placeowner) + return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -191,7 +191,7 @@ class StructuredCablingEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return StructuredCablingEquipment.objects.filter(area__place__place_owner=user.placeowner) + return StructuredCablingEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -222,7 +222,7 @@ class DistributionBoardEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return DistributionBoardEquipment.objects.filter(area__place__place_owner=user.placeowner) + return DistributionBoardEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -253,7 +253,7 @@ class ElectricalCircuitEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return ElectricalCircuitEquipment.objects.filter(area__place__place_owner=user.placeowner) + return ElectricalCircuitEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -284,7 +284,7 @@ class ElectricalLineEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return ElectricalLineEquipment.objects.filter(area__place__place_owner=user.placeowner) + return ElectricalLineEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -315,7 +315,7 @@ class ElectricalLoadEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return ElectricalLoadEquipment.objects.filter(area__place__place_owner=user.placeowner) + return ElectricalLoadEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -346,7 +346,7 @@ class IluminationEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return IluminationEquipment.objects.filter(area__place__place_owner=user.placeowner) + return IluminationEquipment.objects.filter(area__place__place_owner=user.place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() diff --git a/api/users/migrations/0006_alter_placeowner_user.py b/api/users/migrations/0006_alter_placeowner_user.py new file mode 100644 index 00000000..437f24cc --- /dev/null +++ b/api/users/migrations/0006_alter_placeowner_user.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2 on 2024-06-14 13:54 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('users', '0005_placeeditor'), + ] + + operations = [ + migrations.AlterField( + model_name='placeowner', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='place_owner', to=settings.AUTH_USER_MODEL, verbose_name='user'), + ), + ] diff --git a/api/users/models.py b/api/users/models.py index dffa67bc..2648006c 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -3,7 +3,7 @@ class PlaceOwner(models.Model): - user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE, related_name='placeowner') + user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE, related_name='place_owner') def __str__(self): return self.user.first_name From b3f0c60120eb1574cd35f6a1c8d420c7b9479e57 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 14 Jun 2024 14:30:41 -0300 Subject: [PATCH 224/351] backend: refatora categoria de equipments e nome de place owner --- ...ent_generic_equipment_category_and_more.py | 23 ++++++++ api/equipments/models.py | 4 +- api/equipments/permissions.py | 2 +- api/equipments/serializers.py | 2 +- api/equipments/views.py | 54 +++++++++---------- api/places/views.py | 14 ++--- 6 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 api/equipments/migrations/0024_rename_genericequipmentcategory_equipment_generic_equipment_category_and_more.py diff --git a/api/equipments/migrations/0024_rename_genericequipmentcategory_equipment_generic_equipment_category_and_more.py b/api/equipments/migrations/0024_rename_genericequipmentcategory_equipment_generic_equipment_category_and_more.py new file mode 100644 index 00000000..1c60d476 --- /dev/null +++ b/api/equipments/migrations/0024_rename_genericequipmentcategory_equipment_generic_equipment_category_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2 on 2024-06-14 15:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0023_rename_equipmentdetail_equipment_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='equipment', + old_name='genericEquipmentCategory', + new_name='generic_equipment_category', + ), + migrations.RenameField( + model_name='equipment', + old_name='personalEquipmentCategory', + new_name='personal_equipment_category', + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index 1dfca9c1..f9b5a540 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -29,8 +29,8 @@ class Meta: class Equipment(models.Model): place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) - genericEquipmentCategory = models.ForeignKey(GenericEquipmentCategory, on_delete=models.CASCADE, null=True) - personalEquipmentCategory = models.ForeignKey(PersonalEquipmentCategory, on_delete=models.CASCADE, null=True) + generic_equipment_category = models.ForeignKey(GenericEquipmentCategory, on_delete=models.CASCADE, null=True) + personal_equipment_category = models.ForeignKey(PersonalEquipmentCategory, on_delete=models.CASCADE, null=True) class Meta: db_table = 'equipments_equipment_details' diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index c6355834..ae0e829c 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -16,7 +16,7 @@ def has_object_permission(self, request, view, obj): else: return False -class IsPlaceOwner(BasePermission): +class IsAreaOwner(BasePermission): def has_object_permission(self, request, view, obj): if obj.area.place.place_owner == request.user.place_owner: return True diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 2ab065d4..9796eb9f 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -96,7 +96,7 @@ class Meta: def create(self, validated_data): request = self.context.get('request') - validated_data['place_owner'] = request.user.placeowner + validated_data['place_owner'] = request.user.place_owner photos_data = validated_data.pop('photos', []) diff --git a/api/equipments/views.py b/api/equipments/views.py index 80c2dff2..4d714713 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -94,7 +94,7 @@ class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): class RefrigerationEquipmentList(generics.ListCreateAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -111,7 +111,7 @@ def create(self, request, *args, **kwargs): class RefrigerationEquipmentByAreaList(generics.ListAPIView): serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -120,12 +120,12 @@ def get_queryset(self): class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] class FireAlarmEquipmentList(generics.ListCreateAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -142,7 +142,7 @@ def create(self, request, *args, **kwargs): class FireAlarmEquipmentByAreaList(generics.ListAPIView): serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -151,12 +151,12 @@ def get_queryset(self): class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -173,7 +173,7 @@ def create(self, request, *args, **kwargs): class AtmosphericDischargeEquipmentByAreaList(generics.ListAPIView): serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -182,12 +182,12 @@ def get_queryset(self): class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] class StructuredCablingEquipmentList(generics.ListCreateAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -204,7 +204,7 @@ def create(self, request, *args, **kwargs): class StructuredCablingEquipmentByAreaList(generics.ListAPIView): serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -213,12 +213,12 @@ def get_queryset(self): class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] class DistributionBoardEquipmentList(generics.ListCreateAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -235,7 +235,7 @@ def create(self, request, *args, **kwargs): class DistributionBoardEquipmentByAreaList(generics.ListAPIView): serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -244,12 +244,12 @@ def get_queryset(self): class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] class ElectricalCircuitEquipmentList(generics.ListCreateAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -266,7 +266,7 @@ def create(self, request, *args, **kwargs): class ElectricalCircuitEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -275,12 +275,12 @@ def get_queryset(self): class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] class ElectricalLineEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -297,7 +297,7 @@ def create(self, request, *args, **kwargs): class ElectricalLineEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -306,12 +306,12 @@ def get_queryset(self): class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] class ElectricalLoadEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -328,7 +328,7 @@ def create(self, request, *args, **kwargs): class ElectricalLoadEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -337,12 +337,12 @@ def get_queryset(self): class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] class IluminationEquipmentList(generics.ListCreateAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): user = self.request.user @@ -359,7 +359,7 @@ def create(self, request, *args, **kwargs): class IluminationEquipmentByAreaList(generics.ListAPIView): serializer_class = IluminationEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] + permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -368,4 +368,4 @@ def get_queryset(self): class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsPlaceOwner, IsAuthenticated] \ No newline at end of file + permission_classes = [IsAreaOwner, IsAuthenticated] \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index 4755368a..d3e3181e 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -22,7 +22,7 @@ class PlaceViewSet(viewsets.ModelViewSet): def get_place_owner(self, user): try: - return user.placeowner + return user.place_owner except PlaceOwner.DoesNotExist: return PlaceOwner.objects.create(user=user) @@ -34,7 +34,7 @@ def create(self, request, *args, **kwargs): user = request.user try: - place_owner = user.placeowner + place_owner = user.place_owner except PlaceOwner.DoesNotExist: place_owner = PlaceOwner.objects.create(user=user) @@ -75,7 +75,7 @@ def update(self, request, pk=None): return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) def destroy(self, request, pk=None): - place_owner_id = request.user.placeowner.id + place_owner_id = request.user.place_owner.id place = get_object_or_404(Place, pk=pk) if place.place_owner.id == place_owner_id: @@ -104,7 +104,7 @@ class AreaViewSet(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): user = request.user - place_owner = user.placeowner + place_owner = user.place_owner place_id = request.data.get('place') place = get_object_or_404(Place, id=place_id, place_owner=place_owner) @@ -119,7 +119,7 @@ def create(self, request, *args, **kwargs): def list(self,request,*args, **kwargs): user = request.user - place_owner = user.placeowner + place_owner = user.place_owner place_id = request.query_params.get('place') if not place_id: @@ -133,7 +133,7 @@ def list(self,request,*args, **kwargs): return Response(area_serializer.data) def retrieve(self, request, pk=None): - place_owner = request.user.placeowner.id + place_owner = request.user.place_owner.id area = get_object_or_404(Area,pk=pk) @@ -144,7 +144,7 @@ def retrieve(self, request, pk=None): return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) def destroy(self, request, pk=None): - place_owner_id = request.user.placeowner.id + place_owner_id = request.user.place_owner.id area = get_object_or_404(Area, pk=pk) if area.place.place_owner.id == place_owner_id: From 765a08e207e48a0af02cef1ed0dc1362b522ab58 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 14 Jun 2024 14:53:43 -0300 Subject: [PATCH 225/351] =?UTF-8?q?frontend:=20refatora=20cria=C3=A7=C3=A3?= =?UTF-8?q?o=20de=20fire=20alarm=20e=20renomeia=20arquivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/.vscode/settings.json | 22 +++ ...il_service.dart => equipment_service.dart} | 6 +- ...> fire_alarm_equipment_request_model.dart} | 16 +-- .../data/fire-alarm/fire_alarm_service.dart | 22 +-- ...ic_equipment_category_response_model.dart} | 8 +- .../generic_equipment_category_service.dart} | 12 +- .../mix-equipment-type-service.dart | 27 ---- ...nal_equipment_category_request_model.dart} | 4 +- .../personal_equipment_category_service.dart} | 20 +-- ...ersonal_equipment_type_response_model.dart | 28 ---- ...{addFireAlarm.dart => add_fire_alarm.dart} | 133 +++++++++--------- ...tFireAlarms.dart => list_fire_alarms.dart} | 4 +- .../feature/systemConfiguration.dart | 2 +- frontend/sige_ie/lib/main.dart | 2 +- 14 files changed, 139 insertions(+), 167 deletions(-) create mode 100644 frontend/sige_ie/.vscode/settings.json rename frontend/sige_ie/lib/equipments/data/{equipment_detail_service.dart => equipment_service.dart} (92%) rename frontend/sige_ie/lib/equipments/data/fire-alarm/{fire_alarm_equipment_detail_request_model.dart => fire_alarm_equipment_request_model.dart} (56%) rename frontend/sige_ie/lib/equipments/data/{equipment-type/equipment_type_response_model.dart => generic-equipment-category/generic_equipment_category_response_model.dart} (62%) rename frontend/sige_ie/lib/equipments/data/{equipment-type/equipment_type_service.dart => generic-equipment-category/generic_equipment_category_service.dart} (73%) delete mode 100644 frontend/sige_ie/lib/equipments/data/mix-equipment-type/mix-equipment-type-service.dart rename frontend/sige_ie/lib/equipments/data/{personal-equipment-type/personal_equipment_type_request_model.dart => personal-equipment-category/personal_equipment_category_request_model.dart} (68%) rename frontend/sige_ie/lib/equipments/data/{personal-equipment-type/personal_equipment_type_service.dart => personal-equipment-category/personal_equipment_category_service.dart} (77%) delete mode 100644 frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_response_model.dart rename frontend/sige_ie/lib/equipments/feature/fire-alarm/{addFireAlarm.dart => add_fire_alarm.dart} (86%) rename frontend/sige_ie/lib/equipments/feature/fire-alarm/{listFireAlarms.dart => list_fire_alarms.dart} (97%) diff --git a/frontend/sige_ie/.vscode/settings.json b/frontend/sige_ie/.vscode/settings.json new file mode 100644 index 00000000..79eab2c1 --- /dev/null +++ b/frontend/sige_ie/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#3399ff", + "activityBar.background": "#3399ff", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#bf0060", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#e7e7e799", + "sash.hoverBorder": "#3399ff", + "statusBar.background": "#007fff", + "statusBar.foreground": "#e7e7e7", + "statusBarItem.hoverBackground": "#3399ff", + "statusBarItem.remoteBackground": "#007fff", + "statusBarItem.remoteForeground": "#e7e7e7", + "titleBar.activeBackground": "#007fff", + "titleBar.activeForeground": "#e7e7e7", + "titleBar.inactiveBackground": "#007fff99", + "titleBar.inactiveForeground": "#e7e7e799" + }, + "peacock.color": "#007fff" +} \ No newline at end of file diff --git a/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart similarity index 92% rename from frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart rename to frontend/sige_ie/lib/equipments/data/equipment_service.dart index bd66dec1..45ee751a 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_detail_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -2,17 +2,17 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart'; +import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart'; import 'package:sige_ie/main.dart'; -class EquipmentDetailService { +class EquipmentService { final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); Future createFireAlarm( - FireAlarmEquipmentDetailRequestModel + FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { var url = Uri.parse(baseUrl); diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart similarity index 56% rename from frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart rename to frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart index 1d097b26..bb9daf82 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart @@ -1,23 +1,23 @@ import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; -class FireAlarmEquipmentDetailRequestModel { - int? equipmentType; - int? personalEquipmentType; +class FireAlarmEquipmentRequestModel { + int? genericEquipmentCategory; + int? personalEquipmentCategory; FireAlarmRequestModel? fireAlarm; List? photos; - FireAlarmEquipmentDetailRequestModel({ - required this.equipmentType, - required this.personalEquipmentType, + FireAlarmEquipmentRequestModel({ + required this.genericEquipmentCategory, + required this.personalEquipmentCategory, required this.fireAlarm, required this.photos, }); Map toJson() { return { - 'equipmentType': equipmentType, - 'personalEquipmentType': personalEquipmentType, + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, 'fire_alarm_equipment': fireAlarm?.toJson(), 'photos': photos?.map((photo) => photo.toJson()).toList(), }; diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart index c17762e2..05a1f8c5 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; +import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class FireAlarmEquipmentService { @@ -10,13 +10,15 @@ class FireAlarmEquipmentService { http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( + + Future> getAllEquipment( int systemId, - List equipmentTypeList, - List personalEquipmentList) async { - List combinedList = [ - ...equipmentTypeList, - ...personalEquipmentList, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, ]; try { print('Combined list length: ${combinedList.length}'); @@ -27,7 +29,7 @@ class FireAlarmEquipmentService { } } - Future> getEquipmentListByArea(int areaId) async { + Future> getFireAlarmListByArea(int areaId) async { final url = '${baseUrl}fire-alarms/by-area/$areaId'; try { final response = await client.get(Uri.parse(url)); @@ -35,10 +37,10 @@ class FireAlarmEquipmentService { final List data = json.decode(response.body); return data.map((item) => item['name'] as String).toList(); } else { - throw Exception('Failed to load equipment'); + throw Exception('Failed to load fire alarm equipment'); } } catch (e) { - print('Error during get equipment list: $e'); + print('Error during get fire alarm equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_response_model.dart b/frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart similarity index 62% rename from frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_response_model.dart rename to frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart index e2573327..ca97500b 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart @@ -1,16 +1,16 @@ -class EquipmentTypeResponseModel { +class EquipmentCategoryResponseModel { int id; String name; int system; - EquipmentTypeResponseModel({ + EquipmentCategoryResponseModel({ required this.id, required this.name, required this.system, }); - factory EquipmentTypeResponseModel.fromJson(Map json) { - return EquipmentTypeResponseModel( + factory EquipmentCategoryResponseModel.fromJson(Map json) { + return EquipmentCategoryResponseModel( id: json['id'], name: json['name'], system: json['system'], diff --git a/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_service.dart b/frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_service.dart similarity index 73% rename from frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_service.dart rename to frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_service.dart index 020e2b34..ec39632e 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment-type/equipment_type_service.dart +++ b/frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_service.dart @@ -2,17 +2,17 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; +import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; -class EquipmentTypeService { +class GenericEquipmentCategoryService { final String baseUrl = 'http://10.0.2.2:8000/api/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipmentTypeBySystem( - int systemId) async { + Future> + getAllGenericEquipmentCategoryBySystem(int systemId) async { var url = Uri.parse('${baseUrl}equipment-types/by-system/$systemId/'); try { @@ -25,8 +25,8 @@ class EquipmentTypeService { if (response.statusCode == 200) { List responseData = jsonDecode(response.body); - List equipmentList = responseData - .map((item) => EquipmentTypeResponseModel.fromJson(item)) + List equipmentList = responseData + .map((item) => EquipmentCategoryResponseModel.fromJson(item)) .toList(); print('Request successful, received ${equipmentList.length} items'); return equipmentList; diff --git a/frontend/sige_ie/lib/equipments/data/mix-equipment-type/mix-equipment-type-service.dart b/frontend/sige_ie/lib/equipments/data/mix-equipment-type/mix-equipment-type-service.dart deleted file mode 100644 index 750f7060..00000000 --- a/frontend/sige_ie/lib/equipments/data/mix-equipment-type/mix-equipment-type-service.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:http/http.dart' as http; -import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; -import 'package:sige_ie/main.dart'; - -class MixEquipmentTypeService { - final String baseUrl = 'http://10.0.2.2:8000/api/'; - http.Client client = InterceptedClient.build( - interceptors: [AuthInterceptor(cookieJar)], - ); - - Future> getAllEquipmentBySystem( - int systemId, equipmentTypeList, personalEquipmentList) async { - try { - List combinedList = [ - ...equipmentTypeList, - ...personalEquipmentList, - ]; - print('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - print('Error during get all equipment: $e'); - return []; - } - } -} diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart b/frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart similarity index 68% rename from frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart rename to frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart index e1e8d4a4..b1140335 100644 --- a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart @@ -1,8 +1,8 @@ -class PersonalEquipmentTypeRequestModel { +class PersonalEquipmentCategoryRequestModel { String name; int? system; - PersonalEquipmentTypeRequestModel({ + PersonalEquipmentCategoryRequestModel({ required this.name, required this.system, }); diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart b/frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_service.dart similarity index 77% rename from frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart rename to frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_service.dart index ffd2fa7a..99fc3989 100644 --- a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_service.dart +++ b/frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_service.dart @@ -2,18 +2,18 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; -import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart'; +import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart'; import 'package:sige_ie/main.dart'; -class PersonalEquipmentTypeService { +class PersonalEquipmentCategoryService { final String baseUrl = 'http://10.0.2.2:8000/api/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllPersonalEquipmentBySystem( - int systemId) async { + Future> + getAllPersonalEquipmentCategoryBySystem(int systemId) async { var url = Uri.parse('${baseUrl}personal-equipment-types/by-system/$systemId/'); @@ -23,8 +23,8 @@ class PersonalEquipmentTypeService { if (response.statusCode == 200) { List responseData = jsonDecode(response.body); - List equipmentList = responseData - .map((item) => EquipmentTypeResponseModel.fromJson(item)) + List equipmentList = responseData + .map((item) => EquipmentCategoryResponseModel.fromJson(item)) .toList(); //print('Request successful, received ${equipmentList.length} items'); return equipmentList; @@ -39,8 +39,8 @@ class PersonalEquipmentTypeService { } } - Future createPersonalEquipmentType( - PersonalEquipmentTypeRequestModel + Future createPersonalEquipmentCategory( + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel) async { var url = Uri.parse('${baseUrl}personal-equipment-types/'); @@ -69,7 +69,7 @@ class PersonalEquipmentTypeService { } } - Future deletePersonalEquipmentType(int id) async { + Future deletePersonalEquipmentCategory(int id) async { var url = Uri.parse('${baseUrl}personal-equipment-types/$id/'); try { diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_response_model.dart b/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_response_model.dart deleted file mode 100644 index abda96f0..00000000 --- a/frontend/sige_ie/lib/equipments/data/personal-equipment-type/personal_equipment_type_response_model.dart +++ /dev/null @@ -1,28 +0,0 @@ -class PersonalEquipmentTypeResponseModel { - int id; - String name; - int system; - - PersonalEquipmentTypeResponseModel({ - required this.id, - required this.name, - required this.system, - }); - - factory PersonalEquipmentTypeResponseModel.fromJson( - Map json) { - return PersonalEquipmentTypeResponseModel( - id: json['id'], - name: json['name'], - system: json['system'], - ); - } - - Map toJson() { - return { - 'id': id, - 'name': name, - 'system': system, - }; - } -} diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart similarity index 86% rename from frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart rename to frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart index f0b7fa07..53421208 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/addFireAlarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart @@ -1,23 +1,21 @@ -import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/data/equipment-type/equipment_type_response_model.dart'; -import 'package:sige_ie/equipments/data/equipment-type/equipment_type_service.dart'; -import 'package:sige_ie/equipments/data/equipment_detail_service.dart'; -import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_detail_request_model.dart'; +import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; +import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart'; import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/mix-equipment-type/mix-equipment-type-service.dart'; -import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_request_model.dart'; -import 'package:sige_ie/equipments/data/personal-equipment-type/personal_equipment_type_service.dart'; +import 'package:sige_ie/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/equipments/data/personal-equipment-category/personal_equipment_category_service.dart'; import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; class ImageData { - File imageFile; int id; + File imageFile; String description; ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); @@ -47,47 +45,50 @@ class AddfireAlarm extends StatefulWidget { } class _AddEquipmentScreenState extends State { - EquipmentDetailService equipmentDetailService = EquipmentDetailService(); - PersonalEquipmentTypeService personalEquipmentTypeService = - PersonalEquipmentTypeService(); - EquipmentTypeService equipmentTypeService = EquipmentTypeService(); - MixEquipmentTypeService mixEquipmentTypeService = MixEquipmentTypeService(); + EquipmentService equipmentService = EquipmentService(); + + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; String? _newEquipmentTypeName; - int? _selectedTypeId; - int? _selectedPersonalEquipmentTypeId; - bool _isPersonalTypeSelected = false; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; - List> equipmentTypes = []; + List> genericEquipmentTypes = []; List> personalEquipmentTypes = []; Map personalEquipmentMap = {}; @override void initState() { super.initState(); - _fetchEquipmentTypes(); + _fetchEquipmentCategory(); } - Future _fetchEquipmentTypes() async { - List equipmentTypeList = - await equipmentTypeService - .getAllEquipmentTypeBySystem(widget.categoryNumber); + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); - List personalEquipmentList = - await personalEquipmentTypeService - .getAllPersonalEquipmentBySystem(widget.categoryNumber); + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); setState(() { - equipmentTypes = equipmentTypeList - .map((e) => {'name': e.name, 'id': e.id, 'type': 'generico'}) + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) .toList(); - personalEquipmentTypes = personalEquipmentList - .map((e) => {'name': e.name, 'id': e.id, 'type': 'pessoal'}) + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) .toList(); personalEquipmentMap = { - for (var equipment in personalEquipmentList) + for (var equipment in personalEquipmentCategoryList) equipment.name: equipment.id }; }); @@ -196,8 +197,8 @@ class _AddEquipmentScreenState extends State { _registerPersonalEquipmentType().then((_) { setState(() { _selectedType = null; - _selectedTypeId = null; - _fetchEquipmentTypes(); + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); }); }); Navigator.of(context).pop(); @@ -212,12 +213,12 @@ class _AddEquipmentScreenState extends State { Future _registerPersonalEquipmentType() async { int systemId = widget.categoryNumber; - PersonalEquipmentTypeRequestModel personalEquipmentTypeRequestModel = - PersonalEquipmentTypeRequestModel( + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); - int id = await personalEquipmentTypeService - .createPersonalEquipmentType(personalEquipmentTypeRequestModel); + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); if (id != -1) { ScaffoldMessenger.of(context).showSnackBar( @@ -233,7 +234,7 @@ class _AddEquipmentScreenState extends State { .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); personalEquipmentMap[_newEquipmentTypeName!] = id; _newEquipmentTypeName = null; - _fetchEquipmentTypes(); + _fetchEquipmentCategory(); }); } else { ScaffoldMessenger.of(context).showSnackBar( @@ -250,7 +251,8 @@ class _AddEquipmentScreenState extends State { if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Não existem equipamentos pessoais a serem excluídos.'), + content: + Text('Não existem categorias de equipamentos a serem excluídas.'), ), ); return; @@ -259,8 +261,8 @@ class _AddEquipmentScreenState extends State { if (_selectedTypeToDelete == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: - Text('Selecione um tipo de equipamento válido para excluir.'), + content: Text( + 'Selecione uma categoria de equipamento válida para excluir.'), ), ); return; @@ -286,15 +288,15 @@ class _AddEquipmentScreenState extends State { child: const Text('Excluir'), onPressed: () async { Navigator.of(context).pop(); - bool success = await personalEquipmentTypeService - .deletePersonalEquipmentType(equipmentId); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); if (success) { setState(() { personalEquipmentTypes.removeWhere( (element) => element['name'] == _selectedTypeToDelete); _selectedTypeToDelete = null; - _fetchEquipmentTypes(); + _fetchEquipmentCategory(); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -396,39 +398,39 @@ class _AddEquipmentScreenState extends State { print('categoryNumber: ${widget.categoryNumber}'); print('_selectedType: $_selectedType'); print( - '_selectedPersonalEquipmentTypeId: $_selectedPersonalEquipmentTypeId'); + '_selectedPersonalEquipmentCategoryId: $_selectedPersonalEquipmentCategoryId'); List photos = _images.map((imageData) { return PhotoRequestModel( - photo: base64Encode(imageData.imageFile.readAsBytesSync()), + photo: imageData.imageFile.toString(), description: imageData.description.isNotEmpty ? imageData.description : ''); }).toList(); - int? equipmentType; - int? personalEquipmentType; + int? genericEquipmentCategory; + int? personalEquipmentCategory; - if (_isPersonalTypeSelected) { - equipmentType = null; - personalEquipmentType = _selectedPersonalEquipmentTypeId; + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; } else { - equipmentType = _selectedTypeId; - personalEquipmentType = null; + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; } final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( area: widget.areaId, system: widget.categoryNumber); - final FireAlarmEquipmentDetailRequestModel fireAlarmEquipmentDetail = - FireAlarmEquipmentDetailRequestModel( - equipmentType: equipmentType, - personalEquipmentType: personalEquipmentType, + final FireAlarmEquipmentRequestModel fireAlarmEquipmentDetail = + FireAlarmEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, fireAlarm: fireAlarmModel, photos: photos, ); bool success = - await equipmentDetailService.createFireAlarm(fireAlarmEquipmentDetail); + await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); if (success) { ScaffoldMessenger.of(context).showSnackBar( @@ -461,7 +463,7 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { List> combinedTypes = [ - ...equipmentTypes, + ...genericEquipmentTypes, ...personalEquipmentTypes ]; @@ -538,15 +540,16 @@ class _AddEquipmentScreenState extends State { Map selected = combinedTypes.firstWhere((element) => element['name'] == newValue); - _isPersonalTypeSelected = + _isPersonalEquipmentCategorySelected = selected['type'] == 'pessoal'; - if (_isPersonalTypeSelected) { - _selectedPersonalEquipmentTypeId = + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = selected['id'] as int; - _selectedTypeId = null; + _selectedGenericEquipmentCategoryId = null; } else { - _selectedTypeId = selected['id'] as int; - _selectedPersonalEquipmentTypeId = null; + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; } }); } diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart similarity index 97% rename from frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart rename to frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart index 0afd1fff..456eb8c6 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/listFireAlarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_service.dart'; -import 'package:sige_ie/equipments/feature/fire-alarm/addFireAlarm.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/add_fire_alarm.dart'; class ListFireAlarms extends StatefulWidget { final String areaName; @@ -37,7 +37,7 @@ class _ListFireAlarmsState extends State { Future fetchEquipmentList() async { try { final List equipmentList = - await _service.getEquipmentListByArea(widget.areaId); + await _service.getFireAlarmListByArea(widget.areaId); setState(() { this.equipmentList = equipmentList; isLoading = false; diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 4f9c20ed..fed75d17 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -8,7 +8,7 @@ import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; -import 'package:sige_ie/equipments/feature/fire-alarm/listFireAlarms.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; class SystemConfiguration extends StatefulWidget { final String areaName; diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 38cf6855..fb399968 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -8,7 +8,7 @@ import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; -import 'package:sige_ie/equipments/feature/fire-alarm/listFireAlarms.dart'; +import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; From 4801d9824fc66a09f0be2e7b8bcc0413d0085523 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 14 Jun 2024 18:00:24 -0300 Subject: [PATCH 226/351] frontend: ajusta fotos - parte 1 --- .../fire_alarm_equipment_request_model.dart | 4 +-- .../data/fire-alarm/fire_alarm_service.dart | 2 +- ...nation_equipment_detail_request_model.dart | 4 +-- .../feature/fire-alarm/add_fire_alarm.dart | 29 ++++++++++--------- .../equipment-photo}/photo_request_model.dart | 4 +-- ...ric_equipment_category_response_model.dart | 0 .../generic_equipment_category_service.dart | 2 +- ...onal_equipment_category_request_model.dart | 0 .../personal_equipment_category_service.dart | 4 +-- frontend/sige_ie/pubspec.lock | 26 ++++++++--------- frontend/sige_ie/pubspec.yaml | 1 + 11 files changed, 40 insertions(+), 36 deletions(-) rename frontend/sige_ie/lib/{equipments/data/photo => shared/data/equipment-photo}/photo_request_model.dart (76%) rename frontend/sige_ie/lib/{equipments => shared}/data/generic-equipment-category/generic_equipment_category_response_model.dart (100%) rename frontend/sige_ie/lib/{equipments => shared}/data/generic-equipment-category/generic_equipment_category_service.dart (92%) rename frontend/sige_ie/lib/{equipments => shared}/data/personal-equipment-category/personal_equipment_category_request_model.dart (100%) rename frontend/sige_ie/lib/{equipments => shared}/data/personal-equipment-category/personal_equipment_category_service.dart (92%) diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart index bb9daf82..d836fc11 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart @@ -1,11 +1,11 @@ import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; class FireAlarmEquipmentRequestModel { int? genericEquipmentCategory; int? personalEquipmentCategory; FireAlarmRequestModel? fireAlarm; - List? photos; + List? photos; FireAlarmEquipmentRequestModel({ required this.genericEquipmentCategory, diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart index 05a1f8c5..7b73df1b 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class FireAlarmEquipmentService { diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart index 5e5c9003..8db69464 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart @@ -1,10 +1,10 @@ import 'package:sige_ie/equipments/data/iluminations/ilumination_request_model.dart'; -import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; class FireAlarmEquipmentDetailRequestModel { int? equipmenteType; IluminationRequestModel? ilumination; - List? photos; + List? photos; FireAlarmEquipmentDetailRequestModel({ required this.equipmenteType, diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart index 53421208..21eba785 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart @@ -1,17 +1,18 @@ +import 'dart:convert'; +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart'; -import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; import 'package:sige_ie/equipments/data/equipment_service.dart'; import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart'; import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; -import 'package:sige_ie/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart'; -import 'package:sige_ie/equipments/data/personal-equipment-category/personal_equipment_category_service.dart'; -import 'package:sige_ie/equipments/data/photo/photo_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; class ImageData { int id; @@ -384,7 +385,7 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('Adicionar'), onPressed: () { - _registerEquipmentDetail(); + _registerEquipment(); }, ), ], @@ -393,18 +394,20 @@ class _AddEquipmentScreenState extends State { ); } - void _registerEquipmentDetail() async { + void _registerEquipment() async { print('areaId: ${widget.areaId}'); print('categoryNumber: ${widget.categoryNumber}'); print('_selectedType: $_selectedType'); print( '_selectedPersonalEquipmentCategoryId: $_selectedPersonalEquipmentCategoryId'); - List photos = _images.map((imageData) { - return PhotoRequestModel( - photo: imageData.imageFile.toString(), - description: - imageData.description.isNotEmpty ? imageData.description : ''); + List photos = _images.map((imageData) { + List imageBytes = imageData.imageFile.readAsBytesSync(); + String base64Image = base64Encode(imageBytes); + return EquipmentPhotoRequestModel( + photo: base64Image, + description: imageData.description, + ); }).toList(); int? genericEquipmentCategory; diff --git a/frontend/sige_ie/lib/equipments/data/photo/photo_request_model.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart similarity index 76% rename from frontend/sige_ie/lib/equipments/data/photo/photo_request_model.dart rename to frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart index 28c5a361..e026e7e2 100644 --- a/frontend/sige_ie/lib/equipments/data/photo/photo_request_model.dart +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart @@ -1,8 +1,8 @@ -class PhotoRequestModel { +class EquipmentPhotoRequestModel { String photo; String description; - PhotoRequestModel({ + EquipmentPhotoRequestModel({ required this.photo, required this.description, }); diff --git a/frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart b/frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart rename to frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_service.dart b/frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_service.dart similarity index 92% rename from frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_service.dart rename to frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_service.dart index ec39632e..90e33572 100644 --- a/frontend/sige_ie/lib/equipments/data/generic-equipment-category/generic_equipment_category_service.dart +++ b/frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class GenericEquipmentCategoryService { diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart b/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart rename to frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_service.dart b/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart similarity index 92% rename from frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_service.dart rename to frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart index 99fc3989..f0f9e08a 100644 --- a/frontend/sige_ie/lib/equipments/data/personal-equipment-category/personal_equipment_category_service.dart +++ b/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/generic-equipment-category/generic_equipment_category_response_model.dart'; -import 'package:sige_ie/equipments/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; import 'package:sige_ie/main.dart'; class PersonalEquipmentCategoryService { diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 473c2164..71936eb1 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -396,26 +396,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.0" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "2.0.1" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.0.1" lints: dependency: transitive description: @@ -452,12 +452,12 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.0" mime: - dependency: transitive + dependency: "direct main" description: name: mime sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" @@ -641,10 +641,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -721,10 +721,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "13.0.0" web: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 3ee95403..7ce4782d 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: cookie_jar: ^4.0.8 http_interceptor: ^1.0.1 image_picker: ^0.8.5 + mime: ^1.0.0 cupertino_icons: ^1.0.6 get: ^4.6.5 google_maps_flutter: ^2.0.6 From 83cedd956f70e013c56b649c0e795d96eaf81a3b Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 19:59:38 -0300 Subject: [PATCH 227/351] backend: permite a foto ser criada serializada como bytes --- api/equipments/serializers.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 9796eb9f..3bc71235 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -1,3 +1,5 @@ +import base64 +from django.core.files.base import ContentFile from rest_framework import serializers from .models import * from .serializers import * @@ -16,11 +18,24 @@ class Meta: fields = '__all__' class EquipmentPhotoSerializer(serializers.ModelSerializer): + photo = serializers.CharField(write_only=True) class Meta: model = EquipmentPhoto fields = '__all__' + def create(self, validated_data): + photo_data = validated_data.pop('photo') + try: + format, imgstr = photo_data.split(';base64,') + ext = format.split('/')[-1] + photo = ContentFile(base64.b64decode(imgstr), name='temp.' + ext) + except ValueError: + raise serializers.ValidationError("Invalid image data") + + equipment_photo = EquipmentPhoto.objects.create(photo=photo, **validated_data) + return equipment_photo + class FireAlarmEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: @@ -77,7 +92,7 @@ class Meta: class EquipmentSerializer(serializers.ModelSerializer): - photos = EquipmentPhotoSerializer(many=True, required=False) + #photos = EquipmentPhotoSerializer(many=True, required=False) fire_alarm_equipment = FireAlarmEquipmentSerializer(required=False) atmospheric_discharge_equipment = AtmosphericDischargeEquipmentSerializer(required=False) @@ -98,7 +113,7 @@ def create(self, validated_data): request = self.context.get('request') validated_data['place_owner'] = request.user.place_owner - photos_data = validated_data.pop('photos', []) + # photos_data = validated_data.pop('photos', []) fire_alarm_data = validated_data.pop('fire_alarm_equipment', None) atmospheric_discharge_data = validated_data.pop('atmospheric_discharge_equipment', None) @@ -112,8 +127,8 @@ def create(self, validated_data): equipment = Equipment.objects.create(**validated_data) - for photo_data in photos_data: - EquipmentPhoto.objects.create(equipment=equipment, **photo_data) + # for photo_data in photos_data: + # EquipmentPhoto.objects.create(equipment=equipment, **photo_data) if fire_alarm_data: FireAlarmEquipment.objects.create(equipment=equipment, **fire_alarm_data) From e64453b4826db61c83c880c22384a9a5fd83d2ca Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 20:00:35 -0300 Subject: [PATCH 228/351] frotend: muda envio de fotos serializada como bytes --- frontend/sige_ie/lib/Teams/teams.dart | 2 +- .../sige_ie/lib/areas/data/area_service.dart | 10 +++-- .../lib/areas/feature/register/new_area.dart | 1 - .../lib/core/data/auth_interceptor.dart | 10 ++--- .../equipments/data/equipment_service.dart | 13 +++--- .../fire_alarm_equipment_request_model.dart | 10 ++--- ...nation_equipment_detail_request_model.dart | 22 ---------- .../addAtmospheric-dischargesEquipment.dart | 2 +- .../feature/fire-alarm/add_fire_alarm.dart | 32 +++++++------- .../feature/systemConfiguration.dart | 2 +- .../sige_ie/lib/facilities/ui/facilities.dart | 18 ++++---- frontend/sige_ie/lib/home/ui/home.dart | 2 +- frontend/sige_ie/lib/main.dart | 2 +- frontend/sige_ie/lib/maps/feature/maps.dart | 14 +++--- .../equipment_photo_request_model.dart | 25 +++++++++++ .../equipment_photo_service.dart | 43 +++++++++++++++++++ .../equipment-photo/photo_request_model.dart | 16 ------- frontend/sige_ie/pubspec.lock | 38 +++++++++------- frontend/sige_ie/pubspec.yaml | 2 + 19 files changed, 150 insertions(+), 114 deletions(-) delete mode 100644 frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart create mode 100644 frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart create mode 100644 frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart delete mode 100644 frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart diff --git a/frontend/sige_ie/lib/Teams/teams.dart b/frontend/sige_ie/lib/Teams/teams.dart index 8b81531c..e0d37a1d 100644 --- a/frontend/sige_ie/lib/Teams/teams.dart +++ b/frontend/sige_ie/lib/Teams/teams.dart @@ -52,7 +52,7 @@ class TeamsPage extends StatelessWidget { return Card( child: ListTile( leading: - Icon(Icons.group, color: AppColors.sigeIeBlue), + const Icon(Icons.group, color: AppColors.sigeIeBlue), title: Text(team.name), subtitle: Text('Membros: ${team.members.length}'), onTap: () { diff --git a/frontend/sige_ie/lib/areas/data/area_service.dart b/frontend/sige_ie/lib/areas/data/area_service.dart index ade0b98d..fd95432f 100644 --- a/frontend/sige_ie/lib/areas/data/area_service.dart +++ b/frontend/sige_ie/lib/areas/data/area_service.dart @@ -1,12 +1,14 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/areas/data/area_response_model.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/areas/data/area_request_model.dart'; class AreaService { + final Logger _logger = Logger('AreaService'); Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); @@ -64,8 +66,8 @@ class AreaService { Future updateArea(int areaId, AreaRequestModel areaRequestModel) async { var url = Uri.parse('$baseUrl$areaId/'); - print('Sending PUT request to $url'); - print('Request body: ${jsonEncode(areaRequestModel.toJson())}'); + _logger.info('Sending PUT request to $url'); + _logger.info('Request body: ${jsonEncode(areaRequestModel.toJson())}'); var response = await client.put( url, @@ -73,8 +75,8 @@ class AreaService { body: jsonEncode(areaRequestModel.toJson()), ); - print('Response status: ${response.statusCode}'); - print('Response body: ${response.body}'); + _logger.info('Response status: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); return response.statusCode == 200; } diff --git a/frontend/sige_ie/lib/areas/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart index d50ca7b8..e1159d27 100644 --- a/frontend/sige_ie/lib/areas/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -261,7 +261,6 @@ Widget _buildDropdown({ required List items, required String? value, required void Function(String?) onChanged, - VoidCallback? addNew, }) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10), diff --git a/frontend/sige_ie/lib/core/data/auth_interceptor.dart b/frontend/sige_ie/lib/core/data/auth_interceptor.dart index 3b0da707..2a752367 100644 --- a/frontend/sige_ie/lib/core/data/auth_interceptor.dart +++ b/frontend/sige_ie/lib/core/data/auth_interceptor.dart @@ -10,18 +10,16 @@ class AuthInterceptor implements InterceptorContract { Future interceptRequest({required RequestData data}) async { var cookies = await cookieJar .loadForRequest(Uri.parse('http://10.0.2.2:8000/api/login/')); - var sessionCookie; + Cookie? sessionCookie; for (var cookie in cookies) { if (cookie.name == 'sessionid') { sessionCookie = cookie; break; } } - if (sessionCookie != null) { - data.headers - .addAll({'Cookie': '${sessionCookie.name}=${sessionCookie.value}'}); - } - return data; + data.headers + .addAll({'Cookie': '${sessionCookie!.name}=${sessionCookie.value}'}); + return data; } @override diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index 45ee751a..61015f18 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -11,9 +11,8 @@ class EquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future createFireAlarm( - FireAlarmEquipmentRequestModel - fireAlarmEquipmentRequestModel) async { + Future createFireAlarm( + FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { var url = Uri.parse(baseUrl); try { @@ -26,18 +25,18 @@ class EquipmentService { print('Response status code: ${response.statusCode}'); print('Response body: ${response.body}'); - if (response.statusCode == 201) { + if (response.statusCode == 201 || response.statusCode == 200) { Map responseData = jsonDecode(response.body); print('Request successful, received ID: ${responseData['id']}'); - return true; + return responseData['id']; } else { print( 'Failed to register fire alarm equipment: ${response.statusCode}'); - return false; + return null; } } catch (e) { print('Error during register: $e'); - return false; + return null; } } } diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart index d836fc11..f9cdf4b8 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart @@ -1,25 +1,21 @@ import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; -import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; class FireAlarmEquipmentRequestModel { int? genericEquipmentCategory; int? personalEquipmentCategory; - FireAlarmRequestModel? fireAlarm; - List? photos; + FireAlarmRequestModel? fireAlarmRequestModel; FireAlarmEquipmentRequestModel({ required this.genericEquipmentCategory, required this.personalEquipmentCategory, - required this.fireAlarm, - required this.photos, + required this.fireAlarmRequestModel, }); Map toJson() { return { 'generic_equipment_category': genericEquipmentCategory, 'personal_equipment_category': personalEquipmentCategory, - 'fire_alarm_equipment': fireAlarm?.toJson(), - 'photos': photos?.map((photo) => photo.toJson()).toList(), + 'fire_alarm_equipment': fireAlarmRequestModel?.toJson(), }; } } diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart deleted file mode 100644 index 8db69464..00000000 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:sige_ie/equipments/data/iluminations/ilumination_request_model.dart'; -import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; - -class FireAlarmEquipmentDetailRequestModel { - int? equipmenteType; - IluminationRequestModel? ilumination; - List? photos; - - FireAlarmEquipmentDetailRequestModel({ - required this.equipmenteType, - required this.ilumination, - required this.photos, - }); - - Map toJson() { - return { - 'equipmenteType': equipmenteType, - 'ilumination_equipment': ilumination?.toJson(), - 'photos': photos?.map((photo) => photo.toJson()).toList(), - }; - } -} diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart index 580aea79..ea8809c1 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart @@ -595,7 +595,7 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first, style: TextStyle(color: Colors.grey)), + hint: Text(items.first, style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart index 21eba785..9664f0df 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart @@ -1,11 +1,11 @@ -import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; import 'package:sige_ie/equipments/data/equipment_service.dart'; @@ -48,6 +48,8 @@ class AddfireAlarm extends StatefulWidget { class _AddEquipmentScreenState extends State { EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = PersonalEquipmentCategoryService(); @@ -401,15 +403,6 @@ class _AddEquipmentScreenState extends State { print( '_selectedPersonalEquipmentCategoryId: $_selectedPersonalEquipmentCategoryId'); - List photos = _images.map((imageData) { - List imageBytes = imageData.imageFile.readAsBytesSync(); - String base64Image = base64Encode(imageBytes); - return EquipmentPhotoRequestModel( - photo: base64Image, - description: imageData.description, - ); - }).toList(); - int? genericEquipmentCategory; int? personalEquipmentCategory; @@ -428,14 +421,23 @@ class _AddEquipmentScreenState extends State { FireAlarmEquipmentRequestModel( genericEquipmentCategory: genericEquipmentCategory, personalEquipmentCategory: personalEquipmentCategory, - fireAlarm: fireAlarmModel, - photos: photos, + fireAlarmRequestModel: fireAlarmModel, ); - bool success = + int? equipmentId = await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); - if (success) { + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Detalhes do equipamento registrados com sucesso.'), diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index fed75d17..707158e3 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -323,7 +323,7 @@ class SystemIcon extends StatelessWidget { Container( width: 80, height: 80, - decoration: BoxDecoration( + decoration: const BoxDecoration( shape: BoxShape.circle, color: AppColors.sigeIeYellow, ), diff --git a/frontend/sige_ie/lib/facilities/ui/facilities.dart b/frontend/sige_ie/lib/facilities/ui/facilities.dart index 008b0436..325363e2 100644 --- a/frontend/sige_ie/lib/facilities/ui/facilities.dart +++ b/frontend/sige_ie/lib/facilities/ui/facilities.dart @@ -283,11 +283,11 @@ class _FacilitiesPageState extends State { } void _editPlace(BuildContext context, PlaceResponseModel place) { - final TextEditingController _nameController = + final TextEditingController nameController = TextEditingController(text: place.name); - final TextEditingController _lonController = + final TextEditingController lonController = TextEditingController(text: place.lon.toString()); - final TextEditingController _latController = + final TextEditingController latController = TextEditingController(text: place.lat.toString()); showDialog( @@ -299,16 +299,16 @@ class _FacilitiesPageState extends State { child: Column( children: [ TextField( - controller: _nameController, + controller: nameController, decoration: const InputDecoration(labelText: 'Nome do Local'), ), TextField( - controller: _lonController, + controller: lonController, decoration: const InputDecoration(labelText: 'Longitude'), keyboardType: TextInputType.number, ), TextField( - controller: _latController, + controller: latController, decoration: const InputDecoration(labelText: 'Latitude'), keyboardType: TextInputType.number, ), @@ -325,9 +325,9 @@ class _FacilitiesPageState extends State { TextButton( child: const Text('Salvar'), onPressed: () async { - String newName = _nameController.text; - double? newLon = double.tryParse(_lonController.text); - double? newLat = double.tryParse(_latController.text); + String newName = nameController.text; + double? newLon = double.tryParse(lonController.text); + double? newLat = double.tryParse(latController.text); Navigator.of(context).pop(); if (newName.isNotEmpty && newLon != null && newLat != null) { diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 6266f156..92d34e50 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -64,7 +64,7 @@ class _HomePageState extends State { const FacilitiesPage(), const TeamsPage(), const MapsPage(), - ProfilePage() + const ProfilePage() ], ), ], diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index fb399968..123c074b 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -59,7 +59,7 @@ class MyApp extends StatelessWidget { case '/MapsPage': return MaterialPageRoute(builder: (context) => const MapsPage()); case '/newLocation': - return MaterialPageRoute(builder: (context) => NewPlace()); + return MaterialPageRoute(builder: (context) => const NewPlace()); case '/arealocation': if (settings.arguments is Map) { final args = settings.arguments as Map; diff --git a/frontend/sige_ie/lib/maps/feature/maps.dart b/frontend/sige_ie/lib/maps/feature/maps.dart index 94700d01..ed19b902 100644 --- a/frontend/sige_ie/lib/maps/feature/maps.dart +++ b/frontend/sige_ie/lib/maps/feature/maps.dart @@ -4,11 +4,11 @@ import 'package:google_maps_flutter/google_maps_flutter.dart'; import '../controller/maps_controller.dart'; class MapsPage extends StatelessWidget { - const MapsPage({Key? key}) : super(key: key); + const MapsPage({super.key}); @override Widget build(BuildContext context) { - final MapsController _controller = Get.put(MapsController()); + final MapsController controller = Get.put(MapsController()); return Scaffold( appBar: AppBar( @@ -17,8 +17,8 @@ class MapsPage extends StatelessWidget { elevation: 0, ), body: Obx(() { - if (_controller.isLoading.value) { - return Center(child: CircularProgressIndicator()); + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); } return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -47,9 +47,9 @@ class MapsPage extends StatelessWidget { ), Expanded( child: GoogleMap( - onMapCreated: _controller.onMapCreated, - initialCameraPosition: _controller.initialCameraPosition, - markers: Set.of(_controller.markers), + onMapCreated: controller.onMapCreated, + initialCameraPosition: controller.initialCameraPosition, + markers: Set.of(controller.markers), myLocationEnabled: true, myLocationButtonEnabled: true, ), diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart new file mode 100644 index 00000000..63cbb1da --- /dev/null +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart @@ -0,0 +1,25 @@ +import 'dart:io'; +import 'dart:convert'; + +class EquipmentPhotoRequestModel { + String? photoBase64; + String? description; + int? equipment; + + EquipmentPhotoRequestModel({ + required File photo, + required this.description, + required this.equipment, + }) { + String base64Image = base64Encode(photo.readAsBytesSync()); + photoBase64 = 'data:image/jpeg;base64,$base64Image'; + } + + Map toJson() { + return { + 'photo': photoBase64, + 'description': description, + 'equipment': equipment, + }; + } +} diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart new file mode 100644 index 00000000..bbc8d41d --- /dev/null +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:http/http.dart' as http; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/main.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; + +class EquipmentPhotoService { + final String baseUrl = 'http://10.0.2.2:8000/api/equipment-photos/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future createPhoto( + EquipmentPhotoRequestModel equipmentPhotoRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode(equipmentPhotoRequestModel.toJson()), + ); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + print('Request successful, received ID: ${responseData['id']}'); + return true; + } else { + print('Failed to register equipment photo: ${response.statusCode}'); + return false; + } + } catch (e) { + print('Error during register: $e'); + return false; + } + } +} diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart deleted file mode 100644 index e026e7e2..00000000 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart +++ /dev/null @@ -1,16 +0,0 @@ -class EquipmentPhotoRequestModel { - String photo; - String description; - - EquipmentPhotoRequestModel({ - required this.photo, - required this.description, - }); - - Map toJson() { - return { - 'photo': photo, - 'description': description, - }; - } -} diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 71936eb1..e985cef1 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -260,26 +260,26 @@ packages: dependency: transitive description: name: google_maps_flutter_ios - sha256: e5132d17f051600d90d79d9f574b177c24231da702453a036db2490f9ced4646 + sha256: d2d63ae17297a5b045ec115572c5a86fa4e53bb6eceaa0c6d200ac5ca69bfca4 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" google_maps_flutter_platform_interface: dependency: transitive description: name: google_maps_flutter_platform_interface - sha256: "167af879da4d004cd58771f1469b91dcc3b9b0a2c5334cc6bf71fd41d4b35403" + sha256: "2bf21aa97edba4461282af5de693b354e589d09f695f7a6f80437d084a29687e" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.1" google_maps_flutter_web: dependency: transitive description: name: google_maps_flutter_web - sha256: "0c0d5c723d94b295cf86dd1c45ff91d2ac1fff7c05ddca4f01bef9fa0a014690" + sha256: f3155c12119d8a5c2732fdf39ceb5cc095bc662059a03b4ea23294ecebe1d199 url: "https://pub.dev" source: hosted - version: "0.5.7" + version: "0.5.8" html: dependency: transitive description: @@ -305,7 +305,7 @@ packages: source: hosted version: "1.0.2" http_parser: - dependency: transitive + dependency: "direct main" description: name: http_parser sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" @@ -340,10 +340,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "4824d8c7f6f89121ef0122ff79bb00b009607faecc8545b86bca9ab5ce1e95bf" + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" url: "https://pub.dev" source: hosted - version: "0.8.11+2" + version: "0.8.12" image_picker_linux: dependency: transitive description: @@ -432,6 +432,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + logging: + dependency: "direct main" + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: @@ -500,10 +508,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -681,10 +689,10 @@ packages: dependency: "direct main" description: name: video_player - sha256: db6a72d8f4fd155d0189845678f55ad2fd54b02c10dcafd11c068dbb631286c0 + sha256: aced48e701e24c02b0b7f881a8819e4937794e46b5a5821005e2bf3b40a324cc url: "https://pub.dev" source: hosted - version: "2.8.6" + version: "2.8.7" video_player_android: dependency: transitive description: @@ -713,10 +721,10 @@ packages: dependency: transitive description: name: video_player_web - sha256: "41245cef5ef29c4585dbabcbcbe9b209e34376642c7576cabf11b4ad9289d6e4" + sha256: ff4d69a6614b03f055397c27a71c9d3ddea2b2a23d71b2ba0164f59ca32b8fe2 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" vm_service: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 7ce4782d..531f95d4 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -37,11 +37,13 @@ dependencies: http_interceptor: ^1.0.1 image_picker: ^0.8.5 mime: ^1.0.0 + http_parser: ^4.0.2 cupertino_icons: ^1.0.6 get: ^4.6.5 google_maps_flutter: ^2.0.6 geolocator: ^9.0.2 logger: ^1.0.0 + logging: ^1.2.0 dev_dependencies: flutter_test: From ee4c0230cab572ea7c112622f600b7698033b767 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 20:00:35 -0300 Subject: [PATCH 229/351] frontend: muda envio de fotos serializada como bytes --- frontend/sige_ie/lib/Teams/teams.dart | 2 +- .../sige_ie/lib/areas/data/area_service.dart | 10 +++-- .../lib/areas/feature/register/new_area.dart | 1 - .../lib/core/data/auth_interceptor.dart | 10 ++--- .../equipments/data/equipment_service.dart | 13 +++--- .../fire_alarm_equipment_request_model.dart | 10 ++--- ...nation_equipment_detail_request_model.dart | 22 ---------- .../addAtmospheric-dischargesEquipment.dart | 2 +- .../feature/fire-alarm/add_fire_alarm.dart | 32 +++++++------- .../feature/systemConfiguration.dart | 2 +- .../sige_ie/lib/facilities/ui/facilities.dart | 18 ++++---- frontend/sige_ie/lib/home/ui/home.dart | 2 +- frontend/sige_ie/lib/main.dart | 2 +- frontend/sige_ie/lib/maps/feature/maps.dart | 14 +++--- .../equipment_photo_request_model.dart | 25 +++++++++++ .../equipment_photo_service.dart | 43 +++++++++++++++++++ .../equipment-photo/photo_request_model.dart | 16 ------- frontend/sige_ie/pubspec.lock | 38 +++++++++------- frontend/sige_ie/pubspec.yaml | 2 + 19 files changed, 150 insertions(+), 114 deletions(-) delete mode 100644 frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart create mode 100644 frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart create mode 100644 frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart delete mode 100644 frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart diff --git a/frontend/sige_ie/lib/Teams/teams.dart b/frontend/sige_ie/lib/Teams/teams.dart index 8b81531c..e0d37a1d 100644 --- a/frontend/sige_ie/lib/Teams/teams.dart +++ b/frontend/sige_ie/lib/Teams/teams.dart @@ -52,7 +52,7 @@ class TeamsPage extends StatelessWidget { return Card( child: ListTile( leading: - Icon(Icons.group, color: AppColors.sigeIeBlue), + const Icon(Icons.group, color: AppColors.sigeIeBlue), title: Text(team.name), subtitle: Text('Membros: ${team.members.length}'), onTap: () { diff --git a/frontend/sige_ie/lib/areas/data/area_service.dart b/frontend/sige_ie/lib/areas/data/area_service.dart index ade0b98d..fd95432f 100644 --- a/frontend/sige_ie/lib/areas/data/area_service.dart +++ b/frontend/sige_ie/lib/areas/data/area_service.dart @@ -1,12 +1,14 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/areas/data/area_response_model.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/areas/data/area_request_model.dart'; class AreaService { + final Logger _logger = Logger('AreaService'); Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); @@ -64,8 +66,8 @@ class AreaService { Future updateArea(int areaId, AreaRequestModel areaRequestModel) async { var url = Uri.parse('$baseUrl$areaId/'); - print('Sending PUT request to $url'); - print('Request body: ${jsonEncode(areaRequestModel.toJson())}'); + _logger.info('Sending PUT request to $url'); + _logger.info('Request body: ${jsonEncode(areaRequestModel.toJson())}'); var response = await client.put( url, @@ -73,8 +75,8 @@ class AreaService { body: jsonEncode(areaRequestModel.toJson()), ); - print('Response status: ${response.statusCode}'); - print('Response body: ${response.body}'); + _logger.info('Response status: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); return response.statusCode == 200; } diff --git a/frontend/sige_ie/lib/areas/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart index d50ca7b8..e1159d27 100644 --- a/frontend/sige_ie/lib/areas/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -261,7 +261,6 @@ Widget _buildDropdown({ required List items, required String? value, required void Function(String?) onChanged, - VoidCallback? addNew, }) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10), diff --git a/frontend/sige_ie/lib/core/data/auth_interceptor.dart b/frontend/sige_ie/lib/core/data/auth_interceptor.dart index 3b0da707..2a752367 100644 --- a/frontend/sige_ie/lib/core/data/auth_interceptor.dart +++ b/frontend/sige_ie/lib/core/data/auth_interceptor.dart @@ -10,18 +10,16 @@ class AuthInterceptor implements InterceptorContract { Future interceptRequest({required RequestData data}) async { var cookies = await cookieJar .loadForRequest(Uri.parse('http://10.0.2.2:8000/api/login/')); - var sessionCookie; + Cookie? sessionCookie; for (var cookie in cookies) { if (cookie.name == 'sessionid') { sessionCookie = cookie; break; } } - if (sessionCookie != null) { - data.headers - .addAll({'Cookie': '${sessionCookie.name}=${sessionCookie.value}'}); - } - return data; + data.headers + .addAll({'Cookie': '${sessionCookie!.name}=${sessionCookie.value}'}); + return data; } @override diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index 45ee751a..61015f18 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -11,9 +11,8 @@ class EquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future createFireAlarm( - FireAlarmEquipmentRequestModel - fireAlarmEquipmentRequestModel) async { + Future createFireAlarm( + FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { var url = Uri.parse(baseUrl); try { @@ -26,18 +25,18 @@ class EquipmentService { print('Response status code: ${response.statusCode}'); print('Response body: ${response.body}'); - if (response.statusCode == 201) { + if (response.statusCode == 201 || response.statusCode == 200) { Map responseData = jsonDecode(response.body); print('Request successful, received ID: ${responseData['id']}'); - return true; + return responseData['id']; } else { print( 'Failed to register fire alarm equipment: ${response.statusCode}'); - return false; + return null; } } catch (e) { print('Error during register: $e'); - return false; + return null; } } } diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart index d836fc11..f9cdf4b8 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart @@ -1,25 +1,21 @@ import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; -import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; class FireAlarmEquipmentRequestModel { int? genericEquipmentCategory; int? personalEquipmentCategory; - FireAlarmRequestModel? fireAlarm; - List? photos; + FireAlarmRequestModel? fireAlarmRequestModel; FireAlarmEquipmentRequestModel({ required this.genericEquipmentCategory, required this.personalEquipmentCategory, - required this.fireAlarm, - required this.photos, + required this.fireAlarmRequestModel, }); Map toJson() { return { 'generic_equipment_category': genericEquipmentCategory, 'personal_equipment_category': personalEquipmentCategory, - 'fire_alarm_equipment': fireAlarm?.toJson(), - 'photos': photos?.map((photo) => photo.toJson()).toList(), + 'fire_alarm_equipment': fireAlarmRequestModel?.toJson(), }; } } diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart deleted file mode 100644 index 8db69464..00000000 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_equipment_detail_request_model.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:sige_ie/equipments/data/iluminations/ilumination_request_model.dart'; -import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; - -class FireAlarmEquipmentDetailRequestModel { - int? equipmenteType; - IluminationRequestModel? ilumination; - List? photos; - - FireAlarmEquipmentDetailRequestModel({ - required this.equipmenteType, - required this.ilumination, - required this.photos, - }); - - Map toJson() { - return { - 'equipmenteType': equipmenteType, - 'ilumination_equipment': ilumination?.toJson(), - 'photos': photos?.map((photo) => photo.toJson()).toList(), - }; - } -} diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart index 580aea79..ea8809c1 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart @@ -595,7 +595,7 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first, style: TextStyle(color: Colors.grey)), + hint: Text(items.first, style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart index 21eba785..9664f0df 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart @@ -1,11 +1,11 @@ -import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/shared/data/equipment-photo/photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; import 'package:sige_ie/equipments/data/equipment_service.dart'; @@ -48,6 +48,8 @@ class AddfireAlarm extends StatefulWidget { class _AddEquipmentScreenState extends State { EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = PersonalEquipmentCategoryService(); @@ -401,15 +403,6 @@ class _AddEquipmentScreenState extends State { print( '_selectedPersonalEquipmentCategoryId: $_selectedPersonalEquipmentCategoryId'); - List photos = _images.map((imageData) { - List imageBytes = imageData.imageFile.readAsBytesSync(); - String base64Image = base64Encode(imageBytes); - return EquipmentPhotoRequestModel( - photo: base64Image, - description: imageData.description, - ); - }).toList(); - int? genericEquipmentCategory; int? personalEquipmentCategory; @@ -428,14 +421,23 @@ class _AddEquipmentScreenState extends State { FireAlarmEquipmentRequestModel( genericEquipmentCategory: genericEquipmentCategory, personalEquipmentCategory: personalEquipmentCategory, - fireAlarm: fireAlarmModel, - photos: photos, + fireAlarmRequestModel: fireAlarmModel, ); - bool success = + int? equipmentId = await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); - if (success) { + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Detalhes do equipamento registrados com sucesso.'), diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index fed75d17..707158e3 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -323,7 +323,7 @@ class SystemIcon extends StatelessWidget { Container( width: 80, height: 80, - decoration: BoxDecoration( + decoration: const BoxDecoration( shape: BoxShape.circle, color: AppColors.sigeIeYellow, ), diff --git a/frontend/sige_ie/lib/facilities/ui/facilities.dart b/frontend/sige_ie/lib/facilities/ui/facilities.dart index 008b0436..325363e2 100644 --- a/frontend/sige_ie/lib/facilities/ui/facilities.dart +++ b/frontend/sige_ie/lib/facilities/ui/facilities.dart @@ -283,11 +283,11 @@ class _FacilitiesPageState extends State { } void _editPlace(BuildContext context, PlaceResponseModel place) { - final TextEditingController _nameController = + final TextEditingController nameController = TextEditingController(text: place.name); - final TextEditingController _lonController = + final TextEditingController lonController = TextEditingController(text: place.lon.toString()); - final TextEditingController _latController = + final TextEditingController latController = TextEditingController(text: place.lat.toString()); showDialog( @@ -299,16 +299,16 @@ class _FacilitiesPageState extends State { child: Column( children: [ TextField( - controller: _nameController, + controller: nameController, decoration: const InputDecoration(labelText: 'Nome do Local'), ), TextField( - controller: _lonController, + controller: lonController, decoration: const InputDecoration(labelText: 'Longitude'), keyboardType: TextInputType.number, ), TextField( - controller: _latController, + controller: latController, decoration: const InputDecoration(labelText: 'Latitude'), keyboardType: TextInputType.number, ), @@ -325,9 +325,9 @@ class _FacilitiesPageState extends State { TextButton( child: const Text('Salvar'), onPressed: () async { - String newName = _nameController.text; - double? newLon = double.tryParse(_lonController.text); - double? newLat = double.tryParse(_latController.text); + String newName = nameController.text; + double? newLon = double.tryParse(lonController.text); + double? newLat = double.tryParse(latController.text); Navigator.of(context).pop(); if (newName.isNotEmpty && newLon != null && newLat != null) { diff --git a/frontend/sige_ie/lib/home/ui/home.dart b/frontend/sige_ie/lib/home/ui/home.dart index 6266f156..92d34e50 100644 --- a/frontend/sige_ie/lib/home/ui/home.dart +++ b/frontend/sige_ie/lib/home/ui/home.dart @@ -64,7 +64,7 @@ class _HomePageState extends State { const FacilitiesPage(), const TeamsPage(), const MapsPage(), - ProfilePage() + const ProfilePage() ], ), ], diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index fb399968..123c074b 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -59,7 +59,7 @@ class MyApp extends StatelessWidget { case '/MapsPage': return MaterialPageRoute(builder: (context) => const MapsPage()); case '/newLocation': - return MaterialPageRoute(builder: (context) => NewPlace()); + return MaterialPageRoute(builder: (context) => const NewPlace()); case '/arealocation': if (settings.arguments is Map) { final args = settings.arguments as Map; diff --git a/frontend/sige_ie/lib/maps/feature/maps.dart b/frontend/sige_ie/lib/maps/feature/maps.dart index 94700d01..ed19b902 100644 --- a/frontend/sige_ie/lib/maps/feature/maps.dart +++ b/frontend/sige_ie/lib/maps/feature/maps.dart @@ -4,11 +4,11 @@ import 'package:google_maps_flutter/google_maps_flutter.dart'; import '../controller/maps_controller.dart'; class MapsPage extends StatelessWidget { - const MapsPage({Key? key}) : super(key: key); + const MapsPage({super.key}); @override Widget build(BuildContext context) { - final MapsController _controller = Get.put(MapsController()); + final MapsController controller = Get.put(MapsController()); return Scaffold( appBar: AppBar( @@ -17,8 +17,8 @@ class MapsPage extends StatelessWidget { elevation: 0, ), body: Obx(() { - if (_controller.isLoading.value) { - return Center(child: CircularProgressIndicator()); + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); } return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -47,9 +47,9 @@ class MapsPage extends StatelessWidget { ), Expanded( child: GoogleMap( - onMapCreated: _controller.onMapCreated, - initialCameraPosition: _controller.initialCameraPosition, - markers: Set.of(_controller.markers), + onMapCreated: controller.onMapCreated, + initialCameraPosition: controller.initialCameraPosition, + markers: Set.of(controller.markers), myLocationEnabled: true, myLocationButtonEnabled: true, ), diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart new file mode 100644 index 00000000..63cbb1da --- /dev/null +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart @@ -0,0 +1,25 @@ +import 'dart:io'; +import 'dart:convert'; + +class EquipmentPhotoRequestModel { + String? photoBase64; + String? description; + int? equipment; + + EquipmentPhotoRequestModel({ + required File photo, + required this.description, + required this.equipment, + }) { + String base64Image = base64Encode(photo.readAsBytesSync()); + photoBase64 = 'data:image/jpeg;base64,$base64Image'; + } + + Map toJson() { + return { + 'photo': photoBase64, + 'description': description, + 'equipment': equipment, + }; + } +} diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart new file mode 100644 index 00000000..bbc8d41d --- /dev/null +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:http/http.dart' as http; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/main.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; + +class EquipmentPhotoService { + final String baseUrl = 'http://10.0.2.2:8000/api/equipment-photos/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future createPhoto( + EquipmentPhotoRequestModel equipmentPhotoRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode(equipmentPhotoRequestModel.toJson()), + ); + + print('Response status code: ${response.statusCode}'); + print('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + print('Request successful, received ID: ${responseData['id']}'); + return true; + } else { + print('Failed to register equipment photo: ${response.statusCode}'); + return false; + } + } catch (e) { + print('Error during register: $e'); + return false; + } + } +} diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart deleted file mode 100644 index e026e7e2..00000000 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/photo_request_model.dart +++ /dev/null @@ -1,16 +0,0 @@ -class EquipmentPhotoRequestModel { - String photo; - String description; - - EquipmentPhotoRequestModel({ - required this.photo, - required this.description, - }); - - Map toJson() { - return { - 'photo': photo, - 'description': description, - }; - } -} diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 71936eb1..e985cef1 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -260,26 +260,26 @@ packages: dependency: transitive description: name: google_maps_flutter_ios - sha256: e5132d17f051600d90d79d9f574b177c24231da702453a036db2490f9ced4646 + sha256: d2d63ae17297a5b045ec115572c5a86fa4e53bb6eceaa0c6d200ac5ca69bfca4 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" google_maps_flutter_platform_interface: dependency: transitive description: name: google_maps_flutter_platform_interface - sha256: "167af879da4d004cd58771f1469b91dcc3b9b0a2c5334cc6bf71fd41d4b35403" + sha256: "2bf21aa97edba4461282af5de693b354e589d09f695f7a6f80437d084a29687e" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.1" google_maps_flutter_web: dependency: transitive description: name: google_maps_flutter_web - sha256: "0c0d5c723d94b295cf86dd1c45ff91d2ac1fff7c05ddca4f01bef9fa0a014690" + sha256: f3155c12119d8a5c2732fdf39ceb5cc095bc662059a03b4ea23294ecebe1d199 url: "https://pub.dev" source: hosted - version: "0.5.7" + version: "0.5.8" html: dependency: transitive description: @@ -305,7 +305,7 @@ packages: source: hosted version: "1.0.2" http_parser: - dependency: transitive + dependency: "direct main" description: name: http_parser sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" @@ -340,10 +340,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "4824d8c7f6f89121ef0122ff79bb00b009607faecc8545b86bca9ab5ce1e95bf" + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" url: "https://pub.dev" source: hosted - version: "0.8.11+2" + version: "0.8.12" image_picker_linux: dependency: transitive description: @@ -432,6 +432,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + logging: + dependency: "direct main" + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: @@ -500,10 +508,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -681,10 +689,10 @@ packages: dependency: "direct main" description: name: video_player - sha256: db6a72d8f4fd155d0189845678f55ad2fd54b02c10dcafd11c068dbb631286c0 + sha256: aced48e701e24c02b0b7f881a8819e4937794e46b5a5821005e2bf3b40a324cc url: "https://pub.dev" source: hosted - version: "2.8.6" + version: "2.8.7" video_player_android: dependency: transitive description: @@ -713,10 +721,10 @@ packages: dependency: transitive description: name: video_player_web - sha256: "41245cef5ef29c4585dbabcbcbe9b209e34376642c7576cabf11b4ad9289d6e4" + sha256: ff4d69a6614b03f055397c27a71c9d3ddea2b2a23d71b2ba0164f59ca32b8fe2 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" vm_service: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 7ce4782d..531f95d4 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -37,11 +37,13 @@ dependencies: http_interceptor: ^1.0.1 image_picker: ^0.8.5 mime: ^1.0.0 + http_parser: ^4.0.2 cupertino_icons: ^1.0.6 get: ^4.6.5 google_maps_flutter: ^2.0.6 geolocator: ^9.0.2 logger: ^1.0.0 + logging: ^1.2.0 dev_dependencies: flutter_test: From 88f355556feb280b7cb1a391251ec6ff0cdce8f5 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 22:53:54 -0300 Subject: [PATCH 230/351] =?UTF-8?q?frontend:=20substitui=20print=20de=20se?= =?UTF-8?q?rvices=20por=20logs=20e=20renomeia=20arquivos=20nopadr=C3=A3o?= =?UTF-8?q?=20dart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eletrical_load_request_model.dart.dart} | 0 .../eletrical_load_response_model.dart} | 0 .../eletrical_load_service.dart} | 0 .../eletrical_circuit_request_model.dart} | 0 .../eletrical_circuit_response_model.dart} | 0 .../eletrical_circuit_service.dart} | 0 .../eletrical_line_request_model.dart} | 0 .../eletrical_line_response_model.dart} | 0 .../eletrical_line_service.dart} | 0 .../equipments/data/equipment_service.dart | 14 +++++----- .../fire_alarm_equipment_request_model.dart | 2 +- .../fire_alarm_request_model.dart | 0 .../fire_alarm_response_model.dart | 0 .../fire_alarm_service.dart | 8 +++--- .../structured_cabling_request_model.dart} | 0 .../structured_cabling_response_model.dart} | 0 .../structured_cabling_service.dart} | 0 .../feature/fire-alarm/add_fire_alarm.dart | 4 +-- .../feature/fire-alarm/list_fire_alarms.dart | 2 +- .../lib/places/data/place_service.dart | 14 +++++----- .../places/feature/register/new_place.dart | 2 -- .../equipment_photo_service.dart | 12 +++++---- .../generic_equipment_category_service.dart | 16 +++++++----- .../personal_equipment_category_service.dart | 26 ++++++++++--------- .../sige_ie/lib/users/data/user_service.dart | 8 +++--- 25 files changed, 61 insertions(+), 47 deletions(-) rename frontend/sige_ie/lib/equipments/data/{eletrical-circuit/eletrical-circuit_request_model.dart => eletrical-load/eletrical_load_request_model.dart.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-circuit/eletrical-circuit_response_model.dart => eletrical-load/eletrical_load_response_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-circuit/eletrical-circuit_service.dart => eletrical-load/eletrical_load_service.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-line/eletrical-line_request_model.dart => eletrical_circuit/eletrical_circuit_request_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-line/eletrical-line_response_model.dart => eletrical_circuit/eletrical_circuit_response_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-line/eletrical-line_service.dart => eletrical_circuit/eletrical_circuit_service.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-load/eletrical-load_request_model.dart.dart => eletrical_line/eletrical_line_request_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-load/eletrical-load_response_model.dart => eletrical_line/eletrical_line_response_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{eletrical-load/eletrical-load_service.dart => eletrical_line/eletrical_line_service.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{fire-alarm => fire_alarm}/fire_alarm_equipment_request_model.dart (90%) rename frontend/sige_ie/lib/equipments/data/{fire-alarm => fire_alarm}/fire_alarm_request_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{fire-alarm => fire_alarm}/fire_alarm_response_model.dart (100%) rename frontend/sige_ie/lib/equipments/data/{fire-alarm => fire_alarm}/fire_alarm_service.dart (83%) rename frontend/sige_ie/lib/equipments/data/{structured-cabling/structured-cabling_request_model.dart => structured_cabling/structured_cabling_request_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{structured-cabling/structured-cabling_response_model.dart => structured_cabling/structured_cabling_response_model.dart} (100%) rename frontend/sige_ie/lib/equipments/data/{structured-cabling/structured-cabling_service.dart => structured_cabling/structured_cabling_service.dart} (100%) diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_request_model.dart.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_request_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_request_model.dart.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_response_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-circuit/eletrical-circuit_service.dart rename to frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_request_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_response_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-line/eletrical-line_service.dart rename to frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_request_model.dart.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_request_model.dart.dart rename to frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_response_model.dart rename to frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical-load_service.dart rename to frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index 61015f18..1973a089 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -1,11 +1,13 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart'; import 'package:sige_ie/main.dart'; class EquipmentService { + final Logger _logger = Logger('EquipmentService'); final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], @@ -22,20 +24,20 @@ class EquipmentService { body: jsonEncode(fireAlarmEquipmentRequestModel.toJson()), ); - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); if (response.statusCode == 201 || response.statusCode == 200) { Map responseData = jsonDecode(response.body); - print('Request successful, received ID: ${responseData['id']}'); + _logger.info('Request successful, received ID: ${responseData['id']}'); return responseData['id']; } else { - print( + _logger.info( 'Failed to register fire alarm equipment: ${response.statusCode}'); return null; } } catch (e) { - print('Error during register: $e'); + _logger.info('Error during register: $e'); return null; } } diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart similarity index 90% rename from frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart rename to frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart index f9cdf4b8..175b868d 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart @@ -1,4 +1,4 @@ -import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_request_model.dart'; class FireAlarmEquipmentRequestModel { int? genericEquipmentCategory; diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_request_model.dart rename to frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_response_model.dart rename to frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart similarity index 83% rename from frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart rename to frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart index 7b73df1b..7e17bfda 100644 --- a/frontend/sige_ie/lib/equipments/data/fire-alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart @@ -1,11 +1,13 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class FireAlarmEquipmentService { + final Logger _logger = Logger('FireAlarmEquipmentService'); final String baseUrl = 'http://10.0.2.2:8000/api/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], @@ -21,10 +23,10 @@ class FireAlarmEquipmentService { ...personalEquipmentCategoryList, ]; try { - print('Combined list length: ${combinedList.length}'); + _logger.info('Combined list length: ${combinedList.length}'); return combinedList; } catch (e) { - print('Error during get all equipment: $e'); + _logger.info('Error during get all equipment: $e'); return []; } } @@ -40,7 +42,7 @@ class FireAlarmEquipmentService { throw Exception('Failed to load fire alarm equipment'); } } catch (e) { - print('Error during get fire alarm equipment list: $e'); + _logger.info('Error during get fire alarm equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_request_model.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_request_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_request_model.dart rename to frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_response_model.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_response_model.dart rename to frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/data/structured-cabling/structured-cabling_service.dart rename to frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart index 9664f0df..7d05b3af 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart @@ -9,8 +9,8 @@ import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; import 'package:sige_ie/equipments/data/equipment_service.dart'; -import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_equipment_request_model.dart'; -import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_request_model.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_request_model.dart'; import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart index 456eb8c6..60dc0add 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/data/fire-alarm/fire_alarm_service.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_service.dart'; import 'package:sige_ie/equipments/feature/fire-alarm/add_fire_alarm.dart'; class ListFireAlarms extends StatefulWidget { diff --git a/frontend/sige_ie/lib/places/data/place_service.dart b/frontend/sige_ie/lib/places/data/place_service.dart index 91231e91..57182a98 100644 --- a/frontend/sige_ie/lib/places/data/place_service.dart +++ b/frontend/sige_ie/lib/places/data/place_service.dart @@ -1,12 +1,14 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/places/data/place_request_model.dart'; import 'package:sige_ie/places/data/place_response_model.dart'; class PlaceService { + final Logger _logger = Logger('PlaceService'); final String baseUrl = 'http://10.0.2.2:8000/api/places/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], @@ -27,11 +29,11 @@ class PlaceService { Map responseData = jsonDecode(response.body); return responseData['id']; } else { - print('Failed to register place: ${response.statusCode}'); + _logger.info('Failed to register place: ${response.statusCode}'); return null; } } catch (e) { - print('Error during register: $e'); + _logger.info('Error during register: $e'); return null; } } @@ -51,7 +53,7 @@ class PlaceService { throw Exception('Failed to load places'); } } catch (e) { - print('Error during fetchAllPlaces: $e'); + _logger.info('Error during fetchAllPlaces: $e'); throw Exception('Failed to load places'); } } @@ -70,7 +72,7 @@ class PlaceService { throw Exception('Failed to load place with ID $placeId'); } } catch (e) { - print('Error during fetchPlace: $e'); + _logger.info('Error during fetchPlace: $e'); throw Exception('Failed to load place with ID $placeId'); } } @@ -89,7 +91,7 @@ class PlaceService { return response.statusCode == 200; } catch (e) { - print('Error during updatePlace: $e'); + _logger.info('Error during updatePlace: $e'); return false; } } @@ -103,7 +105,7 @@ class PlaceService { return response.statusCode == 204; } catch (e) { - print('Error during deletePlace: $e'); + _logger.info('Error during deletePlace: $e'); return false; } } diff --git a/frontend/sige_ie/lib/places/feature/register/new_place.dart b/frontend/sige_ie/lib/places/feature/register/new_place.dart index f5f0afe3..755fa546 100644 --- a/frontend/sige_ie/lib/places/feature/register/new_place.dart +++ b/frontend/sige_ie/lib/places/feature/register/new_place.dart @@ -171,8 +171,6 @@ class NewPlaceState extends State { int? placeId = await placeService.register(place); if (placeId != null) { - print( - 'Local Registrado: ${nameController.text} em latitude: ${positionController.lat} e longitude: ${positionController.lon}'); Navigator.of(context) .pushNamed('/arealocation', arguments: { 'placeName': nameController.text.trim(), diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart index bbc8d41d..c3acca04 100644 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart @@ -1,11 +1,13 @@ import 'dart:convert'; import 'package:http_interceptor/http_interceptor.dart'; import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; class EquipmentPhotoService { + final Logger _logger = Logger('EquipmentPhotoService'); final String baseUrl = 'http://10.0.2.2:8000/api/equipment-photos/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], @@ -24,19 +26,19 @@ class EquipmentPhotoService { body: jsonEncode(equipmentPhotoRequestModel.toJson()), ); - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); if (response.statusCode == 201 || response.statusCode == 200) { Map responseData = jsonDecode(response.body); - print('Request successful, received ID: ${responseData['id']}'); + _logger.info('Request successful, received ID: ${responseData['id']}'); return true; } else { - print('Failed to register equipment photo: ${response.statusCode}'); + _logger.info('Failed to register equipment photo: ${response.statusCode}'); return false; } } catch (e) { - print('Error during register: $e'); + _logger.info('Error during register: $e'); return false; } } diff --git a/frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_service.dart b/frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_service.dart index 90e33572..4c8bdb73 100644 --- a/frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_service.dart +++ b/frontend/sige_ie/lib/shared/data/generic-equipment-category/generic_equipment_category_service.dart @@ -1,11 +1,13 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class GenericEquipmentCategoryService { + final Logger _logger = Logger('GenericEquipmentCategoryService'); final String baseUrl = 'http://10.0.2.2:8000/api/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], @@ -16,26 +18,28 @@ class GenericEquipmentCategoryService { var url = Uri.parse('${baseUrl}equipment-types/by-system/$systemId/'); try { - print('Sending GET request to $url'); + _logger.info('Sending GET request to $url'); var response = await client.get(url, headers: {'Content-Type': 'application/json'}); - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); if (response.statusCode == 200) { List responseData = jsonDecode(response.body); List equipmentList = responseData .map((item) => EquipmentCategoryResponseModel.fromJson(item)) .toList(); - print('Request successful, received ${equipmentList.length} items'); + _logger + .info('Request successful, received ${equipmentList.length} items'); return equipmentList; } else { - print('Failed to get equipment by system: ${response.statusCode}'); + _logger + .info('Failed to get equipment by system: ${response.statusCode}'); return []; } } catch (e) { - print('Error during get all equipment by system: $e'); + _logger.info('Error during get all equipment by system: $e'); return []; } } diff --git a/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart b/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart index f0f9e08a..8f78a7cd 100644 --- a/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart +++ b/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart @@ -1,12 +1,14 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; import 'package:sige_ie/main.dart'; class PersonalEquipmentCategoryService { + final Logger _logger = Logger('PersonalEquipmentCategoryService'); final String baseUrl = 'http://10.0.2.2:8000/api/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], @@ -26,15 +28,15 @@ class PersonalEquipmentCategoryService { List equipmentList = responseData .map((item) => EquipmentCategoryResponseModel.fromJson(item)) .toList(); - //print('Request successful, received ${equipmentList.length} items'); + //_logger.info('Request successful, received ${equipmentList.length} items'); return equipmentList; } else { - print( + _logger.info( 'Failed to get personal equipment by system: ${response.statusCode}'); return []; } } catch (e) { - print('Error during get all personal equipment by system: $e'); + _logger.info('Error during get all personal equipment by system: $e'); return []; } } @@ -51,20 +53,20 @@ class PersonalEquipmentCategoryService { body: jsonEncode(personalEquipmentTypeRequestModel.toJson()), ); - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); if (response.statusCode == 201) { Map responseData = jsonDecode(response.body); - print('Request successful, received ID: ${responseData['id']}'); + _logger.info('Request successful, received ID: ${responseData['id']}'); return responseData['id']; } else { - print( + _logger.info( 'Failed to register fire alarm equipment: ${response.statusCode}'); return -1; } } catch (e) { - print('Error during register: $e'); + _logger.info('Error during register: $e'); return -1; } } @@ -73,16 +75,16 @@ class PersonalEquipmentCategoryService { var url = Uri.parse('${baseUrl}personal-equipment-types/$id/'); try { - print('Sending DELETE request to $url'); + _logger.info('Sending DELETE request to $url'); var response = await client .delete(url, headers: {'Content-Type': 'application/json'}); - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); return response.statusCode == 204; } catch (e) { - print('Error during delete equipment: $e'); + _logger.info('Error during delete equipment: $e'); return false; } } diff --git a/frontend/sige_ie/lib/users/data/user_service.dart b/frontend/sige_ie/lib/users/data/user_service.dart index 7a17470b..9ea00a24 100644 --- a/frontend/sige_ie/lib/users/data/user_service.dart +++ b/frontend/sige_ie/lib/users/data/user_service.dart @@ -2,12 +2,14 @@ import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/users/data/user_request_model.dart'; import 'package:sige_ie/users/data/user_response_model.dart'; class UserService { + final Logger _logger = Logger('UserService'); Future register(UserRequestModel userRequestModel) async { var url = Uri.parse('http://10.0.2.2:8000/api/users/'); @@ -77,14 +79,14 @@ class UserService { throw Exception('Falha ao carregar dados do perfil'); } } on http.ClientException catch (e) { - print('Erro ao carregar dados do perfil (ClientException): $e'); + _logger.info('Erro ao carregar dados do perfil (ClientException): $e'); throw Exception( 'Erro na conexão com o servidor. Por favor, tente novamente mais tarde.'); } on SocketException catch (e) { - print('Erro ao carregar dados do perfil (SocketException): $e'); + _logger.info('Erro ao carregar dados do perfil (SocketException): $e'); throw Exception('Erro de rede. Verifique sua conexão com a internet.'); } catch (e) { - print('Erro ao carregar dados do perfil: $e'); + _logger.info('Erro ao carregar dados do perfil: $e'); throw Exception( 'Ocorreu um erro inesperado. Por favor, tente novamente.'); } From 8e65a4d88436b0ffc2863b724c10ac60e50eb17d Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 22:56:14 -0300 Subject: [PATCH 231/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20de=20a?= =?UTF-8?q?tmospheric=20discharge=20para=20o=20padr=C3=A3o=20dart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...Equipment.dart => add_atmospheric_discharges_equipment.dart} | 0 ...eric-dischargesList.dart => atmospheric_dischargesList.dart} | 2 +- .../sige_ie/lib/equipments/feature/systemConfiguration.dart | 2 +- frontend/sige_ie/lib/main.dart | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/{addAtmospheric-dischargesEquipment.dart => add_atmospheric_discharges_equipment.dart} (100%) rename frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/{atmospheric-dischargesList.dart => atmospheric_dischargesList.dart} (98%) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart rename to frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart similarity index 98% rename from frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart rename to frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart index 54d04d82..c8407a09 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/atmospheric-discharges/addAtmospheric-dischargesEquipment.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart'; class listatmosphericEquipment extends StatelessWidget { final String areaName; diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart index 707158e3..df5b2ea2 100644 --- a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart +++ b/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart @@ -5,7 +5,7 @@ import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoar import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; -import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 123c074b..9f3c450f 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; -import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric-dischargesList.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart'; import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; From a793acccfcc9e1cf3cc01b091efc1a53810413c5 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 22:57:10 -0300 Subject: [PATCH 232/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20system?= =?UTF-8?q?=20configuration=20para=20padr=C3=A3o=20dart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{systemConfiguration.dart => system_configuration.dart} | 0 frontend/sige_ie/lib/main.dart | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename frontend/sige_ie/lib/equipments/feature/{systemConfiguration.dart => system_configuration.dart} (100%) diff --git a/frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/systemConfiguration.dart rename to frontend/sige_ie/lib/equipments/feature/system_configuration.dart diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 9f3c450f..72009437 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -14,7 +14,7 @@ import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; -import 'package:sige_ie/equipments/feature/systemConfiguration.dart'; +import 'package:sige_ie/equipments/feature/system_configuration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; From a0767fd7615f958d083ccc0dd769d8ad464933ae Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 23:04:56 -0300 Subject: [PATCH 233/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20coolin?= =?UTF-8?q?g=20para=20o=20padr=C3=A3o=20dart=20e=20corrige=20lower=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atmospheric_dischargesList.dart | 4 ++-- .../feature/cooling/{addCooling.dart => add_cooling.dart} | 6 +++--- ...lingEquipmentList.dart => cooling_equipment_list.dart} | 8 ++++---- .../lib/equipments/feature/system_configuration.dart | 6 +++--- frontend/sige_ie/lib/main.dart | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) rename frontend/sige_ie/lib/equipments/feature/cooling/{addCooling.dart => add_cooling.dart} (99%) rename frontend/sige_ie/lib/equipments/feature/cooling/{coolingEquipmentList.dart => cooling_equipment_list.dart} (94%) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart index c8407a09..fbc5d504 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart'; -class listatmosphericEquipment extends StatelessWidget { +class ListAtmosphericEquipment extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listatmosphericEquipment({ + const ListAtmosphericEquipment({ super.key, required this.areaName, required this.categoryNumber, diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart b/frontend/sige_ie/lib/equipments/feature/cooling/add_cooling.dart similarity index 99% rename from frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart rename to frontend/sige_ie/lib/equipments/feature/cooling/add_cooling.dart index 183621b1..cf940795 100644 --- a/frontend/sige_ie/lib/equipments/feature/cooling/addCooling.dart +++ b/frontend/sige_ie/lib/equipments/feature/cooling/add_cooling.dart @@ -16,14 +16,14 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class Addcooling extends StatefulWidget { +class AddCooling extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; final int areaId; - const Addcooling({ + const AddCooling({ super.key, required this.areaName, required this.categoryNumber, @@ -36,7 +36,7 @@ class Addcooling extends StatefulWidget { _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddEquipmentScreenState extends State { final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/cooling/cooling_equipment_list.dart similarity index 94% rename from frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart rename to frontend/sige_ie/lib/equipments/feature/cooling/cooling_equipment_list.dart index 87729983..c5f78a0c 100644 --- a/frontend/sige_ie/lib/equipments/feature/cooling/coolingEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/cooling/cooling_equipment_list.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/cooling/addCooling.dart'; +import 'package:sige_ie/equipments/feature/cooling/add_cooling.dart'; -class listCollingEquipment extends StatelessWidget { +class ListCollingEquipment extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listCollingEquipment({ + const ListCollingEquipment({ super.key, required this.areaName, required this.categoryNumber, @@ -22,7 +22,7 @@ class listCollingEquipment extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => Addcooling( + builder: (context) => AddCooling( areaName: areaName, categoryNumber: categoryNumber, localName: localName, diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index df5b2ea2..a9a72d89 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; @@ -45,7 +45,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/atmosphericDischarges': - return listatmosphericEquipment( + return ListAtmosphericEquipment( areaName: areaName, localName: localName, localId: localId, @@ -93,7 +93,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/cooling': - return listCollingEquipment( + return ListCollingEquipment( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 72009437..0b73bf7c 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -4,7 +4,7 @@ import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart'; -import 'package:sige_ie/equipments/feature/cooling/coolingEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; @@ -206,7 +206,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listatmosphericEquipment( + builder: (context) => ListAtmosphericEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, @@ -294,7 +294,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listCollingEquipment( + builder: (context) => ListCollingEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 8d2901acf3a0a41abc60f1e7d4c1099e4af87919 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 23:11:46 -0300 Subject: [PATCH 234/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20distri?= =?UTF-8?q?buition=20board=20=20para=20o=20padr=C3=A3o=20dart=20e=20corrig?= =?UTF-8?q?e=20lower=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...dischargesList.dart => atmospheric_discharges_list.dart} | 0 ...DistribuitionBoard.dart => add_distribuition_board.dart} | 0 ...entList.dart => distribuition_board_equipment_list.dart} | 6 +++--- .../lib/equipments/feature/system_configuration.dart | 6 +++--- frontend/sige_ie/lib/main.dart | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) rename frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/{atmospheric_dischargesList.dart => atmospheric_discharges_list.dart} (100%) rename frontend/sige_ie/lib/equipments/feature/distribuition-Board/{addDistribuitionBoard.dart => add_distribuition_board.dart} (100%) rename frontend/sige_ie/lib/equipments/feature/distribuition-Board/{distribuitionBoardEquipmentList.dart => distribuition_board_equipment_list.dart} (96%) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart rename to frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/add_distribuition_board.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/distribuition-Board/addDistribuitionBoard.dart rename to frontend/sige_ie/lib/equipments/feature/distribuition-Board/add_distribuition_board.dart diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart similarity index 96% rename from frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart rename to frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart index 24a62c54..987ca2dd 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/distribuition-Board/addDistribuitionBoard.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/add_distribuition_board.dart'; -class listDistribuitionBoard extends StatelessWidget { +class ListDistribuitionBoard extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listDistribuitionBoard({ + const ListDistribuitionBoard({ super.key, required this.areaName, required this.categoryNumber, diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index a9a72d89..6cd0471b 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; -import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; -import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; @@ -85,7 +85,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/distributionBoard': - return listDistribuitionBoard( + return ListDistribuitionBoard( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 0b73bf7c..f135628a 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; -import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_dischargesList.dart'; +import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; -import 'package:sige_ie/equipments/feature/distribuition-Board/distribuitionBoardEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; @@ -235,7 +235,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listDistribuitionBoard( + builder: (context) => ListDistribuitionBoard( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 8015e052c9c3ff6778c1bbb9c569097c1ed5fa57 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 23:19:38 -0300 Subject: [PATCH 235/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20electr?= =?UTF-8?q?ical=20circuit=20=20para=20o=20padr=C3=A3o=20dart=20e=20corrige?= =?UTF-8?q?=20lower=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add_distribuition_board.dart | 0 .../distribuition_board_equipment_list.dart | 2 +- .../add_electrical_circuit.dart} | 0 .../electrical_circuit_list.dart} | 6 +++--- .../lib/equipments/feature/system_configuration.dart | 6 +++--- frontend/sige_ie/lib/main.dart | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) rename frontend/sige_ie/lib/equipments/feature/{distribuition-Board => distribuition_board}/add_distribuition_board.dart (100%) rename frontend/sige_ie/lib/equipments/feature/{distribuition-Board => distribuition_board}/distribuition_board_equipment_list.dart (98%) rename frontend/sige_ie/lib/equipments/feature/{electrical-circuit/addElectricalCircuit.dart => electrical_circuit/add_electrical_circuit.dart} (100%) rename frontend/sige_ie/lib/equipments/feature/{electrical-circuit/electricalCircuitList.dart => electrical_circuit/electrical_circuit_list.dart} (95%) diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/add_distribuition_board.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/distribuition-Board/add_distribuition_board.dart rename to frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart similarity index 98% rename from frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart rename to frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart index 987ca2dd..686ba7ff 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/distribuition-Board/add_distribuition_board.dart'; +import 'package:sige_ie/equipments/feature/distribuition_board/add_distribuition_board.dart'; class ListDistribuitionBoard extends StatelessWidget { final String areaName; diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/electrical-circuit/addElectricalCircuit.dart rename to frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart similarity index 95% rename from frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart rename to frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart index 0f00893a..468b4012 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-circuit/electricalCircuitList.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/electrical-circuit/addElectricalCircuit.dart'; +import 'package:sige_ie/equipments/feature/electrical_circuit/add_electrical_circuit.dart'; -class listCicuitEquipment extends StatelessWidget { +class ListCicuitEquipment extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listCicuitEquipment({ + const ListCicuitEquipment({ super.key, required this.areaName, required this.categoryNumber, diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index 6cd0471b..184be5e4 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; -import 'package:sige_ie/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart'; -import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; +import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; +import 'package:sige_ie/equipments/feature/electrical_circuit/electrical_circuit_list.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; @@ -77,7 +77,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/circuits': - return listCicuitEquipment( + return ListCicuitEquipment( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index f135628a..9fa4824e 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -5,7 +5,7 @@ import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; -import 'package:sige_ie/equipments/feature/distribuition-Board/distribuition_board_equipment_list.dart'; +import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; @@ -17,7 +17,7 @@ import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList import 'package:sige_ie/equipments/feature/system_configuration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; -import 'package:sige_ie/equipments/feature/electrical-circuit/electricalCircuitList.dart'; +import 'package:sige_ie/equipments/feature/electrical_circuit/electrical_circuit_list.dart'; import 'core/feature/login/login.dart'; void main() { @@ -148,7 +148,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listCicuitEquipment( + builder: (context) => ListCicuitEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 07f5f9003c78c132ce9f87f96de4f106949039db Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 23:22:19 -0300 Subject: [PATCH 236/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20electr?= =?UTF-8?q?ical=20line=20para=20o=20padr=C3=A3o=20dart=20e=20corrige=20low?= =?UTF-8?q?er=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add_electrical_line.dart} | 0 .../electrical_line_list.dart} | 6 +++--- .../lib/equipments/feature/system_configuration.dart | 4 ++-- frontend/sige_ie/lib/main.dart | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename frontend/sige_ie/lib/equipments/feature/{electrical-line/addElectricalLine.dart => electrical_line/add_electrical_line.dart} (100%) rename frontend/sige_ie/lib/equipments/feature/{electrical-line/electricaLLineLIst.dart => electrical_line/electrical_line_list.dart} (95%) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/electrical-line/addElectricalLine.dart rename to frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart similarity index 95% rename from frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart rename to frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart index 284990c0..08bb3417 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-line/electricaLLineLIst.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/electrical-line/addElectricalLine.dart'; +import 'package:sige_ie/equipments/feature/electrical_line/add_electrical_line.dart'; -class listElectricalLineEquipment extends StatelessWidget { +class ListElectricalLineEquipment extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listElectricalLineEquipment({ + const ListElectricalLineEquipment({ super.key, required this.areaName, required this.categoryNumber, diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index 184be5e4..d8f3ab35 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -3,7 +3,7 @@ import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical_circuit/electrical_circuit_list.dart'; -import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; +import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; @@ -69,7 +69,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/electricLines': - return listElectricalLineEquipment( + return ListElectricalLineEquipment( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 9fa4824e..6b7e843a 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -6,7 +6,7 @@ import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; -import 'package:sige_ie/equipments/feature/electrical-line/electricaLLineLIst.dart'; +import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; @@ -177,7 +177,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listElectricalLineEquipment( + builder: (context) => ListElectricalLineEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 1e9b76edc0b31c981dfdad8ffc86d1fce0c1478e Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 23:30:52 -0300 Subject: [PATCH 237/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20electr?= =?UTF-8?q?ical=20load=20para=20o=20padr=C3=A3o=20dart=20e=20corrige=20low?= =?UTF-8?q?er=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/electrical_line/add_electrical_line.dart | 4 ++-- .../add_electrical_load.dart} | 6 +++--- .../eletrical_load_list.dart} | 8 ++++---- .../{fire-alarm => fire_alarm}/add_fire_alarm.dart | 0 .../{fire-alarm => fire_alarm}/list_fire_alarms.dart | 2 +- .../lib/equipments/feature/system_configuration.dart | 6 +++--- frontend/sige_ie/lib/main.dart | 6 +++--- 7 files changed, 16 insertions(+), 16 deletions(-) rename frontend/sige_ie/lib/equipments/feature/{electrical-load/addelectricalLoad.dart => electrical_load/add_electrical_load.dart} (99%) rename frontend/sige_ie/lib/equipments/feature/{electrical-load/eletricalLoadList.dart => electrical_load/eletrical_load_list.dart} (93%) rename frontend/sige_ie/lib/equipments/feature/{fire-alarm => fire_alarm}/add_fire_alarm.dart (100%) rename frontend/sige_ie/lib/equipments/feature/{fire-alarm => fire_alarm}/list_fire_alarms.dart (98%) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart index c4ac9938..41a03709 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart @@ -39,7 +39,7 @@ class _AddEquipmentScreenState extends State { String? _selectedType; String? _selectedTypeToDelete; - List ElectricalType = [ + List electricalType = [ 'Selecione o tipo de Linha Elétrica', 'Eletrocalha', 'Eletroduto', @@ -287,7 +287,7 @@ class _AddEquipmentScreenState extends State { @override Widget build(BuildContext context) { - List combinedTypes = ElectricalType + additionalTypes; + List combinedTypes = electricalType + additionalTypes; return Scaffold( appBar: AppBar( diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart similarity index 99% rename from frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart rename to frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart index 4cbbf9e2..c5aa6df4 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-load/addelectricalLoad.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart @@ -16,13 +16,13 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class AddelectricalLoadEquipmentScreen extends StatefulWidget { +class AddElectricalLoadEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; - const AddelectricalLoadEquipmentScreen({ + const AddElectricalLoadEquipmentScreen({ super.key, required this.areaName, required this.categoryNumber, @@ -34,7 +34,7 @@ class AddelectricalLoadEquipmentScreen extends StatefulWidget { _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddEquipmentScreenState extends State { final _equipmentBrandController = TextEditingController(); final _equipmentModelController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); diff --git a/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart similarity index 93% rename from frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart rename to frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart index 45699979..fc84eb15 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical-load/eletricalLoadList.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/electrical-load/addelectricalLoad.dart'; +import 'package:sige_ie/equipments/feature/electrical_load/add_electrical_load.dart'; -class listelectricalLoadEquipment extends StatelessWidget { +class ListElectricalLoadEquipment extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listelectricalLoadEquipment({ + const ListElectricalLoadEquipment({ super.key, required this.areaName, required this.categoryNumber, @@ -22,7 +22,7 @@ class listelectricalLoadEquipment extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => AddelectricalLoadEquipmentScreen( + builder: (context) => AddElectricalLoadEquipmentScreen( areaName: areaName, categoryNumber: categoryNumber, localName: localName, diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/fire-alarm/add_fire_alarm.dart rename to frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart diff --git a/frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart similarity index 98% rename from frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart rename to frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 60dc0add..7272a5d0 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire-alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_service.dart'; -import 'package:sige_ie/equipments/feature/fire-alarm/add_fire_alarm.dart'; +import 'package:sige_ie/equipments/feature/fire_alarm/add_fire_alarm.dart'; class ListFireAlarms extends StatefulWidget { final String areaName; diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index d8f3ab35..f7437cf3 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -6,9 +6,9 @@ import 'package:sige_ie/equipments/feature/electrical_circuit/electrical_circuit import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; -import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; +import 'package:sige_ie/equipments/feature/electrical_load/eletrical_load_list.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; -import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; +import 'package:sige_ie/equipments/feature/fire_alarm/list_fire_alarms.dart'; class SystemConfiguration extends StatefulWidget { final String areaName; @@ -61,7 +61,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/electricLoads': - return listelectricalLoadEquipment( + return ListElectricalLoadEquipment( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 6b7e843a..53982bda 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -7,8 +7,8 @@ import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_di import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; -import 'package:sige_ie/equipments/feature/electrical-load/eletricalLoadList.dart'; -import 'package:sige_ie/equipments/feature/fire-alarm/list_fire_alarms.dart'; +import 'package:sige_ie/equipments/feature/electrical_load/eletrical_load_list.dart'; +import 'package:sige_ie/equipments/feature/fire_alarm/list_fire_alarms.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; @@ -353,7 +353,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listelectricalLoadEquipment( + builder: (context) => ListElectricalLoadEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 32329cbc82585aa0e183eee287279058709af80b Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 23:34:24 -0300 Subject: [PATCH 238/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20ilumin?= =?UTF-8?q?ation=20para=20o=20padr=C3=A3o=20dart=20e=20corrige=20lower=20c?= =?UTF-8?q?ase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...EquipmentList.dart => Ilumination_equipment_list.dart} | 8 ++++---- ...ationEquipment.dart => add_ilumination_equipment.dart} | 6 +++--- .../lib/equipments/feature/system_configuration.dart | 4 ++-- frontend/sige_ie/lib/main.dart | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename frontend/sige_ie/lib/equipments/feature/iluminations/{IluminationEquipmentList.dart => Ilumination_equipment_list.dart} (93%) rename frontend/sige_ie/lib/equipments/feature/iluminations/{addIluminationEquipment.dart => add_ilumination_equipment.dart} (99%) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/Ilumination_equipment_list.dart similarity index 93% rename from frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart rename to frontend/sige_ie/lib/equipments/feature/iluminations/Ilumination_equipment_list.dart index 4e49d48e..6dd2403b 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/IluminationEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/Ilumination_equipment_list.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/iluminations/addIluminationEquipment.dart'; +import 'package:sige_ie/equipments/feature/iluminations/add_ilumination_equipment.dart'; -class listIluminationEquipment extends StatelessWidget { +class ListIluminationEquipment extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listIluminationEquipment({ + const ListIluminationEquipment({ super.key, required this.areaName, required this.categoryNumber, @@ -22,7 +22,7 @@ class listIluminationEquipment extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => AddiluminationEquipmentScreen( + builder: (context) => AddIluminationEquipmentScreen( areaName: areaName, categoryNumber: categoryNumber, localName: localName, diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart similarity index 99% rename from frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart rename to frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart index dcd289aa..13475981 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/addIluminationEquipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart @@ -16,13 +16,13 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class AddiluminationEquipmentScreen extends StatefulWidget { +class AddIluminationEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; - const AddiluminationEquipmentScreen({ + const AddIluminationEquipmentScreen({ super.key, required this.areaName, required this.categoryNumber, @@ -34,7 +34,7 @@ class AddiluminationEquipmentScreen extends StatefulWidget { _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddEquipmentScreenState extends State { final _equipmentchargeController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index f7437cf3..eecff0fc 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -4,7 +4,7 @@ import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical_circuit/electrical_circuit_list.dart'; import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; -import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/iluminations/Ilumination_equipment_list.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; import 'package:sige_ie/equipments/feature/electrical_load/eletrical_load_list.dart'; import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; @@ -101,7 +101,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/lighting': - return listIluminationEquipment( + return ListIluminationEquipment( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 53982bda..250d40ce 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -13,7 +13,7 @@ import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEq import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/equipments/feature/iluminations/IluminationEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/iluminations/Ilumination_equipment_list.dart'; import 'package:sige_ie/equipments/feature/system_configuration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; @@ -119,7 +119,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listIluminationEquipment( + builder: (context) => ListIluminationEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 061b7306949d0d6f2e7a57b71bf096827dde8bf5 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 17 Jun 2024 23:38:50 -0300 Subject: [PATCH 239/351] =?UTF-8?q?frontend:=20renomeia=20arquivo=20struct?= =?UTF-8?q?ured=20cabling=20para=20o=20padr=C3=A3o=20dart=20e=20corrige=20?= =?UTF-8?q?lower=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...quipment_list.dart => ilumination_equipment_list.dart} | 0 .../add_structured_cabling.dart} | 6 +++--- .../structured_cabling_equipment_list.dart} | 8 ++++---- .../lib/equipments/feature/system_configuration.dart | 6 +++--- frontend/sige_ie/lib/main.dart | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) rename frontend/sige_ie/lib/equipments/feature/iluminations/{Ilumination_equipment_list.dart => ilumination_equipment_list.dart} (100%) rename frontend/sige_ie/lib/equipments/feature/{structured-cabling/addStruturedCabling.dart => structured_cabling/add_structured_cabling.dart} (99%) rename frontend/sige_ie/lib/equipments/feature/{structured-cabling/struturedCablingEquipmentList.dart => structured_cabling/structured_cabling_equipment_list.dart} (94%) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/Ilumination_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart similarity index 100% rename from frontend/sige_ie/lib/equipments/feature/iluminations/Ilumination_equipment_list.dart rename to frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart similarity index 99% rename from frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart rename to frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart index 067ce9bd..c1cf4606 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured-cabling/addStruturedCabling.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart @@ -16,13 +16,13 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class AddstruturedCabling extends StatefulWidget { +class AddStructuredCabling extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; - const AddstruturedCabling({ + const AddStructuredCabling({ super.key, required this.areaName, required this.categoryNumber, @@ -34,7 +34,7 @@ class AddstruturedCabling extends StatefulWidget { _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddEquipmentScreenState extends State { final _equipmentchargeController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; diff --git a/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart similarity index 94% rename from frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart rename to frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart index 0dd48fa1..b44f367e 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/structured-cabling/addStruturedCabling.dart'; +import 'package:sige_ie/equipments/feature/structured_cabling/add_structured_cabling.dart'; -class listStruturedCabling extends StatelessWidget { +class ListStructuredCabling extends StatelessWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const listStruturedCabling({ + const ListStructuredCabling({ super.key, required this.areaName, required this.categoryNumber, @@ -22,7 +22,7 @@ class listStruturedCabling extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => AddstruturedCabling( + builder: (context) => AddStructuredCabling( areaName: areaName, categoryNumber: categoryNumber, localName: localName, diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index eecff0fc..69a3a80f 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -4,10 +4,10 @@ import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical_circuit/electrical_circuit_list.dart'; import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; -import 'package:sige_ie/equipments/feature/iluminations/Ilumination_equipment_list.dart'; +import 'package:sige_ie/equipments/feature/iluminations/ilumination_equipment_list.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; import 'package:sige_ie/equipments/feature/electrical_load/eletrical_load_list.dart'; -import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart'; import 'package:sige_ie/equipments/feature/fire_alarm/list_fire_alarms.dart'; class SystemConfiguration extends StatefulWidget { @@ -37,7 +37,7 @@ class _SystemConfigurationState extends State { builder: (context) { switch (routeName) { case '/structuredCabling': - return listStruturedCabling( + return ListStructuredCabling( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 250d40ce..fc31cde7 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -9,11 +9,11 @@ import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_boa import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; import 'package:sige_ie/equipments/feature/electrical_load/eletrical_load_list.dart'; import 'package:sige_ie/equipments/feature/fire_alarm/list_fire_alarms.dart'; -import 'package:sige_ie/equipments/feature/structured-cabling/struturedCablingEquipmentList.dart'; +import 'package:sige_ie/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart'; import 'package:sige_ie/facilities/ui/facilities.dart'; import 'package:sige_ie/home/ui/home.dart'; import 'package:sige_ie/maps/feature/maps.dart'; -import 'package:sige_ie/equipments/feature/iluminations/Ilumination_equipment_list.dart'; +import 'package:sige_ie/equipments/feature/iluminations/ilumination_equipment_list.dart'; import 'package:sige_ie/equipments/feature/system_configuration.dart'; import 'package:sige_ie/places/feature/register/new_place.dart'; import 'package:sige_ie/areas/feature/register/new_area.dart'; @@ -324,7 +324,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => listStruturedCabling( + builder: (context) => ListStructuredCabling( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 92357a3a96fe867f4b02874ab165f4452ef1e19b Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 10:31:50 -0300 Subject: [PATCH 240/351] =?UTF-8?q?Resolu=C3=A7=C3=A3o=20de=20possiveis=20?= =?UTF-8?q?erros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipment_photos/temp.jpeg | Bin 0 -> 63605 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 api/equipment_photos/temp.jpeg diff --git a/api/equipment_photos/temp.jpeg b/api/equipment_photos/temp.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1c26617ba7cddcefbc49b0f72fb09317ca01fb99 GIT binary patch literal 63605 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 KNRPp}%>M__5e$j| literal 0 HcmV?d00001 From be68ac24c759e759d398346d2105520bac2fc5ad Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 10:32:13 -0300 Subject: [PATCH 241/351] =?UTF-8?q?Resolu=C3=A7=C3=A3o=20de=20possiveis=20?= =?UTF-8?q?erros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire_alarm/add_fire_alarm.dart | 24 ++++++++++++----- .../feature/fire_alarm/list_fire_alarms.dart | 26 ++++++++++++++----- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index 7d05b3af..93801ce2 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -397,12 +397,6 @@ class _AddEquipmentScreenState extends State { } void _registerEquipment() async { - print('areaId: ${widget.areaId}'); - print('categoryNumber: ${widget.categoryNumber}'); - print('_selectedType: $_selectedType'); - print( - '_selectedPersonalEquipmentCategoryId: $_selectedPersonalEquipmentCategoryId'); - int? genericEquipmentCategory; int? personalEquipmentCategory; @@ -415,7 +409,9 @@ class _AddEquipmentScreenState extends State { } final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( - area: widget.areaId, system: widget.categoryNumber); + area: widget.areaId, + system: widget.categoryNumber, + ); final FireAlarmEquipmentRequestModel fireAlarmEquipmentDetail = FireAlarmEquipmentRequestModel( @@ -455,6 +451,13 @@ class _AddEquipmentScreenState extends State { 'areaId': widget.areaId, }, ); + setState(() { + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -479,6 +482,13 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { + setState(() { + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); Navigator.pushReplacementNamed( context, '/listFireAlarms', diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 7272a5d0..09770ba2 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -27,26 +27,38 @@ class _ListFireAlarmsState extends State { List equipmentList = []; bool isLoading = true; final FireAlarmEquipmentService _service = FireAlarmEquipmentService(); + bool _isMounted = false; @override void initState() { super.initState(); + _isMounted = true; fetchEquipmentList(); } + @override + void dispose() { + _isMounted = false; + super.dispose(); + } + Future fetchEquipmentList() async { try { final List equipmentList = await _service.getFireAlarmListByArea(widget.areaId); - setState(() { - this.equipmentList = equipmentList; - isLoading = false; - }); + if (_isMounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } } catch (e) { print('Error fetching equipment list: $e'); - setState(() { - isLoading = false; - }); + if (_isMounted) { + setState(() { + isLoading = false; + }); + } } } From 68a11aa2d26759c91e25156a2fc1ffabf195104b Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 12:12:35 -0300 Subject: [PATCH 242/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20cabeamento=20est?= =?UTF-8?q?rutruturado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipments/data/equipment_service.dart | 31 ++ .../ilumination__equipment_request_model.dart | 21 + .../ilumination_request_model.dart | 2 +- .../ilumination_request_response.dart | 28 + .../iluminations/ilumination_service.dart | 49 ++ ...tured_cabling_equipment_request_model.dart | 21 + .../structured_cabling_request_model.dart | 16 + .../structured_cabling_response_model.dart | 28 + .../structured_cabling_service.dart | 49 ++ .../add_structured_cabling.dart | 482 +++++++++++++----- .../structured_cabling_equipment_list.dart | 110 ++-- 11 files changed, 688 insertions(+), 149 deletions(-) create mode 100644 frontend/sige_ie/lib/equipments/data/iluminations/ilumination__equipment_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index 1973a089..9898b5d0 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -4,6 +4,7 @@ import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart'; // Importe o modelo necessário import 'package:sige_ie/main.dart'; class EquipmentService { @@ -41,4 +42,34 @@ class EquipmentService { return null; } } + + Future createStructuredCabling( + StructuredCablingEquipmentRequestModel + structuredCablingEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(structuredCablingEquipmentRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + _logger.info('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + _logger.info( + 'Failed to register structured cabling equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + _logger.info('Error during register: $e'); + return null; + } + } } diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination__equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination__equipment_request_model.dart new file mode 100644 index 00000000..efd20485 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination__equipment_request_model.dart @@ -0,0 +1,21 @@ +import 'package:sige_ie/equipments/data/iluminations/ilumination_request_model.dart'; + +class IluminationEquipmentRequestModel { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + IluminationRequestModel? iluminationRequestModel; + + IluminationEquipmentRequestModel({ + required this.genericEquipmentCategory, + required this.personalEquipmentCategory, + required this.iluminationRequestModel, + }); + + Map toJson() { + return { + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, + 'ilumination_equipment': iluminationRequestModel?.toJson(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_model.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_model.dart index 43441b35..c3b44740 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_model.dart @@ -9,7 +9,7 @@ class IluminationRequestModel { Map toJson() { return { - 'name': area, + 'area': area, 'system': system, }; } diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart index e69de29b..4965e6b5 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart @@ -0,0 +1,28 @@ +class IluminationEquipmentResponseModel { + int id; + String area; + int system; + + IluminationEquipmentResponseModel({ + required this.id, + required this.area, + required this.system, + }); + + factory IluminationEquipmentResponseModel.fromJson( + Map json) { + return IluminationEquipmentResponseModel( + id: json['id'], + area: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart index e69de29b..3ec1c7e4 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class IluminationEquipmentService { + final Logger _logger = Logger('IluminationEquipmentService'); + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAlEquipment( + int systemId, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, + ]; + try { + _logger.info('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + _logger.info('Error during get al equipment: $e'); + return []; + } + } + + Future> getIluminationListByArea(int areaId) async { + final url = '${baseUrl}iluminations/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); + } else { + throw Exception('Failed to load ilumination equipment'); + } + } catch (e) { + _logger.info('Error during get ilumination equipment list: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart new file mode 100644 index 00000000..64cad126 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart @@ -0,0 +1,21 @@ +import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_request_model.dart'; + +class StructuredCablingEquipmentRequestModel { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + StructuredCablingRequestModel? structuredCablingRequestModel; + + StructuredCablingEquipmentRequestModel({ + required this.genericEquipmentCategory, + required this.personalEquipmentCategory, + required this.structuredCablingRequestModel, + }); + + Map toJson() { + return { + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, + 'structured_cabling_equipment': structuredCablingRequestModel?.toJson(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_request_model.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_request_model.dart index e69de29b..df7c53b9 100644 --- a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_request_model.dart @@ -0,0 +1,16 @@ +class StructuredCablingRequestModel { + int? area; + int? system; + + StructuredCablingRequestModel({ + required this.area, + required this.system, + }); + + Map toJson() { + return { + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart index e69de29b..0dcf2f62 100644 --- a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart @@ -0,0 +1,28 @@ +class StructuredCablingEquipmentResponseModel { + int id; + String area; + int system; + + StructuredCablingEquipmentResponseModel({ + required this.id, + required this.area, + required this.system, + }); + + factory StructuredCablingEquipmentResponseModel.fromJson( + Map json) { + return StructuredCablingEquipmentResponseModel( + id: json['id'], + area: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart index e69de29b..f6838018 100644 --- a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class StructuredCablingEquipmentService { + final Logger _logger = Logger('StructuredCablingEquipmentService'); + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipment( + int systemId, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, + ]; + try { + _logger.info('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + _logger.info('Error during get all equipment: $e'); + return []; + } + } + + Future> getStructuredCablingListByArea(int areaId) async { + final url = '${baseUrl}structured-cabling/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); + } else { + throw Exception('Failed to load structured cabling equipment'); + } + } catch (e) { + _logger.info('Error during get structured cabling equipment list: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart index c1cf4606..b8cc926c 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart @@ -1,13 +1,22 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; class ImageData { - File imageFile; int id; + File imageFile; String description; ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); @@ -21,6 +30,7 @@ class AddStructuredCabling extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const AddStructuredCabling({ super.key, @@ -28,33 +38,77 @@ class AddStructuredCabling extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override - _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); + _AddStructuredCablingScreenState createState() => + _AddStructuredCablingScreenState(); } -class _AddEquipmentScreenState extends State { - final _equipmentchargeController = TextEditingController(); +class _AddStructuredCablingScreenState extends State { + EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + final _equipmentQuantityController = TextEditingController(); + final _equipmentDimensionController = + TextEditingController(); // Adicionado para a dimensão String? _selectedType; String? _selectedTypeToDelete; - String? _selectedstruturedType; + String? _newEquipmentTypeName; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; + + List> genericEquipmentTypes = []; + List> personalEquipmentTypes = []; + Map personalEquipmentMap = {}; - List struturedType = [ - 'Selecione o tipo de cabeamento estruturado', - 'Eletroduto', - 'Eletrocalha', - ]; + @override + void initState() { + super.initState(); + _fetchEquipmentCategory(); + } - List equipmentTypes = [ - 'Selecione o tipo de cabeamento estruturado', - ]; + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _fetchEquipmentCategory(); + } + + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + + if (mounted) { + setState(() { + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) + .toList(); + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) + .toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentCategoryList) + equipment.name: equipment.id + }; + }); + } + } @override void dispose() { - _equipmentchargeController.dispose(); _equipmentQuantityController.dispose(); + _equipmentDimensionController.dispose(); // Adicionado para a dimensão categoryImagesMap[widget.categoryNumber]?.clear(); super.dispose(); } @@ -72,9 +126,8 @@ class _AddEquipmentScreenState extends State { } void _showImageDialog(File imageFile, {ImageData? existingImage}) { - TextEditingController descriptionController = TextEditingController( - text: existingImage?.description ?? '', - ); + TextEditingController descriptionController = + TextEditingController(text: existingImage?.description ?? ''); showDialog( context: context, builder: (BuildContext context) { @@ -105,10 +158,8 @@ class _AddEquipmentScreenState extends State { if (existingImage != null) { existingImage.description = descriptionController.text; } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); + final imageData = + ImageData(imageFile, descriptionController.text); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { categoryImagesMap[categoryNumber] = []; @@ -150,8 +201,14 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - equipmentTypes.add(typeController.text); - struturedType.add(typeController.text); // Adicione aqui + _newEquipmentTypeName = typeController.text; + }); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectedType = null; + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); + }); }); Navigator.of(context).pop(); } @@ -163,25 +220,70 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteEquipmentType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentCategory(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _deleteEquipmentType() async { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Selecione um tipo de equipamento válido para excluir.'), + Text('Não existem categorias de equipamentos a serem excluídas.'), + ), + ); + return; + } + + if (_selectedTypeToDelete == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Selecione uma categoria de equipamento válida para excluir.'), ), ); return; } + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -191,13 +293,32 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Excluir'), - onPressed: () { - setState(() { - equipmentTypes.remove(_selectedTypeToDelete); - struturedType.remove(_selectedTypeToDelete); // Adicione aqui - _selectedTypeToDelete = null; - }); + onPressed: () async { Navigator.of(context).pop(); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentCategory(); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } }, ), ], @@ -207,9 +328,10 @@ class _AddEquipmentScreenState extends State { } void _showConfirmationDialog() { - if (_equipmentchargeController.text.isEmpty || - _equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectedstruturedType == null)) { + if (_equipmentQuantityController.text.isEmpty || + _equipmentDimensionController + .text.isEmpty || // Adicionado para a dimensão + (_selectedType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -228,16 +350,17 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectedstruturedType ?? ''), - const SizedBox(height: 10), - const Text('Dimensão:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentchargeController.text), + Text(_selectedType ?? _newEquipmentTypeName ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), Text(_equipmentQuantityController.text), const SizedBox(height: 10), + const Text('Dimensão:', // Adicionado para a dimensão + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentDimensionController + .text), // Adicionado para a dimensão + const SizedBox(height: 10), const Text('Imagens:', style: TextStyle(fontWeight: FontWeight.bold)), Wrap( @@ -273,10 +396,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); + _registerEquipment(); }, ), ], @@ -285,20 +407,88 @@ class _AddEquipmentScreenState extends State { ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/listStruturedCabling', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, + void _registerEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final StructuredCablingRequestModel structuredCablingModel = + StructuredCablingRequestModel( + area: widget.areaId, + system: widget.categoryNumber, ); + + final StructuredCablingEquipmentRequestModel + structuredCablingEquipmentDetail = + StructuredCablingEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + structuredCablingRequestModel: structuredCablingModel, + ); + + int? equipmentId = await equipmentService + .createStructuredCabling(structuredCablingEquipmentDetail); + + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/listStruturedCabling', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentQuantityController.clear(); + _equipmentDimensionController.clear(); // Adicionado para a dimensão + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } } @override Widget build(BuildContext context) { + List> combinedTypes = [ + ...genericEquipmentTypes, + ...personalEquipmentTypes + ]; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -306,7 +496,26 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + setState(() { + _equipmentQuantityController.clear(); + _equipmentDimensionController + .clear(); // Adicionado para a dimensão + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + Navigator.pushReplacementNamed( + context, + '/listStruturedCabling', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), @@ -323,7 +532,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Adicionar equipamentos ', + child: Text('Adicionar cabeamento estruturado', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -335,7 +544,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de cabeamento', + const Text('Tipos de cabeamento estruturado', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -344,38 +553,69 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: struturedType, - value: _selectedstruturedType, + items: [ + { + 'name': + 'Selecione o tipo de cabeamento estruturado', + 'id': -1, + 'type': -1 + } + ] + + combinedTypes, + value: _selectedType, onChanged: (newValue) { - setState(() { - _selectedstruturedType = newValue; - if (newValue == struturedType[0]) { - _selectedstruturedType = null; - } - if (_selectedstruturedType != null) { - _selectedType = null; - } - }); + if (newValue != + 'Selecione o tipo de cabeamento estruturado') { + setState(() { + _selectedType = newValue; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalEquipmentCategorySelected = + selected['type'] == 'pessoal'; + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = + selected['id'] as int; + _selectedGenericEquipmentCategoryId = null; + } else { + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; + } + }); + } }, - enabled: _selectedType == null, + enabled: true, ), ), - Row( - children: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); - }, - ), - ], + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + if (personalEquipmentTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Não existem equipamentos pessoais a serem excluídos.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } + }, + ), + ], + ), ), ], ), @@ -383,13 +623,13 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedstruturedType = null; + _selectedType = null; }); }, child: const Text('Limpar seleção'), ), const SizedBox(height: 30), - const Text('Dimensão', + const Text('Dimensão:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -399,8 +639,9 @@ class _AddEquipmentScreenState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentchargeController, - keyboardType: TextInputType.number, + controller: + _equipmentDimensionController, // Adicionado para a dimensão + keyboardType: TextInputType.text, decoration: const InputDecoration( border: InputBorder.none, contentPadding: @@ -512,25 +753,28 @@ class _AddEquipmentScreenState extends State { 'Selecione um equipamento para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: struturedType // Use struturedType aqui - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: personalEquipmentTypes.map>( + (Map value) { + return DropdownMenuItem( + value: value['name'] as String, + child: Text( + value['name'] as String, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -557,7 +801,7 @@ class _AddEquipmentScreenState extends State { } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -569,18 +813,24 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, + value: value['name'] as String, + enabled: + value['name'] != 'Selecione o tipo de cabeamento estruturado', child: Text( - value, + value['name'] as String, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value['name'] == + 'Selecione o tipo de cabeamento estruturado' + ? Colors.grey + : Colors.black, ), ), ); diff --git a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart index b44f367e..063a3fe0 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_service.dart'; import 'package:sige_ie/equipments/feature/structured_cabling/add_structured_cabling.dart'; -class ListStructuredCabling extends StatelessWidget { +class ListStructuredCabling extends StatefulWidget { final String areaName; final String localName; final int categoryNumber; @@ -10,23 +11,68 @@ class ListStructuredCabling extends StatelessWidget { final int areaId; const ListStructuredCabling({ - super.key, + Key? key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, required this.areaId, - }); + }) : super(key: key); + + @override + _ListStructuredCablingState createState() => _ListStructuredCablingState(); +} + +class _ListStructuredCablingState extends State { + List equipmentList = []; + bool isLoading = true; + final StructuredCablingEquipmentService _service = + StructuredCablingEquipmentService(); + bool _isMounted = false; + + @override + void initState() { + super.initState(); + _isMounted = true; + fetchEquipmentList(); + } + + @override + void dispose() { + _isMounted = false; + super.dispose(); + } + + Future fetchEquipmentList() async { + try { + final List equipmentList = + await _service.getStructuredCablingListByArea(widget.areaId); + if (_isMounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + } catch (e) { + print('Error fetching equipment list: $e'); + if (_isMounted) { + setState(() { + isLoading = false; + }); + } + } + } void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddStructuredCabling( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, ), ), ); @@ -34,10 +80,6 @@ class ListStructuredCabling extends StatelessWidget { @override Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - String systemTitle = 'CABEAMENTO ESTRUTURADO'; return Scaffold( @@ -50,10 +92,10 @@ class ListStructuredCabling extends StatelessWidget { context, '/systemLocation', arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, }, ); }, @@ -72,7 +114,7 @@ class ListStructuredCabling extends StatelessWidget { ), child: Center( child: Text( - '$areaName - $systemTitle', + '${widget.areaName} - $systemTitle', textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, @@ -88,24 +130,28 @@ class ListStructuredCabling extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), + isLoading + ? const Center( + child: CircularProgressIndicator(), ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54, + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), + ), ), - ), - ), const SizedBox(height: 40), ], ), From dfe33ead0735b70d30298f11f415bbe7dbcc399b Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 12:52:53 -0300 Subject: [PATCH 243/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20ilumina=C3=A7?= =?UTF-8?q?=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipment_photos/temp_BA5zilu.jpeg | Bin 0 -> 63920 bytes .../equipments/data/equipment_service.dart | 213 ++++++++- .../add_ilumination_equipment.dart | 424 +++++++++++++----- .../ilumination_equipment_list.dart | 102 +++-- 4 files changed, 591 insertions(+), 148 deletions(-) create mode 100644 api/equipment_photos/temp_BA5zilu.jpeg diff --git a/api/equipment_photos/temp_BA5zilu.jpeg b/api/equipment_photos/temp_BA5zilu.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..11e811280bbdc2154954eefc941471ed51ea26e6 GIT binary patch literal 63920 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 literal 0 HcmV?d00001 diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index 9898b5d0..86ef4ced 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -3,8 +3,11 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/atmospheric/atmospheric_request_model.dart'; +import 'package:sige_ie/equipments/data/cooling/cooling_request_model.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart'; -import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart'; // Importe o modelo necessário +import 'package:sige_ie/equipments/data/iluminations/ilumination__equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart'; import 'package:sige_ie/main.dart'; class EquipmentService { @@ -72,4 +75,212 @@ class EquipmentService { return null; } } + + Future createIlumination( + IluminationEquipmentRequestModel + illuminationEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(illuminationEquipmentRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + _logger.info('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + _logger.info( + 'Failed to register illumination equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + _logger.info('Error during register: $e'); + return null; + } + } + + /* Future createElectricalLoad( + ElectricalLoadEquipmentRequestModel + electricalLoadEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(electricalLoadEquipmentRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + _logger.info('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + _logger.info( + 'Failed to register electrical load equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + _logger.info('Error during register: $e'); + return null; + } + } + + Future createElectricalLine( + ElectricalLineEquipmentRequestModel + electricalLineEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(electricalLineEquipmentRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + _logger.info('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + _logger.info( + 'Failed to register electrical line equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + _logger.info('Error during register: $e'); + return null; + } + } + + Future createElectricalCircuit( + ElectricalCircuitEquipmentRequestModel + electricalCircuitEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(electricalCircuitEquipmentRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + _logger.info('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + _logger.info( + 'Failed to register electrical circuit equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + _logger.info('Error during register: $e'); + return null; + } + } + + Future createDistribution( + DistributionEquipmentRequestModel + distributionEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(distributionEquipmentRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + _logger.info('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + _logger.info( + 'Failed to register distribution equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + _logger.info('Error during register: $e'); + return null; + } + } + */ + Future createCooling( + CoolingEquipmentRequestModel coolingEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(coolingEquipmentRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + _logger.info('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + _logger.info( + 'Failed to register cooling equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + _logger.info('Error during register: $e'); + return null; + } + } + + Future createAtmospheric( + AtmosphericEquipmentRequestModel atmosphericEquipmentRequestModel) async { + var url = Uri.parse(baseUrl); + + try { + var response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(atmosphericEquipmentRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 201 || response.statusCode == 200) { + Map responseData = jsonDecode(response.body); + _logger.info('Request successful, received ID: ${responseData['id']}'); + return responseData['id']; + } else { + _logger.info( + 'Failed to register atmospheric equipment: ${response.statusCode}'); + return null; + } + } catch (e) { + _logger.info('Error during register: $e'); + return null; + } + } } diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart index 13475981..c90f21ac 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart @@ -1,13 +1,23 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/iluminations/ilumination__equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/iluminations/ilumination_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; + class ImageData { - File imageFile; int id; + File imageFile; String description; ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); @@ -21,6 +31,7 @@ class AddIluminationEquipmentScreen extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const AddIluminationEquipmentScreen({ super.key, @@ -28,35 +39,75 @@ class AddIluminationEquipmentScreen extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override - _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); + _AddIlluminationScreenState createState() => _AddIlluminationScreenState(); } -class _AddEquipmentScreenState extends State { - final _equipmentchargeController = TextEditingController(); +class _AddIlluminationScreenState extends State { + EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + final _equipmentQuantityController = TextEditingController(); + final _equipmentPowerController = TextEditingController(); String? _selectedType; - String? _selectedLocation; String? _selectedTypeToDelete; - String? _selectedLampType; + String? _newEquipmentTypeName; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; + + List> genericEquipmentTypes = []; + List> personalEquipmentTypes = []; + Map personalEquipmentMap = {}; + + @override + void initState() { + super.initState(); + _fetchEquipmentCategory(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _fetchEquipmentCategory(); + } + + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); - List lampTypes = [ - 'Selecione o tipo de lâmpada', - 'Halogenia', - 'Fluorescente', - 'LEDs', - 'Incandescentes', - 'Lâmpadas Queimadas', - ]; + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); - List additionalTypes = []; + if (mounted) { + setState(() { + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) + .toList(); + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) + .toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentCategoryList) + equipment.name: equipment.id + }; + }); + } + } @override void dispose() { - _equipmentchargeController.dispose(); _equipmentQuantityController.dispose(); + _equipmentPowerController.dispose(); categoryImagesMap[widget.categoryNumber]?.clear(); super.dispose(); } @@ -74,9 +125,8 @@ class _AddEquipmentScreenState extends State { } void _showImageDialog(File imageFile, {ImageData? existingImage}) { - TextEditingController descriptionController = TextEditingController( - text: existingImage?.description ?? '', - ); + TextEditingController descriptionController = + TextEditingController(text: existingImage?.description ?? ''); showDialog( context: context, builder: (BuildContext context) { @@ -107,10 +157,8 @@ class _AddEquipmentScreenState extends State { if (existingImage != null) { existingImage.description = descriptionController.text; } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); + final imageData = + ImageData(imageFile, descriptionController.text); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { categoryImagesMap[categoryNumber] = []; @@ -128,17 +176,17 @@ class _AddEquipmentScreenState extends State { ); } - void _addNewLampType() { + void _addNewEquipmentType() { TextEditingController typeController = TextEditingController(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Adicionar novo tipo de lâmpada'), + title: const Text('Adicionar novo tipo de equipamento'), content: TextField( controller: typeController, decoration: const InputDecoration( - hintText: 'Digite o novo tipo de lâmpada'), + hintText: 'Digite o novo tipo de equipamento'), ), actions: [ TextButton( @@ -152,7 +200,14 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - additionalTypes.add(typeController.text); + _newEquipmentTypeName = typeController.text; + }); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectedType = null; + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); + }); }); Navigator.of(context).pop(); } @@ -164,24 +219,70 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteLampType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um tipo de lâmpada') { + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentCategory(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _deleteEquipmentType() async { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Selecione um tipo de lâmpada válido para excluir.'), + content: + Text('Não existem categorias de equipamentos a serem excluídas.'), ), ); return; } + if (_selectedTypeToDelete == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Selecione uma categoria de equipamento válida para excluir.'), + ), + ); + return; + } + + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de lâmpada "$_selectedTypeToDelete"?'), + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -191,12 +292,32 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Excluir'), - onPressed: () { - setState(() { - additionalTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); + onPressed: () async { Navigator.of(context).pop(); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentCategory(); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } }, ), ], @@ -206,11 +327,9 @@ class _AddEquipmentScreenState extends State { } void _showConfirmationDialog() { - if (_equipmentchargeController.text.isEmpty || - _equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectedLampType == null) || - _selectedLocation == null || - _selectedLocation == 'Selecione a localização') { + if (_equipmentQuantityController.text.isEmpty || + _equipmentPowerController.text.isEmpty || + (_selectedType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -229,19 +348,15 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectedLampType ?? ''), - const SizedBox(height: 10), - const Text('Potência da Lâmpada(KW):', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentchargeController.text), + Text(_selectedType ?? _newEquipmentTypeName ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), Text(_equipmentQuantityController.text), const SizedBox(height: 10), - const Text('Localização:', + const Text('Potência (KW):', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedLocation ?? ''), + Text(_equipmentPowerController.text), const SizedBox(height: 10), const Text('Imagens:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -278,10 +393,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); + _registerEquipment(); }, ), ], @@ -290,21 +404,85 @@ class _AddEquipmentScreenState extends State { ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/listIluminationEquipment', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, + void _registerEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final IluminationRequestModel iluminationModel = IluminationRequestModel( + area: widget.areaId, + system: widget.categoryNumber, + ); + + final IluminationEquipmentRequestModel iluminationEquipmentDetail = + IluminationEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + iluminationRequestModel: iluminationModel, ); + + int? equipmentId = + await equipmentService.createIlumination(iluminationEquipmentDetail); + + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/listIluminationEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentQuantityController.clear(); + _equipmentPowerController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } } @override Widget build(BuildContext context) { - List combinedTypes = lampTypes + additionalTypes; + List> combinedTypes = [ + ...genericEquipmentTypes, + ...personalEquipmentTypes + ]; return Scaffold( appBar: AppBar( @@ -313,7 +491,25 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + setState(() { + _equipmentQuantityController.clear(); + _equipmentPowerController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + Navigator.pushReplacementNamed( + context, + '/listIluminationEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), @@ -330,7 +526,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Adicionar equipamentos ', + child: Text('Adicionar equipamento', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -342,7 +538,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de Lâmpada', + const Text('Tipos de equipamento', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -351,17 +547,32 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: combinedTypes, - value: _selectedLampType ?? _selectedType, + items: [ + { + 'name': 'Selecione o tipo de equipamento', + 'id': -1, + 'type': -1 + } + ] + + combinedTypes, + value: _selectedType, onChanged: (newValue) { - if (newValue != 'Selecione o tipo de lâmpada') { + if (newValue != 'Selecione o tipo de equipamento') { setState(() { - if (lampTypes.contains(newValue)) { - _selectedLampType = newValue; - _selectedType = null; + _selectedType = newValue; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalEquipmentCategorySelected = + selected['type'] == 'pessoal'; + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = + selected['id'] as int; + _selectedGenericEquipmentCategoryId = null; } else { - _selectedType = newValue; - _selectedLampType = null; + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; } }); } @@ -375,16 +586,16 @@ class _AddEquipmentScreenState extends State { children: [ IconButton( icon: const Icon(Icons.add), - onPressed: _addNewLampType, + onPressed: _addNewEquipmentType, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { - if (additionalTypes.isEmpty) { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - 'Nenhum tipo de lâmpada adicionado para excluir.'), + 'Não existem equipamentos pessoais a serem excluídos.'), ), ); } else { @@ -404,14 +615,13 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedLampType = null; _selectedType = null; }); }, child: const Text('Limpar seleção'), ), const SizedBox(height: 30), - const Text('Potência da Lâmpada(KW)', + const Text('Potência (KW)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -421,7 +631,7 @@ class _AddEquipmentScreenState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentchargeController, + controller: _equipmentPowerController, keyboardType: TextInputType.number, decoration: const InputDecoration( border: InputBorder.none, @@ -453,26 +663,6 @@ class _AddEquipmentScreenState extends State { ), ), ), - const SizedBox(height: 30), - const Text('Localização (Interno ou Externo)', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildStyledDropdown( - items: const [ - 'Selecione a localização', - 'Interno', - 'Externo' - ], - value: _selectedLocation, - onChanged: (newValue) { - if (newValue != 'Selecione a localização') { - setState(() { - _selectedLocation = newValue; - }); - } - }, - ), const SizedBox(height: 15), IconButton( icon: const Icon(Icons.camera_alt), @@ -546,12 +736,12 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de lâmpada'), + title: const Text('Excluir tipo de equipamento'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Selecione um tipo de lâmpada para excluir:', + 'Selecione um equipamento para excluir:', textAlign: TextAlign.center, ), StatefulBuilder( @@ -564,12 +754,12 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: additionalTypes - .map>((String value) { + items: personalEquipmentTypes.map>( + (Map value) { return DropdownMenuItem( - value: value, + value: value['name'] as String, child: Text( - value, + value['name'] as String, style: const TextStyle(color: Colors.black), ), ); @@ -591,7 +781,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (_selectedTypeToDelete != null) { Navigator.of(context).pop(); - _deleteLampType(); + _deleteEquipmentType(); } }, ), @@ -602,7 +792,7 @@ class _AddEquipmentScreenState extends State { } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -614,18 +804,22 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, + value: value['name'] as String, + enabled: value['name'] != 'Selecione o tipo de equipamento', child: Text( - value, + value['name'] as String, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value['name'] == 'Selecione o tipo de equipamento' + ? Colors.grey + : Colors.black, ), ), ); diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart index 6dd2403b..5c331a74 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/iluminations/ilumination_service.dart'; import 'package:sige_ie/equipments/feature/iluminations/add_ilumination_equipment.dart'; -class ListIluminationEquipment extends StatelessWidget { +class ListIluminationEquipment extends StatefulWidget { final String areaName; final String localName; final int categoryNumber; @@ -10,23 +11,60 @@ class ListIluminationEquipment extends StatelessWidget { final int areaId; const ListIluminationEquipment({ - super.key, + Key? key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, required this.areaId, - }); + }) : super(key: key); + + @override + _ListIluminationEquipmentState createState() => + _ListIluminationEquipmentState(); +} + +class _ListIluminationEquipmentState extends State { + List equipmentList = []; + bool isLoading = true; + final IluminationEquipmentService _service = IluminationEquipmentService(); + + @override + void initState() { + super.initState(); + fetchEquipmentList(); + } + + Future fetchEquipmentList() async { + try { + final List equipmentList = + await _service.getIluminationListByArea(widget.areaId); + if (mounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + } catch (e) { + print('Error fetching equipment list: $e'); + if (mounted) { + setState(() { + isLoading = false; + }); + } + } + } void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddIluminationEquipmentScreen( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, ), ), ); @@ -34,10 +72,6 @@ class ListIluminationEquipment extends StatelessWidget { @override Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - String systemTitle = 'ILUMINAÇÃO'; return Scaffold( @@ -50,10 +84,10 @@ class ListIluminationEquipment extends StatelessWidget { context, '/systemLocation', arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, }, ); }, @@ -71,7 +105,7 @@ class ListIluminationEquipment extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', + child: Text('${widget.areaName} - $systemTitle', textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, @@ -85,23 +119,27 @@ class ListIluminationEquipment extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), + isLoading + ? const Center( + child: CircularProgressIndicator(), ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), const SizedBox(height: 40), ], ), From 634a0c2f72ba6f15223c84f57a57f3c313a64ae8 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 13:43:47 -0300 Subject: [PATCH 244/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20descargas=20atmo?= =?UTF-8?q?sf[sf=C3=A9ricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipment_photos/temp_TqeOtKh.jpeg | Bin 0 -> 63357 bytes .../atmospheric_equipment_request_model.dart | 21 + .../atmospheric_request_model.dart | 23 +- .../atmospheric_response_model.dart | 28 ++ .../data/atmospheric/atmospheric_service.dart | 57 ++- .../data/cooling/cooling_request_model.dart | 31 -- .../data/cooling/cooling_response_model.dart | 0 .../data/cooling/cooling_service.dart | 45 -- .../distribution_equipment_request_model.dart | 21 + .../distribution_request_model.dart | 16 + .../distribution_response_model.dart | 28 ++ .../distribution/distribution_service.dart | 49 ++ ...letrical_load_equipment_request_model.dart | 21 + .../eletrical_load_request_model.dart.dart | 16 + .../eletrical_load_response_model.dart | 28 ++ .../eletrical_load_service.dart | 49 ++ ...rical_circuit_equipment_request_model.dart | 21 + .../eletrical_circuit_request_model.dart | 16 + .../eletrical_circuit_response_model.dart | 28 ++ .../eletrical_circuit_service.dart | 49 ++ ...letrical_line_equipment_request_model.dart | 21 + .../eletrical_line_request_model.dart | 16 + .../eletrical_line_response_model.dart | 28 ++ .../eletrical_line_service.dart | 49 ++ .../equipments/data/equipment_service.dart | 22 +- ...efrigerations_equipment_request_model.dart | 21 + .../refrigerations_request_model.dart | 16 + .../refrigerations_response_model.dart | 28 ++ .../refrigerations_service.dart | 49 ++ .../add_atmospheric_discharges_equipment.dart | 25 +- .../electrical_load/add_electrical_load.dart | 461 +++++++++++++----- .../electrical_load/eletrical_load_list.dart | 92 ++-- 32 files changed, 1078 insertions(+), 297 deletions(-) create mode 100644 api/equipment_photos/temp_TqeOtKh.jpeg create mode 100644 frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_equipment_request_model.dart delete mode 100644 frontend/sige_ie/lib/equipments/data/cooling/cooling_request_model.dart delete mode 100644 frontend/sige_ie/lib/equipments/data/cooling/cooling_response_model.dart delete mode 100644 frontend/sige_ie/lib/equipments/data/cooling/cooling_service.dart create mode 100644 frontend/sige_ie/lib/equipments/data/distribution/distribution_equipment_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_equipment_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_equipment_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_equipment_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_equipment_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_request_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_response_model.dart create mode 100644 frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart diff --git a/api/equipment_photos/temp_TqeOtKh.jpeg b/api/equipment_photos/temp_TqeOtKh.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5008381d7ef8c195a18aa8b7c18a24dcac6f2697 GIT binary patch literal 63357 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 toJson() { + return { + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, + 'atmospheric_discharge_equipment': atmosphericRequestModel?.toJson(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_request_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_request_model.dart index f1cc7c46..b5be154e 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_request_model.dart @@ -1,31 +1,16 @@ -class AtmosphericEquipmentRequestModel { - List photos; - int area; - int system; - int equipmentType; +class AtmosphericRequestModel { + int? area; + int? system; - AtmosphericEquipmentRequestModel({ - required this.photos, + AtmosphericRequestModel({ required this.area, required this.system, - required this.equipmentType, }); Map toJson() { return { - 'photos': photos, 'area': area, 'system': system, - 'equipmentType': equipmentType, }; } - - factory AtmosphericEquipmentRequestModel.fromJson(Map json) { - return AtmosphericEquipmentRequestModel( - photos: List.from(json['photos'] ?? []), - area: json['area'], - system: json['system'], - equipmentType: json['equipmentType'], - ); - } } diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart index e69de29b..f29ce20a 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart @@ -0,0 +1,28 @@ +class AtmosphericEquipmentResponseModel { + int id; + String area; + int system; + + AtmosphericEquipmentResponseModel({ + required this.id, + required this.area, + required this.system, + }); + + factory AtmosphericEquipmentResponseModel.fromJson( + Map json) { + return AtmosphericEquipmentResponseModel( + id: json['id'], + area: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart index a2bb4b57..ebeed490 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart @@ -1,46 +1,49 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/atmospheric/atmospheric_request_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class AtmosphericEquipmentService { - final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; + final Logger _logger = Logger('AtmosphericEquipmentService'); + final String baseUrl = 'http://10.0.2.2:8000/api/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); - Future register( - AtmosphericEquipmentRequestModel atmosphericEquipmentRequestModel) async { - var url = Uri.parse(baseUrl); - + Future> getAllEquipment( + int systemId, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, + ]; try { - print('Sending POST request to $url'); - print( - 'Request body: ${jsonEncode(atmosphericEquipmentRequestModel.toJson())}'); - - var response = await client.post( - url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(atmosphericEquipmentRequestModel.toJson()), - ); - - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); + _logger.info('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + _logger.info('Error during get all equipment: $e'); + return []; + } + } - if (response.statusCode == 201) { - Map responseData = jsonDecode(response.body); - print('Request successful, received ID: ${responseData['id']}'); - return responseData['id']; + Future> getAtmosphericListByArea(int areaId) async { + final url = '${baseUrl}atmospheric-discharges/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); } else { - print( - 'Failed to register atmospheric equipment: ${response.statusCode}'); - return null; + throw Exception('Failed to load structured cabling equipment'); } } catch (e) { - print('Error during register: $e'); - return null; + _logger.info('Error during get structured cabling equipment list: $e'); + return []; } } } diff --git a/frontend/sige_ie/lib/equipments/data/cooling/cooling_request_model.dart b/frontend/sige_ie/lib/equipments/data/cooling/cooling_request_model.dart deleted file mode 100644 index 5f9a0107..00000000 --- a/frontend/sige_ie/lib/equipments/data/cooling/cooling_request_model.dart +++ /dev/null @@ -1,31 +0,0 @@ -class CoolingEquipmentRequestModel { - List photos; - int area; - int system; - int equipmentType; - - CoolingEquipmentRequestModel({ - required this.photos, - required this.area, - required this.system, - required this.equipmentType, - }); - - Map toJson() { - return { - 'photos': photos, - 'area': area, - 'system': system, - 'equipmentType': equipmentType, - }; - } - - factory CoolingEquipmentRequestModel.fromJson(Map json) { - return CoolingEquipmentRequestModel( - photos: List.from(json['photos'] ?? []), - area: json['area'], - system: json['system'], - equipmentType: json['equipmentType'], - ); - } -} diff --git a/frontend/sige_ie/lib/equipments/data/cooling/cooling_response_model.dart b/frontend/sige_ie/lib/equipments/data/cooling/cooling_response_model.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/sige_ie/lib/equipments/data/cooling/cooling_service.dart b/frontend/sige_ie/lib/equipments/data/cooling/cooling_service.dart deleted file mode 100644 index 96eeb3ae..00000000 --- a/frontend/sige_ie/lib/equipments/data/cooling/cooling_service.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:convert'; -import 'package:http/http.dart' as http; -import 'package:http_interceptor/http_interceptor.dart'; -import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/cooling/cooling_request_model.dart'; -import 'package:sige_ie/main.dart'; - -class CoolingEquipmentService { - final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; - http.Client client = InterceptedClient.build( - interceptors: [AuthInterceptor(cookieJar)], - ); - - Future register( - CoolingEquipmentRequestModel coolingEquipmentRequestModel) async { - var url = Uri.parse(baseUrl); - - try { - print('Sending POST request to $url'); - print( - 'Request body: ${jsonEncode(coolingEquipmentRequestModel.toJson())}'); - - var response = await client.post( - url, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(coolingEquipmentRequestModel.toJson()), - ); - - print('Response status code: ${response.statusCode}'); - print('Response body: ${response.body}'); - - if (response.statusCode == 201) { - Map responseData = jsonDecode(response.body); - print('Request successful, received ID: ${responseData['id']}'); - return responseData['id']; - } else { - print('Failed to register cooling equipment: ${response.statusCode}'); - return null; - } - } catch (e) { - print('Error during register: $e'); - return null; - } - } -} diff --git a/frontend/sige_ie/lib/equipments/data/distribution/distribution_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_equipment_request_model.dart new file mode 100644 index 00000000..fbbaf814 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/distribution/distribution_equipment_request_model.dart @@ -0,0 +1,21 @@ +import 'package:sige_ie/equipments/data/distribution/distribution_request_model.dart'; + +class DistributionEquipmentRequestModel { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + DistributionRequestModel? distributionRequestModel; + + DistributionEquipmentRequestModel({ + required this.genericEquipmentCategory, + required this.personalEquipmentCategory, + required this.distributionRequestModel, + }); + + Map toJson() { + return { + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, + 'distribution_board_equipment': distributionRequestModel?.toJson(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/distribution/distribution_request_model.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_request_model.dart index e69de29b..031a4684 100644 --- a/frontend/sige_ie/lib/equipments/data/distribution/distribution_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/distribution/distribution_request_model.dart @@ -0,0 +1,16 @@ +class DistributionRequestModel { + int? area; + int? system; + + DistributionRequestModel({ + required this.area, + required this.system, + }); + + Map toJson() { + return { + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart index e69de29b..c2c01676 100644 --- a/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart @@ -0,0 +1,28 @@ +class DistributionEquipmentResponseModel { + int id; + String area; + int system; + + DistributionEquipmentResponseModel({ + required this.id, + required this.area, + required this.system, + }); + + factory DistributionEquipmentResponseModel.fromJson( + Map json) { + return DistributionEquipmentResponseModel( + id: json['id'], + area: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart index e69de29b..3504793d 100644 --- a/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart +++ b/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class DistributionEquipmentService { + final Logger _logger = Logger('DistributionEquipmentService'); + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipment( + int systemId, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, + ]; + try { + _logger.info('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + _logger.info('Error during get all equipment: $e'); + return []; + } + } + + Future> getDistributionListByArea(int areaId) async { + final url = '${baseUrl}distribution-boards/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); + } else { + throw Exception('Failed to load structured cabling equipment'); + } + } catch (e) { + _logger.info('Error during get structured cabling equipment list: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_equipment_request_model.dart new file mode 100644 index 00000000..f894742e --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_equipment_request_model.dart @@ -0,0 +1,21 @@ +import 'package:sige_ie/equipments/data/eletrical-load/eletrical_load_request_model.dart.dart'; + +class EletricalLoadEquipmentRequestModel { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + EletricalLoadRequestModel? eletricalLoadRequestModel; + + EletricalLoadEquipmentRequestModel({ + required this.genericEquipmentCategory, + required this.personalEquipmentCategory, + required this.eletricalLoadRequestModel, + }); + + Map toJson() { + return { + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, + 'electrical_load_equipment': eletricalLoadRequestModel?.toJson(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_request_model.dart.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_request_model.dart.dart index e69de29b..50d38722 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_request_model.dart.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_request_model.dart.dart @@ -0,0 +1,16 @@ +class EletricalLoadRequestModel { + int? area; + int? system; + + EletricalLoadRequestModel({ + required this.area, + required this.system, + }); + + Map toJson() { + return { + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart index e69de29b..eadb03ce 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart @@ -0,0 +1,28 @@ +class EletricalLoadEquipmentResponseModel { + int id; + String area; + int system; + + EletricalLoadEquipmentResponseModel({ + required this.id, + required this.area, + required this.system, + }); + + factory EletricalLoadEquipmentResponseModel.fromJson( + Map json) { + return EletricalLoadEquipmentResponseModel( + id: json['id'], + area: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart index e69de29b..6b2dabfc 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class EletricalLoadEquipmentService { + final Logger _logger = Logger('EletricalLoadEquipmentService'); + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipment( + int systemId, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, + ]; + try { + _logger.info('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + _logger.info('Error during get all equipment: $e'); + return []; + } + } + + Future> getEletricalLoadListByArea(int areaId) async { + final url = '${baseUrl}electrical-loads/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); + } else { + throw Exception('Failed to load structured cabling equipment'); + } + } catch (e) { + _logger.info('Error during get structured cabling equipment list: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_equipment_request_model.dart new file mode 100644 index 00000000..7ecf5095 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_equipment_request_model.dart @@ -0,0 +1,21 @@ +import 'package:sige_ie/equipments/data/eletrical_circuit/eletrical_circuit_request_model.dart'; + +class EletricalCircuitEquipmentRequestModel { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + EletricalCircuitRequestModel? eletricalCircuitRequestModel; + + EletricalCircuitEquipmentRequestModel({ + required this.genericEquipmentCategory, + required this.personalEquipmentCategory, + required this.eletricalCircuitRequestModel, + }); + + Map toJson() { + return { + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, + 'electrical_circuit_equipment': eletricalCircuitRequestModel?.toJson(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_request_model.dart index e69de29b..8068d3c5 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_request_model.dart @@ -0,0 +1,16 @@ +class EletricalCircuitRequestModel { + int? area; + int? system; + + EletricalCircuitRequestModel({ + required this.area, + required this.system, + }); + + Map toJson() { + return { + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart index e69de29b..b942ec0e 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart @@ -0,0 +1,28 @@ +class EletricalCircuitEquipmentResponseModel { + int id; + String area; + int system; + + EletricalCircuitEquipmentResponseModel({ + required this.id, + required this.area, + required this.system, + }); + + factory EletricalCircuitEquipmentResponseModel.fromJson( + Map json) { + return EletricalCircuitEquipmentResponseModel( + id: json['id'], + area: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart index e69de29b..70f0358d 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class EletricalCircuitEquipmentService { + final Logger _logger = Logger('EletricalCircuitEquipmentService'); + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipment( + int systemId, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, + ]; + try { + _logger.info('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + _logger.info('Error during get all equipment: $e'); + return []; + } + } + + Future> getEletricalCircuitListByArea(int areaId) async { + final url = '${baseUrl}electrical-circuits/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); + } else { + throw Exception('Failed to load structured cabling equipment'); + } + } catch (e) { + _logger.info('Error during get structured cabling equipment list: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_equipment_request_model.dart new file mode 100644 index 00000000..9377f5f6 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_equipment_request_model.dart @@ -0,0 +1,21 @@ +import 'package:sige_ie/equipments/data/eletrical_line/eletrical_line_request_model.dart'; + +class EletricalLineEquipmentRequestModel { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + EletricalLineRequestModel? eletricalLineRequestModel; + + EletricalLineEquipmentRequestModel({ + required this.genericEquipmentCategory, + required this.personalEquipmentCategory, + required this.eletricalLineRequestModel, + }); + + Map toJson() { + return { + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, + 'electrical_line_equipment': eletricalLineRequestModel?.toJson(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_request_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_request_model.dart index e69de29b..9bb93058 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_request_model.dart @@ -0,0 +1,16 @@ +class EletricalLineRequestModel { + int? area; + int? system; + + EletricalLineRequestModel({ + required this.area, + required this.system, + }); + + Map toJson() { + return { + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart index e69de29b..dfa9b0b0 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart @@ -0,0 +1,28 @@ +class EletricalLineEquipmentResponseModel { + int id; + String area; + int system; + + EletricalLineEquipmentResponseModel({ + required this.id, + required this.area, + required this.system, + }); + + factory EletricalLineEquipmentResponseModel.fromJson( + Map json) { + return EletricalLineEquipmentResponseModel( + id: json['id'], + area: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart index e69de29b..e66c2970 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class EletricalLineEquipmentService { + final Logger _logger = Logger('EletricalLineEquipmentService'); + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipment( + int systemId, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, + ]; + try { + _logger.info('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + _logger.info('Error during get all equipment: $e'); + return []; + } + } + + Future> getEletricalLineListByArea(int areaId) async { + final url = '${baseUrl}electrical-lines/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); + } else { + throw Exception('Failed to load structured cabling equipment'); + } + } catch (e) { + _logger.info('Error during get structured cabling equipment list: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index 86ef4ced..f04cb379 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -3,8 +3,12 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/equipments/data/atmospheric/atmospheric_request_model.dart'; -import 'package:sige_ie/equipments/data/cooling/cooling_request_model.dart'; +import 'package:sige_ie/equipments/data/atmospheric/atmospheric_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/distribution/distribution_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/eletrical-load/eletrical_load_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/eletrical_circuit/eletrical_circuit_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/eletrical_line/eletrical_line_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/refrigerations/refrigerations_equipment_request_model.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart'; import 'package:sige_ie/equipments/data/iluminations/ilumination__equipment_request_model.dart'; import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_equipment_request_model.dart'; @@ -106,8 +110,8 @@ class EquipmentService { } } - /* Future createElectricalLoad( - ElectricalLoadEquipmentRequestModel + Future createElectricalLoad( + EletricalLoadEquipmentRequestModel electricalLoadEquipmentRequestModel) async { var url = Uri.parse(baseUrl); @@ -137,7 +141,7 @@ class EquipmentService { } Future createElectricalLine( - ElectricalLineEquipmentRequestModel + EletricalLineEquipmentRequestModel electricalLineEquipmentRequestModel) async { var url = Uri.parse(baseUrl); @@ -167,7 +171,7 @@ class EquipmentService { } Future createElectricalCircuit( - ElectricalCircuitEquipmentRequestModel + EletricalCircuitEquipmentRequestModel electricalCircuitEquipmentRequestModel) async { var url = Uri.parse(baseUrl); @@ -225,9 +229,9 @@ class EquipmentService { return null; } } - */ - Future createCooling( - CoolingEquipmentRequestModel coolingEquipmentRequestModel) async { + + Future createRefrigerations( + RefrigerationsEquipmentRequestModel coolingEquipmentRequestModel) async { var url = Uri.parse(baseUrl); try { diff --git a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_equipment_request_model.dart new file mode 100644 index 00000000..ad35b02b --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_equipment_request_model.dart @@ -0,0 +1,21 @@ +import 'package:sige_ie/equipments/data/refrigerations/refrigerations_request_model.dart'; + +class RefrigerationsEquipmentRequestModel { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + RefrigerationsRequestModel? refrigerationsRequestModel; + + RefrigerationsEquipmentRequestModel({ + required this.genericEquipmentCategory, + required this.personalEquipmentCategory, + required this.refrigerationsRequestModel, + }); + + Map toJson() { + return { + 'generic_equipment_category': genericEquipmentCategory, + 'personal_equipment_category': personalEquipmentCategory, + 'refrigeration_equipment': refrigerationsRequestModel?.toJson(), + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_request_model.dart b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_request_model.dart new file mode 100644 index 00000000..231942f8 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_request_model.dart @@ -0,0 +1,16 @@ +class RefrigerationsRequestModel { + int? area; + int? system; + + RefrigerationsRequestModel({ + required this.area, + required this.system, + }); + + Map toJson() { + return { + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_response_model.dart b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_response_model.dart new file mode 100644 index 00000000..830fcf78 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_response_model.dart @@ -0,0 +1,28 @@ +class RefrigerationsEquipmentResponseModel { + int id; + String area; + int system; + + RefrigerationsEquipmentResponseModel({ + required this.id, + required this.area, + required this.system, + }); + + factory RefrigerationsEquipmentResponseModel.fromJson( + Map json) { + return RefrigerationsEquipmentResponseModel( + id: json['id'], + area: json['name'], + system: json['system'], + ); + } + + Map toJson() { + return { + 'id': id, + 'area': area, + 'system': system, + }; + } +} diff --git a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart new file mode 100644 index 00000000..07b8e91c --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:logging/logging.dart'; +import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/main.dart'; + +class RefrigerationsEquipmentService { + final Logger _logger = Logger('RefrigerationsEquipmentService'); + final String baseUrl = 'http://10.0.2.2:8000/api/'; + http.Client client = InterceptedClient.build( + interceptors: [AuthInterceptor(cookieJar)], + ); + + Future> getAllEquipment( + int systemId, + List genericEquipmentCategoryList, + List + personalEquipmentCategoryList) async { + List combinedList = [ + ...genericEquipmentCategoryList, + ...personalEquipmentCategoryList, + ]; + try { + _logger.info('Combined list length: ${combinedList.length}'); + return combinedList; + } catch (e) { + _logger.info('Error during get all equipment: $e'); + return []; + } + } + + Future> getRefrigerationsListByArea(int areaId) async { + final url = '${baseUrl}refrigerations/by-area/$areaId'; + try { + final response = await client.get(Uri.parse(url)); + if (response.statusCode == 200) { + final List data = json.decode(response.body); + return data.map((item) => item['name'] as String).toList(); + } else { + throw Exception('Failed to load structured cabling equipment'); + } + } catch (e) { + _logger.info('Error during get structured cabling equipment list: $e'); + return []; + } + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart index ea8809c1..0cae797c 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/atmospheric/atmospheric_equipment_request_model.dart'; import 'package:sige_ie/equipments/data/atmospheric/atmospheric_request_model.dart'; import 'package:sige_ie/equipments/data/atmospheric/atmospheric_service.dart'; @@ -269,7 +270,6 @@ class _AddEquipmentScreenState extends State { TextButton( child: const Text('OK'), onPressed: () { - _registerAtmosphericEquipment(); Navigator.pushReplacementNamed( context, '/listatmosphericEquipment', @@ -289,29 +289,6 @@ class _AddEquipmentScreenState extends State { ); } - void _registerAtmosphericEquipment() async { - int areaId = widget.areaId; - int systemId = widget.categoryNumber; - int equipmentTypeId = 1; - - AtmosphericEquipmentRequestModel requestModel = - AtmosphericEquipmentRequestModel( - photos: _images.map((imageData) => imageData.imageFile.path).toList(), - area: areaId, - system: systemId, - equipmentType: equipmentTypeId, - ); - - AtmosphericEquipmentService service = AtmosphericEquipmentService(); - int? id = await service.register(requestModel); - - if (id != null) { - print('Atmospheric equipment registered with ID: $id'); - } else { - print('Failed to register atmospheric equipment'); - } - } - @override Widget build(BuildContext context) { List combinedTypes = dischargeType + additionalTypes; diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart index c5aa6df4..172a9da0 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart @@ -1,13 +1,23 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/iluminations/ilumination__equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/iluminations/ilumination_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; + class ImageData { - File imageFile; int id; + File imageFile; String description; ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); @@ -21,6 +31,7 @@ class AddElectricalLoadEquipmentScreen extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const AddElectricalLoadEquipmentScreen({ super.key, @@ -28,31 +39,74 @@ class AddElectricalLoadEquipmentScreen extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override - _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); + _AddElectricalLoadEquipmentScreenState createState() => + _AddElectricalLoadEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddElectricalLoadEquipmentScreenState + extends State { + EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + final _equipmentBrandController = TextEditingController(); final _equipmentModelController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); final _equipmentLoadController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; - String? _selectedLoadType; + String? _newEquipmentTypeName; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; + + List> genericEquipmentTypes = []; + List> personalEquipmentTypes = []; + Map personalEquipmentMap = {}; + + @override + void initState() { + super.initState(); + _fetchEquipmentCategory(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _fetchEquipmentCategory(); + } + + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); - List equipmentTypes = [ - 'Selecione um tipo de Carga', - ]; + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); - List loadTypes = [ - 'Selecione o tipo de Carga', - 'Geladeira', - 'Ar-Condicionado', - 'Tomada (Corrente)' - ]; + if (mounted) { + setState(() { + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) + .toList(); + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) + .toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentCategoryList) + equipment.name: equipment.id + }; + }); + } + } @override void dispose() { @@ -77,9 +131,8 @@ class _AddEquipmentScreenState extends State { } void _showImageDialog(File imageFile, {ImageData? existingImage}) { - TextEditingController descriptionController = TextEditingController( - text: existingImage?.description ?? '', - ); + TextEditingController descriptionController = + TextEditingController(text: existingImage?.description ?? ''); showDialog( context: context, builder: (BuildContext context) { @@ -110,10 +163,8 @@ class _AddEquipmentScreenState extends State { if (existingImage != null) { existingImage.description = descriptionController.text; } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); + final imageData = + ImageData(imageFile, descriptionController.text); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { categoryImagesMap[categoryNumber] = []; @@ -155,8 +206,14 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - loadTypes.add(typeController.text); - equipmentTypes.add(typeController.text); + _newEquipmentTypeName = typeController.text; + }); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectedType = null; + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); + }); }); Navigator.of(context).pop(); } @@ -168,25 +225,70 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteEquipmentType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentCategory(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _deleteEquipmentType() async { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Selecione um tipo de equipamento válido para excluir.'), + Text('Não existem categorias de equipamentos a serem excluídas.'), + ), + ); + return; + } + + if (_selectedTypeToDelete == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Selecione uma categoria de equipamento válida para excluir.'), ), ); return; } + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -196,13 +298,32 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Excluir'), - onPressed: () { - setState(() { - loadTypes.remove(_selectedTypeToDelete); - equipmentTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); + onPressed: () async { Navigator.of(context).pop(); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentCategory(); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } }, ), ], @@ -216,7 +337,7 @@ class _AddEquipmentScreenState extends State { _equipmentModelController.text.isEmpty || _equipmentQuantityController.text.isEmpty || _equipmentLoadController.text.isEmpty || - (_selectedType == null && _selectedLoadType == null)) { + (_selectedType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -235,7 +356,7 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectedLoadType ?? ''), + Text(_selectedType ?? _newEquipmentTypeName ?? ''), const SizedBox(height: 10), const Text('Marca:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -288,10 +409,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); + _registerEquipment(); }, ), ], @@ -300,20 +420,88 @@ class _AddEquipmentScreenState extends State { ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/listelectricalLoadEquipment', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, + void _registerEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final IluminationRequestModel iluminationModel = IluminationRequestModel( + area: widget.areaId, + system: widget.categoryNumber, + ); + + final IluminationEquipmentRequestModel iluminationEquipmentDetail = + IluminationEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + iluminationRequestModel: iluminationModel, ); + + int? equipmentId = + await equipmentService.createIlumination(iluminationEquipmentDetail); + + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/listIluminationEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentBrandController.clear(); + _equipmentModelController.clear(); + _equipmentQuantityController.clear(); + _equipmentLoadController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } } @override Widget build(BuildContext context) { + List> combinedTypes = [ + ...genericEquipmentTypes, + ...personalEquipmentTypes + ]; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -321,7 +509,27 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + setState(() { + _equipmentBrandController.clear(); + _equipmentModelController.clear(); + _equipmentQuantityController.clear(); + _equipmentLoadController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + Navigator.pushReplacementNamed( + context, + '/listIluminationEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), @@ -338,7 +546,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Adicionar equipamentos ', + child: Text('Adicionar equipamentos', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -350,7 +558,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de Carga', + const Text('Tipos de equipamento', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -359,38 +567,67 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: loadTypes, - value: _selectedLoadType, + items: [ + { + 'name': 'Selecione o tipo de equipamento', + 'id': -1, + 'type': -1 + } + ] + + combinedTypes, + value: _selectedType, onChanged: (newValue) { - setState(() { - _selectedLoadType = newValue; - if (newValue == loadTypes[0]) { - _selectedLoadType = null; - } - if (_selectedLoadType != null) { - _selectedType = null; - } - }); + if (newValue != 'Selecione o tipo de equipamento') { + setState(() { + _selectedType = newValue; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalEquipmentCategorySelected = + selected['type'] == 'pessoal'; + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = + selected['id'] as int; + _selectedGenericEquipmentCategoryId = null; + } else { + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; + } + }); + } }, - enabled: _selectedType == null, + enabled: true, ), ), - Row( - children: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); - }, - ), - ], + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + if (personalEquipmentTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Não existem equipamentos pessoais a serem excluídos.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } + }, + ), + ], + ), ), ], ), @@ -398,7 +635,7 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedLoadType = null; + _selectedType = null; }); }, child: const Text('Limpar seleção'), @@ -442,7 +679,7 @@ class _AddEquipmentScreenState extends State { ), ), const SizedBox(height: 30), - const Text('Carga', + const Text('Carga (KW)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -454,9 +691,6 @@ class _AddEquipmentScreenState extends State { child: TextField( controller: _equipmentLoadController, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], decoration: const InputDecoration( border: InputBorder.none, contentPadding: @@ -568,25 +802,28 @@ class _AddEquipmentScreenState extends State { 'Selecione um equipamento para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: equipmentTypes - .where((value) => value != 'Selecione um equipamento') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: personalEquipmentTypes.map>( + (Map value) { + return DropdownMenuItem( + value: value['name'] as String, + child: Text( + value['name'] as String, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -613,7 +850,7 @@ class _AddEquipmentScreenState extends State { } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -625,18 +862,22 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, + value: value['name'] as String, + enabled: value['name'] != 'Selecione o tipo de equipamento', child: Text( - value, + value['name'] as String, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value['name'] == 'Selecione o tipo de equipamento' + ? Colors.grey + : Colors.black, ), ), ); diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart index fc84eb15..cd6fe35c 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/feature/electrical_load/add_electrical_load.dart'; -class ListElectricalLoadEquipment extends StatelessWidget { +class ListElectricalLoadEquipment extends StatefulWidget { final String areaName; final String localName; final int categoryNumber; @@ -10,23 +10,50 @@ class ListElectricalLoadEquipment extends StatelessWidget { final int areaId; const ListElectricalLoadEquipment({ - super.key, + Key? key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, required this.areaId, - }); + }) : super(key: key); + + @override + _ListElectricalLoadEquipmentState createState() => + _ListElectricalLoadEquipmentState(); +} + +class _ListElectricalLoadEquipmentState + extends State { + List equipmentList = []; + bool isLoading = true; + + @override + void initState() { + super.initState(); + fetchEquipmentList(); + } + + Future fetchEquipmentList() async { + // Simulação de requisição ou carga de dados + await Future.delayed(Duration(seconds: 1)); + setState(() { + // Exemplo de lista de equipamentos (vazia neste caso) + equipmentList = []; + isLoading = false; + }); + } void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddElectricalLoadEquipmentScreen( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, ), ), ); @@ -34,11 +61,8 @@ class ListElectricalLoadEquipment extends StatelessWidget { @override Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - String systemTitle = 'CARGAS ELÉTRICAS'; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -49,10 +73,10 @@ class ListElectricalLoadEquipment extends StatelessWidget { context, '/systemLocation', arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, }, ); }, @@ -70,7 +94,7 @@ class ListElectricalLoadEquipment extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', + child: Text('${widget.areaName} - $systemTitle', textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, @@ -84,23 +108,27 @@ class ListElectricalLoadEquipment extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), + isLoading + ? const Center( + child: CircularProgressIndicator(), ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), const SizedBox(height: 40), ], ), From a9025e55347af44343451a5c2608017f7cfcb438 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 13:49:12 -0300 Subject: [PATCH 245/351] Rota arrumada --- .../feature/electrical_load/add_electrical_load.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart index 172a9da0..50c3d34d 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart @@ -466,7 +466,7 @@ class _AddElectricalLoadEquipmentScreenState ); Navigator.pushReplacementNamed( context, - '/listIluminationEquipment', + '/listelectricalLoadEquipment', arguments: { 'areaName': widget.areaName, 'categoryNumber': widget.categoryNumber, @@ -521,7 +521,7 @@ class _AddElectricalLoadEquipmentScreenState }); Navigator.pushReplacementNamed( context, - '/listIluminationEquipment', + '/listelectricalLoadEquipment', arguments: { 'areaName': widget.areaName, 'categoryNumber': widget.categoryNumber, From ed8a65e072b44daf5a8b99477e780c193b82d634 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 15:29:01 -0300 Subject: [PATCH 246/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20descargas=20atmo?= =?UTF-8?q?sf[sf=C3=A9ricas=20completa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add_atmospheric_discharges_equipment.dart | 351 ++++++++++++++---- .../atmospheric_discharges_list.dart | 120 ++++-- .../electrical_load/eletrical_load_list.dart | 58 ++- 3 files changed, 399 insertions(+), 130 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart index 0cae797c..0e7bde1e 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart @@ -1,16 +1,22 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/data/atmospheric/atmospheric_equipment_request_model.dart'; import 'package:sige_ie/equipments/data/atmospheric/atmospheric_request_model.dart'; -import 'package:sige_ie/equipments/data/atmospheric/atmospheric_service.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; class ImageData { - File imageFile; int id; + File imageFile; String description; ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); @@ -19,14 +25,14 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class AddatmosphericEquipmentScreen extends StatefulWidget { +class AddAtmosphericEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; final int areaId; - const AddatmosphericEquipmentScreen({ + const AddAtmosphericEquipmentScreen({ super.key, required this.areaName, required this.categoryNumber, @@ -39,20 +45,62 @@ class AddatmosphericEquipmentScreen extends StatefulWidget { _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddEquipmentScreenState extends State { + EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; - String? _selectDischargeType; + String? _newEquipmentTypeName; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; + + List> genericEquipmentTypes = []; + List> personalEquipmentTypes = []; + Map personalEquipmentMap = {}; + + @override + void initState() { + super.initState(); + _fetchEquipmentCategory(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _fetchEquipmentCategory(); + } + + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); - List dischargeType = [ - 'Selecione o tipo de Descarga Atmosféfica', - 'Para Raios', - 'Captação', - 'Subsistemas', - ]; + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); - List additionalTypes = []; + if (mounted) { + setState(() { + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) + .toList(); + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) + .toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentCategoryList) + equipment.name: equipment.id + }; + }); + } + } @override void dispose() { @@ -74,9 +122,8 @@ class _AddEquipmentScreenState extends State { } void _showImageDialog(File imageFile, {ImageData? existingImage}) { - TextEditingController descriptionController = TextEditingController( - text: existingImage?.description ?? '', - ); + TextEditingController descriptionController = + TextEditingController(text: existingImage?.description ?? ''); showDialog( context: context, builder: (BuildContext context) { @@ -107,10 +154,8 @@ class _AddEquipmentScreenState extends State { if (existingImage != null) { existingImage.description = descriptionController.text; } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); + final imageData = + ImageData(imageFile, descriptionController.text); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { categoryImagesMap[categoryNumber] = []; @@ -152,7 +197,14 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - additionalTypes.add(typeController.text); + _newEquipmentTypeName = typeController.text; + }); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectedType = null; + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); + }); }); Navigator.of(context).pop(); } @@ -164,25 +216,70 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteEquipmentType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentCategory(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _deleteEquipmentType() async { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Selecione um tipo de equipamento válido para excluir.'), + Text('Não existem categorias de equipamentos a serem excluídas.'), + ), + ); + return; + } + + if (_selectedTypeToDelete == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Selecione uma categoria de equipamento válida para excluir.'), ), ); return; } + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de equipamento "$_selectedTypeToDelete"?'), + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -192,12 +289,32 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Excluir'), - onPressed: () { - setState(() { - additionalTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); + onPressed: () async { Navigator.of(context).pop(); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentCategory(); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } }, ), ], @@ -208,7 +325,7 @@ class _AddEquipmentScreenState extends State { void _showConfirmationDialog() { if (_equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectDischargeType == null)) { + (_selectedType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -227,7 +344,7 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectDischargeType ?? ''), + Text(_selectedType ?? _newEquipmentTypeName ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -268,19 +385,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.pushReplacementNamed( - context, - '/listatmosphericEquipment', - arguments: { - 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, - 'localName': widget.localName, - 'localId': widget.localId, - 'areaId': widget.areaId, - }, - ); + _registerEquipment(); }, ), ], @@ -289,9 +396,84 @@ class _AddEquipmentScreenState extends State { ); } + void _registerEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final AtmosphericRequestModel atmosphericModel = AtmosphericRequestModel( + area: widget.areaId, + system: widget.categoryNumber, + ); + + final AtmosphericEquipmentRequestModel atmosphericEquipmentDetail = + AtmosphericEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + atmosphericRequestModel: atmosphericModel, + ); + + int? equipmentId = + await equipmentService.createAtmospheric(atmosphericEquipmentDetail); + + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/listatmosphericEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + @override Widget build(BuildContext context) { - List combinedTypes = dischargeType + additionalTypes; + List> combinedTypes = [ + ...genericEquipmentTypes, + ...personalEquipmentTypes + ]; return Scaffold( appBar: AppBar( @@ -300,6 +482,13 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { + setState(() { + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); Navigator.pushReplacementNamed( context, '/listatmosphericEquipment', @@ -327,7 +516,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Adicionar equipamentos ', + child: Text('Adicionar equipamento', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -339,7 +528,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de descarga atmosférica', + const Text('Tipos de equipamento', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -348,18 +537,32 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: combinedTypes, - value: _selectDischargeType ?? _selectedType, + items: [ + { + 'name': 'Selecione o tipo de equipamento', + 'id': -1, + 'type': -1 + } + ] + + combinedTypes, + value: _selectedType, onChanged: (newValue) { - if (newValue != - 'Selecione o tipo de descarga atmosférica') { + if (newValue != 'Selecione o tipo de equipamento') { setState(() { - if (dischargeType.contains(newValue)) { - _selectDischargeType = newValue; - _selectedType = null; + _selectedType = newValue; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalEquipmentCategorySelected = + selected['type'] == 'pessoal'; + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = + selected['id'] as int; + _selectedGenericEquipmentCategoryId = null; } else { - _selectedType = newValue; - _selectDischargeType = null; + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; } }); } @@ -378,11 +581,11 @@ class _AddEquipmentScreenState extends State { IconButton( icon: const Icon(Icons.delete), onPressed: () { - if (additionalTypes.isEmpty) { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - 'Nenhum equipamento adicionado para excluir.'), + 'Não existem equipamentos pessoais a serem excluídos.'), ), ); } else { @@ -402,7 +605,6 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectDischargeType = null; _selectedType = null; }); }, @@ -522,12 +724,12 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: additionalTypes - .map>((String value) { + items: personalEquipmentTypes.map>( + (Map value) { return DropdownMenuItem( - value: value, + value: value['name'] as String, child: Text( - value, + value['name'] as String, style: const TextStyle(color: Colors.black), ), ); @@ -560,7 +762,7 @@ class _AddEquipmentScreenState extends State { } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -572,19 +774,20 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first, style: const TextStyle(color: Colors.grey)), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, - enabled: value != 'Selecione o tipo de descarga atmosférica', + value: value['name'] as String, + enabled: value['name'] != 'Selecione o tipo de equipamento', child: Text( - value, + value['name'] as String, style: TextStyle( - color: value == 'Selecione o tipo de descarga atmosférica' + color: value['name'] == 'Selecione o tipo de equipamento' ? Colors.grey : Colors.black, ), diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart index fbc5d504..643116e8 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/atmospheric/atmospheric_service.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart'; -class ListAtmosphericEquipment extends StatelessWidget { +class ListAtmosphericEquipment extends StatefulWidget { final String areaName; final String localName; final int categoryNumber; @@ -10,24 +11,60 @@ class ListAtmosphericEquipment extends StatelessWidget { final int areaId; const ListAtmosphericEquipment({ - super.key, + Key? key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, required this.areaId, - }); + }) : super(key: key); + + @override + _ListAtmosphericEquipmentState createState() => + _ListAtmosphericEquipmentState(); +} + +class _ListAtmosphericEquipmentState extends State { + List equipmentList = []; + bool isLoading = true; + final AtmosphericEquipmentService _service = AtmosphericEquipmentService(); + + @override + void initState() { + super.initState(); + fetchEquipmentList(); + } + + Future fetchEquipmentList() async { + try { + final List equipmentList = + await _service.getAtmosphericListByArea(widget.areaId); + if (mounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + } catch (e) { + print('Error fetching equipment list: $e'); + if (mounted) { + setState(() { + isLoading = false; + }); + } + } + } void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( - builder: (context) => AddatmosphericEquipmentScreen( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, - areaId: areaId, + builder: (context) => AddAtmosphericEquipmentScreen( + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, ), ), ); @@ -35,10 +72,6 @@ class ListAtmosphericEquipment extends StatelessWidget { @override Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - String systemTitle = 'DESCARGAS ATMOSFÉRICAS'; return Scaffold( @@ -51,11 +84,10 @@ class ListAtmosphericEquipment extends StatelessWidget { context, '/systemLocation', arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, - 'categoryNumber': categoryNumber, + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, }, ); }, @@ -73,12 +105,15 @@ class ListAtmosphericEquipment extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), ), ), const SizedBox(height: 20), @@ -87,23 +122,28 @@ class ListAtmosphericEquipment extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), + isLoading + ? const Center( + child: CircularProgressIndicator(), ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), + ), + ), const SizedBox(height: 40), ], ), diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart index cd6fe35c..560868ae 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/eletrical-load/eletrical_load_service.dart'; import 'package:sige_ie/equipments/feature/electrical_load/add_electrical_load.dart'; class ListElectricalLoadEquipment extends StatefulWidget { @@ -27,21 +28,42 @@ class _ListElectricalLoadEquipmentState extends State { List equipmentList = []; bool isLoading = true; + // You may need to replace this with actual service integration + final EletricalLoadEquipmentService _service = + EletricalLoadEquipmentService(); + bool _isMounted = false; @override void initState() { super.initState(); + _isMounted = true; fetchEquipmentList(); } + @override + void dispose() { + _isMounted = false; + super.dispose(); + } + Future fetchEquipmentList() async { - // Simulação de requisição ou carga de dados - await Future.delayed(Duration(seconds: 1)); - setState(() { - // Exemplo de lista de equipamentos (vazia neste caso) - equipmentList = []; - isLoading = false; - }); + try { + final List equipmentList = + await _service.getEletricalLoadListByArea(widget.areaId); + if (_isMounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + } catch (e) { + print('Error fetching equipment list: $e'); + if (_isMounted) { + setState(() { + isLoading = false; + }); + } + } } void navigateToAddEquipment(BuildContext context) { @@ -94,12 +116,15 @@ class _ListElectricalLoadEquipmentState BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('${widget.areaName} - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), ), ), const SizedBox(height: 20), @@ -124,9 +149,10 @@ class _ListElectricalLoadEquipmentState child: Text( 'Você ainda não tem equipamentos', style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), ), ), const SizedBox(height: 40), From d0b40f25651c8ff7ba3d87b6a5e40442784089f0 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 15:47:37 -0300 Subject: [PATCH 247/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20linhas=20el?= =?UTF-8?q?=C3=A9tricasas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipment_photos/temp_jlwotYQ.jpeg | Bin 0 -> 64460 bytes .../eletrical_line_service.dart | 2 +- .../electrical_line/add_electrical_line.dart | 350 +++++++++++++++--- .../electrical_line/electrical_line_list.dart | 120 ++++-- 4 files changed, 374 insertions(+), 98 deletions(-) create mode 100644 api/equipment_photos/temp_jlwotYQ.jpeg diff --git a/api/equipment_photos/temp_jlwotYQ.jpeg b/api/equipment_photos/temp_jlwotYQ.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1b8e02d4bd87143931476c256d4d597dd61efed1 GIT binary patch literal 64460 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 getEletricalLineListByArea(int areaId) async { + Future> getElectricalLineListByArea(int areaId) async { final url = '${baseUrl}electrical-lines/by-area/$areaId'; try { final response = await client.get(Uri.parse(url)); diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart index 41a03709..6d3b3ed6 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart @@ -4,10 +4,20 @@ import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/eletrical_line/eletrical_line_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/eletrical_line/eletrical_line_request_model.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; + +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; class ImageData { - File imageFile; int id; + File imageFile; String description; ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); @@ -21,6 +31,7 @@ class AddElectricalLineScreen extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const AddElectricalLineScreen({ super.key, @@ -28,6 +39,7 @@ class AddElectricalLineScreen extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override @@ -35,18 +47,61 @@ class AddElectricalLineScreen extends StatefulWidget { } class _AddEquipmentScreenState extends State { + EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + final _equipmentQuantityController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; + String? _newEquipmentTypeName; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; - List electricalType = [ - 'Selecione o tipo de Linha Elétrica', - 'Eletrocalha', - 'Eletroduto', - 'Interuptor', - ]; + List> genericEquipmentTypes = []; + List> personalEquipmentTypes = []; + Map personalEquipmentMap = {}; - List additionalTypes = []; + @override + void initState() { + super.initState(); + _fetchEquipmentCategory(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _fetchEquipmentCategory(); + } + + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + + if (mounted) { + setState(() { + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) + .toList(); + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) + .toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentCategoryList) + equipment.name: equipment.id + }; + }); + } + } @override void dispose() { @@ -68,9 +123,8 @@ class _AddEquipmentScreenState extends State { } void _showImageDialog(File imageFile, {ImageData? existingImage}) { - TextEditingController descriptionController = TextEditingController( - text: existingImage?.description ?? '', - ); + TextEditingController descriptionController = + TextEditingController(text: existingImage?.description ?? ''); showDialog( context: context, builder: (BuildContext context) { @@ -101,10 +155,8 @@ class _AddEquipmentScreenState extends State { if (existingImage != null) { existingImage.description = descriptionController.text; } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); + final imageData = + ImageData(imageFile, descriptionController.text); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { categoryImagesMap[categoryNumber] = []; @@ -146,7 +198,14 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - additionalTypes.add(typeController.text); + _newEquipmentTypeName = typeController.text; + }); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectedType = null; + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); + }); }); Navigator.of(context).pop(); } @@ -158,25 +217,70 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteElectricalType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um tipo de linha elétrica') { + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentCategory(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _deleteElectricalType() async { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Selecione um tipo de linha elétrica válido para excluir.'), + Text('Não existem categorias de equipamentos a serem excluídas.'), ), ); return; } + if (_selectedTypeToDelete == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Selecione uma categoria de equipamento válida para excluir.'), + ), + ); + return; + } + + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de linha elétrica "$_selectedTypeToDelete"?'), + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -186,12 +290,32 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Excluir'), - onPressed: () { - setState(() { - additionalTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); + onPressed: () async { Navigator.of(context).pop(); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentCategory(); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } }, ), ], @@ -261,10 +385,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); + _registerEquipment(); }, ), ], @@ -273,21 +396,85 @@ class _AddEquipmentScreenState extends State { ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/electricalLineList', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, + void _registerEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final EletricalLineRequestModel electricalLineModel = + EletricalLineRequestModel( + area: widget.areaId, + system: widget.categoryNumber, ); + + final EletricalLineEquipmentRequestModel electricalLineEquipmentDetail = + EletricalLineEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + eletricalLineRequestModel: electricalLineModel, + ); + + int? equipmentId = await equipmentService + .createElectricalLine(electricalLineEquipmentDetail); + + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/electricalLineList', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } } @override Widget build(BuildContext context) { - List combinedTypes = electricalType + additionalTypes; + List> combinedTypes = [ + ...genericEquipmentTypes, + ...personalEquipmentTypes + ]; return Scaffold( appBar: AppBar( @@ -296,7 +483,24 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + setState(() { + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + Navigator.pushReplacementNamed( + context, + '/electricalLineList', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), @@ -313,7 +517,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Adicionar equipamentos ', + child: Text('Adicionar equipamento', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -334,12 +538,36 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: combinedTypes, + items: [ + { + 'name': 'Selecione o tipo de linha elétrica', + 'id': -1, + 'type': -1 + } + ] + + combinedTypes, value: _selectedType, onChanged: (newValue) { - setState(() { - _selectedType = newValue; - }); + if (newValue != + 'Selecione o tipo de linha elétrica') { + setState(() { + _selectedType = newValue; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalEquipmentCategorySelected = + selected['type'] == 'pessoal'; + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = + selected['id'] as int; + _selectedGenericEquipmentCategoryId = null; + } else { + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; + } + }); + } }, enabled: true, ), @@ -355,11 +583,11 @@ class _AddEquipmentScreenState extends State { IconButton( icon: const Icon(Icons.delete), onPressed: () { - if (additionalTypes.isEmpty) { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - 'Nenhum tipo de linha elétrica adicionado para excluir.'), + 'Não existem equipamentos pessoais a serem excluídos.'), ), ); } else { @@ -498,12 +726,12 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: additionalTypes - .map>((String value) { + items: personalEquipmentTypes.map>( + (Map value) { return DropdownMenuItem( - value: value, + value: value['name'] as String, child: Text( - value, + value['name'] as String, style: const TextStyle(color: Colors.black), ), ); @@ -536,7 +764,7 @@ class _AddEquipmentScreenState extends State { } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -548,18 +776,22 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, + value: value['name'] as String, + enabled: value['name'] != 'Selecione o tipo de linha elétrica', child: Text( - value, + value['name'] as String, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value['name'] == 'Selecione o tipo de linha elétrica' + ? Colors.grey + : Colors.black, ), ), ); diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart index 08bb3417..ebad6705 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/eletrical_line/eletrical_line_service.dart'; import 'package:sige_ie/equipments/feature/electrical_line/add_electrical_line.dart'; -class ListElectricalLineEquipment extends StatelessWidget { +class ListElectricalLineEquipment extends StatefulWidget { final String areaName; final String localName; final int categoryNumber; @@ -10,23 +11,62 @@ class ListElectricalLineEquipment extends StatelessWidget { final int areaId; const ListElectricalLineEquipment({ - super.key, + Key? key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, required this.areaId, - }); + }) : super(key: key); + + @override + _ListElectricalLineEquipmentState createState() => + _ListElectricalLineEquipmentState(); +} + +class _ListElectricalLineEquipmentState + extends State { + List equipmentList = []; + bool isLoading = true; + final EletricalLineEquipmentService _service = + EletricalLineEquipmentService(); + + @override + void initState() { + super.initState(); + fetchEquipmentList(); + } + + Future fetchEquipmentList() async { + try { + final List equipmentList = + await _service.getElectricalLineListByArea(widget.areaId); + if (mounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + } catch (e) { + print('Error fetching equipment list: $e'); + if (mounted) { + setState(() { + isLoading = false; + }); + } + } + } void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddElectricalLineScreen( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, ), ), ); @@ -34,11 +74,7 @@ class ListElectricalLineEquipment extends StatelessWidget { @override Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - - String systemTitle = 'LINHA ELÉTRICAS'; + String systemTitle = 'LINHAS ELÉTRICAS'; return Scaffold( appBar: AppBar( @@ -50,10 +86,10 @@ class ListElectricalLineEquipment extends StatelessWidget { context, '/systemLocation', arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, }, ); }, @@ -71,12 +107,15 @@ class ListElectricalLineEquipment extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), ), ), const SizedBox(height: 20), @@ -85,23 +124,28 @@ class ListElectricalLineEquipment extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), + isLoading + ? const Center( + child: CircularProgressIndicator(), ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), + ), + ), const SizedBox(height: 40), ], ), From 477d5a142eb179a6c6484c7505bdd094cbd324b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Tue, 18 Jun 2024 15:48:50 -0300 Subject: [PATCH 248/351] =?UTF-8?q?Backend:=20Cria=20relat=C3=B3rio=20pdf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...sphericdischargeequipment_area_and_more.py | 60 +++++++++ ...sphericdischargeequipment_area_and_more.py | 60 +++++++++ ...sphericdischargeequipment_area_and_more.py | 60 +++++++++ api/equipments/models.py | 24 ++-- api/places/urls.py | 7 +- api/places/views.py | 115 +++++++++++++++++- api/sigeie/urls.py | 3 +- 7 files changed, 314 insertions(+), 15 deletions(-) create mode 100644 api/equipments/migrations/0025_alter_atmosphericdischargeequipment_area_and_more.py create mode 100644 api/equipments/migrations/0026_alter_atmosphericdischargeequipment_area_and_more.py create mode 100644 api/equipments/migrations/0027_alter_atmosphericdischargeequipment_area_and_more.py diff --git a/api/equipments/migrations/0025_alter_atmosphericdischargeequipment_area_and_more.py b/api/equipments/migrations/0025_alter_atmosphericdischargeequipment_area_and_more.py new file mode 100644 index 00000000..8b111a11 --- /dev/null +++ b/api/equipments/migrations/0025_alter_atmosphericdischargeequipment_area_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2 on 2024-06-17 14:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0031_remove_place_access_code'), + ('equipments', '0024_rename_genericequipmentcategory_equipment_generic_equipment_category_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosphericdischargeequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='AtmosphericDischargeEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='distributionboardequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='DistributionBoardEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricalcircuitequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ElectricalCircuitEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricallineequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ElectricalLineEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricalloadequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ElectricalLoadEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='firealarmequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='firealarmequipment', to='places.area'), + ), + migrations.AlterField( + model_name='iluminationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='IluminationEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='refrigerationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='RefrigerationEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='structuredcablingequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='StructuredCablingEquipment', to='places.area'), + ), + ] diff --git a/api/equipments/migrations/0026_alter_atmosphericdischargeequipment_area_and_more.py b/api/equipments/migrations/0026_alter_atmosphericdischargeequipment_area_and_more.py new file mode 100644 index 00000000..5d6bcc24 --- /dev/null +++ b/api/equipments/migrations/0026_alter_atmosphericdischargeequipment_area_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2 on 2024-06-17 16:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0031_remove_place_access_code'), + ('equipments', '0025_alter_atmosphericdischargeequipment_area_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosphericdischargeequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + migrations.AlterField( + model_name='distributionboardequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + migrations.AlterField( + model_name='electricalcircuitequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + migrations.AlterField( + model_name='electricallineequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + migrations.AlterField( + model_name='electricalloadequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + migrations.AlterField( + model_name='firealarmequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + migrations.AlterField( + model_name='iluminationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + migrations.AlterField( + model_name='refrigerationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + migrations.AlterField( + model_name='structuredcablingequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='places.area'), + ), + ] diff --git a/api/equipments/migrations/0027_alter_atmosphericdischargeequipment_area_and_more.py b/api/equipments/migrations/0027_alter_atmosphericdischargeequipment_area_and_more.py new file mode 100644 index 00000000..ebbb285b --- /dev/null +++ b/api/equipments/migrations/0027_alter_atmosphericdischargeequipment_area_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2 on 2024-06-17 16:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0031_remove_place_access_code'), + ('equipments', '0026_alter_atmosphericdischargeequipment_area_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosphericdischargeequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='AtmosphericDischargeEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='distributionboardequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='DistributionBoardEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricalcircuitequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ElectricalCircuitEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricallineequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ElectricalLineEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricalloadequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ElectricalLoadEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='firealarmequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='firealarmequipment', to='places.area'), + ), + migrations.AlterField( + model_name='iluminationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='IluminationEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='refrigerationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='RefrigerationEquipment', to='places.area'), + ), + migrations.AlterField( + model_name='structuredcablingequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='StructuredCablingEquipment', to='places.area'), + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index f9b5a540..8cce2abd 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -32,6 +32,12 @@ class Equipment(models.Model): generic_equipment_category = models.ForeignKey(GenericEquipmentCategory, on_delete=models.CASCADE, null=True) personal_equipment_category = models.ForeignKey(PersonalEquipmentCategory, on_delete=models.CASCADE, null=True) + def __str__(self): + if(self.generic_equipment_category != None): + return str(self.generic_equipment_category) + else: + return str(self.personal_equipment_category) + class Meta: db_table = 'equipments_equipment_details' @@ -46,7 +52,7 @@ def __str__(self): class FireAlarmEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="firealarmequipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) @@ -54,7 +60,7 @@ class Meta: db_table = 'equipments_fire_alarm_equipments' class AtmosphericDischargeEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name= "AtmosphericDischargeEquipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=7) @@ -62,7 +68,7 @@ class Meta: db_table = 'equipments_atmospheric_discharge_equipments' class StructuredCablingEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="StructuredCablingEquipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=6) @@ -70,7 +76,7 @@ class Meta: db_table = 'equipments_structured_cabling_equipments' class DistributionBoardEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="DistributionBoardEquipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=5) @@ -78,7 +84,7 @@ class Meta: db_table = 'equipments_distribution_board_equipments' class ElectricalCircuitEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name='ElectricalCircuitEquipment') equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=4) @@ -86,7 +92,7 @@ class Meta: db_table = 'equipments_electrical_circuit_equipments' class ElectricalLineEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ElectricalLineEquipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=3) @@ -94,7 +100,7 @@ class Meta: db_table = 'equipments_electrical_line_equipments' class ElectricalLoadEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ElectricalLoadEquipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) @@ -102,7 +108,7 @@ class Meta: db_table = 'equipments_electrical_load_equipments' class IluminationEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="IluminationEquipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=1) @@ -110,7 +116,7 @@ class Meta: db_table = 'equipments_ilumination_equipments' class RefrigerationEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="RefrigerationEquipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) diff --git a/api/places/urls.py b/api/places/urls.py index 659a07e3..90635c6d 100644 --- a/api/places/urls.py +++ b/api/places/urls.py @@ -1,8 +1,13 @@ from django.urls import path, include from .views import PlaceViewSet, AreaViewSet, GrantAccessViewSet from rest_framework.routers import SimpleRouter +from .views import GeneratePDFView router = SimpleRouter() router.register(r'places',PlaceViewSet) router.register(r'areas', AreaViewSet) -router.register(r'grant_access', GrantAccessViewSet, basename='grant_access') \ No newline at end of file +router.register(r'grant_access', GrantAccessViewSet, basename='grant_access') + +urlpatterns = [ + path('places//report/', GeneratePDFView.as_view(), name='place-report'), +] \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index d3e3181e..d380e036 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -1,4 +1,3 @@ -from django.shortcuts import render from rest_framework.views import APIView from django.contrib.auth.models import User from users.models import PlaceOwner, PlaceEditor @@ -8,10 +7,11 @@ from places.permissions import IsPlaceOwner from rest_framework import viewsets, status from rest_framework.decorators import action -from django.http import JsonResponse from rest_framework.response import Response from rest_framework.exceptions import NotFound - +from django.http import HttpResponse +from reportlab.lib.pagesizes import A4 +from reportlab.pdfgen import canvas from .models import Place, Area from .serializers import PlaceSerializer, AreaSerializer @@ -175,4 +175,111 @@ def grant_access(self, request, pk=None): place.editors.add(place_editor) - return Response({'message': 'Access granted successfully'}, status=status.HTTP_200_OK) \ No newline at end of file + return Response({'message': 'Access granted successfully'}, status=status.HTTP_200_OK) + +class Altura: + def __init__(self): + self.alt = 840 + + def get_alt(self, p, margin=30): + self.alt -= 40 + if self.alt < margin: + p.showPage() + self.alt = 800 + return self.alt + return self.alt + +def genericOrPersonal(system): + if system.equipment.generic_equipment_category is not None: + return system.equipment.generic_equipment_category + else: + return system.equipment.personal_equipment_category + + + +class GeneratePDFView(APIView): + permission_classes = [IsAuthenticated] + + + def get(self, request, pk=None): + place = get_object_or_404(Place, pk=pk) + + response = HttpResponse(content_type='application/pdf') + response['Content-Disposition'] = f'attachment; filename="place_{place.id}_report.pdf"' + + p = canvas.Canvas(response, pagesize=A4) + alt = Altura() + + + p.setFont('Helvetica-Bold', 16) + + + p.drawString(205, alt.get_alt(p), f"Relatório do Local: {place.name}") + + + p.setFont('Helvetica-Bold', 14) + p.drawString(100, alt.get_alt(p), "Áreas:") + + + for area in place.areas.all(): + p.setFont('Helvetica-Bold', 14) + p.drawString(120, alt.get_alt(p), f"Relatório da Área: {area.name}") + + for system in area.firealarmequipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + for system in area.AtmosphericDischargeEquipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + for system in area.StructuredCablingEquipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + for system in area.DistributionBoardEquipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + for system in area.ElectricalCircuitEquipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + for system in area.ElectricalLineEquipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + for system in area.ElectricalLoadEquipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + for system in area.IluminationEquipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + for system in area.RefrigerationEquipment.all(): + if(system == None): + break + p.setFont('Helvetica', 12) + p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") + + + p.showPage() + p.save() + return response diff --git a/api/sigeie/urls.py b/api/sigeie/urls.py index 1ad0cbab..877cd7b7 100644 --- a/api/sigeie/urls.py +++ b/api/sigeie/urls.py @@ -24,5 +24,6 @@ path('api/', include(router.urls)), path('api/', include('systems.urls')), path('auth/', include('rest_framework.urls')), - path('api/', include('equipments.urls')) + path('api/', include('equipments.urls')), + path('api/', include('places.urls')) ] From c7588adb1754d97f89ec55a604ba104e3f2488ca Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 16:04:23 -0300 Subject: [PATCH 249/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20circuitos=20elet?= =?UTF-8?q?ricos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add_electrical_circuit.dart | 443 ++++++++++++++---- .../electrical_circuit_list.dart | 118 +++-- .../feature/system_configuration.dart | 2 +- frontend/sige_ie/lib/main.dart | 2 +- 4 files changed, 426 insertions(+), 139 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart index 14d50482..ebd27a86 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart @@ -4,6 +4,15 @@ import 'package:flutter/services.dart'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/eletrical_circuit/eletrical_circuit_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/eletrical_circuit/eletrical_circuit_request_model.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; class ImageData { File imageFile; @@ -21,6 +30,7 @@ class AddElectricalCircuitEquipmentScreen extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const AddElectricalCircuitEquipmentScreen({ super.key, @@ -28,14 +38,23 @@ class AddElectricalCircuitEquipmentScreen extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override - _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); + _AddElectricalCircuitEquipmentScreenState createState() => + _AddElectricalCircuitEquipmentScreenState(); } -class _AddEquipmentScreenState +class _AddElectricalCircuitEquipmentScreenState extends State { + EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + final _equipmentQuantityController = TextEditingController(); final _breakerLocationController = TextEditingController(); final _breakerStateController = TextEditingController(); @@ -43,13 +62,51 @@ class _AddEquipmentScreenState final _dimensionController = TextEditingController(); String? _selectedTypeToDelete; String? _selectCircuitType; + String? _newEquipmentTypeName; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; + + List> genericEquipmentTypes = []; + List> personalEquipmentTypes = []; + Map personalEquipmentMap = {}; - List cicuitType = [ - 'Selecione o tipo de Circuito Elétrico', - 'Disjuntor(Local e Estado)', - 'Tipo de Fio', - 'Dimensão', - ]; + @override + void initState() { + super.initState(); + _fetchEquipmentCategory(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _fetchEquipmentCategory(); + } + + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + + if (mounted) { + setState(() { + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) + .toList(); + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) + .toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentCategoryList) + equipment.name: equipment.id + }; + }); + } + } @override void dispose() { @@ -75,9 +132,8 @@ class _AddEquipmentScreenState } void _showImageDialog(File imageFile, {ImageData? existingImage}) { - TextEditingController descriptionController = TextEditingController( - text: existingImage?.description ?? '', - ); + TextEditingController descriptionController = + TextEditingController(text: existingImage?.description ?? ''); showDialog( context: context, builder: (BuildContext context) { @@ -108,10 +164,8 @@ class _AddEquipmentScreenState if (existingImage != null) { existingImage.description = descriptionController.text; } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); + final imageData = + ImageData(imageFile, descriptionController.text); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { categoryImagesMap[categoryNumber] = []; @@ -153,7 +207,14 @@ class _AddEquipmentScreenState onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - cicuitType.add(typeController.text); + _newEquipmentTypeName = typeController.text; + }); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectCircuitType = null; + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); + }); }); Navigator.of(context).pop(); } @@ -165,25 +226,70 @@ class _AddEquipmentScreenState ); } - void _deleteEquipmentType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um equipamento') { + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentCategory(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _deleteEquipmentType() async { + if (personalEquipmentTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Não existem categorias de equipamentos a serem excluídas.'), + ), + ); + return; + } + + if (_selectedTypeToDelete == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - 'Selecione um tipo de Circuito Elétrico válido para excluir.'), + 'Selecione uma categoria de equipamento válida para excluir.'), ), ); return; } + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de Circuito Elétrico "$_selectedTypeToDelete"?'), + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -193,12 +299,32 @@ class _AddEquipmentScreenState ), TextButton( child: const Text('Excluir'), - onPressed: () { - setState(() { - cicuitType.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); + onPressed: () async { Navigator.of(context).pop(); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentCategory(); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } }, ), ], @@ -209,7 +335,11 @@ class _AddEquipmentScreenState void _showConfirmationDialog() { if (_equipmentQuantityController.text.isEmpty || - _selectCircuitType == null) { + _breakerLocationController.text.isEmpty || + _breakerStateController.text.isEmpty || + _wireTypeController.text.isEmpty || + _dimensionController.text.isEmpty || + (_selectCircuitType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -228,7 +358,7 @@ class _AddEquipmentScreenState children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectCircuitType ?? ''), + Text(_selectCircuitType ?? _newEquipmentTypeName ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -261,12 +391,8 @@ class _AddEquipmentScreenState existingImage: imageData), child: Column( children: [ - Image.file( - imageData.imageFile, - width: 100, - height: 100, - fit: BoxFit.cover, - ), + Image.file(imageData.imageFile, + width: 100, height: 100, fit: BoxFit.cover), Text(imageData.description), ], ), @@ -285,10 +411,9 @@ class _AddEquipmentScreenState }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.of(context).pop(); - navigateToElectricalCircuitList(); + _registerEquipment(); }, ), ], @@ -297,21 +422,91 @@ class _AddEquipmentScreenState ); } - void navigateToElectricalCircuitList() { - Navigator.pushReplacementNamed( - context, - '/electricalCircuitList', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, + void _registerEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final EletricalCircuitRequestModel electricalCircuitModel = + EletricalCircuitRequestModel( + area: widget.areaId, + system: widget.categoryNumber, ); + + final EletricalCircuitEquipmentRequestModel + electricalCircuitEquipmentDetail = + EletricalCircuitEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + eletricalCircuitRequestModel: electricalCircuitModel, + ); + + int? equipmentId = await equipmentService + .createElectricalCircuit(electricalCircuitEquipmentDetail); + + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/electricalCircuitList', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentQuantityController.clear(); + _breakerLocationController.clear(); + _breakerStateController.clear(); + _wireTypeController.clear(); + _dimensionController.clear(); + _selectCircuitType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } } @override Widget build(BuildContext context) { + List> combinedTypes = [ + ...genericEquipmentTypes, + ...personalEquipmentTypes + ]; + return Scaffold( appBar: AppBar( backgroundColor: AppColors.sigeIeBlue, @@ -319,7 +514,17 @@ class _AddEquipmentScreenState leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + Navigator.pushReplacementNamed( + context, + '/electricalCircuitList', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), @@ -336,7 +541,7 @@ class _AddEquipmentScreenState BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Adicionar equipamentos ', + child: Text('Adicionar Circuito Elétrico', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -357,35 +562,69 @@ class _AddEquipmentScreenState Expanded( flex: 4, child: _buildStyledDropdown( - items: cicuitType, + items: [ + { + 'name': + 'Selecione o tipo de Circuito Elétrico', + 'id': -1, + 'type': -1 + } + ] + + combinedTypes, value: _selectCircuitType, onChanged: (newValue) { - setState(() { - _selectCircuitType = newValue; - if (newValue == cicuitType[0]) { - _selectCircuitType = null; - } - }); + if (newValue != + 'Selecione o tipo de Circuito Elétrico') { + setState(() { + _selectCircuitType = newValue; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalEquipmentCategorySelected = + selected['type'] == 'pessoal'; + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = + selected['id'] as int; + _selectedGenericEquipmentCategoryId = null; + } else { + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; + } + }); + } }, enabled: true, ), ), - Row( - children: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: _addNewEquipmentType, - ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - _selectedTypeToDelete = null; - }); - _showDeleteDialog(); - }, - ), - ], + Expanded( + flex: 0, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: _addNewEquipmentType, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + if (personalEquipmentTypes.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Não existem equipamentos pessoais a serem excluídos.'), + ), + ); + } else { + setState(() { + _selectedTypeToDelete = null; + }); + _showDeleteDialog(); + } + }, + ), + ], + ), ), ], ), @@ -582,26 +821,28 @@ class _AddEquipmentScreenState 'Selecione um tipo de Circuito Elétrico para excluir:', textAlign: TextAlign.center, ), - DropdownButton( - isExpanded: true, - value: _selectedTypeToDelete, - onChanged: (String? newValue) { - setState(() { - _selectedTypeToDelete = newValue; - }); - }, - items: cicuitType - .where((value) => - value != 'Selecione um tipo de Circuito Elétrico') - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(color: Colors.black), - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return DropdownButton( + isExpanded: true, + value: _selectedTypeToDelete, + onChanged: (String? newValue) { + setState(() { + _selectedTypeToDelete = newValue; + }); + }, + items: personalEquipmentTypes.map>( + (Map value) { + return DropdownMenuItem( + value: value['name'] as String, + child: Text( + value['name'] as String, + style: const TextStyle(color: Colors.black), + ), + ); + }).toList(), ); - }).toList(), + }, ), ], ), @@ -628,7 +869,7 @@ class _AddEquipmentScreenState } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -640,18 +881,22 @@ class _AddEquipmentScreenState ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, + value: value['name'] as String, + enabled: value['name'] != 'Selecione o tipo de Circuito Elétrico', child: Text( - value, + value['name'] as String, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value['name'] == 'Selecione o tipo de Circuito Elétrico' + ? Colors.grey + : Colors.black, ), ), ); diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart index 468b4012..7794f706 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart @@ -1,32 +1,70 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/eletrical_circuit/eletrical_circuit_service.dart'; import 'package:sige_ie/equipments/feature/electrical_circuit/add_electrical_circuit.dart'; -class ListCicuitEquipment extends StatelessWidget { +class ListCircuitEquipment extends StatefulWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const ListCicuitEquipment({ - super.key, + const ListCircuitEquipment({ + Key? key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, required this.areaId, - }); + }) : super(key: key); + + @override + _ListCircuitEquipmentState createState() => _ListCircuitEquipmentState(); +} + +class _ListCircuitEquipmentState extends State { + List equipmentList = []; + bool isLoading = true; + final EletricalCircuitEquipmentService _service = + EletricalCircuitEquipmentService(); + + @override + void initState() { + super.initState(); + fetchEquipmentList(); + } + + Future fetchEquipmentList() async { + try { + final List equipmentList = + await _service.getEletricalCircuitListByArea(widget.areaId); + if (mounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + } catch (e) { + print('Error fetching equipment list: $e'); + if (mounted) { + setState(() { + isLoading = false; + }); + } + } + } void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddElectricalCircuitEquipmentScreen( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, ), ), ); @@ -34,10 +72,6 @@ class ListCicuitEquipment extends StatelessWidget { @override Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - String systemTitle = 'CIRCUITO ELÉTRICO'; return Scaffold( @@ -50,10 +84,10 @@ class ListCicuitEquipment extends StatelessWidget { context, '/systemLocation', arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, }, ); }, @@ -71,12 +105,15 @@ class ListCicuitEquipment extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), ), ), const SizedBox(height: 20), @@ -85,23 +122,28 @@ class ListCicuitEquipment extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), + isLoading + ? const Center( + child: CircularProgressIndicator(), ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), + ), + ), const SizedBox(height: 40), ], ), diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index 69a3a80f..543f3f36 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -77,7 +77,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/circuits': - return ListCicuitEquipment( + return ListCircuitEquipment( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index fc31cde7..382d3bee 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -148,7 +148,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => ListCicuitEquipment( + builder: (context) => ListCircuitEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From cd0b6b154c4615a962976d7c9be8c6aa3e470431 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 16:56:48 -0300 Subject: [PATCH 250/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20quadros=20de=20d?= =?UTF-8?q?istribui=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipment_photos/temp_P0M070S.jpeg | Bin 0 -> 63357 bytes .../add_distribuition_board.dart | 415 +++++++++++++----- .../distribuition_board_equipment_list.dart | 117 +++-- .../feature/system_configuration.dart | 2 +- frontend/sige_ie/lib/main.dart | 2 +- 5 files changed, 388 insertions(+), 148 deletions(-) create mode 100644 api/equipment_photos/temp_P0M070S.jpeg diff --git a/api/equipment_photos/temp_P0M070S.jpeg b/api/equipment_photos/temp_P0M070S.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ac2a0debdcc54878ba43f552a6451778cba0d22d GIT binary patch literal 63357 zcmeHQ30zah)}Ig{BDSdtqP7}qYo+2VDsHvP)gq;@eHGER*jmlAF1UTQ?kik{DjM4t zwYDy=8U(FdHMXdIZd?TwtJFYU5VQqE0YM}XmMnMq&JCcDL~}vJrIX+9-rU?fcjlZk z|2cE!oS6xCh0AjI(tqBh2?%}g0qTMfYJ!@GUO*0T%s!oG8E$>u)Es7OM&FLkO?t3`U%yCoXAzx0M_Qm+#J>~QI_LleV ziF$PF(X(H--u=4w?a;kpYT-w8QaQ|d4P-sUCBr}szg z%;{a>dKCW(d^*7a6^hzaeoILB205VxmA|X=LE5VORked!Rz2@iGu=II5SNKwc<-Hm zzJmZxPyn2`tH=*Ii1-hGcHlpbV*c#p=qPq{c5-$$9}<@)&Jq`ivvU*ICN53+4_q~K zZQ6|g!DmwWs)LhQ?9^1^EHNjk(}_Ednm3Uwc8U^+Jb@m!7?P_v!m)Kfl5L?*zR2&-aFZ_|eC~BR=_b z?6~n0LMBd{Jag9UIdkXDUl8%t(q)m$S18teyY{>9*R9{MF*;`Jwja0e*tzSM*!>3% z{`$W|hvO4Yo;sa)=C`wdT)24Ya_W_|^sMXIH*V(S=H1FKF42{il~?EuMxHMb5?8VX zXPbOA2fiHmI*{;ui5%v_A#U#I^m2FS7K4H$W2U$4*kef(x3^bs`T2y)D?Y=t?w`*{ zY1-=5o>`rWdDhIF31ck0UYy-!>>gj~sF@gAMRRd;L?C8Ww8Bdpn;oz4jB>ySmI1~9 z#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u z#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#=tXy0i8@gRh1UQ zDgxPI-h}>lnKq4x4Pe|<^w!a2@kSko4*a4vHi={$HPx2B$C z%0D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 Ia4zlt0o0)}g#Z8m literal 0 HcmV?d00001 diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart index 129fed2e..56cda21b 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart @@ -1,13 +1,23 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/distribution/distribution_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/distribution/distribution_request_model.dart'; + +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; class ImageData { - File imageFile; int id; + File imageFile; String description; ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); @@ -21,6 +31,7 @@ class AddDistribuitionBoard extends StatefulWidget { final String localName; final int localId; final int categoryNumber; + final int areaId; const AddDistribuitionBoard({ super.key, @@ -28,32 +39,74 @@ class AddDistribuitionBoard extends StatefulWidget { required this.categoryNumber, required this.localName, required this.localId, + required this.areaId, }); @override - _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); + _AddDistribuitionBoardState createState() => _AddDistribuitionBoardState(); } -class _AddEquipmentScreenState extends State { - final _equipmentchargeController = TextEditingController(); +class _AddDistribuitionBoardState extends State { + EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + + final _equipmentChargeController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; - String? _selectedLocation; String? _selectedTypeToDelete; - String? _selectedBoardType; + String? _newEquipmentTypeName; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; + + List> genericEquipmentTypes = []; + List> personalEquipmentTypes = []; + Map personalEquipmentMap = {}; + + @override + void initState() { + super.initState(); + _fetchEquipmentCategory(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _fetchEquipmentCategory(); + } + + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); - List boardType = [ - 'Selecione o tipo de quadro', - 'quadro 1', - 'quadro 2', - 'quadro 3', - ]; + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); - List additionalTypes = []; + if (mounted) { + setState(() { + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) + .toList(); + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) + .toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentCategoryList) + equipment.name: equipment.id + }; + }); + } + } @override void dispose() { - _equipmentchargeController.dispose(); + _equipmentChargeController.dispose(); _equipmentQuantityController.dispose(); categoryImagesMap[widget.categoryNumber]?.clear(); super.dispose(); @@ -72,9 +125,8 @@ class _AddEquipmentScreenState extends State { } void _showImageDialog(File imageFile, {ImageData? existingImage}) { - TextEditingController descriptionController = TextEditingController( - text: existingImage?.description ?? '', - ); + TextEditingController descriptionController = + TextEditingController(text: existingImage?.description ?? ''); showDialog( context: context, builder: (BuildContext context) { @@ -105,10 +157,8 @@ class _AddEquipmentScreenState extends State { if (existingImage != null) { existingImage.description = descriptionController.text; } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); + final imageData = + ImageData(imageFile, descriptionController.text); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { categoryImagesMap[categoryNumber] = []; @@ -126,17 +176,17 @@ class _AddEquipmentScreenState extends State { ); } - void _addNewBoardType() { + void _addNewEquipmentType() { TextEditingController typeController = TextEditingController(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Adicionar novo tipo de quadro'), + title: const Text('Adicionar novo tipo de equipamento'), content: TextField( controller: typeController, - decoration: - const InputDecoration(hintText: 'Digite o novo tipo de quadro'), + decoration: const InputDecoration( + hintText: 'Digite o novo tipo de equipamento'), ), actions: [ TextButton( @@ -150,7 +200,14 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - additionalTypes.add(typeController.text); + _newEquipmentTypeName = typeController.text; + }); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectedType = null; + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); + }); }); Navigator.of(context).pop(); } @@ -162,24 +219,70 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteBoardType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um tipo de quadro') { + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentCategory(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _deleteEquipmentType() async { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Selecione um tipo de quadro válido para excluir.'), + content: + Text('Não existem categorias de equipamentos a serem excluídas.'), ), ); return; } + if (_selectedTypeToDelete == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Selecione uma categoria de equipamento válida para excluir.'), + ), + ); + return; + } + + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de quadro "$_selectedTypeToDelete"?'), + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -189,12 +292,32 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Excluir'), - onPressed: () { - setState(() { - additionalTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); + onPressed: () async { Navigator.of(context).pop(); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentCategory(); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } }, ), ], @@ -204,11 +327,9 @@ class _AddEquipmentScreenState extends State { } void _showConfirmationDialog() { - if (_equipmentchargeController.text.isEmpty || + if (_equipmentChargeController.text.isEmpty || _equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectedBoardType == null) || - _selectedLocation == null || - _selectedLocation == 'Selecione a opção') { + (_selectedType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -227,20 +348,16 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectedBoardType ?? ''), + Text(_selectedType ?? _newEquipmentTypeName ?? ''), const SizedBox(height: 10), const Text('Especificação:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentchargeController.text), + Text(_equipmentChargeController.text), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), Text(_equipmentQuantityController.text), const SizedBox(height: 10), - const Text('Existe dispositivos de proteção dentro do quadro:', - style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedLocation ?? ''), - const SizedBox(height: 10), const Text('Imagens:', style: TextStyle(fontWeight: FontWeight.bold)), Wrap( @@ -276,10 +393,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); + _registerEquipment(); }, ), ], @@ -288,21 +404,85 @@ class _AddEquipmentScreenState extends State { ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/listDistribuitionBoard', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - }, + void _registerEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final DistributionRequestModel distributionModel = DistributionRequestModel( + area: widget.areaId, + system: widget.categoryNumber, + ); + + final DistributionEquipmentRequestModel distributionEquipmentDetail = + DistributionEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + distributionRequestModel: distributionModel, ); + + int? equipmentId = + await equipmentService.createDistribution(distributionEquipmentDetail); + + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/listDistribuitionBoard', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentChargeController.clear(); + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } } @override Widget build(BuildContext context) { - List combinedTypes = boardType + additionalTypes; + List> combinedTypes = [ + ...genericEquipmentTypes, + ...personalEquipmentTypes + ]; return Scaffold( appBar: AppBar( @@ -311,7 +491,25 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + setState(() { + _equipmentChargeController.clear(); + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + Navigator.pushReplacementNamed( + context, + '/listDistribuitionBoard', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), @@ -328,7 +526,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Adicionar equipamentos ', + child: Text('Adicionar equipamento', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -340,7 +538,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de Quadro', + const Text('Tipos de equipamento', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -349,17 +547,32 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: ['Selecione o tipo de quadro'] + combinedTypes, - value: _selectedBoardType ?? _selectedType, + items: [ + { + 'name': 'Selecione o tipo de equipamento', + 'id': -1, + 'type': -1 + } + ] + + combinedTypes, + value: _selectedType, onChanged: (newValue) { - if (newValue != 'Selecione o tipo de quadro') { + if (newValue != 'Selecione o tipo de equipamento') { setState(() { - if (boardType.contains(newValue)) { - _selectedBoardType = newValue; - _selectedType = null; + _selectedType = newValue; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalEquipmentCategorySelected = + selected['type'] == 'pessoal'; + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = + selected['id'] as int; + _selectedGenericEquipmentCategoryId = null; } else { - _selectedType = newValue; - _selectedBoardType = null; + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; } }); } @@ -373,16 +586,16 @@ class _AddEquipmentScreenState extends State { children: [ IconButton( icon: const Icon(Icons.add), - onPressed: _addNewBoardType, + onPressed: _addNewEquipmentType, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { - if (additionalTypes.isEmpty) { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - 'Nenhum tipo de quadro adicionado para excluir.'), + 'Não existem equipamentos pessoais a serem excluídos.'), ), ); } else { @@ -402,7 +615,6 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedBoardType = null; _selectedType = null; }); }, @@ -419,7 +631,7 @@ class _AddEquipmentScreenState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentchargeController, + controller: _equipmentChargeController, decoration: const InputDecoration( border: InputBorder.none, contentPadding: @@ -450,23 +662,6 @@ class _AddEquipmentScreenState extends State { ), ), ), - const SizedBox(height: 30), - const Text( - 'Existe dispositivos de proteção dentro do quadro:', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(height: 8), - _buildStyledDropdown( - items: const ['Selecione a opção', 'Sim', 'Não'], - value: _selectedLocation, - onChanged: (newValue) { - if (newValue != 'Selecione a opção') { - setState(() { - _selectedLocation = newValue; - }); - } - }, - ), const SizedBox(height: 15), IconButton( icon: const Icon(Icons.camera_alt), @@ -540,12 +735,12 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de quadro'), + title: const Text('Excluir tipo de equipamento'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Selecione um tipo de quadro para excluir:', + 'Selecione um equipamento para excluir:', textAlign: TextAlign.center, ), StatefulBuilder( @@ -558,12 +753,12 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: additionalTypes - .map>((String value) { + items: personalEquipmentTypes.map>( + (Map value) { return DropdownMenuItem( - value: value, + value: value['name'] as String, child: Text( - value, + value['name'] as String, style: const TextStyle(color: Colors.black), ), ); @@ -585,7 +780,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (_selectedTypeToDelete != null) { Navigator.of(context).pop(); - _deleteBoardType(); + _deleteEquipmentType(); } }, ), @@ -596,7 +791,7 @@ class _AddEquipmentScreenState extends State { } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -608,18 +803,22 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, + value: value['name'] as String, + enabled: value['name'] != 'Selecione o tipo de equipamento', child: Text( - value, + value['name'] as String, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value['name'] == 'Selecione o tipo de equipamento' + ? Colors.grey + : Colors.black, ), ), ); diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart index 686ba7ff..1dad3b8d 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart @@ -1,32 +1,69 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/distribution/distribution_service.dart'; import 'package:sige_ie/equipments/feature/distribuition_board/add_distribuition_board.dart'; -class ListDistribuitionBoard extends StatelessWidget { +class ListDistributionBoard extends StatefulWidget { final String areaName; final String localName; final int categoryNumber; final int localId; final int areaId; - const ListDistribuitionBoard({ - super.key, + const ListDistributionBoard({ + Key? key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, required this.areaId, - }); + }) : super(key: key); + + @override + _ListDistributionBoardState createState() => _ListDistributionBoardState(); +} + +class _ListDistributionBoardState extends State { + List equipmentList = []; + bool isLoading = true; + final DistributionEquipmentService _service = DistributionEquipmentService(); + + @override + void initState() { + super.initState(); + fetchEquipmentList(); + } + + Future fetchEquipmentList() async { + try { + final List equipmentList = + await _service.getDistributionListByArea(widget.areaId); + if (mounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + } catch (e) { + print('Error fetching equipment list: $e'); + if (mounted) { + setState(() { + isLoading = false; + }); + } + } + } void navigateToAddEquipment(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddDistribuitionBoard( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, ), ), ); @@ -34,10 +71,6 @@ class ListDistribuitionBoard extends StatelessWidget { @override Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - String systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; return Scaffold( @@ -50,10 +83,10 @@ class ListDistribuitionBoard extends StatelessWidget { context, '/systemLocation', arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, }, ); }, @@ -71,12 +104,15 @@ class ListDistribuitionBoard extends StatelessWidget { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('$areaName - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), ), ), const SizedBox(height: 20), @@ -85,23 +121,28 @@ class ListDistribuitionBoard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), + isLoading + ? const Center( + child: CircularProgressIndicator(), ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), + ), + ), const SizedBox(height: 40), ], ), diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index 543f3f36..edda8188 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -85,7 +85,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/distributionBoard': - return ListDistribuitionBoard( + return ListDistributionBoard( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 382d3bee..d2cf7a99 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -235,7 +235,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => ListDistribuitionBoard( + builder: (context) => ListDistributionBoard( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 4861e58783627375a9806516c9cfa770de069684 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Tue, 18 Jun 2024 16:58:58 -0300 Subject: [PATCH 251/351] =?UTF-8?q?backend:=20altera=20views=20de=20equipa?= =?UTF-8?q?mentos=20para=20usar=20serializer=20espec=C3=ADfico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/models.py | 18 ++++---- api/equipments/serializers.py | 85 ++++++++++++++++++++++++++++++++++- api/equipments/views.py | 18 ++++---- api/places/views.py | 25 ++++------- 4 files changed, 111 insertions(+), 35 deletions(-) diff --git a/api/equipments/models.py b/api/equipments/models.py index 8cce2abd..5b2befde 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -52,7 +52,7 @@ def __str__(self): class FireAlarmEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="firealarmequipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="fire_alarm_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) @@ -60,7 +60,7 @@ class Meta: db_table = 'equipments_fire_alarm_equipments' class AtmosphericDischargeEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name= "AtmosphericDischargeEquipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name= "atmospheric_discharge_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=7) @@ -68,7 +68,7 @@ class Meta: db_table = 'equipments_atmospheric_discharge_equipments' class StructuredCablingEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="StructuredCablingEquipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="structured_cabling_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=6) @@ -76,7 +76,7 @@ class Meta: db_table = 'equipments_structured_cabling_equipments' class DistributionBoardEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="DistributionBoardEquipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="distribution_board_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=5) @@ -84,7 +84,7 @@ class Meta: db_table = 'equipments_distribution_board_equipments' class ElectricalCircuitEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name='ElectricalCircuitEquipment') + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name='electrical_circuit_equipment') equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=4) @@ -92,7 +92,7 @@ class Meta: db_table = 'equipments_electrical_circuit_equipments' class ElectricalLineEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ElectricalLineEquipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_line_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=3) @@ -100,7 +100,7 @@ class Meta: db_table = 'equipments_electrical_line_equipments' class ElectricalLoadEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ElectricalLoadEquipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_load_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) @@ -108,7 +108,7 @@ class Meta: db_table = 'equipments_electrical_load_equipments' class IluminationEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="IluminationEquipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ilumination_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=1) @@ -116,7 +116,7 @@ class Meta: db_table = 'equipments_ilumination_equipments' class RefrigerationEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="RefrigerationEquipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="refrigeration_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 3bc71235..486affb4 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -11,12 +11,32 @@ class Meta: model = PersonalEquipmentCategory fields = '__all__' +class PersonalEquipmentCategoryResponseSerializer(serializers.ModelSerializer): + + class Meta: + model = PersonalEquipmentCategory + fields = ['name'] + class GenericEquipmentCategorySerializer(serializers.ModelSerializer): class Meta: model = GenericEquipmentCategory fields = '__all__' +class GenericEquipmentCategoryResponseSerializer(serializers.ModelSerializer): + + class Meta: + model = GenericEquipmentCategory + fields = ['name'] + +class EquipmentResponseSerializer(serializers.ModelSerializer): + generic_equipment_category = serializers.CharField(source='generic_equipment_category.name', read_only=True) + personal_equipment_category = serializers.CharField(source='personal_equipment_category.name', read_only=True) + + class Meta: + model = Equipment + fields = ['generic_equipment_category', 'personal_equipment_category'] + class EquipmentPhotoSerializer(serializers.ModelSerializer): photo = serializers.CharField(write_only=True) @@ -42,47 +62,103 @@ class Meta: model = FireAlarmEquipment fields = '__all__' +class FireAlarmEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = FireAlarmEquipment + fields = ['id', 'area', 'equipment', 'system'] + class AtmosphericDischargeEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = AtmosphericDischargeEquipment fields = '__all__' +class AtmosphericDischargeEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = AtmosphericDischargeEquipment + fields = ['id', 'area', 'equipment', 'system'] + class StructuredCablingEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = StructuredCablingEquipment fields = '__all__' +class StructuredCablingEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = StructuredCablingEquipment + fields = ['id', 'area', 'equipment', 'system'] + class DistributionBoardEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = DistributionBoardEquipment fields = '__all__' +class DistributionBoardEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = StructuredCablingEquipment + fields = ['id', 'area', 'equipment', 'system'] + class ElectricalCircuitEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalCircuitEquipment fields = '__all__' +class ElectricalCircuitEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = ElectricalCircuitEquipment + fields = ['id', 'area', 'equipment', 'system'] + class ElectricalLineEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalLineEquipment fields = '__all__' +class ElectricalLineEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = ElectricalLineEquipment + fields = ['id', 'area', 'equipment', 'system'] + class ElectricalLoadEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = ElectricalLoadEquipment fields = '__all__' +class ElectricalLoadEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = ElectricalLoadEquipment + fields = ['id', 'area', 'equipment', 'system'] + class IluminationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = IluminationEquipment - fields = '__all__' + fields = '__all__' + +class IluminationEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = IluminationEquipment + fields = ['id', 'area', 'equipment', 'system'] class RefrigerationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): @@ -90,6 +166,13 @@ class Meta: model = RefrigerationEquipment fields = '__all__' +class RefrigerationEquipmentResponseSerializer(serializers.ModelSerializer): + equipment = EquipmentResponseSerializer() + + class Meta: + model = RefrigerationEquipment + fields = ['id', 'area', 'equipment', 'system'] + class EquipmentSerializer(serializers.ModelSerializer): #photos = EquipmentPhotoSerializer(many=True, required=False) diff --git a/api/equipments/views.py b/api/equipments/views.py index 4d714713..5d727fae 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -110,7 +110,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class RefrigerationEquipmentByAreaList(generics.ListAPIView): - serializer_class = RefrigerationEquipmentSerializer + serializer_class = RefrigerationEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): @@ -141,7 +141,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class FireAlarmEquipmentByAreaList(generics.ListAPIView): - serializer_class = FireAlarmEquipmentSerializer + serializer_class = FireAlarmEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): @@ -172,7 +172,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class AtmosphericDischargeEquipmentByAreaList(generics.ListAPIView): - serializer_class = AtmosphericDischargeEquipmentSerializer + serializer_class = AtmosphericDischargeEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): @@ -203,7 +203,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class StructuredCablingEquipmentByAreaList(generics.ListAPIView): - serializer_class = StructuredCablingEquipmentSerializer + serializer_class = StructuredCablingEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): @@ -234,7 +234,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class DistributionBoardEquipmentByAreaList(generics.ListAPIView): - serializer_class = DistributionBoardEquipmentSerializer + serializer_class = DistributionBoardEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): @@ -265,7 +265,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class ElectricalCircuitEquipmentByAreaList(generics.ListAPIView): - serializer_class = ElectricalCircuitEquipmentSerializer + serializer_class = ElectricalCircuitEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): @@ -296,7 +296,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class ElectricalLineEquipmentByAreaList(generics.ListAPIView): - serializer_class = ElectricalLineEquipmentSerializer + serializer_class = ElectricalLineEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): @@ -327,7 +327,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class ElectricalLoadEquipmentByAreaList(generics.ListAPIView): - serializer_class = ElectricalLoadEquipmentSerializer + serializer_class = ElectricalLoadEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): @@ -358,7 +358,7 @@ def create(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) class IluminationEquipmentByAreaList(generics.ListAPIView): - serializer_class = IluminationEquipmentSerializer + serializer_class = IluminationEquipmentResponseSerializer permission_classes = [IsAreaOwner, IsAuthenticated] def get_queryset(self): diff --git a/api/places/views.py b/api/places/views.py index d380e036..28a9a78b 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -195,11 +195,8 @@ def genericOrPersonal(system): else: return system.equipment.personal_equipment_category - - class GeneratePDFView(APIView): permission_classes = [IsAuthenticated] - def get(self, request, pk=None): place = get_object_or_404(Place, pk=pk) @@ -210,70 +207,66 @@ def get(self, request, pk=None): p = canvas.Canvas(response, pagesize=A4) alt = Altura() - p.setFont('Helvetica-Bold', 16) - p.drawString(205, alt.get_alt(p), f"Relatório do Local: {place.name}") - p.setFont('Helvetica-Bold', 14) p.drawString(100, alt.get_alt(p), "Áreas:") - for area in place.areas.all(): p.setFont('Helvetica-Bold', 14) p.drawString(120, alt.get_alt(p), f"Relatório da Área: {area.name}") - for system in area.firealarmequipment.all(): + for system in area.fire_alarm_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - for system in area.AtmosphericDischargeEquipment.all(): + for system in area.atmospheric_discharge_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - for system in area.StructuredCablingEquipment.all(): + for system in area.structured_cabling_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - for system in area.DistributionBoardEquipment.all(): + for system in area.distribution_board_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - for system in area.ElectricalCircuitEquipment.all(): + for system in area.electrical_circuit_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - for system in area.ElectricalLineEquipment.all(): + for system in area.electrical_line_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - for system in area.ElectricalLoadEquipment.all(): + for system in area.electrical_load_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - for system in area.IluminationEquipment.all(): + for system in area.ilumination_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - for system in area.RefrigerationEquipment.all(): + for system in area.refrigeration_equipment.all(): if(system == None): break p.setFont('Helvetica', 12) From 1023b202771431d07e441d3ee71ee3012ce25c66 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 18 Jun 2024 17:25:07 -0300 Subject: [PATCH 252/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20refrigera=C3=A7?= =?UTF-8?q?=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: OscarDebrito --- .../cooling/cooling_equipment_list.dart | 120 ------ .../add_refrigeration.dart} | 408 ++++++++++++++---- .../refrigeration_equipment_list.dart | 163 +++++++ .../feature/system_configuration.dart | 4 +- frontend/sige_ie/lib/main.dart | 4 +- 5 files changed, 493 insertions(+), 206 deletions(-) delete mode 100644 frontend/sige_ie/lib/equipments/feature/cooling/cooling_equipment_list.dart rename frontend/sige_ie/lib/equipments/feature/{cooling/add_cooling.dart => refrigerations/add_refrigeration.dart} (54%) create mode 100644 frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/cooling_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/cooling/cooling_equipment_list.dart deleted file mode 100644 index c5f78a0c..00000000 --- a/frontend/sige_ie/lib/equipments/feature/cooling/cooling_equipment_list.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/cooling/add_cooling.dart'; - -class ListCollingEquipment extends StatelessWidget { - final String areaName; - final String localName; - final int categoryNumber; - final int localId; - final int areaId; - - const ListCollingEquipment({ - super.key, - required this.areaName, - required this.categoryNumber, - required this.localName, - required this.localId, - required this.areaId, - }); - - void navigateToAddEquipment(BuildContext context) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AddCooling( - areaName: areaName, - categoryNumber: categoryNumber, - localName: localName, - localId: localId, - areaId: areaId, - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - List equipmentList = [ - // Vazio para simular nenhum equipamento - ]; - - String systemTitle = 'Refrigeração'; - - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.pushReplacementNamed( - context, - '/systemLocation', - arguments: { - 'areaName': areaName, - 'localName': localName, - 'localId': localId, - 'areaId': areaId, - }, - ); - }, - ), - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text('$areaName - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), - ), - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), - ); - }).toList(), - ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ), - const SizedBox(height: 40), - ], - ), - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () => navigateToAddEquipment(context), - backgroundColor: AppColors.sigeIeYellow, - child: const Icon(Icons.add, color: AppColors.sigeIeBlue), - ), - ); - } -} diff --git a/frontend/sige_ie/lib/equipments/feature/cooling/add_cooling.dart b/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart similarity index 54% rename from frontend/sige_ie/lib/equipments/feature/cooling/add_cooling.dart rename to frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart index cf940795..ed268b41 100644 --- a/frontend/sige_ie/lib/equipments/feature/cooling/add_cooling.dart +++ b/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart @@ -1,13 +1,22 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:io'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/refrigerations/refrigerations_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/refrigerations/refrigerations_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; +import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; +import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; class ImageData { - File imageFile; int id; + File imageFile; String description; ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); @@ -16,14 +25,14 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class AddCooling extends StatefulWidget { +class AddRefrigeration extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; final int areaId; - const AddCooling({ + const AddRefrigeration({ super.key, required this.areaName, required this.categoryNumber, @@ -33,27 +42,71 @@ class AddCooling extends StatefulWidget { }); @override - _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); + _AddRefrigerationState createState() => _AddRefrigerationState(); } -class _AddEquipmentScreenState extends State { +class _AddRefrigerationState extends State { + EquipmentService equipmentService = EquipmentService(); + EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); + PersonalEquipmentCategoryService personalEquipmentCategoryService = + PersonalEquipmentCategoryService(); + GenericEquipmentCategoryService genericEquipmentCategoryService = + GenericEquipmentCategoryService(); + final _equipmentQuantityController = TextEditingController(); + final _equipmentPowerController = TextEditingController(); String? _selectedType; String? _selectedTypeToDelete; - String? _selectedCoolingType; + String? _newEquipmentTypeName; + int? _selectedGenericEquipmentCategoryId; + int? _selectedPersonalEquipmentCategoryId; + bool _isPersonalEquipmentCategorySelected = false; - List coolingTypes = [ - 'Selecione o tipo de refrigeração', - 'Refrigeração1', - 'Refrigeração2', - 'Refrigeração3', - ]; + List> genericEquipmentTypes = []; + List> personalEquipmentTypes = []; + Map personalEquipmentMap = {}; - List additionalCoolingTypes = []; + @override + void initState() { + super.initState(); + _fetchEquipmentCategory(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _fetchEquipmentCategory(); + } + + Future _fetchEquipmentCategory() async { + List genericEquipmentCategoryList = + await genericEquipmentCategoryService + .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + + List personalEquipmentCategoryList = + await personalEquipmentCategoryService + .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + + if (mounted) { + setState(() { + genericEquipmentTypes = genericEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'generico'}) + .toList(); + personalEquipmentTypes = personalEquipmentCategoryList + .map((e) => {'id': e.id, 'name': e.name, 'type': 'pessoal'}) + .toList(); + personalEquipmentMap = { + for (var equipment in personalEquipmentCategoryList) + equipment.name: equipment.id + }; + }); + } + } @override void dispose() { _equipmentQuantityController.dispose(); + _equipmentPowerController.dispose(); categoryImagesMap[widget.categoryNumber]?.clear(); super.dispose(); } @@ -71,9 +124,8 @@ class _AddEquipmentScreenState extends State { } void _showImageDialog(File imageFile, {ImageData? existingImage}) { - TextEditingController descriptionController = TextEditingController( - text: existingImage?.description ?? '', - ); + TextEditingController descriptionController = + TextEditingController(text: existingImage?.description ?? ''); showDialog( context: context, builder: (BuildContext context) { @@ -104,10 +156,8 @@ class _AddEquipmentScreenState extends State { if (existingImage != null) { existingImage.description = descriptionController.text; } else { - final imageData = ImageData( - imageFile, - descriptionController.text, - ); + final imageData = + ImageData(imageFile, descriptionController.text); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { categoryImagesMap[categoryNumber] = []; @@ -125,17 +175,17 @@ class _AddEquipmentScreenState extends State { ); } - void _addNewCoolingType() { + void _addNewEquipmentType() { TextEditingController typeController = TextEditingController(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Adicionar novo tipo de refrigeração'), + title: const Text('Adicionar novo tipo de equipamento'), content: TextField( controller: typeController, decoration: const InputDecoration( - hintText: 'Digite o novo tipo de refrigeração'), + hintText: 'Digite o novo tipo de equipamento'), ), actions: [ TextButton( @@ -149,7 +199,14 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (typeController.text.isNotEmpty) { setState(() { - additionalCoolingTypes.add(typeController.text); + _newEquipmentTypeName = typeController.text; + }); + _registerPersonalEquipmentType().then((_) { + setState(() { + _selectedType = null; + _selectedGenericEquipmentCategoryId = null; + _fetchEquipmentCategory(); + }); }); Navigator.of(context).pop(); } @@ -161,25 +218,70 @@ class _AddEquipmentScreenState extends State { ); } - void _deleteCoolingType() { - if (_selectedTypeToDelete == null || - _selectedTypeToDelete == 'Selecione um tipo de refrigeração') { + Future _registerPersonalEquipmentType() async { + int systemId = widget.categoryNumber; + PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = + PersonalEquipmentCategoryRequestModel( + name: _newEquipmentTypeName ?? '', system: systemId); + + int id = await personalEquipmentCategoryService + .createPersonalEquipmentCategory(personalEquipmentTypeRequestModel); + + if (id != -1) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento registrado com sucesso.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + personalEquipmentTypes + .add({'name': _newEquipmentTypeName!, 'id': id, 'type': 'pessoal'}); + personalEquipmentMap[_newEquipmentTypeName!] = id; + _newEquipmentTypeName = null; + _fetchEquipmentCategory(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _deleteEquipmentType() async { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: - Text('Selecione um tipo de refrigeração válido para excluir.'), + Text('Não existem categorias de equipamentos a serem excluídas.'), ), ); return; } + if (_selectedTypeToDelete == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Selecione uma categoria de equipamento válida para excluir.'), + ), + ); + return; + } + + int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Confirmar exclusão'), - content: Text( - 'Tem certeza de que deseja excluir o tipo de refrigeração "$_selectedTypeToDelete"?'), + title: const Text('Confirmar Exclusão'), + content: + const Text('Tem certeza de que deseja excluir este equipamento?'), actions: [ TextButton( child: const Text('Cancelar'), @@ -189,12 +291,32 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Excluir'), - onPressed: () { - setState(() { - additionalCoolingTypes.remove(_selectedTypeToDelete); - _selectedTypeToDelete = null; - }); + onPressed: () async { Navigator.of(context).pop(); + bool success = await personalEquipmentCategoryService + .deletePersonalEquipmentCategory(equipmentId); + + if (success) { + setState(() { + personalEquipmentTypes.removeWhere( + (element) => element['name'] == _selectedTypeToDelete); + _selectedTypeToDelete = null; + _fetchEquipmentCategory(); + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipamento excluído com sucesso.'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao excluir o equipamento.'), + backgroundColor: Colors.red, + ), + ); + } }, ), ], @@ -205,7 +327,8 @@ class _AddEquipmentScreenState extends State { void _showConfirmationDialog() { if (_equipmentQuantityController.text.isEmpty || - (_selectedType == null && _selectedCoolingType == null)) { + _equipmentPowerController.text.isEmpty || + (_selectedType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Por favor, preencha todos os campos.'), @@ -224,12 +347,16 @@ class _AddEquipmentScreenState extends State { children: [ const Text('Tipo:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_selectedType ?? _selectedCoolingType ?? ''), + Text(_selectedType ?? _newEquipmentTypeName ?? ''), const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), Text(_equipmentQuantityController.text), const SizedBox(height: 10), + const Text('Potência (KW):', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(_equipmentPowerController.text), + const SizedBox(height: 10), const Text('Imagens:', style: TextStyle(fontWeight: FontWeight.bold)), Wrap( @@ -265,10 +392,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('OK'), + child: const Text('Adicionar'), onPressed: () { - Navigator.of(context).pop(); - navigateToEquipmentScreen(); + _registerEquipment(); }, ), ], @@ -277,22 +403,86 @@ class _AddEquipmentScreenState extends State { ); } - void navigateToEquipmentScreen() { - Navigator.of(context).pushNamed( - '/listCollingEquipment', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'categoryNumber': widget.categoryNumber, - 'areaId': widget.areaId, - }, + void _registerEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final RefrigerationsRequestModel refrigerationsModel = + RefrigerationsRequestModel( + area: widget.areaId, + system: widget.categoryNumber, ); + + final RefrigerationsEquipmentRequestModel refrigerationsEquipmentDetail = + RefrigerationsEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + refrigerationsRequestModel: refrigerationsModel, + ); + + int? equipmentId = await equipmentService + .createRefrigerations(refrigerationsEquipmentDetail); + + if (equipmentId != null) { + await Future.wait(_images.map((imageData) async { + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description, + equipment: equipmentId, + ), + ); + })); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento registrados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/listCollingEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentQuantityController.clear(); + _equipmentPowerController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao registrar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } } @override Widget build(BuildContext context) { - List combinedTypes = coolingTypes + additionalCoolingTypes; + List> combinedTypes = [ + ...genericEquipmentTypes, + ...personalEquipmentTypes + ]; return Scaffold( appBar: AppBar( @@ -301,7 +491,25 @@ class _AddEquipmentScreenState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { - Navigator.of(context).pop(); + setState(() { + _equipmentQuantityController.clear(); + _equipmentPowerController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + Navigator.pushReplacementNamed( + context, + '/listCollingEquipment', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); }, ), ), @@ -318,7 +526,7 @@ class _AddEquipmentScreenState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: const Center( - child: Text('Adicionar equipamentos ', + child: Text('Adicionar equipamento', style: TextStyle( fontSize: 26, fontWeight: FontWeight.bold, @@ -330,7 +538,7 @@ class _AddEquipmentScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Tipos de refrigeração', + const Text('Tipos de equipamento', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -339,19 +547,32 @@ class _AddEquipmentScreenState extends State { Expanded( flex: 4, child: _buildStyledDropdown( - items: ['Selecione o tipo de refrigeração'] + + items: [ + { + 'name': 'Selecione o tipo de equipamento', + 'id': -1, + 'type': -1 + } + ] + combinedTypes, - value: _selectedCoolingType ?? _selectedType, + value: _selectedType, onChanged: (newValue) { - if (newValue != - 'Selecione o tipo de refrigeração') { + if (newValue != 'Selecione o tipo de equipamento') { setState(() { - if (coolingTypes.contains(newValue)) { - _selectedCoolingType = newValue; - _selectedType = null; + _selectedType = newValue; + Map selected = + combinedTypes.firstWhere((element) => + element['name'] == newValue); + _isPersonalEquipmentCategorySelected = + selected['type'] == 'pessoal'; + if (_isPersonalEquipmentCategorySelected) { + _selectedPersonalEquipmentCategoryId = + selected['id'] as int; + _selectedGenericEquipmentCategoryId = null; } else { - _selectedType = newValue; - _selectedCoolingType = null; + _selectedGenericEquipmentCategoryId = + selected['id'] as int; + _selectedPersonalEquipmentCategoryId = null; } }); } @@ -365,16 +586,16 @@ class _AddEquipmentScreenState extends State { children: [ IconButton( icon: const Icon(Icons.add), - onPressed: _addNewCoolingType, + onPressed: _addNewEquipmentType, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { - if (additionalCoolingTypes.isEmpty) { + if (personalEquipmentTypes.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - 'Nenhum tipo de refrigeração adicionado para excluir.'), + 'Não existem equipamentos pessoais a serem excluídos.'), ), ); } else { @@ -394,13 +615,32 @@ class _AddEquipmentScreenState extends State { TextButton( onPressed: () { setState(() { - _selectedCoolingType = null; _selectedType = null; }); }, child: const Text('Limpar seleção'), ), const SizedBox(height: 30), + const Text('Potência (KW)', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _equipmentPowerController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 30), const Text('Quantidade', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), @@ -496,12 +736,12 @@ class _AddEquipmentScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('Excluir tipo de refrigeração'), + title: const Text('Excluir tipo de equipamento'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Selecione um tipo de refrigeração para excluir:', + 'Selecione um equipamento para excluir:', textAlign: TextAlign.center, ), StatefulBuilder( @@ -514,12 +754,12 @@ class _AddEquipmentScreenState extends State { _selectedTypeToDelete = newValue; }); }, - items: additionalCoolingTypes - .map>((String value) { + items: personalEquipmentTypes.map>( + (Map value) { return DropdownMenuItem( - value: value, + value: value['name'] as String, child: Text( - value, + value['name'] as String, style: const TextStyle(color: Colors.black), ), ); @@ -541,7 +781,7 @@ class _AddEquipmentScreenState extends State { onPressed: () { if (_selectedTypeToDelete != null) { Navigator.of(context).pop(); - _deleteCoolingType(); + _deleteEquipmentType(); } }, ), @@ -552,7 +792,7 @@ class _AddEquipmentScreenState extends State { } Widget _buildStyledDropdown({ - required List items, + required List> items, String? value, required Function(String?) onChanged, bool enabled = true, @@ -564,18 +804,22 @@ class _AddEquipmentScreenState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 10), child: DropdownButton( - hint: Text(items.first), + hint: Text(items.first['name'] as String, + style: const TextStyle(color: Colors.grey)), value: value, isExpanded: true, underline: Container(), onChanged: enabled ? onChanged : null, - items: items.map>((String value) { + items: items.map>((Map value) { return DropdownMenuItem( - value: value.isEmpty ? null : value, + value: value['name'] as String, + enabled: value['name'] != 'Selecione o tipo de equipamento', child: Text( - value, + value['name'] as String, style: TextStyle( - color: enabled ? Colors.black : Colors.grey, + color: value['name'] == 'Selecione o tipo de equipamento' + ? Colors.grey + : Colors.black, ), ), ); diff --git a/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart new file mode 100644 index 00000000..a7bb4781 --- /dev/null +++ b/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; +import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/refrigerations/refrigerations_service.dart'; +import 'package:sige_ie/equipments/feature/refrigerations/add_refrigeration.dart'; + +class ListRefrigerationEquipment extends StatefulWidget { + final String areaName; + final String localName; + final int categoryNumber; + final int localId; + final int areaId; + + const ListRefrigerationEquipment({ + Key? key, + required this.areaName, + required this.categoryNumber, + required this.localName, + required this.localId, + required this.areaId, + }) : super(key: key); + + @override + _ListRefrigerationEquipmentState createState() => + _ListRefrigerationEquipmentState(); +} + +class _ListRefrigerationEquipmentState + extends State { + List equipmentList = []; + bool isLoading = true; + final RefrigerationsEquipmentService _service = + RefrigerationsEquipmentService(); + + @override + void initState() { + super.initState(); + fetchEquipmentList(); + } + + Future fetchEquipmentList() async { + try { + final List equipmentList = + await _service.getRefrigerationsListByArea(widget.areaId); + if (mounted) { + setState(() { + this.equipmentList = equipmentList; + isLoading = false; + }); + } + } catch (e) { + print('Error fetching equipment list: $e'); + if (mounted) { + setState(() { + isLoading = false; + }); + } + } + } + + void navigateToAddEquipment(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddRefrigeration( + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + String systemTitle = 'Refrigeração'; + + return Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : equipmentList.isNotEmpty + ? Column( + children: equipmentList.map((equipment) { + return ListTile( + title: Text(equipment), + ); + }).toList(), + ) + : const Center( + child: Text( + 'Você ainda não tem equipamentos', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index edda8188..fcf1af7c 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; +import 'package:sige_ie/equipments/feature/refrigerations/refrigeration_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical_circuit/electrical_circuit_list.dart'; import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; @@ -93,7 +93,7 @@ class _SystemConfigurationState extends State { areaId: areaId, ); case '/cooling': - return ListCollingEquipment( + return ListRefrigerationEquipment( areaName: areaName, localName: localName, localId: localId, diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index d2cf7a99..2d4a7887 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -4,7 +4,7 @@ import 'package:sige_ie/core/ui/first_scren.dart'; import 'package:sige_ie/core/feature/register/register.dart'; import 'package:sige_ie/core/ui/splash_screen.dart'; import 'package:sige_ie/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart'; -import 'package:sige_ie/equipments/feature/cooling/cooling_equipment_list.dart'; +import 'package:sige_ie/equipments/feature/refrigerations/refrigeration_equipment_list.dart'; import 'package:sige_ie/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart'; import 'package:sige_ie/equipments/feature/electrical_line/electrical_line_list.dart'; import 'package:sige_ie/equipments/feature/electrical_load/eletrical_load_list.dart'; @@ -294,7 +294,7 @@ class MyApp extends StatelessWidget { localId != null && areaId != null) { return MaterialPageRoute( - builder: (context) => ListCollingEquipment( + builder: (context) => ListRefrigerationEquipment( areaName: areaName, categoryNumber: categoryNumber, localName: localName, From 95edb51062d5d86a7310a42e0a3e26c323143aed Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 19 Jun 2024 14:03:24 -0300 Subject: [PATCH 253/351] backend: atualiza gitignore e remove equipment_photos --- api/.gitignore | 2 ++ api/equipment_photos/temp.jpeg | Bin 63605 -> 0 bytes api/equipment_photos/temp_BA5zilu.jpeg | Bin 63920 -> 0 bytes api/equipment_photos/temp_P0M070S.jpeg | Bin 63357 -> 0 bytes api/equipment_photos/temp_TqeOtKh.jpeg | Bin 63357 -> 0 bytes api/equipment_photos/temp_jlwotYQ.jpeg | Bin 64460 -> 0 bytes 6 files changed, 2 insertions(+) delete mode 100644 api/equipment_photos/temp.jpeg delete mode 100644 api/equipment_photos/temp_BA5zilu.jpeg delete mode 100644 api/equipment_photos/temp_P0M070S.jpeg delete mode 100644 api/equipment_photos/temp_TqeOtKh.jpeg delete mode 100644 api/equipment_photos/temp_jlwotYQ.jpeg diff --git a/api/.gitignore b/api/.gitignore index b4268dfd..a2b4fdf4 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -7,6 +7,8 @@ db.sqlite3 media/ */data/ +# Photos # +equipment_photos/ # Backup files # *.bak diff --git a/api/equipment_photos/temp.jpeg b/api/equipment_photos/temp.jpeg deleted file mode 100644 index 1c26617ba7cddcefbc49b0f72fb09317ca01fb99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63605 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 KNRPp}%>M__5e$j| diff --git a/api/equipment_photos/temp_BA5zilu.jpeg b/api/equipment_photos/temp_BA5zilu.jpeg deleted file mode 100644 index 11e811280bbdc2154954eefc941471ed51ea26e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63920 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 diff --git a/api/equipment_photos/temp_P0M070S.jpeg b/api/equipment_photos/temp_P0M070S.jpeg deleted file mode 100644 index ac2a0debdcc54878ba43f552a6451778cba0d22d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63357 zcmeHQ30zah)}Ig{BDSdtqP7}qYo+2VDsHvP)gq;@eHGER*jmlAF1UTQ?kik{DjM4t zwYDy=8U(FdHMXdIZd?TwtJFYU5VQqE0YM}XmMnMq&JCcDL~}vJrIX+9-rU?fcjlZk z|2cE!oS6xCh0AjI(tqBh2?%}g0qTMfYJ!@GUO*0T%s!oG8E$>u)Es7OM&FLkO?t3`U%yCoXAzx0M_Qm+#J>~QI_LleV ziF$PF(X(H--u=4w?a;kpYT-w8QaQ|d4P-sUCBr}szg z%;{a>dKCW(d^*7a6^hzaeoILB205VxmA|X=LE5VORked!Rz2@iGu=II5SNKwc<-Hm zzJmZxPyn2`tH=*Ii1-hGcHlpbV*c#p=qPq{c5-$$9}<@)&Jq`ivvU*ICN53+4_q~K zZQ6|g!DmwWs)LhQ?9^1^EHNjk(}_Ednm3Uwc8U^+Jb@m!7?P_v!m)Kfl5L?*zR2&-aFZ_|eC~BR=_b z?6~n0LMBd{Jag9UIdkXDUl8%t(q)m$S18teyY{>9*R9{MF*;`Jwja0e*tzSM*!>3% z{`$W|hvO4Yo;sa)=C`wdT)24Ya_W_|^sMXIH*V(S=H1FKF42{il~?EuMxHMb5?8VX zXPbOA2fiHmI*{;ui5%v_A#U#I^m2FS7K4H$W2U$4*kef(x3^bs`T2y)D?Y=t?w`*{ zY1-=5o>`rWdDhIF31ck0UYy-!>>gj~sF@gAMRRd;L?C8Ww8Bdpn;oz4jB>ySmI1~9 z#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u z#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#sJ0u#=tXy0i8@gRh1UQ zDgxPI-h}>lnKq4x4Pe|<^w!a2@kSko4*a4vHi={$HPx2B$C z%0D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 Ia4zlt0o0)}g#Z8m diff --git a/api/equipment_photos/temp_TqeOtKh.jpeg b/api/equipment_photos/temp_TqeOtKh.jpeg deleted file mode 100644 index 5008381d7ef8c195a18aa8b7c18a24dcac6f2697..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63357 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 Date: Wed, 19 Jun 2024 15:13:04 -0300 Subject: [PATCH 254/351] =?UTF-8?q?backend:=20atualiza=20permiss=C3=B5es?= =?UTF-8?q?=20de=20place=20e=20area=20para=20editors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...sphericdischargeequipment_area_and_more.py | 60 +++++++++++++++++++ api/places/permissions.py | 6 +- api/places/views.py | 37 ++++++------ 3 files changed, 82 insertions(+), 21 deletions(-) create mode 100644 api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py diff --git a/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py b/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py new file mode 100644 index 00000000..f34f9e1a --- /dev/null +++ b/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2 on 2024-06-19 17:19 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0031_remove_place_access_code'), + ('equipments', '0027_alter_atmosphericdischargeequipment_area_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosphericdischargeequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='distributionboardequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='distribution_board_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricalcircuitequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='electrical_circuit_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricallineequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='electrical_line_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricalloadequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='electrical_load_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='firealarmequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fire_alarm_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='iluminationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ilumination_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='refrigerationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='refrigeration_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='structuredcablingequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='structured_cabling_equipment', to='places.area'), + ), + ] diff --git a/api/places/permissions.py b/api/places/permissions.py index 46fd51bc..446e140f 100644 --- a/api/places/permissions.py +++ b/api/places/permissions.py @@ -1,7 +1,9 @@ from rest_framework import permissions -from places.models import Place - class IsPlaceOwner(permissions.BasePermission): def has_object_permission(self, request, view, obj): return obj.place_owner.user == request.user + +class IsPlaceEditor(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + return obj.editors.filter(user=request.user).exists() \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index 28a9a78b..8acb1423 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -1,10 +1,10 @@ from rest_framework.views import APIView +from django.db.models import Q from django.contrib.auth.models import User from users.models import PlaceOwner, PlaceEditor -from rest_framework import generics from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated -from places.permissions import IsPlaceOwner +from places.permissions import IsPlaceOwner, IsPlaceEditor from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response @@ -18,7 +18,7 @@ class PlaceViewSet(viewsets.ModelViewSet): queryset = Place.objects.all() serializer_class = PlaceSerializer - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, IsPlaceOwner | IsPlaceEditor] def get_place_owner(self, user): try: @@ -26,10 +26,6 @@ def get_place_owner(self, user): except PlaceOwner.DoesNotExist: return PlaceOwner.objects.create(user=user) - def _has_permission(self, request, place): - place_owner = place.place_owner - return request.user == place_owner.user or place.editors.filter(user=request.user).exists() - def create(self, request, *args, **kwargs): user = request.user @@ -51,7 +47,10 @@ def list(self, request, *args, **kwargs): user = request.user place_owner = self.get_place_owner(user) - places = Place.objects.filter(place_owner=place_owner) + places = Place.objects.filter( + Q(place_owner=place_owner) | + Q(editors__user=user) + ).distinct() place_serializer = PlaceSerializer(places, many=True) return Response(place_serializer.data) @@ -84,13 +83,13 @@ def destroy(self, request, pk=None): else: return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) - @action(detail=True, methods=['get']) + @action(detail=True, methods=['get'], permission_classes=[IsAuthenticated, IsPlaceOwner | IsPlaceEditor]) def areas(self, request, pk=None): place = self.get_object() serializer = AreaSerializer(place.areas.all(), many=True) return Response(serializer.data) - @action(detail=True, methods=['get'], url_path='areas/(?P\d+)') + @action(detail=True, methods=['get'], url_path='areas/(?P\d+)', permission_classes=[IsAuthenticated, IsPlaceOwner | IsPlaceEditor]) def area(self, request, pk=None, area_pk=None): place = self.get_object() area = get_object_or_404(place.areas.all(), pk=area_pk) @@ -104,19 +103,18 @@ class AreaViewSet(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): user = request.user - place_owner = user.place_owner place_id = request.data.get('place') - place = get_object_or_404(Place, id=place_id, place_owner=place_owner) + place = get_object_or_404(Place, id=place_id) - if place.place_owner == place_owner: + if place.place_owner.user == user or place.editors.filter(user=user).exists(): area_serializer = AreaSerializer(data=request.data) area_serializer.is_valid(raise_exception=True) area_serializer.save() return Response(area_serializer.data, status=status.HTTP_201_CREATED) else: - return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) - + return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) + def list(self,request,*args, **kwargs): user = request.user place_owner = user.place_owner @@ -137,7 +135,7 @@ def retrieve(self, request, pk=None): area = get_object_or_404(Area,pk=pk) - if(area.place.place_owner.id == place_owner): + if(area.place.place_owner.id == place_owner or area.place.editors.filter(user=request.user).exists()): serializer = AreaSerializer(area) return Response(serializer.data) else: @@ -196,11 +194,13 @@ def genericOrPersonal(system): return system.equipment.personal_equipment_category class GeneratePDFView(APIView): - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, IsPlaceOwner | IsPlaceEditor] def get(self, request, pk=None): place = get_object_or_404(Place, pk=pk) + self.check_object_permissions(request, place) + response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = f'attachment; filename="place_{place.id}_report.pdf"' @@ -259,7 +259,7 @@ def get(self, request, pk=None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - + for system in area.ilumination_equipment.all(): if(system == None): break @@ -272,7 +272,6 @@ def get(self, request, pk=None): p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - p.showPage() p.save() return response From 852cab6ca95dea2f1c81418d0a120def60ce7656 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 20 Jun 2024 11:42:51 -0300 Subject: [PATCH 255/351] =?UTF-8?q?backend:=20cria=20permiss=C3=A3o=20para?= =?UTF-8?q?=20editores=20editar=20equipamentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/mixins.py | 2 +- api/equipments/permissions.py | 4 +++ api/equipments/views.py | 58 +++++++++++++++++------------------ 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index 874ab9a3..2ec3def1 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -9,7 +9,7 @@ def validate_equipment(self, value): Garante que o equipment pertence ao place owner. """ user = self.context['request'].user - if value.equipment.place_owner != user.place_owner: + if value.place_owner != user.place_owner: raise serializers.ValidationError("You are not the owner of the equipment") return value diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index ae0e829c..a870a269 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -16,6 +16,10 @@ def has_object_permission(self, request, view, obj): else: return False +class IsSpecificEquipmentEditor(BasePermission): + def has_object_permission(self, request, view, obj): + return obj.area.place.editors.filter(user=request.user).exists() + class IsAreaOwner(BasePermission): def has_object_permission(self, request, view, obj): if obj.area.place.place_owner == request.user.place_owner: diff --git a/api/equipments/views.py b/api/equipments/views.py index 5d727fae..b9d0031d 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -62,7 +62,7 @@ def get_queryset(self): class EquipmentCreate(generics.CreateAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] def get_serializer_context(self): context = super().get_serializer_context() @@ -74,7 +74,7 @@ def get_serializer_context(self): class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] class EquipmentPhotoList(generics.ListCreateAPIView): queryset = EquipmentPhoto.objects.all() @@ -94,7 +94,7 @@ class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): class RefrigerationEquipmentList(generics.ListCreateAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -111,7 +111,7 @@ def create(self, request, *args, **kwargs): class RefrigerationEquipmentByAreaList(generics.ListAPIView): serializer_class = RefrigerationEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -120,12 +120,12 @@ def get_queryset(self): class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class FireAlarmEquipmentList(generics.ListCreateAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -142,7 +142,7 @@ def create(self, request, *args, **kwargs): class FireAlarmEquipmentByAreaList(generics.ListAPIView): serializer_class = FireAlarmEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -151,12 +151,12 @@ def get_queryset(self): class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -173,7 +173,7 @@ def create(self, request, *args, **kwargs): class AtmosphericDischargeEquipmentByAreaList(generics.ListAPIView): serializer_class = AtmosphericDischargeEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -182,12 +182,12 @@ def get_queryset(self): class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class StructuredCablingEquipmentList(generics.ListCreateAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -204,7 +204,7 @@ def create(self, request, *args, **kwargs): class StructuredCablingEquipmentByAreaList(generics.ListAPIView): serializer_class = StructuredCablingEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -213,12 +213,12 @@ def get_queryset(self): class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class DistributionBoardEquipmentList(generics.ListCreateAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -235,7 +235,7 @@ def create(self, request, *args, **kwargs): class DistributionBoardEquipmentByAreaList(generics.ListAPIView): serializer_class = DistributionBoardEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -244,12 +244,12 @@ def get_queryset(self): class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class ElectricalCircuitEquipmentList(generics.ListCreateAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -266,7 +266,7 @@ def create(self, request, *args, **kwargs): class ElectricalCircuitEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalCircuitEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -275,12 +275,12 @@ def get_queryset(self): class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class ElectricalLineEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -297,7 +297,7 @@ def create(self, request, *args, **kwargs): class ElectricalLineEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLineEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -306,12 +306,12 @@ def get_queryset(self): class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class ElectricalLoadEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -328,7 +328,7 @@ def create(self, request, *args, **kwargs): class ElectricalLoadEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLoadEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -337,12 +337,12 @@ def get_queryset(self): class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class IluminationEquipmentList(generics.ListCreateAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -359,7 +359,7 @@ def create(self, request, *args, **kwargs): class IluminationEquipmentByAreaList(generics.ListAPIView): serializer_class = IluminationEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -368,4 +368,4 @@ def get_queryset(self): class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] \ No newline at end of file + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] \ No newline at end of file From 8f4703ca3ea3fc9b06443a1a5fb028e287077f9e Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 20 Jun 2024 11:42:51 -0300 Subject: [PATCH 256/351] =?UTF-8?q?backend:=20cria=20permiss=C3=A3o=20para?= =?UTF-8?q?=20editores=20editar=20equipamentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kauan Jose --- api/equipments/mixins.py | 2 +- api/equipments/permissions.py | 4 +++ api/equipments/views.py | 58 +++++++++++++++++------------------ 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index 874ab9a3..2ec3def1 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -9,7 +9,7 @@ def validate_equipment(self, value): Garante que o equipment pertence ao place owner. """ user = self.context['request'].user - if value.equipment.place_owner != user.place_owner: + if value.place_owner != user.place_owner: raise serializers.ValidationError("You are not the owner of the equipment") return value diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index ae0e829c..a870a269 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -16,6 +16,10 @@ def has_object_permission(self, request, view, obj): else: return False +class IsSpecificEquipmentEditor(BasePermission): + def has_object_permission(self, request, view, obj): + return obj.area.place.editors.filter(user=request.user).exists() + class IsAreaOwner(BasePermission): def has_object_permission(self, request, view, obj): if obj.area.place.place_owner == request.user.place_owner: diff --git a/api/equipments/views.py b/api/equipments/views.py index 5d727fae..b9d0031d 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -62,7 +62,7 @@ def get_queryset(self): class EquipmentCreate(generics.CreateAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] def get_serializer_context(self): context = super().get_serializer_context() @@ -74,7 +74,7 @@ def get_serializer_context(self): class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] class EquipmentPhotoList(generics.ListCreateAPIView): queryset = EquipmentPhoto.objects.all() @@ -94,7 +94,7 @@ class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): class RefrigerationEquipmentList(generics.ListCreateAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -111,7 +111,7 @@ def create(self, request, *args, **kwargs): class RefrigerationEquipmentByAreaList(generics.ListAPIView): serializer_class = RefrigerationEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -120,12 +120,12 @@ def get_queryset(self): class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class FireAlarmEquipmentList(generics.ListCreateAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -142,7 +142,7 @@ def create(self, request, *args, **kwargs): class FireAlarmEquipmentByAreaList(generics.ListAPIView): serializer_class = FireAlarmEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -151,12 +151,12 @@ def get_queryset(self): class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -173,7 +173,7 @@ def create(self, request, *args, **kwargs): class AtmosphericDischargeEquipmentByAreaList(generics.ListAPIView): serializer_class = AtmosphericDischargeEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -182,12 +182,12 @@ def get_queryset(self): class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class StructuredCablingEquipmentList(generics.ListCreateAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -204,7 +204,7 @@ def create(self, request, *args, **kwargs): class StructuredCablingEquipmentByAreaList(generics.ListAPIView): serializer_class = StructuredCablingEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -213,12 +213,12 @@ def get_queryset(self): class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class DistributionBoardEquipmentList(generics.ListCreateAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -235,7 +235,7 @@ def create(self, request, *args, **kwargs): class DistributionBoardEquipmentByAreaList(generics.ListAPIView): serializer_class = DistributionBoardEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -244,12 +244,12 @@ def get_queryset(self): class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class ElectricalCircuitEquipmentList(generics.ListCreateAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -266,7 +266,7 @@ def create(self, request, *args, **kwargs): class ElectricalCircuitEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalCircuitEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -275,12 +275,12 @@ def get_queryset(self): class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class ElectricalLineEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -297,7 +297,7 @@ def create(self, request, *args, **kwargs): class ElectricalLineEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLineEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -306,12 +306,12 @@ def get_queryset(self): class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class ElectricalLoadEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -328,7 +328,7 @@ def create(self, request, *args, **kwargs): class ElectricalLoadEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLoadEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -337,12 +337,12 @@ def get_queryset(self): class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] class IluminationEquipmentList(generics.ListCreateAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -359,7 +359,7 @@ def create(self, request, *args, **kwargs): class IluminationEquipmentByAreaList(generics.ListAPIView): serializer_class = IluminationEquipmentResponseSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -368,4 +368,4 @@ def get_queryset(self): class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsAreaOwner, IsAuthenticated] \ No newline at end of file + permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] \ No newline at end of file From 202c7fb165bc721be34cc85a60974ebdcd915a2f Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 21 Jun 2024 13:34:11 -0300 Subject: [PATCH 257/351] =?UTF-8?q?backend:=20altera=C3=A7=C3=B5es=20nas?= =?UTF-8?q?=20permissions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...29_alter_equipment_place_owner_and_more.py | 35 +++++++ api/equipments/mixins.py | 16 +--- api/equipments/models.py | 91 +++++++++---------- api/equipments/permissions.py | 18 +--- api/equipments/serializers.py | 11 +-- api/equipments/views.py | 69 +++++++------- api/places/views.py | 8 +- 7 files changed, 126 insertions(+), 122 deletions(-) create mode 100644 api/equipments/migrations/0029_alter_equipment_place_owner_and_more.py diff --git a/api/equipments/migrations/0029_alter_equipment_place_owner_and_more.py b/api/equipments/migrations/0029_alter_equipment_place_owner_and_more.py new file mode 100644 index 00000000..2c28d3f2 --- /dev/null +++ b/api/equipments/migrations/0029_alter_equipment_place_owner_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2 on 2024-06-21 12:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_alter_placeowner_user'), + ('systems', '0008_delete_atmosphericdischargesystem'), + ('equipments', '0028_alter_atmosphericdischargeequipment_area_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='equipment', + name='place_owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='equipment', to='users.placeowner'), + ), + migrations.AlterField( + model_name='firealarmequipment', + name='equipment', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fire_alarm', to='equipments.equipment'), + ), + migrations.AlterField( + model_name='refrigerationequipment', + name='system', + field=models.ForeignKey(default=9, on_delete=django.db.models.deletion.CASCADE, to='systems.system'), + ), + migrations.AlterModelTable( + name='refrigerationequipment', + table='equipments_refrigeration_equipments', + ), + ] diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index 2ec3def1..2cb1edc6 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -1,23 +1,15 @@ from .models import Equipment, PersonalEquipmentCategory from rest_framework import serializers from django.core.exceptions import ObjectDoesNotExist +from .permissions import * class ValidateAreaMixin: - def validate_equipment(self, value): - """ - Garante que o equipment pertence ao place owner. - """ - user = self.context['request'].user - if value.place_owner != user.place_owner: - raise serializers.ValidationError("You are not the owner of the equipment") - return value - def validate_area(self, value): """ - Garante que a area pertence ao place owner. + Garante que a area pertence ao place owner ou ao editor. """ user = self.context['request'].user - if value.place.place_owner != user.place_owner: - raise serializers.ValidationError("You are not the owner of this place") + if value.place.place_owner != user.place_owner or not value.area.place.editors.filter(user=user).exists(): + raise serializers.ValidationError("You are not the owner or editor of this place") return value \ No newline at end of file diff --git a/api/equipments/models.py b/api/equipments/models.py index 5b2befde..043f03b4 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -1,10 +1,9 @@ from django.db import models -from places.models import Area +from places.models import Place, Area from systems.models import System from users.models import PlaceOwner class PersonalEquipmentCategory(models.Model): - name = models.CharField(max_length=50) system = models.ForeignKey(System, on_delete=models.CASCADE) place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) @@ -16,7 +15,6 @@ class Meta: db_table = 'equipments_personal_equipment_categories' class GenericEquipmentCategory(models.Model): - name = models.CharField(max_length=50) system = models.ForeignKey(System, on_delete=models.CASCADE) @@ -27,10 +25,9 @@ class Meta: db_table = 'equipments_generic_equipment_categories' class Equipment(models.Model): - - place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) generic_equipment_category = models.ForeignKey(GenericEquipmentCategory, on_delete=models.CASCADE, null=True) personal_equipment_category = models.ForeignKey(PersonalEquipmentCategory, on_delete=models.CASCADE, null=True) + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True, related_name='equipment') def __str__(self): if(self.generic_equipment_category != None): @@ -42,7 +39,6 @@ class Meta: db_table = 'equipments_equipment_details' class EquipmentPhoto(models.Model): - photo = models.ImageField(null=True, upload_to='equipment_photos/') description = models.CharField(max_length=50, null=True) equipment = models.ForeignKey(Equipment, on_delete=models.CASCADE, null=True) @@ -50,75 +46,74 @@ class EquipmentPhoto(models.Model): def __str__(self): return self.description -class FireAlarmEquipment(models.Model): - - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="fire_alarm_equipment") - equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) - system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) - - class Meta: - db_table = 'equipments_fire_alarm_equipments' - -class AtmosphericDischargeEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name= "atmospheric_discharge_equipment") +class IluminationEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ilumination_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) - system = models.ForeignKey(System, on_delete=models.CASCADE, default=7) + system = models.ForeignKey(System, on_delete=models.CASCADE, default=1) class Meta: - db_table = 'equipments_atmospheric_discharge_equipments' + db_table = 'equipments_ilumination_equipments' -class StructuredCablingEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="structured_cabling_equipment") +class ElectricalLoadEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_load_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) - system = models.ForeignKey(System, on_delete=models.CASCADE, default=6) - + system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) + class Meta: - db_table = 'equipments_structured_cabling_equipments' + db_table = 'equipments_electrical_load_equipments' -class DistributionBoardEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="distribution_board_equipment") +class ElectricalLineEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_line_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) - system = models.ForeignKey(System, on_delete=models.CASCADE, default=5) - + system = models.ForeignKey(System, on_delete=models.CASCADE, default=3) + class Meta: - db_table = 'equipments_distribution_board_equipments' + db_table = 'equipments_electrical_line_equipments' class ElectricalCircuitEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name='electrical_circuit_equipment') equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=4) - + class Meta: db_table = 'equipments_electrical_circuit_equipments' -class ElectricalLineEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_line_equipment") +class DistributionBoardEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="distribution_board_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) - system = models.ForeignKey(System, on_delete=models.CASCADE, default=3) - + system = models.ForeignKey(System, on_delete=models.CASCADE, default=5) + class Meta: - db_table = 'equipments_electrical_line_equipments' + db_table = 'equipments_distribution_board_equipments' -class ElectricalLoadEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_load_equipment") +class StructuredCablingEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="structured_cabling_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) - system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) - + system = models.ForeignKey(System, on_delete=models.CASCADE, default=6) + class Meta: - db_table = 'equipments_electrical_load_equipments' + db_table = 'equipments_structured_cabling_equipments' -class IluminationEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ilumination_equipment") +class AtmosphericDischargeEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name= "atmospheric_discharge_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) - system = models.ForeignKey(System, on_delete=models.CASCADE, default=1) - + system = models.ForeignKey(System, on_delete=models.CASCADE, default=7) + class Meta: - db_table = 'equipments_ilumination_equipments' + db_table = 'equipments_atmospheric_discharge_equipments' + +class FireAlarmEquipment(models.Model): + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="fire_alarm_equipment") + equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True, related_name="fire_alarm") + system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) + + class Meta: + db_table = 'equipments_fire_alarm_equipments' class RefrigerationEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="refrigeration_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) - system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) - + system = models.ForeignKey(System, on_delete=models.CASCADE, default=9) + class Meta: - db_table = 'refrigeration_equipments' \ No newline at end of file + db_table = 'equipments_refrigeration_equipments' \ No newline at end of file diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index a870a269..9d193b05 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -1,28 +1,18 @@ from rest_framework.permissions import BasePermission -from places.models import Area - -class IsOwner(BasePermission): +class IsPlaceOwner(BasePermission): def has_object_permission(self, request, view, obj): if obj.place_owner == request.user.place_owner: return True - else: - return False + return False class IsEquipmentOwner(BasePermission): def has_object_permission(self, request, view, obj): - if obj.equipment.place_owner == request.user.place_owner: + if obj.equipment.place.place_owner == request.user.place_owner: return True else: return False class IsSpecificEquipmentEditor(BasePermission): def has_object_permission(self, request, view, obj): - return obj.area.place.editors.filter(user=request.user).exists() - -class IsAreaOwner(BasePermission): - def has_object_permission(self, request, view, obj): - if obj.area.place.place_owner == request.user.place_owner: - return True - return False - + return obj.area.place.editors.filter(user=request.user).exists() \ No newline at end of file diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 486affb4..fca3877b 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -15,7 +15,7 @@ class PersonalEquipmentCategoryResponseSerializer(serializers.ModelSerializer): class Meta: model = PersonalEquipmentCategory - fields = ['name'] + fields = ['id', 'name'] class GenericEquipmentCategorySerializer(serializers.ModelSerializer): @@ -27,7 +27,7 @@ class GenericEquipmentCategoryResponseSerializer(serializers.ModelSerializer): class Meta: model = GenericEquipmentCategory - fields = ['name'] + fields = ['id', 'name'] class EquipmentResponseSerializer(serializers.ModelSerializer): generic_equipment_category = serializers.CharField(source='generic_equipment_category.name', read_only=True) @@ -175,8 +175,6 @@ class Meta: class EquipmentSerializer(serializers.ModelSerializer): - #photos = EquipmentPhotoSerializer(many=True, required=False) - fire_alarm_equipment = FireAlarmEquipmentSerializer(required=False) atmospheric_discharge_equipment = AtmosphericDischargeEquipmentSerializer(required=False) structured_cabling_equipment = StructuredCablingEquipmentSerializer(required=False) @@ -196,8 +194,6 @@ def create(self, validated_data): request = self.context.get('request') validated_data['place_owner'] = request.user.place_owner - # photos_data = validated_data.pop('photos', []) - fire_alarm_data = validated_data.pop('fire_alarm_equipment', None) atmospheric_discharge_data = validated_data.pop('atmospheric_discharge_equipment', None) structured_cabling_data = validated_data.pop('structured_cabling_equipment', None) @@ -210,9 +206,6 @@ def create(self, validated_data): equipment = Equipment.objects.create(**validated_data) - # for photo_data in photos_data: - # EquipmentPhoto.objects.create(equipment=equipment, **photo_data) - if fire_alarm_data: FireAlarmEquipment.objects.create(equipment=equipment, **fire_alarm_data) elif atmospheric_discharge_data: diff --git a/api/equipments/views.py b/api/equipments/views.py index b9d0031d..94ee324c 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -1,7 +1,6 @@ from rest_framework import generics from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated -from places.models import Area from .models import * from .serializers import * from .permissions import * @@ -10,11 +9,11 @@ class PersonalEquipmentCategoryCreate(generics.CreateAPIView): queryset = PersonalEquipmentCategory.objects.all() serializer_class = PersonalEquipmentCategorySerializer - permission_classes = [IsOwner, IsAuthenticated] + permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): - if(IsOwner): + if(IsPlaceOwner): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(place_owner=request.user.place_owner) @@ -52,7 +51,7 @@ class GenericEquipmentCategoryDetail(generics.RetrieveAPIView): class EquipmentList(generics.ListAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsOwner, IsAuthenticated] + permission_classes = [IsAuthenticated] def get_queryset(self): user = self.request.user @@ -62,7 +61,7 @@ def get_queryset(self): class EquipmentCreate(generics.CreateAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsAuthenticated, IsOwner] + permission_classes = [IsAuthenticated] def get_serializer_context(self): context = super().get_serializer_context() @@ -74,12 +73,12 @@ def get_serializer_context(self): class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsAuthenticated, IsOwner] + permission_classes = [IsAuthenticated] class EquipmentPhotoList(generics.ListCreateAPIView): queryset = EquipmentPhoto.objects.all() serializer_class = EquipmentPhotoSerializer - permission_classes = [IsEquipmentOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsEquipmentOwner] def get_queryset(self): user = self.request.user @@ -89,12 +88,12 @@ def get_queryset(self): class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentPhoto.objects.all() serializer_class = EquipmentPhotoSerializer - permission_classes = [IsEquipmentOwner, IsAuthenticated] + permission_classes = [IsAuthenticated, IsEquipmentOwner] class RefrigerationEquipmentList(generics.ListCreateAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner, IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -111,7 +110,7 @@ def create(self, request, *args, **kwargs): class RefrigerationEquipmentByAreaList(generics.ListAPIView): serializer_class = RefrigerationEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -120,12 +119,12 @@ def get_queryset(self): class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] class FireAlarmEquipmentList(generics.ListCreateAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -142,7 +141,7 @@ def create(self, request, *args, **kwargs): class FireAlarmEquipmentByAreaList(generics.ListAPIView): serializer_class = FireAlarmEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -151,12 +150,12 @@ def get_queryset(self): class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner | IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -173,7 +172,7 @@ def create(self, request, *args, **kwargs): class AtmosphericDischargeEquipmentByAreaList(generics.ListAPIView): serializer_class = AtmosphericDischargeEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner, IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -182,12 +181,12 @@ def get_queryset(self): class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] class StructuredCablingEquipmentList(generics.ListCreateAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -204,7 +203,7 @@ def create(self, request, *args, **kwargs): class StructuredCablingEquipmentByAreaList(generics.ListAPIView): serializer_class = StructuredCablingEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -213,12 +212,12 @@ def get_queryset(self): class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] class DistributionBoardEquipmentList(generics.ListCreateAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -235,7 +234,7 @@ def create(self, request, *args, **kwargs): class DistributionBoardEquipmentByAreaList(generics.ListAPIView): serializer_class = DistributionBoardEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -244,12 +243,12 @@ def get_queryset(self): class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] class ElectricalCircuitEquipmentList(generics.ListCreateAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -266,7 +265,7 @@ def create(self, request, *args, **kwargs): class ElectricalCircuitEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalCircuitEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -275,12 +274,12 @@ def get_queryset(self): class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] class ElectricalLineEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -297,7 +296,7 @@ def create(self, request, *args, **kwargs): class ElectricalLineEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLineEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -306,12 +305,12 @@ def get_queryset(self): class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] class ElectricalLoadEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -328,7 +327,7 @@ def create(self, request, *args, **kwargs): class ElectricalLoadEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLoadEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -337,12 +336,12 @@ def get_queryset(self): class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] class IluminationEquipmentList(generics.ListCreateAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): user = self.request.user @@ -359,7 +358,7 @@ def create(self, request, *args, **kwargs): class IluminationEquipmentByAreaList(generics.ListAPIView): serializer_class = IluminationEquipmentResponseSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] @@ -368,4 +367,4 @@ def get_queryset(self): class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsAuthenticated, IsAreaOwner, IsSpecificEquipmentEditor] \ No newline at end of file + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] \ No newline at end of file diff --git a/api/places/views.py b/api/places/views.py index 8acb1423..fa3fc700 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -188,10 +188,10 @@ def get_alt(self, p, margin=30): return self.alt def genericOrPersonal(system): - if system.equipment.generic_equipment_category is not None: - return system.equipment.generic_equipment_category - else: - return system.equipment.personal_equipment_category + if system.equipment.generic_equipment_category is not None: + return system.equipment.generic_equipment_category + else: + return system.equipment.personal_equipment_category class GeneratePDFView(APIView): permission_classes = [IsAuthenticated, IsPlaceOwner | IsPlaceEditor] From 9a5fa902c63662579c5604c48ecf1fa8305cea78 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 21 Jun 2024 16:42:07 -0300 Subject: [PATCH 258/351] =?UTF-8?q?backend:=20verifica=20se=20editor=20tam?= =?UTF-8?q?b=C3=A9m=20=C3=A9=20place=20owner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kauan --- .../0030_alter_equipment_place_owner.py | 20 ++++++++++ api/equipments/mixins.py | 2 +- api/equipments/models.py | 4 +- api/equipments/serializers.py | 18 +++++++++ api/equipments/views.py | 20 ++++++---- api/places/views.py | 39 ++++++++++--------- 6 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 api/equipments/migrations/0030_alter_equipment_place_owner.py diff --git a/api/equipments/migrations/0030_alter_equipment_place_owner.py b/api/equipments/migrations/0030_alter_equipment_place_owner.py new file mode 100644 index 00000000..6eedb78d --- /dev/null +++ b/api/equipments/migrations/0030_alter_equipment_place_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-06-21 18:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_alter_placeowner_user'), + ('equipments', '0029_alter_equipment_place_owner_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='equipment', + name='place_owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.placeowner'), + ), + ] diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index 2cb1edc6..f794a41a 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -10,6 +10,6 @@ def validate_area(self, value): Garante que a area pertence ao place owner ou ao editor. """ user = self.context['request'].user - if value.place.place_owner != user.place_owner or not value.area.place.editors.filter(user=user).exists(): + if value.place.place_owner != user.place_owner and not value.place.editors.filter(user=user).exists(): raise serializers.ValidationError("You are not the owner or editor of this place") return value \ No newline at end of file diff --git a/api/equipments/models.py b/api/equipments/models.py index 043f03b4..d9b160b7 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -6,7 +6,7 @@ class PersonalEquipmentCategory(models.Model): name = models.CharField(max_length=50) system = models.ForeignKey(System, on_delete=models.CASCADE) - place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) # remover def __str__(self): return self.name @@ -27,7 +27,7 @@ class Meta: class Equipment(models.Model): generic_equipment_category = models.ForeignKey(GenericEquipmentCategory, on_delete=models.CASCADE, null=True) personal_equipment_category = models.ForeignKey(PersonalEquipmentCategory, on_delete=models.CASCADE, null=True) - place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True, related_name='equipment') + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) # remover def __str__(self): if(self.generic_equipment_category != None): diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index fca3877b..edfbc0f4 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -207,22 +207,40 @@ def create(self, validated_data): equipment = Equipment.objects.create(**validated_data) if fire_alarm_data: + if 'equipment' in fire_alarm_data: + fire_alarm_data.pop('equipment') FireAlarmEquipment.objects.create(equipment=equipment, **fire_alarm_data) elif atmospheric_discharge_data: + if 'equipment' in atmospheric_discharge_data: + atmospheric_discharge_data.pop('equipment') AtmosphericDischargeEquipment.objects.create(equipment=equipment, **atmospheric_discharge_data) elif structured_cabling_data: + if 'equipment' in structured_cabling_data: + structured_cabling_data.pop('equipment') StructuredCablingEquipment.objects.create(equipment=equipment, **structured_cabling_data) elif distribution_board_data: + if 'equipment' in distribution_board_data: + distribution_board_data.pop('equipment') DistributionBoardEquipment.objects.create(equipment=equipment, **distribution_board_data) elif electrical_circuit_data: + if 'equipment' in electrical_circuit_data: + electrical_circuit_data.pop('equipment') ElectricalCircuitEquipment.objects.create(equipment=equipment, **electrical_circuit_data) elif electrical_line_data: + if 'equipment' in electrical_line_data: + electrical_line_data.pop('equipment') ElectricalLineEquipment.objects.create(equipment=equipment, **electrical_line_data) elif electrical_load_data: + if 'equipment' in electrical_load_data: + electrical_load_data.pop('equipment') ElectricalLoadEquipment.objects.create(equipment=equipment, **electrical_load_data) elif ilumination_equipment_data: + if 'equipment' in ilumination_equipment_data: + ilumination_equipment_data.pop('equipment') IluminationEquipment.objects.create(equipment=equipment, **ilumination_equipment_data) elif refrigeration_equipment_data: + if 'equipment' in refrigeration_equipment_data: + refrigeration_equipment_data.pop('equipment') RefrigerationEquipment.objects.create(equipment=equipment, **refrigeration_equipment_data) return equipment \ No newline at end of file diff --git a/api/equipments/views.py b/api/equipments/views.py index 94ee324c..aa553373 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -6,19 +6,25 @@ from .permissions import * from rest_framework import status +def get_place_owner(self, user): + try: + return user.place_owner + except PlaceOwner.DoesNotExist: + return PlaceOwner.objects.create(user=user) + class PersonalEquipmentCategoryCreate(generics.CreateAPIView): queryset = PersonalEquipmentCategory.objects.all() serializer_class = PersonalEquipmentCategorySerializer permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): - - if(IsPlaceOwner): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save(place_owner=request.user.place_owner) - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + user = request.user + place_owner = self.get_place_owner(user) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(place_owner=place_owner) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) class PersonalEquipmentCategoryList(generics.ListAPIView): queryset = PersonalEquipmentCategory.objects.all() diff --git a/api/places/views.py b/api/places/views.py index fa3fc700..7011058d 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -28,11 +28,7 @@ def get_place_owner(self, user): def create(self, request, *args, **kwargs): user = request.user - - try: - place_owner = user.place_owner - except PlaceOwner.DoesNotExist: - place_owner = PlaceOwner.objects.create(user=user) + place_owner = self.get_place_owner(user) place_data = request.data.copy() place_data['place_owner'] = place_owner.id @@ -57,7 +53,9 @@ def list(self, request, *args, **kwargs): def retrieve(self, request, pk=None): place = get_object_or_404(Place, pk=pk) - if place.place_owner.user == request.user or place.editors.filter(user=request.user).exists(): + user = request.user + place_owner = self.get_place_owner(user) + if place.place_owner.id == place_owner.id or place.editors.filter(user=user).exists(): serializer = PlaceSerializer(place) return Response(serializer.data) else: @@ -65,7 +63,9 @@ def retrieve(self, request, pk=None): def update(self, request, pk=None): place = get_object_or_404(Place, pk=pk) - if place.place_owner.user == request.user or place.editors.filter(user=request.user).exists(): + user = request.user + place_owner = self.get_place_owner(user) + if place.place_owner.id == place_owner.id or place.editors.filter(user=user).exists(): serializer = PlaceSerializer(place, data=request.data) serializer.is_valid(raise_exception=True) serializer.save() @@ -74,10 +74,11 @@ def update(self, request, pk=None): return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) def destroy(self, request, pk=None): - place_owner_id = request.user.place_owner.id + user = request.user + place_owner = self.get_place_owner(user) place = get_object_or_404(Place, pk=pk) - if place.place_owner.id == place_owner_id: + if place.place_owner.id == place_owner.id: place.delete() return Response({"message": "Place deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: @@ -103,11 +104,11 @@ class AreaViewSet(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): user = request.user + place_owner = self.get_place_owner(user) place_id = request.data.get('place') - place = get_object_or_404(Place, id=place_id) - if place.place_owner.user == user or place.editors.filter(user=user).exists(): + if place.place_owner.id == place_owner.id or place.editors.filter(user=user).exists(): area_serializer = AreaSerializer(data=request.data) area_serializer.is_valid(raise_exception=True) area_serializer.save() @@ -117,7 +118,7 @@ def create(self, request, *args, **kwargs): def list(self,request,*args, **kwargs): user = request.user - place_owner = user.place_owner + place_owner = self.get_place_owner(user) place_id = request.query_params.get('place') if not place_id: @@ -131,21 +132,23 @@ def list(self,request,*args, **kwargs): return Response(area_serializer.data) def retrieve(self, request, pk=None): - place_owner = request.user.place_owner.id + user = request.user + place_owner = self.get_place_owner(user) area = get_object_or_404(Area,pk=pk) - if(area.place.place_owner.id == place_owner or area.place.editors.filter(user=request.user).exists()): + if(area.place.place_owner.id == place_owner.id or area.place.editors.filter(user=user).exists()): serializer = AreaSerializer(area) return Response(serializer.data) else: - return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner or an editor of this area"}, status=status.HTTP_403_FORBIDDEN) def destroy(self, request, pk=None): - place_owner_id = request.user.place_owner.id + user = request.user + place_owner = self.get_place_owner(user) area = get_object_or_404(Area, pk=pk) - if area.place.place_owner.id == place_owner_id: + if area.place.place_owner.id == place_owner.id: area.delete() return Response({"message": "Area deleted successfully"}, status=status.HTTP_204_NO_CONTENT) else: @@ -274,4 +277,4 @@ def get(self, request, pk=None): p.showPage() p.save() - return response + return response \ No newline at end of file From 476d902b3d09fc4ab1b195f165c4fe48d2927da3 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 24 Jun 2024 08:51:58 -0300 Subject: [PATCH 259/351] =?UTF-8?q?Adiciona=20l=C3=B3gica=20de=20adicicion?= =?UTF-8?q?ar=20equipe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...sphericdischargeequipment_area_and_more.py | 60 +++++++++++++++++++ api/requirements.txt | 2 + frontend/sige_ie/lib/Teams/teams.dart | 46 +++++++++++++- 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py diff --git a/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py b/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py new file mode 100644 index 00000000..120a3f47 --- /dev/null +++ b/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2 on 2024-06-18 20:55 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0031_remove_place_access_code'), + ('equipments', '0027_alter_atmosphericdischargeequipment_area_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='atmosphericdischargeequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='atmospheric_discharge_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='distributionboardequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='distribution_board_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricalcircuitequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='electrical_circuit_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricallineequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='electrical_line_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='electricalloadequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='electrical_load_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='firealarmequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fire_alarm_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='iluminationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ilumination_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='refrigerationequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='refrigeration_equipment', to='places.area'), + ), + migrations.AlterField( + model_name='structuredcablingequipment', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='structured_cabling_equipment', to='places.area'), + ), + ] diff --git a/api/requirements.txt b/api/requirements.txt index 23875533..44f1d13f 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,5 +1,6 @@ asgiref==3.7.2 async-timeout==4.0.3 +chardet==5.2.0 Django==4.2 django-cors-headers==4.3.1 django-redis==5.4.0 @@ -10,4 +11,5 @@ mysqlclient==2.2.4 pillow==10.3.0 pytz==2024.1 redis==5.0.3 +reportlab==4.2.0 sqlparse==0.4.4 diff --git a/frontend/sige_ie/lib/Teams/teams.dart b/frontend/sige_ie/lib/Teams/teams.dart index e0d37a1d..2419fd39 100644 --- a/frontend/sige_ie/lib/Teams/teams.dart +++ b/frontend/sige_ie/lib/Teams/teams.dart @@ -46,15 +46,22 @@ class TeamsPage extends StatelessWidget { children: [ ListView.builder( shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), itemCount: teams.length, itemBuilder: (context, index) { final team = teams[index]; return Card( child: ListTile( - leading: - const Icon(Icons.group, color: AppColors.sigeIeBlue), + leading: const Icon(Icons.group, + color: AppColors.sigeIeBlue), title: Text(team.name), subtitle: Text('Membros: ${team.members.length}'), + trailing: IconButton( + icon: const Icon(Icons.delete, color: Colors.red), + onPressed: () { + // Lógica para sair da equipe + }, + ), onTap: () { // Navegação para a página de detalhes da equipe }, @@ -68,6 +75,41 @@ class TeamsPage extends StatelessWidget { ], ), ), + floatingActionButton: FloatingActionButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + final TextEditingController _controller = TextEditingController(); + return AlertDialog( + title: const Text('Entrar em uma equipe'), + content: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: 'Digite o código da equipe', + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancelar'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Entrar'), + ), + ], + ); + }, + ); + }, + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), + ), ); } } From 1c4c655a40a3e13d2ac706e91fdc3477c81e43c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Mon, 24 Jun 2024 09:31:28 -0300 Subject: [PATCH 260/351] backend: corrige criacao area --- api/places/views.py | 2 +- frontend/sige_ie/pubspec.lock | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/places/views.py b/api/places/views.py index 7011058d..5d1e86f2 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -104,7 +104,7 @@ class AreaViewSet(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): user = request.user - place_owner = self.get_place_owner(user) + place_owner = user.place_owner place_id = request.data.get('place') place = get_object_or_404(Place, id=place_id) diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index e985cef1..8f9c048e 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -396,26 +396,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -460,10 +460,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mime: dependency: "direct main" description: @@ -649,10 +649,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" typed_data: dependency: transitive description: @@ -729,10 +729,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" web: dependency: transitive description: From be357a0b779a371ef35357318520ccee6bb29ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Mon, 24 Jun 2024 09:38:43 -0300 Subject: [PATCH 261/351] backend: corrige outros erros area --- api/places/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/places/views.py b/api/places/views.py index 5d1e86f2..b1a00c26 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -118,7 +118,7 @@ def create(self, request, *args, **kwargs): def list(self,request,*args, **kwargs): user = request.user - place_owner = self.get_place_owner(user) + place_owner = user.place_owner place_id = request.query_params.get('place') if not place_id: @@ -133,7 +133,7 @@ def list(self,request,*args, **kwargs): def retrieve(self, request, pk=None): user = request.user - place_owner = self.get_place_owner(user) + place_owner = user.place_owner area = get_object_or_404(Area,pk=pk) @@ -145,7 +145,7 @@ def retrieve(self, request, pk=None): def destroy(self, request, pk=None): user = request.user - place_owner = self.get_place_owner(user) + place_owner = user.place_owner area = get_object_or_404(Area, pk=pk) if area.place.place_owner.id == place_owner.id: From e5220623ad1b4563186b3858babb9974ca68b54d Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 24 Jun 2024 10:00:57 -0300 Subject: [PATCH 262/351] Corrige bug em places Co-authored-by: Kauan --- .../0028_alter_atmosphericdischargeequipment_area_and_more.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py b/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py index a28953a1..324c01eb 100644 --- a/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py +++ b/api/equipments/migrations/0028_alter_atmosphericdischargeequipment_area_and_more.py @@ -1,8 +1,5 @@ -<<<<<<< HEAD # Generated by Django 4.2 on 2024-06-18 20:55 -======= # Generated by Django 4.2 on 2024-06-19 17:19 ->>>>>>> e260bab191b745a2f6aeae909f64ebf246e6ef1f from django.db import migrations, models import django.db.models.deletion From a9314b7109a49dd65bf5c7150a93305b3f62f275 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 24 Jun 2024 10:27:36 -0300 Subject: [PATCH 263/351] =?UTF-8?q?Integra=C3=A7=C3=A3o=20da=20lista=20de?= =?UTF-8?q?=20fire=20alarm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: OscarDebrito --- frontend/sige_ie/lib/Teams/teams.dart | 3 ++- .../data/fire_alarm/fire_alarm_service.dart | 12 +++++++++++- .../data/iluminations/ilumination_service.dart | 16 +++++++++++++--- .../feature/fire_alarm/list_fire_alarms.dart | 1 + 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/frontend/sige_ie/lib/Teams/teams.dart b/frontend/sige_ie/lib/Teams/teams.dart index 2419fd39..be8f14d3 100644 --- a/frontend/sige_ie/lib/Teams/teams.dart +++ b/frontend/sige_ie/lib/Teams/teams.dart @@ -57,7 +57,8 @@ class TeamsPage extends StatelessWidget { title: Text(team.name), subtitle: Text('Membros: ${team.members.length}'), trailing: IconButton( - icon: const Icon(Icons.delete, color: Colors.red), + icon: + const Icon(Icons.delete, color: AppColors.warn), onPressed: () { // Lógica para sair da equipe }, diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart index 7e17bfda..5130bfb2 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart @@ -37,7 +37,17 @@ class FireAlarmEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { throw Exception('Failed to load fire alarm equipment'); } diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart index 3ec1c7e4..bfa29d58 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart @@ -37,12 +37,22 @@ class IluminationEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { - throw Exception('Failed to load ilumination equipment'); + throw Exception('Failed to load fire alarm equipment'); } } catch (e) { - _logger.info('Error during get ilumination equipment list: $e'); + _logger.info('Error during get fire alarm equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 09770ba2..3f51f89f 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -52,6 +52,7 @@ class _ListFireAlarmsState extends State { isLoading = false; }); } + print('Equipment list fetched: $equipmentList'); } catch (e) { print('Error fetching equipment list: $e'); if (_isMounted) { From a4c0798d230b91a719c3e8f8f8dbc0de89fc97fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Mon, 24 Jun 2024 13:19:36 -0300 Subject: [PATCH 264/351] backend: corrige views equipments --- api/equipments/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/equipments/views.py b/api/equipments/views.py index aa553373..18b3c073 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -19,7 +19,7 @@ class PersonalEquipmentCategoryCreate(generics.CreateAPIView): def create(self, request, *args, **kwargs): user = request.user - place_owner = self.get_place_owner(user) + place_owner = user.place_owner serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(place_owner=place_owner) From f51317246fa26a7304789c9128f2ef430bc99c01 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 15:58:42 -0300 Subject: [PATCH 265/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20atmosf=C3=A9ricos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../data/atmospheric/atmospheric_service.dart | 17 +++++++-- .../atmospheric_discharges_list.dart | 38 ++++++++++++++++++- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart index ebeed490..2c95cc6d 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart @@ -37,12 +37,23 @@ class AtmosphericEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { - throw Exception('Failed to load structured cabling equipment'); + throw Exception('Failed to load atmospheric-discharges equipment'); } } catch (e) { - _logger.info('Error during get structured cabling equipment list: $e'); + _logger + .info('Error during get atmospheric-discharges equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart index 643116e8..74c17f8a 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart @@ -70,6 +70,14 @@ class _ListAtmosphericEquipmentState extends State { ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } + @override Widget build(BuildContext context) { String systemTitle = 'DESCARGAS ATMOSFÉRICAS'; @@ -129,8 +137,34 @@ class _ListAtmosphericEquipmentState extends State { : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From ab45893799549c160c0b876746dceaa6715c9eda Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 15:59:49 -0300 Subject: [PATCH 266/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20ilumina=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../iluminations/ilumination_service.dart | 4 +- .../ilumination_equipment_list.dart | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart index bfa29d58..24770a48 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart @@ -49,10 +49,10 @@ class IluminationEquipmentService { } }).toList(); } else { - throw Exception('Failed to load fire alarm equipment'); + throw Exception('Failed to load ilumination equipment'); } } catch (e) { - _logger.info('Error during get fire alarm equipment list: $e'); + _logger.info('Error during get ilumination equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart index 5c331a74..e6b3604c 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart @@ -70,6 +70,14 @@ class _ListIluminationEquipmentState extends State { ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } + @override Widget build(BuildContext context) { String systemTitle = 'ILUMINAÇÃO'; @@ -126,8 +134,34 @@ class _ListIluminationEquipmentState extends State { : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From e298d5aa75a81a8119a4f3301a075d585252676f Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 16:00:37 -0300 Subject: [PATCH 267/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20circuito?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../eletrical_circuit_service.dart | 16 ++++++-- .../electrical_circuit_list.dart | 38 ++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart index 70f0358d..43c8da4d 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart @@ -37,12 +37,22 @@ class EletricalCircuitEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { - throw Exception('Failed to load structured cabling equipment'); + throw Exception('Failed to load electrical-circuits equipment'); } } catch (e) { - _logger.info('Error during get structured cabling equipment list: $e'); + _logger.info('Error during get electrical-circuits equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart index 7794f706..20619126 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart @@ -70,6 +70,14 @@ class _ListCircuitEquipmentState extends State { ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } + @override Widget build(BuildContext context) { String systemTitle = 'CIRCUITO ELÉTRICO'; @@ -129,8 +137,34 @@ class _ListCircuitEquipmentState extends State { : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From 69ae3842da6590288353615fdcb6b0aaa607f6cc Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 16:01:15 -0300 Subject: [PATCH 268/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20cabeamento=20estruturado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../structured_cabling_service.dart | 16 ++++++-- .../structured_cabling_equipment_list.dart | 38 ++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart index f6838018..af0563f6 100644 --- a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart @@ -37,12 +37,22 @@ class StructuredCablingEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { - throw Exception('Failed to load structured cabling equipment'); + throw Exception('Failed to load structured-cabling equipment'); } } catch (e) { - _logger.info('Error during get structured cabling equipment list: $e'); + _logger.info('Error during get structured-cabling equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart index 063a3fe0..f1f962b9 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart @@ -78,6 +78,14 @@ class _ListStructuredCablingState extends State { ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } + @override Widget build(BuildContext context) { String systemTitle = 'CABEAMENTO ESTRUTURADO'; @@ -137,8 +145,34 @@ class _ListStructuredCablingState extends State { : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From 5d1f6c17b09a2612f8dbafed05a9aaa02dde3ed4 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 16:01:43 -0300 Subject: [PATCH 269/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20linha=20eletrica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../eletrical_line_service.dart | 16 ++++++-- .../electrical_line/electrical_line_list.dart | 38 ++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart index 4472f05f..25f18757 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart @@ -37,12 +37,22 @@ class EletricalLineEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { - throw Exception('Failed to load structured cabling equipment'); + throw Exception('Failed to load electrical-lines equipment'); } } catch (e) { - _logger.info('Error during get structured cabling equipment list: $e'); + _logger.info('Error during get electrical-lines equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart index ebad6705..bfb891b6 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart @@ -72,6 +72,14 @@ class _ListElectricalLineEquipmentState ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } + @override Widget build(BuildContext context) { String systemTitle = 'LINHAS ELÉTRICAS'; @@ -131,8 +139,34 @@ class _ListElectricalLineEquipmentState : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From 27c95c8a8eab6ad6bdd454b16f1b48cf352dd13c Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 16:02:24 -0300 Subject: [PATCH 270/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20quadro=20de=20distribui=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../distribution/distribution_service.dart | 16 ++++++-- .../distribuition_board_equipment_list.dart | 38 ++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart index 3504793d..1f50a1af 100644 --- a/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart +++ b/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart @@ -37,12 +37,22 @@ class DistributionEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { - throw Exception('Failed to load structured cabling equipment'); + throw Exception('Failed to load distribution-boards equipment'); } } catch (e) { - _logger.info('Error during get structured cabling equipment list: $e'); + _logger.info('Error during get distribution-boards equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart index 1dad3b8d..9d23aaf6 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart @@ -69,6 +69,14 @@ class _ListDistributionBoardState extends State { ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } + @override Widget build(BuildContext context) { String systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; @@ -128,8 +136,34 @@ class _ListDistributionBoardState extends State { : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From 6c5a8da65b6fcb02e1685ad91ad8ce62e5ed67a1 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 16:04:02 -0300 Subject: [PATCH 271/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20alarme=20de=20incendio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../feature/fire_alarm/list_fire_alarms.dart | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 3f51f89f..6274f39c 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -78,6 +78,14 @@ class _ListFireAlarmsState extends State { ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } + @override Widget build(BuildContext context) { String systemTitle = 'ALARME DE INCÊNDIO'; @@ -134,8 +142,34 @@ class _ListFireAlarmsState extends State { : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From 12c7132b19dd790008a1ed20633a72d1a4a24168 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 16:04:31 -0300 Subject: [PATCH 272/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20carga=20el=C3=A9trica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../eletrical_load_service.dart | 16 ++++++-- .../electrical_load/add_electrical_load.dart | 18 ++++----- .../electrical_load/eletrical_load_list.dart | 37 ++++++++++++++++++- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart index 6b2dabfc..07bd61de 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart @@ -37,12 +37,22 @@ class EletricalLoadEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { - throw Exception('Failed to load structured cabling equipment'); + throw Exception('Failed to load electrical-loads equipment'); } } catch (e) { - _logger.info('Error during get structured cabling equipment list: $e'); + _logger.info('Error during get electrical-loads equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart index 50c3d34d..3d6bcf31 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart @@ -4,9 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; - -import 'package:sige_ie/equipments/data/iluminations/ilumination__equipment_request_model.dart'; -import 'package:sige_ie/equipments/data/iluminations/ilumination_request_model.dart'; +import 'package:sige_ie/equipments/data/eletrical-load/eletrical_load_equipment_request_model.dart'; +import 'package:sige_ie/equipments/data/eletrical-load/eletrical_load_request_model.dart.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; @@ -432,20 +431,21 @@ class _AddElectricalLoadEquipmentScreenState personalEquipmentCategory = null; } - final IluminationRequestModel iluminationModel = IluminationRequestModel( + final EletricalLoadRequestModel EletricalLoadModel = + EletricalLoadRequestModel( area: widget.areaId, system: widget.categoryNumber, ); - final IluminationEquipmentRequestModel iluminationEquipmentDetail = - IluminationEquipmentRequestModel( + final EletricalLoadEquipmentRequestModel EletricalLoadEquipmentDetail = + EletricalLoadEquipmentRequestModel( genericEquipmentCategory: genericEquipmentCategory, personalEquipmentCategory: personalEquipmentCategory, - iluminationRequestModel: iluminationModel, + eletricalLoadRequestModel: EletricalLoadModel, ); - int? equipmentId = - await equipmentService.createIlumination(iluminationEquipmentDetail); + int? equipmentId = await equipmentService + .createElectricalLoad(EletricalLoadEquipmentDetail); if (equipmentId != null) { await Future.wait(_images.map((imageData) async { diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart index 560868ae..792dc0d7 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart @@ -81,6 +81,13 @@ class _ListElectricalLoadEquipmentState ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } @override Widget build(BuildContext context) { String systemTitle = 'CARGAS ELÉTRICAS'; @@ -140,8 +147,34 @@ class _ListElectricalLoadEquipmentState : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From c6bef4d1df529e9303f9e092c5895cf7aaea98e9 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 16:05:00 -0300 Subject: [PATCH 273/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20equipamentos=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../sige_ie/lib/equipments/data/equipment_service.dart | 7 ++++--- frontend/sige_ie/lib/main.dart | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index f04cb379..2b4c402f 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -231,14 +231,15 @@ class EquipmentService { } Future createRefrigerations( - RefrigerationsEquipmentRequestModel coolingEquipmentRequestModel) async { + RefrigerationsEquipmentRequestModel + RefrigerationsEquipmentRequestModel) async { var url = Uri.parse(baseUrl); try { var response = await client.post( url, headers: {'Content-Type': 'application/json'}, - body: jsonEncode(coolingEquipmentRequestModel.toJson()), + body: jsonEncode(RefrigerationsEquipmentRequestModel.toJson()), ); _logger.info('Response status code: ${response.statusCode}'); @@ -250,7 +251,7 @@ class EquipmentService { return responseData['id']; } else { _logger.info( - 'Failed to register cooling equipment: ${response.statusCode}'); + 'Failed to register refrigeration equipment: ${response.statusCode}'); return null; } } catch (e) { diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index 2d4a7887..cf29fea5 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -280,7 +280,7 @@ class MyApp extends StatelessWidget { throw Exception( 'Invalid route: Expected Map arguments for /listFireAlarms.'); - case '/listCollingEquipment': + case '/listRefrigerationEquipment': if (settings.arguments is Map) { final args = settings.arguments as Map; final String? areaName = args['areaName']?.toString(); From df2407998573a830d34ce5eb498dcb416daa595e Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Mon, 24 Jun 2024 16:06:56 -0300 Subject: [PATCH 274/351] =?UTF-8?q?Atualiza=20a=20p=C3=A1gina=20de=20lista?= =?UTF-8?q?gem=20de=20refrigera=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danilo --- .../refrigerations_service.dart | 16 ++++++-- .../refrigerations/add_refrigeration.dart | 2 +- .../refrigeration_equipment_list.dart | 37 ++++++++++++++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart index 07b8e91c..c6a167fe 100644 --- a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart +++ b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart @@ -37,12 +37,22 @@ class RefrigerationsEquipmentService { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); - return data.map((item) => item['name'] as String).toList(); + _logger.info('API response data: $data'); + return data.map((item) { + if (item['equipment'].containsKey('generic_equipment_category')) { + return item['equipment']['generic_equipment_category'] as String; + } else if (item['equipment'] + .containsKey('personal_equipment_category')) { + return item['equipment']['personal_equipment_category'] as String; + } else { + return 'Unknown Equipment'; + } + }).toList(); } else { - throw Exception('Failed to load structured cabling equipment'); + throw Exception('Failed to load ilumination equipment'); } } catch (e) { - _logger.info('Error during get structured cabling equipment list: $e'); + _logger.info('Error during get ilumination equipment list: $e'); return []; } } diff --git a/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart b/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart index ed268b41..bdef01a5 100644 --- a/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart +++ b/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart @@ -450,7 +450,7 @@ class _AddRefrigerationState extends State { ); Navigator.pushReplacementNamed( context, - '/listCollingEquipment', + '/listRefrigerationEquipment', arguments: { 'areaName': widget.areaName, 'categoryNumber': widget.categoryNumber, diff --git a/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart index a7bb4781..1c71e101 100644 --- a/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart @@ -72,6 +72,13 @@ class _ListRefrigerationEquipmentState ); } + void _editEquipment(BuildContext context, String equipment) { + // Implement the logic to edit the equipment + } + + void _deleteEquipment(BuildContext context, String equipment) { + // Implement the logic to delete the equipment + } @override Widget build(BuildContext context) { String systemTitle = 'Refrigeração'; @@ -131,8 +138,34 @@ class _ListRefrigerationEquipmentState : equipmentList.isNotEmpty ? Column( children: equipmentList.map((equipment) { - return ListTile( - title: Text(equipment), + return Container( + color: AppColors.sigeIeBlue, + margin: + const EdgeInsets.symmetric(vertical: 5), + child: ListTile( + title: Text( + equipment, + style: + const TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => _editEquipment( + context, equipment), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.white), + onPressed: () => _deleteEquipment( + context, equipment), + ), + ], + ), + ), ); }).toList(), ) From def4e5dc3bcfb816734938b29e28aa04cc2b9a75 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 25 Jun 2024 10:36:34 -0300 Subject: [PATCH 275/351] =?UTF-8?q?conserto=20da=20l=C3=B3gica=20dos=20equ?= =?UTF-8?q?ipamentos=20de=20refrigera=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: OscarDebrito --- .../refrigerations_service.dart | 2 +- .../refrigerations/add_refrigeration.dart | 2 +- .../refrigeration_equipment_list.dart | 30 +++++++++++++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart index c6a167fe..8be867fa 100644 --- a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart +++ b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart @@ -32,7 +32,7 @@ class RefrigerationsEquipmentService { } Future> getRefrigerationsListByArea(int areaId) async { - final url = '${baseUrl}refrigerations/by-area/$areaId'; + final url = '${baseUrl}refrigeration/by-area/$areaId'; try { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { diff --git a/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart b/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart index bdef01a5..e516880d 100644 --- a/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart +++ b/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart @@ -501,7 +501,7 @@ class _AddRefrigerationState extends State { }); Navigator.pushReplacementNamed( context, - '/listCollingEquipment', + '/listRefrigerationEquipment', arguments: { 'areaName': widget.areaName, 'categoryNumber': widget.categoryNumber, diff --git a/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart index 1c71e101..fd9eedda 100644 --- a/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart @@ -79,6 +79,7 @@ class _ListRefrigerationEquipmentState void _deleteEquipment(BuildContext context, String equipment) { // Implement the logic to delete the equipment } + @override Widget build(BuildContext context) { String systemTitle = 'Refrigeração'; @@ -139,18 +140,29 @@ class _ListRefrigerationEquipmentState ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), @@ -159,7 +171,7 @@ class _ListRefrigerationEquipmentState ), IconButton( icon: const Icon(Icons.delete, - color: Colors.white), + color: Colors.red), onPressed: () => _deleteEquipment( context, equipment), ), From 288c3674268a54e8b4678354c358a2dcc6d39d51 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 25 Jun 2024 15:08:36 -0300 Subject: [PATCH 276/351] =?UTF-8?q?Atuliza=20p=C3=A1gina=20de=20listagem?= =?UTF-8?q?=20dos=20equipamentos=20-=20atmosf=C3=A9rico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atmospheric_discharges_list.dart | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart index 74c17f8a..9b6efb52 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart @@ -80,7 +80,7 @@ class _ListAtmosphericEquipmentState extends State { @override Widget build(BuildContext context) { - String systemTitle = 'DESCARGAS ATMOSFÉRICAS'; + String systemTitle = 'Descargas Atmosféricas'; return Scaffold( appBar: AppBar( @@ -138,18 +138,29 @@ class _ListAtmosphericEquipmentState extends State { ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), From 1892920c65ed1af03416dcefe0bc8f3d78003a14 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 25 Jun 2024 15:08:57 -0300 Subject: [PATCH 277/351] =?UTF-8?q?Atuliza=20p=C3=A1gina=20de=20listagem?= =?UTF-8?q?=20dos=20equipamentos=20-=20quadro=20de=20distribui=C3=A7=C3=A3?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../distribuition_board_equipment_list.dart | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart index 9d23aaf6..a39d6e80 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart @@ -79,7 +79,7 @@ class _ListDistributionBoardState extends State { @override Widget build(BuildContext context) { - String systemTitle = 'QUADRO DE DISTRIBUIÇÃO'; + String systemTitle = 'Quadro de Distribuição'; return Scaffold( appBar: AppBar( @@ -137,18 +137,29 @@ class _ListDistributionBoardState extends State { ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), From 7f6ba5ca0c4c55895528dadaa4779512b034c977 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 25 Jun 2024 15:09:18 -0300 Subject: [PATCH 278/351] =?UTF-8?q?Atuliza=20p=C3=A1gina=20de=20listagem?= =?UTF-8?q?=20dos=20equipamentos=20-=20circuito=20el=C3=A9trico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../electrical_circuit_list.dart | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart index 20619126..8b07d0f2 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart @@ -80,7 +80,7 @@ class _ListCircuitEquipmentState extends State { @override Widget build(BuildContext context) { - String systemTitle = 'CIRCUITO ELÉTRICO'; + String systemTitle = 'Circuito Elétrico'; return Scaffold( appBar: AppBar( @@ -138,18 +138,29 @@ class _ListCircuitEquipmentState extends State { ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), From 0e2d4ccdc94e84962ea83c7392012543e21e7ab4 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 25 Jun 2024 15:09:36 -0300 Subject: [PATCH 279/351] =?UTF-8?q?Atuliza=20p=C3=A1gina=20de=20listagem?= =?UTF-8?q?=20dos=20equipamentos=20-=20linha=20el=C3=A9trica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../electrical_line/electrical_line_list.dart | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart index bfb891b6..91e602cc 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart @@ -82,7 +82,7 @@ class _ListElectricalLineEquipmentState @override Widget build(BuildContext context) { - String systemTitle = 'LINHAS ELÉTRICAS'; + String systemTitle = 'Linhas Elétricas'; return Scaffold( appBar: AppBar( @@ -140,18 +140,29 @@ class _ListElectricalLineEquipmentState ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), From f453818c9fc8b61dddab16a68c22ad5920bb86f1 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 25 Jun 2024 15:09:50 -0300 Subject: [PATCH 280/351] =?UTF-8?q?Atuliza=20p=C3=A1gina=20de=20listagem?= =?UTF-8?q?=20dos=20equipamentos=20-=20carga=20el=C3=A9trica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../electrical_load/eletrical_load_list.dart | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart index 792dc0d7..b016ab99 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart @@ -90,7 +90,7 @@ class _ListElectricalLoadEquipmentState } @override Widget build(BuildContext context) { - String systemTitle = 'CARGAS ELÉTRICAS'; + String systemTitle = 'Cargas Elétricas'; return Scaffold( appBar: AppBar( @@ -148,18 +148,29 @@ class _ListElectricalLoadEquipmentState ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), From 278e9d799bb221cfaa5d47ae9c3e1d875a766ff5 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 25 Jun 2024 15:10:09 -0300 Subject: [PATCH 281/351] =?UTF-8?q?Atuliza=20p=C3=A1gina=20de=20listagem?= =?UTF-8?q?=20dos=20equipamentos=20-=20alarme=20de=20incendio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire_alarm/list_fire_alarms.dart | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 6274f39c..b770d77d 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -88,7 +88,7 @@ class _ListFireAlarmsState extends State { @override Widget build(BuildContext context) { - String systemTitle = 'ALARME DE INCÊNDIO'; + String systemTitle = 'Alarme de Incêndio'; return Scaffold( appBar: AppBar( @@ -121,12 +121,15 @@ class _ListFireAlarmsState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('${widget.areaName} - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), ), ), const SizedBox(height: 20), @@ -143,18 +146,29 @@ class _ListFireAlarmsState extends State { ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), @@ -177,9 +191,10 @@ class _ListFireAlarmsState extends State { child: Text( 'Você ainda não tem equipamentos', style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), ), ), const SizedBox(height: 40), From a61d4f5897db02750aa5622903de11458731b2e2 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 25 Jun 2024 15:10:24 -0300 Subject: [PATCH 282/351] =?UTF-8?q?Atuliza=20p=C3=A1gina=20de=20listagem?= =?UTF-8?q?=20dos=20equipamentos=20-=20ilumina=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ilumination_equipment_list.dart | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart index e6b3604c..7ac5cf72 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart @@ -80,7 +80,7 @@ class _ListIluminationEquipmentState extends State { @override Widget build(BuildContext context) { - String systemTitle = 'ILUMINAÇÃO'; + String systemTitle = 'Iluminação'; return Scaffold( appBar: AppBar( @@ -113,12 +113,15 @@ class _ListIluminationEquipmentState extends State { BorderRadius.vertical(bottom: Radius.circular(20)), ), child: Center( - child: Text('${widget.areaName} - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText)), + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), + ), ), ), const SizedBox(height: 20), @@ -135,18 +138,29 @@ class _ListIluminationEquipmentState extends State { ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), @@ -169,9 +183,10 @@ class _ListIluminationEquipmentState extends State { child: Text( 'Você ainda não tem equipamentos', style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), ), ), const SizedBox(height: 40), From 28a34bb4951c04521eda979876185378bfb0bbb6 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 25 Jun 2024 15:10:40 -0300 Subject: [PATCH 283/351] =?UTF-8?q?Atuliza=20p=C3=A1gina=20de=20listagem?= =?UTF-8?q?=20dos=20equipamentos=20-=20cabeamento=20estruturado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../structured_cabling_equipment_list.dart | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart index f1f962b9..c8c8b844 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart @@ -88,7 +88,7 @@ class _ListStructuredCablingState extends State { @override Widget build(BuildContext context) { - String systemTitle = 'CABEAMENTO ESTRUTURADO'; + String systemTitle = 'Cabeamento Estruturado'; return Scaffold( appBar: AppBar( @@ -146,18 +146,29 @@ class _ListStructuredCablingState extends State { ? Column( children: equipmentList.map((equipment) { return Container( - color: AppColors.sigeIeBlue, margin: const EdgeInsets.symmetric(vertical: 5), - child: ListTile( - title: Text( - equipment, - style: - const TextStyle(color: Colors.white), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, + child: Row( children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.only(left: 10), + child: Text( + equipment, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ), + ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), From 1b0f1746cde8c147898406cbfb1a4dd14067d7b6 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 26 Jun 2024 09:09:57 -0300 Subject: [PATCH 284/351] =?UTF-8?q?backend:=20corrige=20verifica=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20place=5Fowner=20em=20equipments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/views.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/api/equipments/views.py b/api/equipments/views.py index 18b3c073..e14b1bae 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -6,7 +6,7 @@ from .permissions import * from rest_framework import status -def get_place_owner(self, user): +def get_place_owner_or_create(user): try: return user.place_owner except PlaceOwner.DoesNotExist: @@ -19,7 +19,7 @@ class PersonalEquipmentCategoryCreate(generics.CreateAPIView): def create(self, request, *args, **kwargs): user = request.user - place_owner = user.place_owner + place_owner = get_place_owner_or_create(user) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(place_owner=place_owner) @@ -103,7 +103,8 @@ class RefrigerationEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return RefrigerationEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return RefrigerationEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -134,7 +135,8 @@ class FireAlarmEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return FireAlarmEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return FireAlarmEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -165,7 +167,8 @@ class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -196,7 +199,8 @@ class StructuredCablingEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return StructuredCablingEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return StructuredCablingEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -227,7 +231,8 @@ class DistributionBoardEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return DistributionBoardEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return DistributionBoardEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -258,7 +263,8 @@ class ElectricalCircuitEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return ElectricalCircuitEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return ElectricalCircuitEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -289,7 +295,8 @@ class ElectricalLineEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return ElectricalLineEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return ElectricalLineEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -320,7 +327,8 @@ class ElectricalLoadEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return ElectricalLoadEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return ElectricalLoadEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() @@ -351,7 +359,8 @@ class IluminationEquipmentList(generics.ListCreateAPIView): def get_queryset(self): user = self.request.user - return IluminationEquipment.objects.filter(area__place__place_owner=user.place_owner) + place_owner = get_place_owner_or_create(user) + return IluminationEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): data = request.data.copy() From ee3cade7ad8a021bc34a1a34f1b1f63f5227e3ca Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 26 Jun 2024 09:10:30 -0300 Subject: [PATCH 285/351] =?UTF-8?q?backend:=20corrige=20verifica=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20place=5Fowner=20em=20places?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/places/views.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/api/places/views.py b/api/places/views.py index b1a00c26..9bccb067 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -15,20 +15,20 @@ from .models import Place, Area from .serializers import PlaceSerializer, AreaSerializer +def get_place_owner_or_create(user): + try: + return user.place_owner + except PlaceOwner.DoesNotExist: + return PlaceOwner.objects.create(user=user) + class PlaceViewSet(viewsets.ModelViewSet): queryset = Place.objects.all() serializer_class = PlaceSerializer permission_classes = [IsAuthenticated, IsPlaceOwner | IsPlaceEditor] - def get_place_owner(self, user): - try: - return user.place_owner - except PlaceOwner.DoesNotExist: - return PlaceOwner.objects.create(user=user) - def create(self, request, *args, **kwargs): user = request.user - place_owner = self.get_place_owner(user) + place_owner = get_place_owner_or_create(user) place_data = request.data.copy() place_data['place_owner'] = place_owner.id @@ -41,7 +41,7 @@ def create(self, request, *args, **kwargs): def list(self, request, *args, **kwargs): user = request.user - place_owner = self.get_place_owner(user) + place_owner = get_place_owner_or_create(user) places = Place.objects.filter( Q(place_owner=place_owner) | @@ -54,7 +54,7 @@ def list(self, request, *args, **kwargs): def retrieve(self, request, pk=None): place = get_object_or_404(Place, pk=pk) user = request.user - place_owner = self.get_place_owner(user) + place_owner = get_place_owner_or_create(user) if place.place_owner.id == place_owner.id or place.editors.filter(user=user).exists(): serializer = PlaceSerializer(place) return Response(serializer.data) @@ -64,7 +64,7 @@ def retrieve(self, request, pk=None): def update(self, request, pk=None): place = get_object_or_404(Place, pk=pk) user = request.user - place_owner = self.get_place_owner(user) + place_owner = get_place_owner_or_create(user) if place.place_owner.id == place_owner.id or place.editors.filter(user=user).exists(): serializer = PlaceSerializer(place, data=request.data) serializer.is_valid(raise_exception=True) @@ -75,7 +75,7 @@ def update(self, request, pk=None): def destroy(self, request, pk=None): user = request.user - place_owner = self.get_place_owner(user) + place_owner = get_place_owner_or_create(user) place = get_object_or_404(Place, pk=pk) if place.place_owner.id == place_owner.id: @@ -104,7 +104,7 @@ class AreaViewSet(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): user = request.user - place_owner = user.place_owner + place_owner = get_place_owner_or_create(user) place_id = request.data.get('place') place = get_object_or_404(Place, id=place_id) @@ -116,24 +116,26 @@ def create(self, request, *args, **kwargs): else: return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) - def list(self,request,*args, **kwargs): + def list(self, request, *args, **kwargs): user = request.user - place_owner = user.place_owner + place_owner = get_place_owner_or_create(user) place_id = request.query_params.get('place') if not place_id: raise NotFound("Place ID must be provided.") - place = get_object_or_404(Place, id=place_id, place_owner=place_owner) - - areas = Area.objects.filter(place=place) + place = get_object_or_404(Place, id=place_id) - area_serializer = AreaSerializer(areas, many=True) - return Response(area_serializer.data) + if place.place_owner.id == place_owner.id or place.editors.filter(user=user).exists(): + areas = Area.objects.filter(place=place) + area_serializer = AreaSerializer(areas, many=True) + return Response(area_serializer.data) + else: + return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) def retrieve(self, request, pk=None): user = request.user - place_owner = user.place_owner + place_owner = get_place_owner_or_create(user) area = get_object_or_404(Area,pk=pk) @@ -145,7 +147,7 @@ def retrieve(self, request, pk=None): def destroy(self, request, pk=None): user = request.user - place_owner = user.place_owner + place_owner = get_place_owner_or_create(user) area = get_object_or_404(Area, pk=pk) if area.place.place_owner.id == place_owner.id: From e521916dbb14b2a115c1540e32b322fcad14c2cf Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 26 Jun 2024 15:55:40 -0300 Subject: [PATCH 286/351] backend: corrige permission IsEquipmentOwner --- api/equipments/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 9d193b05..9284271a 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -8,7 +8,7 @@ def has_object_permission(self, request, view, obj): class IsEquipmentOwner(BasePermission): def has_object_permission(self, request, view, obj): - if obj.equipment.place.place_owner == request.user.place_owner: + if obj.equipment.place_owner == request.user.place_owner: return True else: return False From 3e2f9ebf54d2c1e0ca9e31e2f9f8a8e096694983 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 11:50:02 -0300 Subject: [PATCH 287/351] backend: reformata arquivos e corrige serializer de fire alarm --- api/.idea/.gitignore | 8 + api/.idea/api.iml | 34 ++++ api/.idea/dataSources.xml | 12 ++ .../inspectionProfiles/profiles_settings.xml | 6 + api/.idea/misc.xml | 7 + api/.idea/modules.xml | 8 + api/.idea/vcs.xml | 6 + api/.vscode/settings.json | 30 +-- ...phericdischargeequipment_table_and_more.py | 65 ++++++ ...phericdischargeequipment_table_and_more.py | 53 +++++ api/equipments/mixins.py | 17 +- api/equipments/models.py | 63 +++--- api/equipments/permissions.py | 15 +- api/equipments/serializers.py | 186 +++++++++--------- api/equipments/urls.py | 1 + api/equipments/views.py | 106 +++++++--- api/places/models.py | 9 +- api/places/permissions.py | 4 +- api/places/serializers.py | 9 +- api/places/views.py | 71 ++++--- api/sigeie/base_views.py | 27 +++ api/systems/models.py | 4 +- api/systems/serializers.py | 3 +- api/systems/views.py | 4 +- api/users/models.py | 8 +- api/users/permissions.py | 6 +- api/users/serializers.py | 17 +- api/users/views.py | 79 ++++---- 28 files changed, 601 insertions(+), 257 deletions(-) create mode 100644 api/.idea/.gitignore create mode 100644 api/.idea/api.iml create mode 100644 api/.idea/dataSources.xml create mode 100644 api/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 api/.idea/misc.xml create mode 100644 api/.idea/modules.xml create mode 100644 api/.idea/vcs.xml create mode 100644 api/equipments/migrations/0031_alter_atmosphericdischargeequipment_table_and_more.py create mode 100644 api/equipments/migrations/0032_alter_atmosphericdischargeequipment_table_and_more.py create mode 100644 api/sigeie/base_views.py diff --git a/api/.idea/.gitignore b/api/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/api/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/api/.idea/api.iml b/api/.idea/api.iml new file mode 100644 index 00000000..b00c88e2 --- /dev/null +++ b/api/.idea/api.iml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api/.idea/dataSources.xml b/api/.idea/dataSources.xml new file mode 100644 index 00000000..bb66d67c --- /dev/null +++ b/api/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/sigeie_db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/api/.idea/inspectionProfiles/profiles_settings.xml b/api/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/api/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/api/.idea/misc.xml b/api/.idea/misc.xml new file mode 100644 index 00000000..6ae386b6 --- /dev/null +++ b/api/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/api/.idea/modules.xml b/api/.idea/modules.xml new file mode 100644 index 00000000..d50cf45f --- /dev/null +++ b/api/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/api/.idea/vcs.xml b/api/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/api/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/api/.vscode/settings.json b/api/.vscode/settings.json index 79eab2c1..b92839d5 100644 --- a/api/.vscode/settings.json +++ b/api/.vscode/settings.json @@ -1,22 +1,22 @@ { "workbench.colorCustomizations": { - "activityBar.activeBackground": "#3399ff", - "activityBar.background": "#3399ff", + "activityBar.activeBackground": "#65c89b", + "activityBar.background": "#65c89b", "activityBar.foreground": "#15202b", "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#bf0060", + "activityBarBadge.background": "#945bc4", "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#e7e7e799", - "sash.hoverBorder": "#3399ff", - "statusBar.background": "#007fff", - "statusBar.foreground": "#e7e7e7", - "statusBarItem.hoverBackground": "#3399ff", - "statusBarItem.remoteBackground": "#007fff", - "statusBarItem.remoteForeground": "#e7e7e7", - "titleBar.activeBackground": "#007fff", - "titleBar.activeForeground": "#e7e7e7", - "titleBar.inactiveBackground": "#007fff99", - "titleBar.inactiveForeground": "#e7e7e799" + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#65c89b", + "statusBar.background": "#42b883", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#359268", + "statusBarItem.remoteBackground": "#42b883", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#42b883", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#42b88399", + "titleBar.inactiveForeground": "#15202b99" }, - "peacock.color": "#007fff" + "peacock.color": "#42b883" } \ No newline at end of file diff --git a/api/equipments/migrations/0031_alter_atmosphericdischargeequipment_table_and_more.py b/api/equipments/migrations/0031_alter_atmosphericdischargeequipment_table_and_more.py new file mode 100644 index 00000000..7448428c --- /dev/null +++ b/api/equipments/migrations/0031_alter_atmosphericdischargeequipment_table_and_more.py @@ -0,0 +1,65 @@ +# Generated by Django 4.2 on 2024-06-30 14:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0030_alter_equipment_place_owner'), + ] + + operations = [ + migrations.AlterModelTable( + name='atmosphericdischargeequipment', + table='atmospheric_discharges', + ), + migrations.AlterModelTable( + name='distributionboardequipment', + table='distribution_boards', + ), + migrations.AlterModelTable( + name='electricalcircuitequipment', + table='electrical_circuits', + ), + migrations.AlterModelTable( + name='electricallineequipment', + table='electrical_lines', + ), + migrations.AlterModelTable( + name='electricalloadequipment', + table='electrical_loads', + ), + migrations.AlterModelTable( + name='equipment', + table='equipments', + ), + migrations.AlterModelTable( + name='equipmentphoto', + table='equipment_photos', + ), + migrations.AlterModelTable( + name='firealarmequipment', + table='fire_alarms', + ), + migrations.AlterModelTable( + name='genericequipmentcategory', + table='generic_equipment_categories', + ), + migrations.AlterModelTable( + name='iluminationequipment', + table='illuminations', + ), + migrations.AlterModelTable( + name='personalequipmentcategory', + table='personal_equipment_categories', + ), + migrations.AlterModelTable( + name='refrigerationequipment', + table='refrigeration_equipments', + ), + migrations.AlterModelTable( + name='structuredcablingequipment', + table='structured_cabling', + ), + ] diff --git a/api/equipments/migrations/0032_alter_atmosphericdischargeequipment_table_and_more.py b/api/equipments/migrations/0032_alter_atmosphericdischargeequipment_table_and_more.py new file mode 100644 index 00000000..05913c04 --- /dev/null +++ b/api/equipments/migrations/0032_alter_atmosphericdischargeequipment_table_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 4.2 on 2024-06-30 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0031_alter_atmosphericdischargeequipment_table_and_more'), + ] + + operations = [ + migrations.AlterModelTable( + name='atmosphericdischargeequipment', + table='equipments_atmospheric_discharges', + ), + migrations.AlterModelTable( + name='distributionboardequipment', + table='equipments_distribution_boards', + ), + migrations.AlterModelTable( + name='electricalcircuitequipment', + table='equipments_electrical_circuits', + ), + migrations.AlterModelTable( + name='electricallineequipment', + table='equipments_electrical_lines', + ), + migrations.AlterModelTable( + name='electricalloadequipment', + table='equipments_electrical_loads', + ), + migrations.AlterModelTable( + name='equipmentphoto', + table='equipments_photos', + ), + migrations.AlterModelTable( + name='firealarmequipment', + table='equipments_fire_alarms', + ), + migrations.AlterModelTable( + name='iluminationequipment', + table='equipments_illuminations', + ), + migrations.AlterModelTable( + name='refrigerationequipment', + table='equipments_refrigeration', + ), + migrations.AlterModelTable( + name='structuredcablingequipment', + table='equipments_structured_cabling', + ), + ] diff --git a/api/equipments/mixins.py b/api/equipments/mixins.py index f794a41a..51d406a4 100644 --- a/api/equipments/mixins.py +++ b/api/equipments/mixins.py @@ -1,7 +1,5 @@ -from .models import Equipment, PersonalEquipmentCategory from rest_framework import serializers -from django.core.exceptions import ObjectDoesNotExist -from .permissions import * + class ValidateAreaMixin: @@ -12,4 +10,15 @@ def validate_area(self, value): user = self.context['request'].user if value.place.place_owner != user.place_owner and not value.place.editors.filter(user=user).exists(): raise serializers.ValidationError("You are not the owner or editor of this place") - return value \ No newline at end of file + return value + + +class EquipmentCategoryMixin: + + def get_equipment_category(self, obj): + equipment = obj.equipment + if equipment.generic_equipment_category is not None: + return equipment.generic_equipment_category.name + elif equipment.personal_equipment_category is not None: + return equipment.personal_equipment_category.name + return None diff --git a/api/equipments/models.py b/api/equipments/models.py index d9b160b7..fcb8bd7c 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -3,16 +3,18 @@ from systems.models import System from users.models import PlaceOwner + class PersonalEquipmentCategory(models.Model): name = models.CharField(max_length=50) system = models.ForeignKey(System, on_delete=models.CASCADE) - place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) # remover + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) def __str__(self): return self.name class Meta: - db_table = 'equipments_personal_equipment_categories' + db_table = 'personal_equipment_categories' + class GenericEquipmentCategory(models.Model): name = models.CharField(max_length=50) @@ -22,21 +24,24 @@ def __str__(self): return self.name class Meta: - db_table = 'equipments_generic_equipment_categories' + db_table = 'generic_equipment_categories' + class Equipment(models.Model): generic_equipment_category = models.ForeignKey(GenericEquipmentCategory, on_delete=models.CASCADE, null=True) personal_equipment_category = models.ForeignKey(PersonalEquipmentCategory, on_delete=models.CASCADE, null=True) - place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) # remover + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) def __str__(self): - if(self.generic_equipment_category != None): - return str(self.generic_equipment_category) - else: - return str(self.personal_equipment_category) + if self.generic_equipment_category is not None: + return str(self.generic_equipment_category.name) + elif self.personal_equipment_category is not None: + return str(self.personal_equipment_category.name) + return 'No category' class Meta: - db_table = 'equipments_equipment_details' + db_table = 'equipments' + class EquipmentPhoto(models.Model): photo = models.ImageField(null=True, upload_to='equipment_photos/') @@ -46,13 +51,18 @@ class EquipmentPhoto(models.Model): def __str__(self): return self.description + class Meta: + db_table = 'equipments_photos' + + class IluminationEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ilumination_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=1) class Meta: - db_table = 'equipments_ilumination_equipments' + db_table = 'equipments_illuminations' + class ElectricalLoadEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_load_equipment") @@ -60,7 +70,8 @@ class ElectricalLoadEquipment(models.Model): system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) class Meta: - db_table = 'equipments_electrical_load_equipments' + db_table = 'equipments_electrical_loads' + class ElectricalLineEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_line_equipment") @@ -68,7 +79,8 @@ class ElectricalLineEquipment(models.Model): system = models.ForeignKey(System, on_delete=models.CASCADE, default=3) class Meta: - db_table = 'equipments_electrical_line_equipments' + db_table = 'equipments_electrical_lines' + class ElectricalCircuitEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name='electrical_circuit_equipment') @@ -76,7 +88,8 @@ class ElectricalCircuitEquipment(models.Model): system = models.ForeignKey(System, on_delete=models.CASCADE, default=4) class Meta: - db_table = 'equipments_electrical_circuit_equipments' + db_table = 'equipments_electrical_circuits' + class DistributionBoardEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="distribution_board_equipment") @@ -84,7 +97,8 @@ class DistributionBoardEquipment(models.Model): system = models.ForeignKey(System, on_delete=models.CASCADE, default=5) class Meta: - db_table = 'equipments_distribution_board_equipments' + db_table = 'equipments_distribution_boards' + class StructuredCablingEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="structured_cabling_equipment") @@ -92,23 +106,26 @@ class StructuredCablingEquipment(models.Model): system = models.ForeignKey(System, on_delete=models.CASCADE, default=6) class Meta: - db_table = 'equipments_structured_cabling_equipments' + db_table = 'equipments_structured_cabling' + class AtmosphericDischargeEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name= "atmospheric_discharge_equipment") + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="atmospheric_discharge_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=7) class Meta: - db_table = 'equipments_atmospheric_discharge_equipments' + db_table = 'equipments_atmospheric_discharges' + class FireAlarmEquipment(models.Model): - area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="fire_alarm_equipment") - equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True, related_name="fire_alarm") - system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) + area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="fire_alarm_equipment") + equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True, related_name="fire_alarm") + system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) + + class Meta: + db_table = 'equipments_fire_alarms' - class Meta: - db_table = 'equipments_fire_alarm_equipments' class RefrigerationEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="refrigeration_equipment") @@ -116,4 +133,4 @@ class RefrigerationEquipment(models.Model): system = models.ForeignKey(System, on_delete=models.CASCADE, default=9) class Meta: - db_table = 'equipments_refrigeration_equipments' \ No newline at end of file + db_table = 'equipments_refrigeration' diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 9284271a..426dbbb8 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -1,18 +1,23 @@ from rest_framework.permissions import BasePermission + class IsPlaceOwner(BasePermission): def has_object_permission(self, request, view, obj): if obj.place_owner == request.user.place_owner: return True return False + class IsEquipmentOwner(BasePermission): def has_object_permission(self, request, view, obj): - if obj.equipment.place_owner == request.user.place_owner: - return True - else: - return False + if obj.equipment and obj.equipment.place_owner: + return obj.equipment.place_owner == request.user.place_owner + return False + class IsSpecificEquipmentEditor(BasePermission): def has_object_permission(self, request, view, obj): - return obj.area.place.editors.filter(user=request.user).exists() \ No newline at end of file + if obj.area and obj.area.place: + return obj.area.place.editors.filter(user=request.user).exists() + return False + diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index edfbc0f4..b2fc78fa 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -2,40 +2,21 @@ from django.core.files.base import ContentFile from rest_framework import serializers from .models import * -from .serializers import * -from .mixins import ValidateAreaMixin -class PersonalEquipmentCategorySerializer(serializers.ModelSerializer): +from .mixins import ValidateAreaMixin, EquipmentCategoryMixin + +class PersonalEquipmentCategorySerializer(serializers.ModelSerializer): class Meta: model = PersonalEquipmentCategory fields = '__all__' -class PersonalEquipmentCategoryResponseSerializer(serializers.ModelSerializer): - - class Meta: - model = PersonalEquipmentCategory - fields = ['id', 'name'] class GenericEquipmentCategorySerializer(serializers.ModelSerializer): - class Meta: model = GenericEquipmentCategory fields = '__all__' -class GenericEquipmentCategoryResponseSerializer(serializers.ModelSerializer): - - class Meta: - model = GenericEquipmentCategory - fields = ['id', 'name'] - -class EquipmentResponseSerializer(serializers.ModelSerializer): - generic_equipment_category = serializers.CharField(source='generic_equipment_category.name', read_only=True) - personal_equipment_category = serializers.CharField(source='personal_equipment_category.name', read_only=True) - - class Meta: - model = Equipment - fields = ['generic_equipment_category', 'personal_equipment_category'] class EquipmentPhotoSerializer(serializers.ModelSerializer): photo = serializers.CharField(write_only=True) @@ -56,131 +37,140 @@ def create(self, validated_data): equipment_photo = EquipmentPhoto.objects.create(photo=photo, **validated_data) return equipment_photo -class FireAlarmEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): +class FireAlarmEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: model = FireAlarmEquipment fields = '__all__' -class FireAlarmEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() +class AtmosphericDischargeEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: - model = FireAlarmEquipment - fields = ['id', 'area', 'equipment', 'system'] + model = AtmosphericDischargeEquipment + fields = '__all__' -class AtmosphericDischargeEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): +class StructuredCablingEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: - model = AtmosphericDischargeEquipment + model = StructuredCablingEquipment fields = '__all__' -class AtmosphericDischargeEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() +class DistributionBoardEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: - model = AtmosphericDischargeEquipment - fields = ['id', 'area', 'equipment', 'system'] + model = DistributionBoardEquipment + fields = '__all__' -class StructuredCablingEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): +class ElectricalCircuitEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: - model = StructuredCablingEquipment - fields = '__all__' + model = ElectricalCircuitEquipment + fields = '__all__' -class StructuredCablingEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() +class ElectricalLineEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: - model = StructuredCablingEquipment - fields = ['id', 'area', 'equipment', 'system'] + model = ElectricalLineEquipment + fields = '__all__' -class DistributionBoardEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): - class Meta: - model = DistributionBoardEquipment - fields = '__all__' +class ElectricalLoadEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): + class Meta: + model = ElectricalLoadEquipment + fields = '__all__' -class DistributionBoardEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() + +class IluminationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): + class Meta: + model = IluminationEquipment + fields = '__all__' + + +class RefrigerationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): + class Meta: + model = RefrigerationEquipment + fields = '__all__' + + +class FireAlarmEquipmentResponseSerializer(ValidateAreaMixin, EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() + + class Meta: + model = FireAlarmEquipment + fields = ['id', 'area', 'equipment_category', 'system'] + + +class AtmosphericDischargeEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() + + class Meta: + model = AtmosphericDischargeEquipment + fields = ['id', 'area', 'equipment_category', 'system'] + + +class StructuredCablingEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() class Meta: model = StructuredCablingEquipment - fields = ['id', 'area', 'equipment', 'system'] + fields = ['id', 'area', 'equipment_category', 'system'] -class ElectricalCircuitEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): - class Meta: - model = ElectricalCircuitEquipment - fields = '__all__' +class DistributionBoardEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() + + class Meta: + model = StructuredCablingEquipment + fields = ['id', 'area', 'equipment_category', 'system'] + -class ElectricalCircuitEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() +class ElectricalCircuitEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() class Meta: model = ElectricalCircuitEquipment - fields = ['id', 'area', 'equipment', 'system'] + fields = ['id', 'area', 'equipment_category', 'system'] -class ElectricalLineEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): - - class Meta: - model = ElectricalLineEquipment - fields = '__all__' -class ElectricalLineEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() +class ElectricalLineEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() class Meta: model = ElectricalLineEquipment - fields = ['id', 'area', 'equipment', 'system'] + fields = ['id', 'area', 'equipment_category', 'system'] -class ElectricalLoadEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): - class Meta: - model = ElectricalLoadEquipment - fields = '__all__' - -class ElectricalLoadEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() +class ElectricalLoadEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() class Meta: model = ElectricalLoadEquipment - fields = ['id', 'area', 'equipment', 'system'] + fields = ['id', 'area', 'equipment_category', 'system'] -class IluminationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): - class Meta: - model = IluminationEquipment - fields = '__all__' - -class IluminationEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() +class IluminationEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() class Meta: model = IluminationEquipment - fields = ['id', 'area', 'equipment', 'system'] - -class RefrigerationEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): + fields = ['id', 'area', 'equipment_category', 'system'] - class Meta: - model = RefrigerationEquipment - fields = '__all__' -class RefrigerationEquipmentResponseSerializer(serializers.ModelSerializer): - equipment = EquipmentResponseSerializer() +class RefrigerationEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): + equipment_category = serializers.SerializerMethodField() class Meta: model = RefrigerationEquipment - fields = ['id', 'area', 'equipment', 'system'] + fields = ['id', 'area', 'equipment_category', 'system'] -class EquipmentSerializer(serializers.ModelSerializer): +class EquipmentSerializer(serializers.ModelSerializer): fire_alarm_equipment = FireAlarmEquipmentSerializer(required=False) atmospheric_discharge_equipment = AtmosphericDischargeEquipmentSerializer(required=False) structured_cabling_equipment = StructuredCablingEquipmentSerializer(required=False) distribution_board_equipment = DistributionBoardEquipmentSerializer(required=False) electrical_circuit_equipment = ElectricalCircuitEquipmentSerializer(required=False) - electrical_line_equipment = ElectricalLineEquipmentSerializer(required=False) + electrical_line_equipment = ElectricalLineEquipmentSerializer(required=False) electrical_load_equipment = ElectricalLoadEquipmentSerializer(required=False) ilumination_equipment = IluminationEquipmentSerializer(required=False) refrigeration_equipment = RefrigerationEquipmentSerializer(required=False) @@ -189,7 +179,7 @@ class Meta: model = Equipment fields = '__all__' extra_kwargs = {'place_owner': {'read_only': True}} - + def create(self, validated_data): request = self.context.get('request') validated_data['place_owner'] = request.user.place_owner @@ -212,35 +202,35 @@ def create(self, validated_data): FireAlarmEquipment.objects.create(equipment=equipment, **fire_alarm_data) elif atmospheric_discharge_data: if 'equipment' in atmospheric_discharge_data: - atmospheric_discharge_data.pop('equipment') + atmospheric_discharge_data.pop('equipment') AtmosphericDischargeEquipment.objects.create(equipment=equipment, **atmospheric_discharge_data) elif structured_cabling_data: if 'equipment' in structured_cabling_data: - structured_cabling_data.pop('equipment') + structured_cabling_data.pop('equipment') StructuredCablingEquipment.objects.create(equipment=equipment, **structured_cabling_data) elif distribution_board_data: if 'equipment' in distribution_board_data: - distribution_board_data.pop('equipment') + distribution_board_data.pop('equipment') DistributionBoardEquipment.objects.create(equipment=equipment, **distribution_board_data) elif electrical_circuit_data: if 'equipment' in electrical_circuit_data: - electrical_circuit_data.pop('equipment') + electrical_circuit_data.pop('equipment') ElectricalCircuitEquipment.objects.create(equipment=equipment, **electrical_circuit_data) elif electrical_line_data: if 'equipment' in electrical_line_data: - electrical_line_data.pop('equipment') + electrical_line_data.pop('equipment') ElectricalLineEquipment.objects.create(equipment=equipment, **electrical_line_data) elif electrical_load_data: if 'equipment' in electrical_load_data: - electrical_load_data.pop('equipment') + electrical_load_data.pop('equipment') ElectricalLoadEquipment.objects.create(equipment=equipment, **electrical_load_data) elif ilumination_equipment_data: if 'equipment' in ilumination_equipment_data: - ilumination_equipment_data.pop('equipment') + ilumination_equipment_data.pop('equipment') IluminationEquipment.objects.create(equipment=equipment, **ilumination_equipment_data) elif refrigeration_equipment_data: if 'equipment' in refrigeration_equipment_data: - refrigeration_equipment_data.pop('equipment') + refrigeration_equipment_data.pop('equipment') RefrigerationEquipment.objects.create(equipment=equipment, **refrigeration_equipment_data) - return equipment \ No newline at end of file + return equipment diff --git a/api/equipments/urls.py b/api/equipments/urls.py index c428016b..71574057 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -11,6 +11,7 @@ path('equipment-details/', EquipmentList.as_view()), path('equipment-details//', EquipmentDetail.as_view()), path('equipment-photos/', EquipmentPhotoList.as_view()), + path('equipment-photos/by-equipment//', EquipmentPhotoByEquipmentList.as_view(), name='equipment-photo-list'), path('equipment-photos//', EquipmentPhotoDetail.as_view()), path('refrigerations/', RefrigerationEquipmentList.as_view()), path('refrigeration/by-area//', RefrigerationEquipmentByAreaList.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index e14b1bae..98b7db7f 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -1,10 +1,11 @@ from rest_framework import generics -from rest_framework.response import Response +from rest_framework import status from rest_framework.permissions import IsAuthenticated -from .models import * -from .serializers import * +from rest_framework.response import Response + from .permissions import * -from rest_framework import status +from .serializers import * + def get_place_owner_or_create(user): try: @@ -12,6 +13,7 @@ def get_place_owner_or_create(user): except PlaceOwner.DoesNotExist: return PlaceOwner.objects.create(user=user) + class PersonalEquipmentCategoryCreate(generics.CreateAPIView): queryset = PersonalEquipmentCategory.objects.all() serializer_class = PersonalEquipmentCategorySerializer @@ -26,6 +28,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + class PersonalEquipmentCategoryList(generics.ListAPIView): queryset = PersonalEquipmentCategory.objects.all() serializer_class = PersonalEquipmentCategorySerializer @@ -35,11 +38,13 @@ def get_queryset(self): system_id = self.kwargs['system_id'] return PersonalEquipmentCategory.objects.filter(system_id=system_id) + class PersonalEquipmentCategoryDetail(generics.RetrieveUpdateDestroyAPIView): queryset = PersonalEquipmentCategory.objects.all() serializer_class = PersonalEquipmentCategorySerializer permission_classes = [IsAuthenticated] + class GenericEquipmentCategoryList(generics.ListAPIView): queryset = GenericEquipmentCategory.objects.all() serializer_class = GenericEquipmentCategorySerializer @@ -49,11 +54,13 @@ def get_queryset(self): system_id = self.kwargs['system_id'] return GenericEquipmentCategory.objects.filter(system_id=system_id) + class GenericEquipmentCategoryDetail(generics.RetrieveAPIView): queryset = GenericEquipmentCategory.objects.all() serializer_class = GenericEquipmentCategorySerializer permission_classes = [IsAuthenticated] + class EquipmentList(generics.ListAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer @@ -64,6 +71,7 @@ def get_queryset(self): queryset = super().get_queryset() return queryset.filter(place_owner__user=user) + class EquipmentCreate(generics.CreateAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer @@ -76,11 +84,13 @@ def get_serializer_context(self): }) return context + class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer permission_classes = [IsAuthenticated] + class EquipmentPhotoList(generics.ListCreateAPIView): queryset = EquipmentPhoto.objects.all() serializer_class = EquipmentPhotoSerializer @@ -91,11 +101,23 @@ def get_queryset(self): queryset = super().get_queryset() return queryset.filter(equipment__place_owner__user=user) + +class EquipmentPhotoByEquipmentList(generics.ListAPIView): + serializer_class = EquipmentPhotoSerializer + permission_classes = [IsAuthenticated, IsEquipmentOwner] + + def get_queryset(self): + equipment_id = self.kwargs['equipment_id'] + user = self.request.user + return EquipmentPhoto.objects.filter(equipment_id=equipment_id, equipment__place_owner__user=user) + + class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): queryset = EquipmentPhoto.objects.all() serializer_class = EquipmentPhotoSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner] + class RefrigerationEquipmentList(generics.ListCreateAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer @@ -107,14 +129,15 @@ def get_queryset(self): return RefrigerationEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 9 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - + + class RefrigerationEquipmentByAreaList(generics.ListAPIView): serializer_class = RefrigerationEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] @@ -123,11 +146,13 @@ def get_queryset(self): area_id = self.kwargs['area_id'] return RefrigerationEquipment.objects.filter(area_id=area_id) + class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] + class FireAlarmEquipmentList(generics.ListCreateAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer @@ -139,7 +164,7 @@ def get_queryset(self): return FireAlarmEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 8 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) @@ -147,19 +172,36 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + class FireAlarmEquipmentByAreaList(generics.ListAPIView): serializer_class = FireAlarmEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] def get_queryset(self): area_id = self.kwargs['area_id'] - return FireAlarmEquipment.objects.filter(area_id=area_id) + queryset = FireAlarmEquipment.objects.filter(area_id=area_id) + + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True + class FireAlarmEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = FireAlarmEquipment.objects.all() serializer_class = FireAlarmEquipmentSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] + class AtmosphericDischargeEquipmentList(generics.ListCreateAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer @@ -171,7 +213,7 @@ def get_queryset(self): return AtmosphericDischargeEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 7 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) @@ -179,6 +221,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + class AtmosphericDischargeEquipmentByAreaList(generics.ListAPIView): serializer_class = AtmosphericDischargeEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner, IsSpecificEquipmentEditor] @@ -187,11 +230,13 @@ def get_queryset(self): area_id = self.kwargs['area_id'] return AtmosphericDischargeEquipment.objects.filter(area_id=area_id) + class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] + class StructuredCablingEquipmentList(generics.ListCreateAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer @@ -203,7 +248,7 @@ def get_queryset(self): return StructuredCablingEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 6 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) @@ -211,6 +256,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + class StructuredCablingEquipmentByAreaList(generics.ListAPIView): serializer_class = StructuredCablingEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] @@ -219,11 +265,13 @@ def get_queryset(self): area_id = self.kwargs['area_id'] return StructuredCablingEquipment.objects.filter(area_id=area_id) + class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] + class DistributionBoardEquipmentList(generics.ListCreateAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer @@ -233,9 +281,9 @@ def get_queryset(self): user = self.request.user place_owner = get_place_owner_or_create(user) return DistributionBoardEquipment.objects.filter(area__place__place_owner=place_owner) - + def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 5 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) @@ -243,6 +291,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + class DistributionBoardEquipmentByAreaList(generics.ListAPIView): serializer_class = DistributionBoardEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] @@ -251,11 +300,13 @@ def get_queryset(self): area_id = self.kwargs['area_id'] return DistributionBoardEquipment.objects.filter(area_id=area_id) + class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = DistributionBoardEquipment.objects.all() serializer_class = DistributionBoardEquipmentSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] + class ElectricalCircuitEquipmentList(generics.ListCreateAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer @@ -265,9 +316,9 @@ def get_queryset(self): user = self.request.user place_owner = get_place_owner_or_create(user) return ElectricalCircuitEquipment.objects.filter(area__place__place_owner=place_owner) - + def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 4 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) @@ -275,6 +326,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + class ElectricalCircuitEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalCircuitEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] @@ -283,11 +335,13 @@ def get_queryset(self): area_id = self.kwargs['area_id'] return ElectricalCircuitEquipment.objects.filter(area_id=area_id) + class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] + class ElectricalLineEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer @@ -297,16 +351,17 @@ def get_queryset(self): user = self.request.user place_owner = get_place_owner_or_create(user) return ElectricalLineEquipment.objects.filter(area__place__place_owner=place_owner) - + def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 3 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) - + + class ElectricalLineEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLineEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] @@ -314,12 +369,14 @@ class ElectricalLineEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] return ElectricalLineEquipment.objects.filter(area_id=area_id) - + + class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLineEquipment.objects.all() serializer_class = ElectricalLineEquipmentSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] + class ElectricalLoadEquipmentList(generics.ListCreateAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer @@ -329,9 +386,9 @@ def get_queryset(self): user = self.request.user place_owner = get_place_owner_or_create(user) return ElectricalLoadEquipment.objects.filter(area__place__place_owner=place_owner) - + def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 2 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) @@ -339,6 +396,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + class ElectricalLoadEquipmentByAreaList(generics.ListAPIView): serializer_class = ElectricalLoadEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] @@ -347,11 +405,13 @@ def get_queryset(self): area_id = self.kwargs['area_id'] return ElectricalLoadEquipment.objects.filter(area_id=area_id) + class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalLoadEquipment.objects.all() serializer_class = ElectricalLoadEquipmentSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] + class IluminationEquipmentList(generics.ListCreateAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer @@ -363,7 +423,7 @@ def get_queryset(self): return IluminationEquipment.objects.filter(area__place__place_owner=place_owner) def create(self, request, *args, **kwargs): - data = request.data.copy() + data = request.data.copy() data["system"] = 1 serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) @@ -371,6 +431,7 @@ def create(self, request, *args, **kwargs): headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) + class IluminationEquipmentByAreaList(generics.ListAPIView): serializer_class = IluminationEquipmentResponseSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] @@ -379,7 +440,8 @@ def get_queryset(self): area_id = self.kwargs['area_id'] return IluminationEquipment.objects.filter(area_id=area_id) + class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = IluminationEquipment.objects.all() serializer_class = IluminationEquipmentSerializer - permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] \ No newline at end of file + permission_classes = [IsAuthenticated, IsEquipmentOwner | IsSpecificEquipmentEditor] diff --git a/api/places/models.py b/api/places/models.py index f27fdbb8..2a16aab9 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -1,10 +1,9 @@ -import uuid from django.db import models -from django.core.validators import MinValueValidator + from users.models import PlaceOwner, PlaceEditor -class Place(models.Model): +class Place(models.Model): name = models.CharField(max_length=50) place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) lon = models.FloatField(null=True) @@ -15,11 +14,11 @@ class Place(models.Model): def __str__(self): return self.name -class Area(models.Model): +class Area(models.Model): name = models.CharField(max_length=50) floor = models.IntegerField(default=0) place = models.ForeignKey(Place, on_delete=models.CASCADE, null=True, related_name='areas') - + def __str__(self): return self.name diff --git a/api/places/permissions.py b/api/places/permissions.py index 446e140f..ca57185d 100644 --- a/api/places/permissions.py +++ b/api/places/permissions.py @@ -1,9 +1,11 @@ from rest_framework import permissions + class IsPlaceOwner(permissions.BasePermission): def has_object_permission(self, request, view, obj): return obj.place_owner.user == request.user + class IsPlaceEditor(permissions.BasePermission): def has_object_permission(self, request, view, obj): - return obj.editors.filter(user=request.user).exists() \ No newline at end of file + return obj.editors.filter(user=request.user).exists() diff --git a/api/places/serializers.py b/api/places/serializers.py index 5fdc9575..faacb881 100644 --- a/api/places/serializers.py +++ b/api/places/serializers.py @@ -1,9 +1,9 @@ from rest_framework import serializers + from .models import Place, Area -from users.serializers import PlaceOwnerSerializer -class PlaceSerializer(serializers.ModelSerializer): +class PlaceSerializer(serializers.ModelSerializer): class Meta: model = Place fields = ['id', 'name', 'place_owner', 'lon', 'lat'] @@ -11,6 +11,7 @@ class Meta: 'name': {'required': True} } + class AreaSerializer(serializers.ModelSerializer): class Meta: model = Area @@ -18,5 +19,5 @@ class Meta: extra_kwargs = { 'name': {'required': True}, 'floor': {'required': True}, - 'place_id': {'read_only': True} - } \ No newline at end of file + 'place': {'read_only': True} + } diff --git a/api/places/views.py b/api/places/views.py index 9bccb067..19a682db 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -15,12 +15,14 @@ from .models import Place, Area from .serializers import PlaceSerializer, AreaSerializer + def get_place_owner_or_create(user): try: return user.place_owner except PlaceOwner.DoesNotExist: return PlaceOwner.objects.create(user=user) + class PlaceViewSet(viewsets.ModelViewSet): queryset = Place.objects.all() serializer_class = PlaceSerializer @@ -44,7 +46,7 @@ def list(self, request, *args, **kwargs): place_owner = get_place_owner_or_create(user) places = Place.objects.filter( - Q(place_owner=place_owner) | + Q(place_owner=place_owner) | Q(editors__user=user) ).distinct() @@ -59,7 +61,8 @@ def retrieve(self, request, pk=None): serializer = PlaceSerializer(place) return Response(serializer.data) else: - return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner or an editor of this place"}, + status=status.HTTP_403_FORBIDDEN) def update(self, request, pk=None): place = get_object_or_404(Place, pk=pk) @@ -71,7 +74,8 @@ def update(self, request, pk=None): serializer.save() return Response(serializer.data) else: - return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner or an editor of this place"}, + status=status.HTTP_403_FORBIDDEN) def destroy(self, request, pk=None): user = request.user @@ -90,13 +94,15 @@ def areas(self, request, pk=None): serializer = AreaSerializer(place.areas.all(), many=True) return Response(serializer.data) - @action(detail=True, methods=['get'], url_path='areas/(?P\d+)', permission_classes=[IsAuthenticated, IsPlaceOwner | IsPlaceEditor]) + @action(detail=True, methods=['get'], url_path='areas/(?P\d+)', + permission_classes=[IsAuthenticated, IsPlaceOwner | IsPlaceEditor]) def area(self, request, pk=None, area_pk=None): place = self.get_object() area = get_object_or_404(place.areas.all(), pk=area_pk) serializer = AreaSerializer(area) return Response(serializer.data) + class AreaViewSet(viewsets.ModelViewSet): queryset = Area.objects.all() serializer_class = AreaSerializer @@ -114,7 +120,8 @@ def create(self, request, *args, **kwargs): area_serializer.save() return Response(area_serializer.data, status=status.HTTP_201_CREATED) else: - return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner or an editor of this place"}, + status=status.HTTP_403_FORBIDDEN) def list(self, request, *args, **kwargs): user = request.user @@ -131,19 +138,21 @@ def list(self, request, *args, **kwargs): area_serializer = AreaSerializer(areas, many=True) return Response(area_serializer.data) else: - return Response({"message": "You are not the owner or an editor of this place"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner or an editor of this place"}, + status=status.HTTP_403_FORBIDDEN) def retrieve(self, request, pk=None): user = request.user place_owner = get_place_owner_or_create(user) - area = get_object_or_404(Area,pk=pk) + area = get_object_or_404(Area, pk=pk) - if(area.place.place_owner.id == place_owner.id or area.place.editors.filter(user=user).exists()): + if area.place.place_owner.id == place_owner.id or area.place.editors.filter(user=user).exists(): serializer = AreaSerializer(area) return Response(serializer.data) else: - return Response({"message": "You are not the owner or an editor of this area"}, status=status.HTTP_403_FORBIDDEN) + return Response({"message": "You are not the owner or an editor of this area"}, + status=status.HTTP_403_FORBIDDEN) def destroy(self, request, pk=None): user = request.user @@ -156,6 +165,7 @@ def destroy(self, request, pk=None): else: return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) + class GrantAccessViewSet(viewsets.ViewSet): permission_classes = [IsAuthenticated, IsPlaceOwner] @@ -180,24 +190,27 @@ def grant_access(self, request, pk=None): return Response({'message': 'Access granted successfully'}, status=status.HTTP_200_OK) + class Altura: def __init__(self): self.alt = 840 def get_alt(self, p, margin=30): - self.alt -= 40 + self.alt -= 40 if self.alt < margin: - p.showPage() - self.alt = 800 + p.showPage() + self.alt = 800 return self.alt return self.alt + def genericOrPersonal(system): if system.equipment.generic_equipment_category is not None: return system.equipment.generic_equipment_category else: return system.equipment.personal_equipment_category + class GeneratePDFView(APIView): permission_classes = [IsAuthenticated, IsPlaceOwner | IsPlaceEditor] @@ -210,10 +223,10 @@ def get(self, request, pk=None): response['Content-Disposition'] = f'attachment; filename="place_{place.id}_report.pdf"' p = canvas.Canvas(response, pagesize=A4) - alt = Altura() + alt = Altura() p.setFont('Helvetica-Bold', 16) - + p.drawString(205, alt.get_alt(p), f"Relatório do Local: {place.name}") p.setFont('Helvetica-Bold', 14) @@ -224,59 +237,59 @@ def get(self, request, pk=None): p.drawString(120, alt.get_alt(p), f"Relatório da Área: {area.name}") for system in area.fire_alarm_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - + for system in area.atmospheric_discharge_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - + for system in area.structured_cabling_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - + for system in area.distribution_board_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") for system in area.electrical_circuit_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - + for system in area.electrical_line_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") - + for system in area.electrical_load_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") for system in area.ilumination_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") for system in area.refrigeration_equipment.all(): - if(system == None): + if (system == None): break p.setFont('Helvetica', 12) p.drawString(140, alt.get_alt(p), f"Sistema: {system.system} - Tipo: {genericOrPersonal(system)}") p.showPage() p.save() - return response \ No newline at end of file + return response diff --git a/api/sigeie/base_views.py b/api/sigeie/base_views.py new file mode 100644 index 00000000..f3981182 --- /dev/null +++ b/api/sigeie/base_views.py @@ -0,0 +1,27 @@ +from rest_framework import generics + + +class PermissionFilteredListView(generics.ListAPIView): + """ + Classe base para ListAPIView que filtra o queryset baseado nas permissões do usuário. + """ + + def get_queryset(self): + queryset = super().get_queryset() + + user = self.request.user + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + """ + Verifica as permissões de objeto para cada item no queryset. + """ + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True diff --git a/api/systems/models.py b/api/systems/models.py index 878d054f..d35f60da 100644 --- a/api/systems/models.py +++ b/api/systems/models.py @@ -1,10 +1,8 @@ -from django.db import models from places.models import models class System(models.Model): - name = models.CharField(max_length=50) def __str__(self): - return self.name \ No newline at end of file + return self.name diff --git a/api/systems/serializers.py b/api/systems/serializers.py index 210a7fa2..f99c5b40 100644 --- a/api/systems/serializers.py +++ b/api/systems/serializers.py @@ -1,7 +1,8 @@ from rest_framework import serializers from .models import System + class SystemSerializer(serializers.ModelSerializer): class Meta: model = System - fields = ['id', 'name'] \ No newline at end of file + fields = ['id', 'name'] diff --git a/api/systems/views.py b/api/systems/views.py index 98e8b448..b676c274 100644 --- a/api/systems/views.py +++ b/api/systems/views.py @@ -2,12 +2,14 @@ from .models import System from .serializers import SystemSerializer + class SystemViewList(generics.ListAPIView): queryset = System.objects.all() serializer_class = SystemSerializer permission_classes = [] + class SystemViewDetail(generics.RetrieveAPIView): queryset = System.objects.all() serializer_class = SystemSerializer - permission_classes = [] \ No newline at end of file + permission_classes = [] diff --git a/api/users/models.py b/api/users/models.py index 2648006c..0c582299 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -1,16 +1,16 @@ from django.db import models from django.contrib.auth.models import User -class PlaceOwner(models.Model): - user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE, related_name='place_owner') +class PlaceOwner(models.Model): + user = models.OneToOneField(User, verbose_name="user", on_delete=models.CASCADE, related_name='place_owner') def __str__(self): return self.user.first_name -class PlaceEditor(models.Model): - user = models.OneToOneField(User, verbose_name=("user"), on_delete=models.CASCADE, related_name='place_editor') +class PlaceEditor(models.Model): + user = models.OneToOneField(User, verbose_name="user", on_delete=models.CASCADE, related_name='place_editor') def __str__(self): return self.user.first_name diff --git a/api/users/permissions.py b/api/users/permissions.py index e2d4f9e7..9335de03 100644 --- a/api/users/permissions.py +++ b/api/users/permissions.py @@ -1,11 +1,13 @@ from rest_framework import permissions + class IsOwner(permissions.BasePermission): def has_object_permission(self, request, view, obj): - return request.user == obj + return request.user == obj + class IsOwnerOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in permissions.SAFE_METHODS: return True - return obj.owner == request.user \ No newline at end of file + return obj.owner == request.user diff --git a/api/users/serializers.py b/api/users/serializers.py index 581402d6..c2875908 100644 --- a/api/users/serializers.py +++ b/api/users/serializers.py @@ -4,17 +4,19 @@ from .models import PlaceOwner, PlaceEditor import re + +def validate_username(value): + if not re.match("^[a-zA-Z0-9]+$", value): + raise serializers.ValidationError("The username must contain only letters and numbers.") + return value + + class UserSerializer(serializers.ModelSerializer): username = serializers.CharField(min_length=6, max_length=23, required=True) password = serializers.CharField(min_length=6, max_length=200, write_only=True) first_name = serializers.CharField(required=True) email = serializers.EmailField(required=True) - def validate_username(self, value): - if not re.match("^[a-zA-Z0-9]+$", value): - raise serializers.ValidationError("The username must contain only letters and numbers.") - return value - class Meta: model = User fields = ['id', 'password', 'username', 'first_name', 'email', 'is_active', 'date_joined', 'groups'] @@ -36,13 +38,16 @@ def create(self, validated_data): ) return user + class UserLoginSerializer(serializers.Serializer): username = serializers.CharField(min_length=6, max_length=23, required=True) password = serializers.CharField(min_length=6, max_length=200, required=True) + class UsernameSerializer(serializers.Serializer): username = serializers.CharField(min_length=6, max_length=23, required=True) + class UserUpdateSerializer(serializers.Serializer): first_name = serializers.CharField(required=True) email = serializers.EmailField(required=True) @@ -53,11 +58,13 @@ def update(self, instance, validated_data): instance.save() return instance + class PlaceOwnerSerializer(serializers.ModelSerializer): class Meta: model = PlaceOwner fields = ['id'] + class PlaceEditorSerializer(serializers.ModelSerializer): class Meta: model = PlaceEditor diff --git a/api/users/views.py b/api/users/views.py index e4c4ad05..456115c8 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -1,33 +1,30 @@ # views.py +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth import get_user_model +from django.contrib.auth.models import User +from django.contrib.auth.tokens import default_token_generator +from django.core.mail import EmailMessage from django.http import HttpResponse, JsonResponse -from rest_framework import viewsets, permissions, status -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework.permissions import AllowAny, IsAuthenticated -from rest_framework.authentication import BasicAuthentication +from django.template.loader import render_to_string +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import ensure_csrf_cookie from rest_framework import generics -from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect, csrf_exempt +from rest_framework import status +from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + from .permissions import IsOwner from .serializers import UserSerializer, UserLoginSerializer, UserUpdateSerializer -from django.contrib.auth.models import User -from django.contrib.auth import authenticate, login, logout -from django.utils.decorators import method_decorator -from django.contrib.auth.tokens import default_token_generator -from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode -from django.template.loader import render_to_string -from django.utils.encoding import force_bytes -from django.core.mail import EmailMessage -from django.contrib.sites.shortcuts import get_current_site -from django.utils.http import urlsafe_base64_decode -from django.contrib.auth import get_user_model -from django.shortcuts import render, redirect -from django.contrib import messages + @method_decorator(ensure_csrf_cookie, name='dispatch') class GetCSRFToken(APIView): permission_classes = [AllowAny] + def get(self, request): - return Response({'success':'CSRF Cookie Set'}) + return Response({'success': 'CSRF Cookie Set'}) + class GetSessionCookie(APIView): permission_classes = [IsAuthenticated] @@ -38,24 +35,29 @@ def get(self, request): response.set_cookie('sessionid', sessionid) return response + class CheckAuthenticatedView(APIView): - permission_classes=[AllowAny] + permission_classes = [AllowAny] + def get(self, request): if request.user.is_authenticated: return Response({'isAuthenticated': True}) else: return Response({'isAuthenticated': False}) + class UserCreateView(generics.CreateAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [AllowAny] + class UserDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = User.objects.all() serializer_class = UserUpdateSerializer permission_classes = [IsOwner, IsAuthenticated] + class AuthenticatedUserView(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer @@ -64,20 +66,23 @@ class AuthenticatedUserView(generics.RetrieveAPIView): def get_object(self): return self.request.user + class LoginView(APIView): permission_classes = [AllowAny] - def post(self, request, format=None): - serializer = UserLoginSerializer(data=request.data) - if(serializer.is_valid()): - username = serializer.validated_data["username"] - password = serializer.validated_data["password"] - user = authenticate(username=username, password=password) - if user is not None: - login(request, user) - return Response({'message': 'Login successful'}, status=status.HTTP_200_OK) - else: - return Response({'message': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) - return JsonResponse(serializer.errors) + + def post(self, request, format=None): + serializer = UserLoginSerializer(data=request.data) + if serializer.is_valid(): + username = serializer.validated_data["username"] + password = serializer.validated_data["password"] + user = authenticate(username=username, password=password) + if user is not None: + login(request, user) + return Response({'message': 'Login successful'}, status=status.HTTP_200_OK) + else: + return Response({'message': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) + return JsonResponse(serializer.errors) + class LogoutView(APIView): permission_classes = [IsAuthenticated] @@ -86,6 +91,7 @@ def post(self, request, format=None): logout(request) return Response({'message': 'Logout successful'}, status=status.HTTP_200_OK) + class Email(APIView): permission_classes = [] @@ -105,13 +111,15 @@ def post(self, request, *args, **kwargs): else: return Response({'message': 'Usuário não encontrado'}, status=status.HTTP_404_NOT_FOUND) + class PasswordResetConfirmView(APIView): permission_classes = [] + def post(self, request): try: email = request.data.get("email") token = request.data.get("token") - + user = get_user_model().objects.get(email=email) except (TypeError, ValueError, OverflowError, get_user_model().DoesNotExist): user = None @@ -126,4 +134,5 @@ def post(self, request): else: return Response({'message': 'As senhas não correspondem'}, status=status.HTTP_400_BAD_REQUEST) else: - return Response({'message': 'O link de redefinição de senha é inválido'}, status=status.HTTP_400_BAD_REQUEST) + return Response({'message': 'O link de redefinição de senha é inválido'}, + status=status.HTTP_400_BAD_REQUEST) From 87aa4e5aa59fdd4959fbdf43adda5753093f321a Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 11:51:23 -0300 Subject: [PATCH 288/351] frontend: refatora service e listagem de fire alarm --- .../fire_alarm/fire_alarm_response_model.dart | 15 +- .../data/fire_alarm/fire_alarm_service.dart | 30 ++- .../feature/fire_alarm/list_fire_alarms.dart | 188 +++++++----------- frontend/sige_ie/pubspec.lock | 24 +-- 4 files changed, 98 insertions(+), 159 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart index 0ad038d7..fd9275a2 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart @@ -1,27 +1,22 @@ class FireAlarmEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; FireAlarmEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); factory FireAlarmEquipmentResponseModel.fromJson(Map json) { return FireAlarmEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart index 5130bfb2..1abbc1de 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart @@ -3,6 +3,7 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_model.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; @@ -31,29 +32,26 @@ class FireAlarmEquipmentService { } } - Future> getFireAlarmListByArea(int areaId) async { - final url = '${baseUrl}fire-alarms/by-area/$areaId'; + Future> getFireAlarmListByArea( + int areaId) async { + var url = Uri.parse('${baseUrl}fire-alarms/by-area/$areaId/'); try { - final response = await client.get(Uri.parse(url)); + var response = await client.get(url); + if (response.statusCode == 200) { - final List data = json.decode(response.body); - _logger.info('API response data: $data'); - return data.map((item) { - if (item['equipment'].containsKey('generic_equipment_category')) { - return item['equipment']['generic_equipment_category'] as String; - } else if (item['equipment'] - .containsKey('personal_equipment_category')) { - return item['equipment']['personal_equipment_category'] as String; - } else { - return 'Unknown Equipment'; - } - }).toList(); + List dataList = jsonDecode(response.body); + return dataList + .map((data) => FireAlarmEquipmentResponseModel.fromJson(data)) + .toList(); } else { + _logger.info( + 'Failed to load fire alarm equipment with status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); throw Exception('Failed to load fire alarm equipment'); } } catch (e) { _logger.info('Error during get fire alarm equipment list: $e'); - return []; + throw Exception('Failed to load fire alarm equipment'); } } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index b770d77d..d0d56d24 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_model.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_service.dart'; import 'package:sige_ie/equipments/feature/fire_alarm/add_fire_alarm.dart'; @@ -24,45 +25,22 @@ class ListFireAlarms extends StatefulWidget { } class _ListFireAlarmsState extends State { - List equipmentList = []; + late Future> _fireAlarmList; bool isLoading = true; - final FireAlarmEquipmentService _service = FireAlarmEquipmentService(); - bool _isMounted = false; + final FireAlarmEquipmentService _fireAlarmService = + FireAlarmEquipmentService(); @override void initState() { super.initState(); - _isMounted = true; - fetchEquipmentList(); + _fireAlarmList = _fireAlarmService.getFireAlarmListByArea(widget.areaId); } @override void dispose() { - _isMounted = false; super.dispose(); } - Future fetchEquipmentList() async { - try { - final List equipmentList = - await _service.getFireAlarmListByArea(widget.areaId); - if (_isMounted) { - setState(() { - this.equipmentList = equipmentList; - isLoading = false; - }); - } - print('Equipment list fetched: $equipmentList'); - } catch (e) { - print('Error fetching equipment list: $e'); - if (_isMounted) { - setState(() { - isLoading = false; - }); - } - } - } - void navigateToAddEquipment(BuildContext context) { Navigator.push( context, @@ -78,11 +56,11 @@ class _ListFireAlarmsState extends State { ); } - void _editEquipment(BuildContext context, String equipment) { + void _editEquipment(BuildContext context, int equipmentId) { // Implement the logic to edit the equipment } - void _deleteEquipment(BuildContext context, String equipment) { + void _deleteEquipment(BuildContext context, int equipmentId) { // Implement the logic to delete the equipment } @@ -109,100 +87,68 @@ class _ListFireAlarmsState extends State { }, ), ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text( - '${widget.areaName} - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText, + body: FutureBuilder>( + future: _fireAlarmList, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Erro: ${snapshot.error}')); + } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var equipment = snapshot.data![index]; + return Container( + margin: + const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - ), - ), - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - isLoading - ? const Center( - child: CircularProgressIndicator(), - ) - : equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return Container( - margin: - const EdgeInsets.symmetric(vertical: 5), - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: [ - Expanded( - child: Padding( - padding: - const EdgeInsets.only(left: 10), - child: Text( - equipment, - style: const TextStyle( - color: Colors.white, - fontSize: 18, - ), - ), - ), - ), - IconButton( - icon: const Icon(Icons.edit, - color: Colors.blue), - onPressed: () => _editEquipment( - context, equipment), - ), - IconButton( - icon: const Icon(Icons.delete, - color: Colors.red), - onPressed: () => _deleteEquipment( - context, equipment), - ), - ], - ), - ), - ); - }).toList(), - ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54, - ), - ), - ), - const SizedBox(height: 40), - ], + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + title: Text( + equipment.equipmentCategory, + style: const TextStyle( + color: AppColors.lightText, + fontWeight: FontWeight.bold), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, color: Colors.blue), + onPressed: () => + _editEquipment(context, equipment.id), + ), + IconButton( + icon: const Icon(Icons.delete, color: Colors.red), + onPressed: () => + _deleteEquipment(context, equipment.id), + ), + ], + ), + ), + ); + }, + ); + } else { + return const Padding( + padding: EdgeInsets.all(10.0), + child: Text( + 'Nenhum equipamento encontrado.', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), ), - ), - ], - ), + ); + } + }, ), floatingActionButton: FloatingActionButton( onPressed: () => navigateToAddEquipment(context), diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 8f9c048e..e985cef1 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -396,26 +396,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.0" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "2.0.1" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.0.1" lints: dependency: transitive description: @@ -460,10 +460,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.0" mime: dependency: "direct main" description: @@ -649,10 +649,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -729,10 +729,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "13.0.0" web: dependency: transitive description: From 01ff982ee27082264274352c1abc5bb2e56ce2c2 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 12:11:51 -0300 Subject: [PATCH 289/351] =?UTF-8?q?frontend:=20remove=20fun=C3=A7=C3=B5es?= =?UTF-8?q?=20n=C3=A3o=20utilizadas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/atmospheric/atmospheric_service.dart | 19 ------------------- .../distribution/distribution_service.dart | 19 ------------------- .../eletrical_load_service.dart | 19 ------------------- .../eletrical_circuit_service.dart | 19 ------------------- .../eletrical_line_service.dart | 19 ------------------- .../data/fire_alarm/fire_alarm_service.dart | 19 ------------------- .../iluminations/ilumination_service.dart | 19 ------------------- .../refrigerations_service.dart | 19 ------------------- .../structured_cabling_service.dart | 19 ------------------- 9 files changed, 171 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart index 2c95cc6d..b58013e8 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_service.dart @@ -3,7 +3,6 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class AtmosphericEquipmentService { @@ -13,24 +12,6 @@ class AtmosphericEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get all equipment: $e'); - return []; - } - } - Future> getAtmosphericListByArea(int areaId) async { final url = '${baseUrl}atmospheric-discharges/by-area/$areaId'; try { diff --git a/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart index 1f50a1af..baefcef3 100644 --- a/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart +++ b/frontend/sige_ie/lib/equipments/data/distribution/distribution_service.dart @@ -3,7 +3,6 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class DistributionEquipmentService { @@ -13,24 +12,6 @@ class DistributionEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get all equipment: $e'); - return []; - } - } - Future> getDistributionListByArea(int areaId) async { final url = '${baseUrl}distribution-boards/by-area/$areaId'; try { diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart index 07bd61de..189b2b54 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_service.dart @@ -3,7 +3,6 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class EletricalLoadEquipmentService { @@ -13,24 +12,6 @@ class EletricalLoadEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get all equipment: $e'); - return []; - } - } - Future> getEletricalLoadListByArea(int areaId) async { final url = '${baseUrl}electrical-loads/by-area/$areaId'; try { diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart index 43c8da4d..48c34588 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_service.dart @@ -3,7 +3,6 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class EletricalCircuitEquipmentService { @@ -13,24 +12,6 @@ class EletricalCircuitEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get all equipment: $e'); - return []; - } - } - Future> getEletricalCircuitListByArea(int areaId) async { final url = '${baseUrl}electrical-circuits/by-area/$areaId'; try { diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart index 25f18757..53db71ea 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_service.dart @@ -3,7 +3,6 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class EletricalLineEquipmentService { @@ -13,24 +12,6 @@ class EletricalLineEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get all equipment: $e'); - return []; - } - } - Future> getElectricalLineListByArea(int areaId) async { final url = '${baseUrl}electrical-lines/by-area/$areaId'; try { diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart index 1abbc1de..af07293e 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart @@ -4,7 +4,6 @@ import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_model.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class FireAlarmEquipmentService { @@ -14,24 +13,6 @@ class FireAlarmEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get all equipment: $e'); - return []; - } - } - Future> getFireAlarmListByArea( int areaId) async { var url = Uri.parse('${baseUrl}fire-alarms/by-area/$areaId/'); diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart index 24770a48..28f20cdd 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_service.dart @@ -3,7 +3,6 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class IluminationEquipmentService { @@ -13,24 +12,6 @@ class IluminationEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAlEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get al equipment: $e'); - return []; - } - } - Future> getIluminationListByArea(int areaId) async { final url = '${baseUrl}iluminations/by-area/$areaId'; try { diff --git a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart index 8be867fa..32eabb58 100644 --- a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart +++ b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_service.dart @@ -3,7 +3,6 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class RefrigerationsEquipmentService { @@ -13,24 +12,6 @@ class RefrigerationsEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get all equipment: $e'); - return []; - } - } - Future> getRefrigerationsListByArea(int areaId) async { final url = '${baseUrl}refrigeration/by-area/$areaId'; try { diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart index af0563f6..ad31f6a0 100644 --- a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart @@ -3,7 +3,6 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; -import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/main.dart'; class StructuredCablingEquipmentService { @@ -13,24 +12,6 @@ class StructuredCablingEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getAllEquipment( - int systemId, - List genericEquipmentCategoryList, - List - personalEquipmentCategoryList) async { - List combinedList = [ - ...genericEquipmentCategoryList, - ...personalEquipmentCategoryList, - ]; - try { - _logger.info('Combined list length: ${combinedList.length}'); - return combinedList; - } catch (e) { - _logger.info('Error during get all equipment: $e'); - return []; - } - } - Future> getStructuredCablingListByArea(int areaId) async { final url = '${baseUrl}structured-cabling/by-area/$areaId'; try { From 6924ce0fcfa9e9d59c44140eca777d4dedca8e90 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 12:30:32 -0300 Subject: [PATCH 290/351] frontend: corrige model de response de cabos estruturados --- .../structured_cabling_response_model.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart index 0dcf2f62..7d3c05e8 100644 --- a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_response_model.dart @@ -1,11 +1,13 @@ class StructuredCablingEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; StructuredCablingEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); @@ -13,16 +15,9 @@ class StructuredCablingEquipmentResponseModel { Map json) { return StructuredCablingEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } From a32d47ce2991b9f5caf6491794d0304fe0850079 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 12:31:47 -0300 Subject: [PATCH 291/351] =?UTF-8?q?frontend:=20corrige=20model=20de=20resp?= =?UTF-8?q?onse=20de=20cargas=20atmosf=C3=A9ricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atmospheric/atmospheric_response_model.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart index f29ce20a..dab15cc6 100644 --- a/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/atmospheric/atmospheric_response_model.dart @@ -1,11 +1,13 @@ class AtmosphericEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; AtmosphericEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); @@ -13,16 +15,9 @@ class AtmosphericEquipmentResponseModel { Map json) { return AtmosphericEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } From 00ca78f90a0c59f69050c518993c400c240b0875 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 12:32:16 -0300 Subject: [PATCH 292/351] =?UTF-8?q?frontend:=20corrige=20model=20de=20resp?= =?UTF-8?q?onse=20de=20distribui=C3=A7=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../distribution/distribution_response_model.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart index c2c01676..c0c300a0 100644 --- a/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/distribution/distribution_response_model.dart @@ -1,11 +1,13 @@ class DistributionEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; DistributionEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); @@ -13,16 +15,9 @@ class DistributionEquipmentResponseModel { Map json) { return DistributionEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } From d60636d514392fa9f5e96f9a25c155b15a58cc15 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 12:33:48 -0300 Subject: [PATCH 293/351] =?UTF-8?q?frontend:=20corrige=20model=20de=20resp?= =?UTF-8?q?onse=20de=20cargas=20el=C3=A9tricas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eletrical_load_response_model.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart index eadb03ce..9a950c3a 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical-load/eletrical_load_response_model.dart @@ -1,11 +1,13 @@ class EletricalLoadEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; EletricalLoadEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); @@ -13,16 +15,9 @@ class EletricalLoadEquipmentResponseModel { Map json) { return EletricalLoadEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } From b5ce8a5c44cd35bdf10d16a3ebaa28fed3911c92 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 12:35:16 -0300 Subject: [PATCH 294/351] =?UTF-8?q?frontend:=20corrige=20model=20de=20resp?= =?UTF-8?q?onse=20de=20circuitos=20el=C3=A9tricos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eletrical_circuit_response_model.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart index b942ec0e..926c7a8f 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_circuit/eletrical_circuit_response_model.dart @@ -1,11 +1,13 @@ class EletricalCircuitEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; EletricalCircuitEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); @@ -13,16 +15,9 @@ class EletricalCircuitEquipmentResponseModel { Map json) { return EletricalCircuitEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } From a08e5dfd836ee147170c037f7c4fd5a465b2d763 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Mon, 1 Jul 2024 12:36:01 -0300 Subject: [PATCH 295/351] =?UTF-8?q?frontend:=20corrige=20response=20de=20i?= =?UTF-8?q?lumina=C3=A7=C3=B5es,=20refrigera=C3=A7=C3=B5es=20e=20linhas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eletrical_line_response_model.dart | 15 +++++---------- .../ilumination_request_response.dart | 15 +++++---------- .../refrigerations_response_model.dart | 15 +++++---------- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart index dfa9b0b0..c1653dbf 100644 --- a/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/eletrical_line/eletrical_line_response_model.dart @@ -1,11 +1,13 @@ class EletricalLineEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; EletricalLineEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); @@ -13,16 +15,9 @@ class EletricalLineEquipmentResponseModel { Map json) { return EletricalLineEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } diff --git a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart index 4965e6b5..66238ad1 100644 --- a/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart +++ b/frontend/sige_ie/lib/equipments/data/iluminations/ilumination_request_response.dart @@ -1,11 +1,13 @@ class IluminationEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; IluminationEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); @@ -13,16 +15,9 @@ class IluminationEquipmentResponseModel { Map json) { return IluminationEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } diff --git a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_response_model.dart b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_response_model.dart index 830fcf78..9e03c896 100644 --- a/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/refrigerations/refrigerations_response_model.dart @@ -1,11 +1,13 @@ class RefrigerationsEquipmentResponseModel { int id; - String area; + int area; + String equipmentCategory; int system; RefrigerationsEquipmentResponseModel({ required this.id, required this.area, + required this.equipmentCategory, required this.system, }); @@ -13,16 +15,9 @@ class RefrigerationsEquipmentResponseModel { Map json) { return RefrigerationsEquipmentResponseModel( id: json['id'], - area: json['name'], + area: json['area'], + equipmentCategory: json['equipment_category'], system: json['system'], ); } - - Map toJson() { - return { - 'id': id, - 'area': area, - 'system': system, - }; - } } From 1727c5b7c652c67e291a8d79defac02e1c940176 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Tue, 2 Jul 2024 16:22:06 -0300 Subject: [PATCH 296/351] =?UTF-8?q?backend:=20ajusta=20as=20permiss=C3=B5e?= =?UTF-8?q?s=20nas=20views=20de=20listagem=20by=20area?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/views.py | 125 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 8 deletions(-) diff --git a/api/equipments/views.py b/api/equipments/views.py index 98b7db7f..622308bf 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -144,7 +144,21 @@ class RefrigerationEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] - return RefrigerationEquipment.objects.filter(area_id=area_id) + queryset = RefrigerationEquipment.objects.filter(area_id=area_id) + + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True class RefrigerationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): @@ -228,8 +242,21 @@ class AtmosphericDischargeEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] - return AtmosphericDischargeEquipment.objects.filter(area_id=area_id) + queryset = AtmosphericDischargeEquipment.objects.filter(area_id=area_id) + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() @@ -263,8 +290,21 @@ class StructuredCablingEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] - return StructuredCablingEquipment.objects.filter(area_id=area_id) + queryset = StructuredCablingEquipment.objects.filter(area_id=area_id) + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() @@ -298,7 +338,21 @@ class DistributionBoardEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] - return DistributionBoardEquipment.objects.filter(area_id=area_id) + queryset = DistributionBoardEquipment.objects.filter(area_id=area_id) + + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True class DistributionBoardEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): @@ -333,8 +387,21 @@ class ElectricalCircuitEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] - return ElectricalCircuitEquipment.objects.filter(area_id=area_id) + queryset = ElectricalCircuitEquipment.objects.filter(area_id=area_id) + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() @@ -368,7 +435,21 @@ class ElectricalLineEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] - return ElectricalLineEquipment.objects.filter(area_id=area_id) + queryset = ElectricalLineEquipment.objects.filter(area_id=area_id) + + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True class ElectricalLineEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): @@ -403,7 +484,21 @@ class ElectricalLoadEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] - return ElectricalLoadEquipment.objects.filter(area_id=area_id) + queryset = ElectricalLoadEquipment.objects.filter(area_id=area_id) + + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True class ElectricalLoadEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): @@ -438,7 +533,21 @@ class IluminationEquipmentByAreaList(generics.ListAPIView): def get_queryset(self): area_id = self.kwargs['area_id'] - return IluminationEquipment.objects.filter(area_id=area_id) + queryset = IluminationEquipment.objects.filter(area_id=area_id) + + permitted_objects = [] + for obj in queryset: + if self.check_object_permissions(self.request, obj): + permitted_objects.append(obj.id) + + return queryset.filter(id__in=permitted_objects) + + def check_object_permissions(self, request, obj): + + for permission in self.get_permissions(): + if not permission.has_object_permission(request, self, obj): + return False + return True class IluminationEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): From c48f9dea9e14c09885e9325622dd2596cd85c4c9 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Tue, 2 Jul 2024 16:54:09 -0300 Subject: [PATCH 297/351] =?UTF-8?q?backend:=20adiciona=20verifica=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20place=5Fowner=20em=20equipments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kauan --- api/equipments/permissions.py | 6 +++++- api/equipments/views.py | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 426dbbb8..4292aa81 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -1,6 +1,11 @@ from rest_framework.permissions import BasePermission +class IsOwner(BasePermission): + def has_object_permission(self, request, view, obj): + return obj.place_owner.user == request.user.place_owner + + class IsPlaceOwner(BasePermission): def has_object_permission(self, request, view, obj): if obj.place_owner == request.user.place_owner: @@ -20,4 +25,3 @@ def has_object_permission(self, request, view, obj): if obj.area and obj.area.place: return obj.area.place.editors.filter(user=request.user).exists() return False - diff --git a/api/equipments/views.py b/api/equipments/views.py index 622308bf..ea223273 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -17,7 +17,7 @@ def get_place_owner_or_create(user): class PersonalEquipmentCategoryCreate(generics.CreateAPIView): queryset = PersonalEquipmentCategory.objects.all() serializer_class = PersonalEquipmentCategorySerializer - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] def create(self, request, *args, **kwargs): user = request.user @@ -32,7 +32,7 @@ def create(self, request, *args, **kwargs): class PersonalEquipmentCategoryList(generics.ListAPIView): queryset = PersonalEquipmentCategory.objects.all() serializer_class = PersonalEquipmentCategorySerializer - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] def get_queryset(self): system_id = self.kwargs['system_id'] @@ -64,7 +64,7 @@ class GenericEquipmentCategoryDetail(generics.RetrieveAPIView): class EquipmentList(generics.ListAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] def get_queryset(self): user = self.request.user @@ -75,7 +75,7 @@ def get_queryset(self): class EquipmentCreate(generics.CreateAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] def get_serializer_context(self): context = super().get_serializer_context() @@ -88,7 +88,7 @@ def get_serializer_context(self): class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, IsOwner] class EquipmentPhotoList(generics.ListCreateAPIView): From 7ba6743dbc48e166dd61f16335da6f99991d93aa Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 3 Jul 2024 14:32:37 -0300 Subject: [PATCH 298/351] Conserto de tela de listagem de fire alarm Co-authored-by: OscarDebrito --- .../feature/fire_alarm/list_fire_alarms.dart | 144 +++++++++++------- 1 file changed, 87 insertions(+), 57 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index d0d56d24..7dba5cc2 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -87,68 +87,98 @@ class _ListFireAlarmsState extends State { }, ), ), - body: FutureBuilder>( - future: _fireAlarmList, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Erro: ${snapshot.error}')); - } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { - return ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: snapshot.data!.length, - itemBuilder: (context, index) { - var equipment = snapshot.data![index]; - return Container( - margin: - const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.circular(10), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, ), - child: ListTile( - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 10), - title: Text( - equipment.equipmentCategory, - style: const TextStyle( - color: AppColors.lightText, - fontWeight: FontWeight.bold), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.edit, color: Colors.blue), - onPressed: () => - _editEquipment(context, equipment.id), + ), + ), + ), + SizedBox(height: 20), + FutureBuilder>( + future: _fireAlarmList, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Erro: ${snapshot.error}')); + } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var equipment = snapshot.data![index]; + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - IconButton( - icon: const Icon(Icons.delete, color: Colors.red), - onPressed: () => - _deleteEquipment(context, equipment.id), + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + title: Text( + equipment.equipmentCategory, + style: const TextStyle( + color: AppColors.lightText, + fontWeight: FontWeight.bold), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: + const Icon(Icons.edit, color: Colors.blue), + onPressed: () => + _editEquipment(context, equipment.id), + ), + IconButton( + icon: + const Icon(Icons.delete, color: Colors.red), + onPressed: () => + _deleteEquipment(context, equipment.id), + ), + ], + ), ), - ], + ); + }, + ); + } else { + return const Padding( + padding: EdgeInsets.all(10.0), + child: Text( + 'Nenhum equipamento encontrado.', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), ), - ), - ); + ); + } }, - ); - } else { - return const Padding( - padding: EdgeInsets.all(10.0), - child: Text( - 'Nenhum equipamento encontrado.', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), - ), - ); - } - }, + ), + ], + ), ), floatingActionButton: FloatingActionButton( onPressed: () => navigateToAddEquipment(context), From 084bf2cf9a6fab874ade3e7f00dde7ca1555f1cf Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 3 Jul 2024 14:58:27 -0300 Subject: [PATCH 299/351] equipamento fire alarm pode ser deletado Co-authored-by: OscarDebrito --- .../sige_ie/lib/areas/data/area_service.dart | 1 - .../data/fire_alarm/fire_alarm_service.dart | 19 ++++++ .../feature/fire_alarm/list_fire_alarms.dart | 61 ++++++++++++++++--- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/frontend/sige_ie/lib/areas/data/area_service.dart b/frontend/sige_ie/lib/areas/data/area_service.dart index fd95432f..549b968c 100644 --- a/frontend/sige_ie/lib/areas/data/area_service.dart +++ b/frontend/sige_ie/lib/areas/data/area_service.dart @@ -48,7 +48,6 @@ class AreaService { } } - // Ainda não testado // GET Future fetchArea(int areaId) async { var url = Uri.parse('$baseUrl$areaId/'); diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart index af07293e..05d03583 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart @@ -35,4 +35,23 @@ class FireAlarmEquipmentService { throw Exception('Failed to load fire alarm equipment'); } } + + Future deleteFireAlarm(int equipmentId) async { + var url = Uri.parse('${baseUrl}fire-alarms/$equipmentId/'); + try { + var response = await client.delete(url); + if (response.statusCode == 204) { + _logger.info( + 'Successfully deleted fire alarm equipment with ID: $equipmentId'); + } else { + _logger.info( + 'Failed to delete fire alarm equipment with status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + throw Exception('Failed to delete fire alarm equipment'); + } + } catch (e) { + _logger.info('Error during delete fire alarm equipment: $e'); + throw Exception('Failed to delete fire alarm equipment'); + } + } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 7dba5cc2..1d90fb03 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -60,8 +60,49 @@ class _ListFireAlarmsState extends State { // Implement the logic to edit the equipment } - void _deleteEquipment(BuildContext context, int equipmentId) { - // Implement the logic to delete the equipment + Future _deleteEquipment(BuildContext context, int equipmentId) async { + try { + await _fireAlarmService.deleteFireAlarm(equipmentId); + setState(() { + _fireAlarmList = + _fireAlarmService.getFireAlarmListByArea(widget.areaId); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Equipamento deletado com sucesso')), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Falha ao deletar o equipamento')), + ); + } + } + + void _confirmDelete(BuildContext context, int equipmentId) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Confirmar Exclusão'), + content: + Text('Você tem certeza que deseja excluir este equipamento?'), + actions: [ + TextButton( + child: Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('Excluir'), + onPressed: () { + Navigator.of(context).pop(); + _deleteEquipment(context, equipmentId); + }, + ), + ], + ); + }, + ); } @override @@ -155,7 +196,7 @@ class _ListFireAlarmsState extends State { icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => - _deleteEquipment(context, equipment.id), + _confirmDelete(context, equipment.id), ), ], ), @@ -166,12 +207,14 @@ class _ListFireAlarmsState extends State { } else { return const Padding( padding: EdgeInsets.all(10.0), - child: Text( - 'Nenhum equipamento encontrado.', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), + child: Center( + child: Text( + 'Nenhum equipamento encontrado.', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), ), ); } From 205e4db76e370fd1dcba69025693216f8ac90a32 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 4 Jul 2024 11:17:24 -0300 Subject: [PATCH 300/351] =?UTF-8?q?Altera=C3=A7=C3=B5es=20visuais=20para?= =?UTF-8?q?=20interatividade=20e=20cria=C3=A7=C3=A3o=20de=20logs=20para=20?= =?UTF-8?q?identificar=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/Teams/teams.dart | 105 +++++++++++++++--- .../sige_ie/lib/areas/data/area_service.dart | 12 +- .../lib/areas/feature/register/new_area.dart | 13 +++ .../add_atmospheric_discharges_equipment.dart | 2 +- .../add_distribuition_board.dart | 3 +- .../sige_ie/lib/facilities/ui/facilities.dart | 69 +++++++----- 6 files changed, 159 insertions(+), 45 deletions(-) diff --git a/frontend/sige_ie/lib/Teams/teams.dart b/frontend/sige_ie/lib/Teams/teams.dart index be8f14d3..3fa79b22 100644 --- a/frontend/sige_ie/lib/Teams/teams.dart +++ b/frontend/sige_ie/lib/Teams/teams.dart @@ -1,9 +1,89 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -class TeamsPage extends StatelessWidget { +class TeamsPage extends StatefulWidget { const TeamsPage({super.key}); + @override + _TeamsPageState createState() => _TeamsPageState(); +} + +class _TeamsPageState extends State { + final List teams = [ + Team('Equipe 1', ['Membro 1', 'Membro 2', 'Membro 3']), + Team('Equipe 2', ['Membro 4', 'Membro 5']), + Team('Equipe 3', ['Membro 6', 'Membro 7', 'Membro 8']), + ]; + + void _addTeam(String teamName) { + setState(() { + teams.add(Team(teamName, [])); + }); + } + + void _removeTeam(int index) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Exclusão'), + content: const Text('Você realmente deseja excluir esta equipe?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + setState(() { + teams.removeAt(index); + }); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + void _switchTeam(String teamName) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Troca de Equipe'), + content: + Text('Você realmente deseja trocar para a equipe "$teamName"?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Trocar'), + onPressed: () { + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Troca concluída. Você agora está na equipe "$teamName".'), + backgroundColor: AppColors.sigeIeBlue, + ), + ); + }, + ), + ], + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -46,7 +126,7 @@ class TeamsPage extends StatelessWidget { children: [ ListView.builder( shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), + physics: const NeverScrollableScrollPhysics(), itemCount: teams.length, itemBuilder: (context, index) { final team = teams[index]; @@ -60,11 +140,11 @@ class TeamsPage extends StatelessWidget { icon: const Icon(Icons.delete, color: AppColors.warn), onPressed: () { - // Lógica para sair da equipe + _removeTeam(index); }, ), onTap: () { - // Navegação para a página de detalhes da equipe + _switchTeam(team.name); }, ), ); @@ -83,11 +163,11 @@ class TeamsPage extends StatelessWidget { builder: (context) { final TextEditingController _controller = TextEditingController(); return AlertDialog( - title: const Text('Entrar em uma equipe'), + title: const Text('Adicionar nova equipe'), content: TextField( controller: _controller, decoration: const InputDecoration( - hintText: 'Digite o código da equipe', + hintText: 'Digite o nome da equipe', ), ), actions: [ @@ -99,9 +179,12 @@ class TeamsPage extends StatelessWidget { ), TextButton( onPressed: () { - Navigator.of(context).pop(); + if (_controller.text.isNotEmpty) { + _addTeam(_controller.text); + Navigator.of(context).pop(); + } }, - child: const Text('Entrar'), + child: const Text('Adicionar'), ), ], ); @@ -121,9 +204,3 @@ class Team { Team(this.name, this.members); } - -final List teams = [ - Team('Equipe 1', ['Membro 1', 'Membro 2', 'Membro 3']), - Team('Equipe 2', ['Membro 4', 'Membro 5']), - Team('Equipe 3', ['Membro 6', 'Membro 7', 'Membro 8']), -]; diff --git a/frontend/sige_ie/lib/areas/data/area_service.dart b/frontend/sige_ie/lib/areas/data/area_service.dart index 549b968c..157117dc 100644 --- a/frontend/sige_ie/lib/areas/data/area_service.dart +++ b/frontend/sige_ie/lib/areas/data/area_service.dart @@ -20,17 +20,27 @@ class AreaService { Future createArea( AreaRequestModel areaRequestModel) async { var url = Uri.parse(baseUrl); + print('URL: $url'); // Log da URL + print( + 'Request Body: ${jsonEncode(areaRequestModel.toJson())}'); // Log do corpo da requisição + var response = await client.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode(areaRequestModel.toJson()), ); + print( + 'Response Status Code: ${response.statusCode}'); // Log do status da resposta + print('Response Body: ${response.body}'); // Log do corpo da resposta + if (response.statusCode == 201) { - // Assumindo que a resposta inclua o id da área criada. var data = jsonDecode(response.body); + print('Response Data: $data'); // Log dos dados da resposta return AreaResponseModel.fromJson(data); } else { + print( + 'Failed to create area. Status Code: ${response.statusCode}'); // Log do erro com status code throw Exception('Failed to create area'); } } diff --git a/frontend/sige_ie/lib/areas/feature/register/new_area.dart b/frontend/sige_ie/lib/areas/feature/register/new_area.dart index e1159d27..9a2d4eee 100644 --- a/frontend/sige_ie/lib/areas/feature/register/new_area.dart +++ b/frontend/sige_ie/lib/areas/feature/register/new_area.dart @@ -32,6 +32,12 @@ class _AreaLocationState extends State { super.dispose(); } + void logState() { + print('selectedFloor: $selectedFloor'); + print('areaController.text: ${areaController.text}'); + print('floorController.text: ${floorController.text}'); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -123,6 +129,7 @@ class _AreaLocationState extends State { setState(() { selectedFloor = int.tryParse(value); }); + logState(); // Log the state after changing the value }, ), ), @@ -144,6 +151,9 @@ class _AreaLocationState extends State { border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 10), ), + onChanged: (value) { + logState(); // Log the state after changing the value + }, ), ), const SizedBox(height: 60), @@ -186,6 +196,7 @@ class _AreaLocationState extends State { ), ), onPressed: () async { + logState(); // Log the state before making the API call if (selectedFloor != null && areaController.text.isNotEmpty) { AreaService areaService = AreaService(); @@ -206,6 +217,7 @@ class _AreaLocationState extends State { 'areaId': newArea.id, }); } catch (e) { + print('Erro ao criar sala: $e'); // Log the error showDialog( context: context, builder: (BuildContext context) { @@ -289,3 +301,4 @@ Widget _buildDropdown({ ), ); } + diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart index 0e7bde1e..9941bb46 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart @@ -6,11 +6,11 @@ import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/data/atmospheric/atmospheric_equipment_request_model.dart'; import 'package:sige_ie/equipments/data/atmospheric/atmospheric_request_model.dart'; +import 'package:sige_ie/equipments/data/equipment_service.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; -import 'package:sige_ie/equipments/data/equipment_service.dart'; import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart index 56cda21b..7b8488d7 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart @@ -6,12 +6,11 @@ import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; import 'package:sige_ie/equipments/data/distribution/distribution_equipment_request_model.dart'; import 'package:sige_ie/equipments/data/distribution/distribution_request_model.dart'; - +import 'package:sige_ie/equipments/data/equipment_service.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; -import 'package:sige_ie/equipments/data/equipment_service.dart'; import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_request_model.dart'; import 'package:sige_ie/shared/data/personal-equipment-category/personal_equipment_category_service.dart'; diff --git a/frontend/sige_ie/lib/facilities/ui/facilities.dart b/frontend/sige_ie/lib/facilities/ui/facilities.dart index 325363e2..dfddaf9c 100644 --- a/frontend/sige_ie/lib/facilities/ui/facilities.dart +++ b/frontend/sige_ie/lib/facilities/ui/facilities.dart @@ -505,34 +505,49 @@ class FloorAreaWidget extends StatelessWidget { return Column( children: [ Expanded( - child: ListView( - children: sortedKeys.map((floor) { - List areas = groupedAreas[floor]!; - String floorName = floor == 0 ? "Térreo" : "$floor° andar"; - return ExpansionTile( - title: Text(floorName), - children: areas.map((area) { - return ListTile( - title: Text(area.name), - onTap: () => onTapArea(area.id), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.edit, color: Colors.blue), - onPressed: () => onEditArea(area.id), - ), - IconButton( - icon: const Icon(Icons.delete, color: Colors.red), - onPressed: () => onDeleteArea(area.id), - ), - ], + child: groupedAreas.isEmpty + ? Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Você ainda não tem uma sala neste local.', + style: TextStyle( + fontSize: 18, + color: Colors.grey[600], + ), ), - ); - }).toList(), - ); - }).toList(), - ), + ), + ) + : ListView( + children: sortedKeys.map((floor) { + List areas = groupedAreas[floor]!; + String floorName = floor == 0 ? "Térreo" : "$floor° andar"; + return ExpansionTile( + title: Text(floorName), + children: areas.map((area) { + return ListTile( + title: Text(area.name), + onTap: () => onTapArea(area.id), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: + const Icon(Icons.edit, color: Colors.blue), + onPressed: () => onEditArea(area.id), + ), + IconButton( + icon: + const Icon(Icons.delete, color: Colors.red), + onPressed: () => onDeleteArea(area.id), + ), + ], + ), + ); + }).toList(), + ); + }).toList(), + ), ), ListTile( leading: const Icon(Icons.add), From b1bf01cda7a0a85258bfb7c8f2e78c7de8e245cc Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 4 Jul 2024 11:25:20 -0300 Subject: [PATCH 301/351] backend: corrige serializar de Area para usar required --- api/places/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/places/serializers.py b/api/places/serializers.py index faacb881..07b7fc18 100644 --- a/api/places/serializers.py +++ b/api/places/serializers.py @@ -19,5 +19,5 @@ class Meta: extra_kwargs = { 'name': {'required': True}, 'floor': {'required': True}, - 'place': {'read_only': True} + 'place': {'required': True} } From cdb20a1bbda2fb3882605ef64013eab8705383d4 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 4 Jul 2024 11:26:40 -0300 Subject: [PATCH 302/351] =?UTF-8?q?Altera=C3=A7=C3=B5es=20visuais=20em=20e?= =?UTF-8?q?quipes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/Teams/teams.dart | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/frontend/sige_ie/lib/Teams/teams.dart b/frontend/sige_ie/lib/Teams/teams.dart index 3fa79b22..8ccf8cdd 100644 --- a/frontend/sige_ie/lib/Teams/teams.dart +++ b/frontend/sige_ie/lib/Teams/teams.dart @@ -42,6 +42,12 @@ class _TeamsPageState extends State { teams.removeAt(index); }); Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipe excluída com sucesso!'), + backgroundColor: Colors.green, + ), + ); }, ), ], @@ -73,7 +79,7 @@ class _TeamsPageState extends State { SnackBar( content: Text( 'Troca concluída. Você agora está na equipe "$teamName".'), - backgroundColor: AppColors.sigeIeBlue, + backgroundColor: Colors.green, ), ); }, @@ -167,7 +173,7 @@ class _TeamsPageState extends State { content: TextField( controller: _controller, decoration: const InputDecoration( - hintText: 'Digite o nome da equipe', + hintText: 'Digite o código da equipe', ), ), actions: [ @@ -182,6 +188,19 @@ class _TeamsPageState extends State { if (_controller.text.isNotEmpty) { _addTeam(_controller.text); Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Equipe adicionada com sucesso!'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Nome da equipe não pode ser vazio!'), + backgroundColor: Colors.red, + ), + ); } }, child: const Text('Adicionar'), From b21a23a3c4c52420d136f574e37b1472d66e248c Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 4 Jul 2024 11:35:43 -0300 Subject: [PATCH 303/351] Conserto na listagem e no bug --- .../feature/fire_alarm/list_fire_alarms.dart | 255 +++++++++--------- 1 file changed, 131 insertions(+), 124 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 1d90fb03..c11f33ac 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -26,21 +26,19 @@ class ListFireAlarms extends StatefulWidget { class _ListFireAlarmsState extends State { late Future> _fireAlarmList; - bool isLoading = true; final FireAlarmEquipmentService _fireAlarmService = FireAlarmEquipmentService(); + // Chave global para ScaffoldMessenger + final GlobalKey _scaffoldMessengerKey = + GlobalKey(); + @override void initState() { super.initState(); _fireAlarmList = _fireAlarmService.getFireAlarmListByArea(widget.areaId); } - @override - void dispose() { - super.dispose(); - } - void navigateToAddEquipment(BuildContext context) { Navigator.push( context, @@ -67,12 +65,18 @@ class _ListFireAlarmsState extends State { _fireAlarmList = _fireAlarmService.getFireAlarmListByArea(widget.areaId); }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Equipamento deletado com sucesso')), + _scaffoldMessengerKey.currentState?.showSnackBar( + const SnackBar( + content: Text('Equipamento deletado com sucesso'), + backgroundColor: Colors.green, + ), ); } catch (e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Falha ao deletar o equipamento')), + _scaffoldMessengerKey.currentState?.showSnackBar( + const SnackBar( + content: Text('Falha ao deletar o equipamento'), + backgroundColor: Colors.red, + ), ); } } @@ -82,18 +86,18 @@ class _ListFireAlarmsState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Confirmar Exclusão'), - content: - Text('Você tem certeza que deseja excluir este equipamento?'), + title: const Text('Confirmar Exclusão'), + content: const Text( + 'Você tem certeza que deseja excluir este equipamento?'), actions: [ TextButton( - child: Text('Cancelar'), + child: const Text('Cancelar'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( - child: Text('Excluir'), + child: const Text('Excluir'), onPressed: () { Navigator.of(context).pop(); _deleteEquipment(context, equipmentId); @@ -109,124 +113,127 @@ class _ListFireAlarmsState extends State { Widget build(BuildContext context) { String systemTitle = 'Alarme de Incêndio'; - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.pushReplacementNamed( - context, - '/systemLocation', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'areaId': widget.areaId, - }, - ); - }, + return ScaffoldMessenger( + key: _scaffoldMessengerKey, + child: Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + }, + ), ), - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text( - '${widget.areaName} - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText, + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), ), ), ), - ), - SizedBox(height: 20), - FutureBuilder>( - future: _fireAlarmList, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Erro: ${snapshot.error}')); - } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { - return ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: snapshot.data!.length, - itemBuilder: (context, index) { - var equipment = snapshot.data![index]; - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.circular(10), - ), - child: ListTile( - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 10), - title: Text( - equipment.equipmentCategory, - style: const TextStyle( - color: AppColors.lightText, - fontWeight: FontWeight.bold), + const SizedBox(height: 20), + FutureBuilder>( + future: _fireAlarmList, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Erro: ${snapshot.error}')); + } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var equipment = snapshot.data![index]; + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: - const Icon(Icons.edit, color: Colors.blue), - onPressed: () => - _editEquipment(context, equipment.id), - ), - IconButton( - icon: - const Icon(Icons.delete, color: Colors.red), - onPressed: () => - _confirmDelete(context, equipment.id), - ), - ], + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + title: Text( + equipment.equipmentCategory, + style: const TextStyle( + color: AppColors.lightText, + fontWeight: FontWeight.bold), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => + _editEquipment(context, equipment.id), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => + _confirmDelete(context, equipment.id), + ), + ], + ), ), + ); + }, + ); + } else { + return const Padding( + padding: EdgeInsets.all(10.0), + child: Center( + child: Text( + 'Nenhum equipamento encontrado.', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), ), - ); - }, - ); - } else { - return const Padding( - padding: EdgeInsets.all(10.0), - child: Center( - child: Text( - 'Nenhum equipamento encontrado.', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54), ), - ), - ); - } - }, - ), - ], + ); + } + }, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () => navigateToAddEquipment(context), - backgroundColor: AppColors.sigeIeYellow, - child: const Icon(Icons.add, color: AppColors.sigeIeBlue), ), ); } From a2cbf91cf40ec7c12eb5ba530dd9b1893588173f Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 4 Jul 2024 12:48:34 -0300 Subject: [PATCH 304/351] =?UTF-8?q?Mudan=C3=A7a=20de=20cor=20na=20primeira?= =?UTF-8?q?=20tela?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/sige_ie/lib/core/ui/first_scren.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/sige_ie/lib/core/ui/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_scren.dart index 5ed95ff4..d61f9d2c 100644 --- a/frontend/sige_ie/lib/core/ui/first_scren.dart +++ b/frontend/sige_ie/lib/core/ui/first_scren.dart @@ -6,7 +6,7 @@ class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xffe1e1e1), + backgroundColor: Color.fromARGB(255, 99, 153, 190), body: Center( child: Column( mainAxisSize: MainAxisSize.min, From a488da26450e02a240777dcc4d104266982205eb Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 4 Jul 2024 13:36:35 -0300 Subject: [PATCH 305/351] Consertando listagem de cabeamento estruturado --- .../structured_cabling_service.dart | 46 ++- .../structured_cabling_equipment_list.dart | 313 ++++++++++-------- 2 files changed, 201 insertions(+), 158 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart index ad31f6a0..490734cd 100644 --- a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart @@ -4,6 +4,7 @@ import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; +import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_response_model.dart'; class StructuredCablingEquipmentService { final Logger _logger = Logger('StructuredCablingEquipmentService'); @@ -12,29 +13,46 @@ class StructuredCablingEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getStructuredCablingListByArea(int areaId) async { + Future> + getStructuredCablingListByArea(int areaId) async { final url = '${baseUrl}structured-cabling/by-area/$areaId'; try { final response = await client.get(Uri.parse(url)); if (response.statusCode == 200) { final List data = json.decode(response.body); _logger.info('API response data: $data'); - return data.map((item) { - if (item['equipment'].containsKey('generic_equipment_category')) { - return item['equipment']['generic_equipment_category'] as String; - } else if (item['equipment'] - .containsKey('personal_equipment_category')) { - return item['equipment']['personal_equipment_category'] as String; - } else { - return 'Unknown Equipment'; - } - }).toList(); + return data + .map((item) => + StructuredCablingEquipmentResponseModel.fromJson(item)) + .toList(); } else { - throw Exception('Failed to load structured-cabling equipment'); + _logger.severe( + 'Failed to load structured-cabling equipment with status code: ${response.statusCode}, response body: ${response.body}'); + throw Exception( + 'Failed to load structured-cabling equipment with status code: ${response.statusCode}'); } } catch (e) { - _logger.info('Error during get structured-cabling equipment list: $e'); - return []; + _logger.severe('Error during get structured-cabling equipment list: $e'); + throw Exception('Failed to load structured-cabling equipment. Error: $e'); + } + } + + Future deleteStructuredCabling(int equipmentId) async { + var url = Uri.parse('${baseUrl}structured-cabling/$equipmentId/'); + try { + var response = await client.delete(url); + if (response.statusCode == 204) { + _logger.info( + 'Successfully deleted structured cabling equipment with ID: $equipmentId'); + } else { + _logger.info( + 'Failed to delete structured cabling equipment with status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + throw Exception('Failed to delete structured cabling equipment'); + } + } catch (e) { + _logger.info('Error during delete structured cabling equipment: $e'); + throw Exception('Failed to delete structured cabling equipment'); } } } diff --git a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart index c8c8b844..29b36243 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_response_model.dart'; import 'package:sige_ie/equipments/data/structured_cabling/structured_cabling_service.dart'; import 'package:sige_ie/equipments/feature/structured_cabling/add_structured_cabling.dart'; @@ -24,43 +25,18 @@ class ListStructuredCabling extends StatefulWidget { } class _ListStructuredCablingState extends State { - List equipmentList = []; - bool isLoading = true; + late Future> _equipmentList; final StructuredCablingEquipmentService _service = StructuredCablingEquipmentService(); - bool _isMounted = false; + + // Chave global para ScaffoldMessenger + final GlobalKey _scaffoldMessengerKey = + GlobalKey(); @override void initState() { super.initState(); - _isMounted = true; - fetchEquipmentList(); - } - - @override - void dispose() { - _isMounted = false; - super.dispose(); - } - - Future fetchEquipmentList() async { - try { - final List equipmentList = - await _service.getStructuredCablingListByArea(widget.areaId); - if (_isMounted) { - setState(() { - this.equipmentList = equipmentList; - isLoading = false; - }); - } - } catch (e) { - print('Error fetching equipment list: $e'); - if (_isMounted) { - setState(() { - isLoading = false; - }); - } - } + _equipmentList = _service.getStructuredCablingListByArea(widget.areaId); } void navigateToAddEquipment(BuildContext context) { @@ -78,136 +54,185 @@ class _ListStructuredCablingState extends State { ); } - void _editEquipment(BuildContext context, String equipment) { + void _editEquipment(BuildContext context, int equipmentId) { // Implement the logic to edit the equipment } - void _deleteEquipment(BuildContext context, String equipment) { - // Implement the logic to delete the equipment + Future _deleteEquipment(BuildContext context, int equipmentId) async { + try { + await _service.deleteStructuredCabling(equipmentId); + setState(() { + _equipmentList = _service.getStructuredCablingListByArea(widget.areaId); + }); + _scaffoldMessengerKey.currentState?.showSnackBar( + const SnackBar( + content: Text('Equipamento deletado com sucesso'), + backgroundColor: Colors.green, + ), + ); + } catch (e) { + _scaffoldMessengerKey.currentState?.showSnackBar( + const SnackBar( + content: Text('Falha ao deletar o equipamento'), + backgroundColor: Colors.red, + ), + ); + } + } + + void _confirmDelete(BuildContext context, int equipmentId) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Confirmar Exclusão'), + content: const Text( + 'Você tem certeza que deseja excluir este equipamento?'), + actions: [ + TextButton( + child: const Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Excluir'), + onPressed: () { + Navigator.of(context).pop(); + _deleteEquipment(context, equipmentId); + }, + ), + ], + ); + }, + ); } @override Widget build(BuildContext context) { String systemTitle = 'Cabeamento Estruturado'; - return Scaffold( - appBar: AppBar( - backgroundColor: AppColors.sigeIeBlue, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () { - Navigator.pushReplacementNamed( - context, - '/systemLocation', - arguments: { - 'areaName': widget.areaName, - 'localName': widget.localName, - 'localId': widget.localId, - 'areaId': widget.areaId, - }, - ); - }, + return ScaffoldMessenger( + key: _scaffoldMessengerKey, + child: Scaffold( + appBar: AppBar( + backgroundColor: AppColors.sigeIeBlue, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () { + Navigator.pushReplacementNamed( + context, + '/systemLocation', + arguments: { + 'areaName': widget.areaName, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + }, + ), ), - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), - decoration: const BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: - BorderRadius.vertical(bottom: Radius.circular(20)), - ), - child: Center( - child: Text( - '${widget.areaName} - $systemTitle', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: AppColors.lightText, + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 35), + decoration: const BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: + BorderRadius.vertical(bottom: Radius.circular(20)), + ), + child: Center( + child: Text( + '${widget.areaName} - $systemTitle', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.lightText, + ), ), ), ), - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - isLoading - ? const Center( - child: CircularProgressIndicator(), - ) - : equipmentList.isNotEmpty - ? Column( - children: equipmentList.map((equipment) { - return Container( - margin: - const EdgeInsets.symmetric(vertical: 5), - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: AppColors.sigeIeBlue, - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: [ - Expanded( - child: Padding( - padding: - const EdgeInsets.only(left: 10), - child: Text( - equipment, - style: const TextStyle( - color: Colors.white, - fontSize: 18, - ), - ), - ), - ), - IconButton( - icon: const Icon(Icons.edit, - color: Colors.blue), - onPressed: () => _editEquipment( - context, equipment), - ), - IconButton( - icon: const Icon(Icons.delete, - color: Colors.red), - onPressed: () => _deleteEquipment( - context, equipment), - ), - ], - ), - ), - ); - }).toList(), - ) - : const Center( - child: Text( - 'Você ainda não tem equipamentos', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black54, + const SizedBox(height: 20), + FutureBuilder>( + future: _equipmentList, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Erro: ${snapshot.error}')); + } else if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var equipment = snapshot.data![index]; + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: AppColors.sigeIeBlue, + borderRadius: BorderRadius.circular(10), + ), + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + title: Text( + equipment.equipmentCategory, + style: const TextStyle( + color: AppColors.lightText, + fontWeight: FontWeight.bold), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit, + color: Colors.blue), + onPressed: () => + _editEquipment(context, equipment.id), + ), + IconButton( + icon: const Icon(Icons.delete, + color: Colors.red), + onPressed: () => + _confirmDelete(context, equipment.id), ), - ), + ], ), - const SizedBox(height: 40), - ], + ), + ); + }, + ); + } else { + return const Padding( + padding: EdgeInsets.all(10.0), + child: Center( + child: Text( + 'Nenhum equipamento encontrado.', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black54), + ), + ), + ); + } + }, ), - ), - ], + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => navigateToAddEquipment(context), + backgroundColor: AppColors.sigeIeYellow, + child: const Icon(Icons.add, color: AppColors.sigeIeBlue), ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () => navigateToAddEquipment(context), - backgroundColor: AppColors.sigeIeYellow, - child: const Icon(Icons.add, color: AppColors.sigeIeBlue), ), ); } From b203c55981656830b2374d12d931c880dce12f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Thu, 4 Jul 2024 21:26:47 -0300 Subject: [PATCH 306/351] backend: editor cria e edita equipments, correcao area --- api/equipments/permissions.py | 7 +++++++ api/equipments/views.py | 6 +++--- api/places/models.py | 2 +- api/places/permissions.py | 1 + api/places/urls.py | 3 ++- api/places/views.py | 37 +++++++++++++++++++++++++++++++---- 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 4292aa81..36ea2d24 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -19,6 +19,13 @@ def has_object_permission(self, request, view, obj): return obj.equipment.place_owner == request.user.place_owner return False +class IsEquipmentEditor(BasePermission): + def has_object_permission(self, request, view, obj): + return obj.place_owner.places.editors.filter(user=request.user).exists() + +class IsEquipmentEditorPhoto(BasePermission): + def has_object_permission(self, request, view, obj): + return obj.equipment.place_owner.places.editors.filter(user=request.user).exists() class IsSpecificEquipmentEditor(BasePermission): def has_object_permission(self, request, view, obj): diff --git a/api/equipments/views.py b/api/equipments/views.py index ea223273..4e4ceba4 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -75,7 +75,7 @@ def get_queryset(self): class EquipmentCreate(generics.CreateAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsAuthenticated, IsOwner] + permission_classes = [IsAuthenticated, IsOwner or IsEquipmentEditor] def get_serializer_context(self): context = super().get_serializer_context() @@ -88,13 +88,13 @@ def get_serializer_context(self): class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer - permission_classes = [IsAuthenticated, IsOwner] + permission_classes = [IsAuthenticated, IsOwner or IsEquipmentEditor] class EquipmentPhotoList(generics.ListCreateAPIView): queryset = EquipmentPhoto.objects.all() serializer_class = EquipmentPhotoSerializer - permission_classes = [IsAuthenticated, IsEquipmentOwner] + permission_classes = [IsAuthenticated, IsEquipmentOwner or IsEquipmentEditorPhoto] def get_queryset(self): user = self.request.user diff --git a/api/places/models.py b/api/places/models.py index 2a16aab9..57b3d024 100644 --- a/api/places/models.py +++ b/api/places/models.py @@ -5,7 +5,7 @@ class Place(models.Model): name = models.CharField(max_length=50) - place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True) + place_owner = models.ForeignKey(PlaceOwner, on_delete=models.CASCADE, null=True, related_name="places") lon = models.FloatField(null=True) lat = models.FloatField(null=True) photo = models.ImageField(null=True, upload_to='place_photos/') diff --git a/api/places/permissions.py b/api/places/permissions.py index ca57185d..13818834 100644 --- a/api/places/permissions.py +++ b/api/places/permissions.py @@ -1,4 +1,5 @@ from rest_framework import permissions +from .models import Area class IsPlaceOwner(permissions.BasePermission): diff --git a/api/places/urls.py b/api/places/urls.py index 90635c6d..fd79ac9a 100644 --- a/api/places/urls.py +++ b/api/places/urls.py @@ -1,5 +1,5 @@ from django.urls import path, include -from .views import PlaceViewSet, AreaViewSet, GrantAccessViewSet +from .views import * from rest_framework.routers import SimpleRouter from .views import GeneratePDFView @@ -7,6 +7,7 @@ router.register(r'places',PlaceViewSet) router.register(r'areas', AreaViewSet) router.register(r'grant_access', GrantAccessViewSet, basename='grant_access') +router.register(r'refuse_access', RefuseAccessViewSet, basename = 'refuse_access') urlpatterns = [ path('places//report/', GeneratePDFView.as_view(), name='place-report'), diff --git a/api/places/views.py b/api/places/views.py index 19a682db..621e87d4 100644 --- a/api/places/views.py +++ b/api/places/views.py @@ -4,7 +4,7 @@ from users.models import PlaceOwner, PlaceEditor from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated -from places.permissions import IsPlaceOwner, IsPlaceEditor +from places.permissions import * from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response @@ -95,12 +95,14 @@ def areas(self, request, pk=None): return Response(serializer.data) @action(detail=True, methods=['get'], url_path='areas/(?P\d+)', - permission_classes=[IsAuthenticated, IsPlaceOwner | IsPlaceEditor]) + permission_classes=[IsAuthenticated]) def area(self, request, pk=None, area_pk=None): place = self.get_object() area = get_object_or_404(place.areas.all(), pk=area_pk) - serializer = AreaSerializer(area) - return Response(serializer.data) + if(request.user.place_owner == area.place.place_owner or area.place.editors.filter(user=request.user).exists()): + serializer = AreaSerializer(area) + return Response(serializer.data) + return Response({"Error" : "You're not the owner or editor of this Area"}) class AreaViewSet(viewsets.ModelViewSet): @@ -166,6 +168,33 @@ def destroy(self, request, pk=None): return Response({"message": "You are not the owner of this area"}, status=status.HTTP_403_FORBIDDEN) +class RefuseAccessViewSet(viewsets.ViewSet): + permission_classes = [IsAuthenticated, IsPlaceOwner] + + @action(detail=True, methods=['post']) + def refuse_access(self, request, pk=None): + place = get_object_or_404(Place, pk=pk) + place_owner = place.place_owner + + if request.user != place_owner.user: + return Response({"message": "You are not the owner of this place"}, status=status.HTTP_403_FORBIDDEN) + + username = request.data.get('username') + + if not username: + return Response({'error': 'Username is required'}, status=status.HTTP_400_BAD_REQUEST) + + user = get_object_or_404(User, username=username) + + place_editor = PlaceEditor.objects.filter(user=user).first() + + if not place_editor: + return Response({'error': 'This user is not an editor of the place'}, status=status.HTTP_400_BAD_REQUEST) + + place.editors.remove(place_editor) + + return Response({'message': 'Access revoked successfully'}, status=status.HTTP_200_OK) + class GrantAccessViewSet(viewsets.ViewSet): permission_classes = [IsAuthenticated, IsPlaceOwner] From 818c02056d2f8e8aa3ba8df7ab1d45adf31f8095 Mon Sep 17 00:00:00 2001 From: EngDann Date: Fri, 5 Jul 2024 11:23:17 -0300 Subject: [PATCH 307/351] =?UTF-8?q?Altera=C3=A7=C3=B5es=20visuais=20na=20p?= =?UTF-8?q?rimeira=20tela?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sige_ie/assets/1000x1000Horizontal.png | Bin 0 -> 15119 bytes frontend/sige_ie/lib/core/ui/first_scren.dart | 55 +++++++++--------- frontend/sige_ie/pubspec.yaml | 1 + 3 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 frontend/sige_ie/assets/1000x1000Horizontal.png diff --git a/frontend/sige_ie/assets/1000x1000Horizontal.png b/frontend/sige_ie/assets/1000x1000Horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..38cf8d67e0d433e954a6f9d9da21582a21f5b508 GIT binary patch literal 15119 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 f>;Clr+DAq~>VN-87Hh;?=A7Uf`sz5[ Image.asset('assets/UNB.png'), + Image.asset('assets/1000x1000Horizontal.png'), const SizedBox( - height: 50, + height: 130, ), ElevatedButton( - onPressed: () { - Navigator.pushNamed(context, '/loginScreen'); - }, - style: ElevatedButton.styleFrom( - elevation: 6, - minimumSize: const Size(200, 50), - backgroundColor: const Color(0xfff1f60e), - foregroundColor: const Color(0xff123c75), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 12), // Arredondamento dos cantos do botão - )), - child: const Text( - "Login", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - )), + onPressed: () { + Navigator.pushNamed(context, '/loginScreen'); + }, + style: ElevatedButton.styleFrom( + elevation: 6, + minimumSize: const Size(200, 50), + backgroundColor: const Color(0xfff1f60e), + foregroundColor: const Color(0xff123c75), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: const Text( + "Login", + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), const SizedBox( - height: 15, // Espaço entre os botões + height: 15, ), ElevatedButton( onPressed: () { Navigator.pushNamed(context, '/registerScreen'); }, style: ElevatedButton.styleFrom( - elevation: 6, - minimumSize: const Size(200, 50), - backgroundColor: const Color(0xfff1f60e), - foregroundColor: const Color(0xff123c75), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - )), + elevation: 6, + minimumSize: const Size(200, 50), + backgroundColor: const Color(0xfff1f60e), + foregroundColor: const Color(0xff123c75), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), child: const Text( "Registro", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 531f95d4..813747ff 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -63,6 +63,7 @@ dev_dependencies: flutter: assets: - assets/Loading.mp4 + - assets/1000x1000Horizontal.png - assets/1000x1000.png - assets/lighting.png - assets/UNB.png From b357029bd65dac7064b63a4c770d3204a47e05ee Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 5 Jul 2024 11:27:49 -0300 Subject: [PATCH 308/351] backend: Atualiza view e url de equipment --- api/equipments/permissions.py | 17 +++++++++++++++-- api/equipments/urls.py | 5 ++--- api/equipments/views.py | 35 ++++++++++------------------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/api/equipments/permissions.py b/api/equipments/permissions.py index 36ea2d24..21377d5a 100644 --- a/api/equipments/permissions.py +++ b/api/equipments/permissions.py @@ -1,9 +1,19 @@ from rest_framework.permissions import BasePermission +from users.models import PlaceOwner + + +def get_place_owner_or_create(user): + try: + return user.place_owner + except PlaceOwner.DoesNotExist: + return PlaceOwner.objects.create(user=user) + class IsOwner(BasePermission): def has_object_permission(self, request, view, obj): - return obj.place_owner.user == request.user.place_owner + get_place_owner_or_create(request.user) + return obj.place_owner == request.user.place_owner class IsPlaceOwner(BasePermission): @@ -19,14 +29,17 @@ def has_object_permission(self, request, view, obj): return obj.equipment.place_owner == request.user.place_owner return False + class IsEquipmentEditor(BasePermission): def has_object_permission(self, request, view, obj): - return obj.place_owner.places.editors.filter(user=request.user).exists() + return obj.place_owner.places.editors.filter(user=request.user).exists() + class IsEquipmentEditorPhoto(BasePermission): def has_object_permission(self, request, view, obj): return obj.equipment.place_owner.places.editors.filter(user=request.user).exists() + class IsSpecificEquipmentEditor(BasePermission): def has_object_permission(self, request, view, obj): if obj.area and obj.area.place: diff --git a/api/equipments/urls.py b/api/equipments/urls.py index 71574057..16510fc1 100644 --- a/api/equipments/urls.py +++ b/api/equipments/urls.py @@ -7,9 +7,8 @@ path('personal-equipment-types//', PersonalEquipmentCategoryDetail.as_view()), path('equipment-types/by-system//', GenericEquipmentCategoryList.as_view(), name='personal_equipment_types_by_system'), path('equipment-types//', GenericEquipmentCategoryDetail.as_view()), - path('equipment-details/', EquipmentCreate.as_view(), name='equipment-create'), - path('equipment-details/', EquipmentList.as_view()), - path('equipment-details//', EquipmentDetail.as_view()), + path('equipments/', EquipmentListCreateView.as_view(), name='equipments'), + path('equipments//', EquipmentDetail.as_view()), path('equipment-photos/', EquipmentPhotoList.as_view()), path('equipment-photos/by-equipment//', EquipmentPhotoByEquipmentList.as_view(), name='equipment-photo-list'), path('equipment-photos//', EquipmentPhotoDetail.as_view()), diff --git a/api/equipments/views.py b/api/equipments/views.py index 4e4ceba4..57858caf 100644 --- a/api/equipments/views.py +++ b/api/equipments/views.py @@ -61,29 +61,11 @@ class GenericEquipmentCategoryDetail(generics.RetrieveAPIView): permission_classes = [IsAuthenticated] -class EquipmentList(generics.ListAPIView): - queryset = Equipment.objects.all() - serializer_class = EquipmentSerializer - permission_classes = [IsAuthenticated, IsOwner] - - def get_queryset(self): - user = self.request.user - queryset = super().get_queryset() - return queryset.filter(place_owner__user=user) - - -class EquipmentCreate(generics.CreateAPIView): +class EquipmentListCreateView(generics.ListCreateAPIView): queryset = Equipment.objects.all() serializer_class = EquipmentSerializer permission_classes = [IsAuthenticated, IsOwner or IsEquipmentEditor] - def get_serializer_context(self): - context = super().get_serializer_context() - context.update({ - 'request': self.request - }) - return context - class EquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Equipment.objects.all() @@ -102,6 +84,12 @@ def get_queryset(self): return queryset.filter(equipment__place_owner__user=user) +class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = EquipmentPhoto.objects.all() + serializer_class = EquipmentPhotoSerializer + permission_classes = [IsAuthenticated, IsEquipmentOwner or IsEquipmentEditorPhoto] + + class EquipmentPhotoByEquipmentList(generics.ListAPIView): serializer_class = EquipmentPhotoSerializer permission_classes = [IsAuthenticated, IsEquipmentOwner] @@ -112,12 +100,6 @@ def get_queryset(self): return EquipmentPhoto.objects.filter(equipment_id=equipment_id, equipment__place_owner__user=user) -class EquipmentPhotoDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = EquipmentPhoto.objects.all() - serializer_class = EquipmentPhotoSerializer - permission_classes = [IsAuthenticated, IsEquipmentOwner] - - class RefrigerationEquipmentList(generics.ListCreateAPIView): queryset = RefrigerationEquipment.objects.all() serializer_class = RefrigerationEquipmentSerializer @@ -258,6 +240,7 @@ def check_object_permissions(self, request, obj): return False return True + class AtmosphericDischargeEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AtmosphericDischargeEquipment.objects.all() serializer_class = AtmosphericDischargeEquipmentSerializer @@ -306,6 +289,7 @@ def check_object_permissions(self, request, obj): return False return True + class StructuredCablingEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = StructuredCablingEquipment.objects.all() serializer_class = StructuredCablingEquipmentSerializer @@ -403,6 +387,7 @@ def check_object_permissions(self, request, obj): return False return True + class ElectricalCircuitEquipmentDetail(generics.RetrieveUpdateDestroyAPIView): queryset = ElectricalCircuitEquipment.objects.all() serializer_class = ElectricalCircuitEquipmentSerializer From 4c1d4e49409407d3b19e8dbfd90ca440e75da8a0 Mon Sep 17 00:00:00 2001 From: EngDann Date: Fri, 5 Jul 2024 17:35:59 -0300 Subject: [PATCH 309/351] =?UTF-8?q?Mudan=C3=A7as=20nas=20telas=20principai?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0032_alter_place_place_owner.py | 20 ++++++++++++++++++ frontend/sige_ie/assets/Loading.mp4 | Bin 50401 -> 46551 bytes frontend/sige_ie/lib/core/ui/first_scren.dart | 4 ++-- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 api/places/migrations/0032_alter_place_place_owner.py diff --git a/api/places/migrations/0032_alter_place_place_owner.py b/api/places/migrations/0032_alter_place_place_owner.py new file mode 100644 index 00000000..c9e6265b --- /dev/null +++ b/api/places/migrations/0032_alter_place_place_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-07-05 20:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_alter_placeowner_user'), + ('places', '0031_remove_place_access_code'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='place_owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='places', to='users.placeowner'), + ), + ] diff --git a/frontend/sige_ie/assets/Loading.mp4 b/frontend/sige_ie/assets/Loading.mp4 index b185dce263ed8d03b4eecd804b403b8154431846..e7cd990120473191b8f60c8cb6ed5527d83d69d7 100644 GIT binary patch literal 46551 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%0A9zfmQ>GLdK#NJMs2<@e}A7r&m{ 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 aA~6VaUd9QhHrPxoG;_Yqyk-o*`|*D@qL;e> delta 46069 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 diff --git a/frontend/sige_ie/lib/core/ui/first_scren.dart b/frontend/sige_ie/lib/core/ui/first_scren.dart index 9107f064..cc91a5e5 100644 --- a/frontend/sige_ie/lib/core/ui/first_scren.dart +++ b/frontend/sige_ie/lib/core/ui/first_scren.dart @@ -6,7 +6,7 @@ class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color.fromARGB(255, 99, 153, 190), + backgroundColor: Color(0xFF6399BE), body: Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -14,7 +14,7 @@ class FirstScreen extends StatelessWidget { Image.asset('assets/UNB.png'), Image.asset('assets/1000x1000Horizontal.png'), const SizedBox( - height: 130, + height: 140, ), ElevatedButton( onPressed: () { From 5678a317f4752215497564e9f8825f68def97c19 Mon Sep 17 00:00:00 2001 From: EngDann Date: Fri, 5 Jul 2024 19:17:00 -0300 Subject: [PATCH 310/351] =?UTF-8?q?Equipamento=20=C3=A9=20listado=20pelo?= =?UTF-8?q?=20acesso=20de=20edi=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipments/data/equipment_service.dart | 33 ++++++++++++++ .../feature/fire_alarm/add_fire_alarm.dart | 45 ++++++++++++++++--- .../feature/fire_alarm/list_fire_alarms.dart | 15 ++++++- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index 2b4c402f..bcf2f8b8 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -20,6 +20,39 @@ class EquipmentService { http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); + Future> getFireAlarmById(int id) async { + var url = Uri.parse('http://10.0.2.2:8000/api/fire-alarms/$id/'); + try { + var response = await client.get(url); + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + _logger.info( + 'Failed to load fire alarm with status code: ${response.statusCode}'); + throw Exception('Failed to load fire alarm'); + } + } catch (e) { + _logger.info('Error during get fire alarm: $e'); + throw Exception('Failed to load fire alarm'); + } + } + + Future> getEquipmentById(int id) async { + var url = Uri.parse('http://10.0.2.2:8000/api/equipments/$id/'); + try { + var response = await client.get(url); + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + _logger.info( + 'Failed to load equipment with status code: ${response.statusCode}'); + throw Exception('Failed to load equipment'); + } + } catch (e) { + _logger.info('Error during get equipment: $e'); + throw Exception('Failed to load equipment'); + } + } Future createFireAlarm( FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index 93801ce2..da4f4884 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -31,6 +31,7 @@ class AddfireAlarm extends StatefulWidget { final int localId; final int categoryNumber; final int areaId; + final int? equipmentId; // Add this parameter const AddfireAlarm({ super.key, @@ -39,6 +40,7 @@ class AddfireAlarm extends StatefulWidget { required this.localName, required this.localId, required this.areaId, + this.equipmentId, // Add this parameter }); @override @@ -47,22 +49,19 @@ class AddfireAlarm extends StatefulWidget { class _AddEquipmentScreenState extends State { EquipmentService equipmentService = EquipmentService(); - EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); - PersonalEquipmentCategoryService personalEquipmentCategoryService = PersonalEquipmentCategoryService(); - GenericEquipmentCategoryService genericEquipmentCategoryService = GenericEquipmentCategoryService(); final _equipmentQuantityController = TextEditingController(); String? _selectedType; - String? _selectedTypeToDelete; - String? _newEquipmentTypeName; int? _selectedGenericEquipmentCategoryId; int? _selectedPersonalEquipmentCategoryId; bool _isPersonalEquipmentCategorySelected = false; + String? _newEquipmentTypeName; + String? _selectedTypeToDelete; List> genericEquipmentTypes = []; List> personalEquipmentTypes = []; @@ -72,6 +71,42 @@ class _AddEquipmentScreenState extends State { void initState() { super.initState(); _fetchEquipmentCategory(); + + if (widget.equipmentId != null) { + _fetchEquipmentDetails(widget.equipmentId!); + } + } + + Future _fetchEquipmentDetails(int equipmentId) async { + try { + // Buscar detalhes do alarme de incêndio + final fireAlarmDetails = + await equipmentService.getFireAlarmById(equipmentId); + + // Buscar detalhes do equipamento + final equipmentDetails = await equipmentService + .getEquipmentById(fireAlarmDetails['equipment']); + + setState(() { + if (equipmentDetails['personal_equipment_category'] != null) { + _selectedPersonalEquipmentCategoryId = + equipmentDetails['personal_equipment_category']; + _selectedType = personalEquipmentTypes.firstWhere((element) => + element['id'] == _selectedPersonalEquipmentCategoryId)['name'] + as String; + _isPersonalEquipmentCategorySelected = true; + } else { + _selectedGenericEquipmentCategoryId = + equipmentDetails['generic_equipment_category']; + _selectedType = genericEquipmentTypes.firstWhere((element) => + element['id'] == _selectedGenericEquipmentCategoryId)['name'] + as String; + _isPersonalEquipmentCategorySelected = false; + } + }); + } catch (e) { + print('Erro ao buscar detalhes do equipamento: $e'); + } } Future _fetchEquipmentCategory() async { diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index c11f33ac..fc3ce24e 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -49,13 +49,26 @@ class _ListFireAlarmsState extends State { localName: widget.localName, localId: widget.localId, areaId: widget.areaId, + equipmentId: null, ), ), ); } void _editEquipment(BuildContext context, int equipmentId) { - // Implement the logic to edit the equipment + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddfireAlarm( + areaName: widget.areaName, + categoryNumber: widget.categoryNumber, + localName: widget.localName, + localId: widget.localId, + areaId: widget.areaId, + equipmentId: equipmentId, // Passando o ID do equipamento + ), + ), + ); } Future _deleteEquipment(BuildContext context, int equipmentId) async { From 4b176aa4251cb1d5ce1ad142053adce4fb13b01f Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 8 Jul 2024 11:06:28 -0300 Subject: [PATCH 311/351] Editar tipo de equipamentoem fire alarm concluido --- .../equipments/data/equipment_service.dart | 36 +++-- .../data/fire_alarm/fire_alarm_service.dart | 43 +++++ .../feature/fire_alarm/add_fire_alarm.dart | 151 +++++++++++++++--- .../feature/fire_alarm/list_fire_alarms.dart | 7 +- 4 files changed, 196 insertions(+), 41 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index bcf2f8b8..5e9a9f3b 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -16,41 +16,49 @@ import 'package:sige_ie/main.dart'; class EquipmentService { final Logger _logger = Logger('EquipmentService'); - final String baseUrl = 'http://10.0.2.2:8000/api/equipment-details/'; + final String baseUrl = 'http://10.0.2.2:8000/api/equipments/'; http.Client client = InterceptedClient.build( interceptors: [AuthInterceptor(cookieJar)], ); - Future> getFireAlarmById(int id) async { - var url = Uri.parse('http://10.0.2.2:8000/api/fire-alarms/$id/'); + + Future> getEquipmentById(int id) async { + var url = Uri.parse('http://10.0.2.2:8000/api/equipments/$id/'); try { var response = await client.get(url); if (response.statusCode == 200) { return jsonDecode(response.body); } else { _logger.info( - 'Failed to load fire alarm with status code: ${response.statusCode}'); - throw Exception('Failed to load fire alarm'); + 'Failed to load equipment with status code: ${response.statusCode}'); + throw Exception('Failed to load equipment'); } } catch (e) { - _logger.info('Error during get fire alarm: $e'); - throw Exception('Failed to load fire alarm'); + _logger.info('Error during get equipment: $e'); + throw Exception('Failed to load equipment'); } } - Future> getEquipmentById(int id) async { + Future updateEquipmentType(int id, Map data) async { var url = Uri.parse('http://10.0.2.2:8000/api/equipments/$id/'); + try { - var response = await client.get(url); + var response = await client.put( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(data), + ); + if (response.statusCode == 200) { - return jsonDecode(response.body); + _logger.info('Successfully updated equipment type with ID: $id'); + return true; } else { _logger.info( - 'Failed to load equipment with status code: ${response.statusCode}'); - throw Exception('Failed to load equipment'); + 'Failed to update equipment type with status code: ${response.statusCode}'); + return false; } } catch (e) { - _logger.info('Error during get equipment: $e'); - throw Exception('Failed to load equipment'); + _logger.info('Error during update equipment type: $e'); + return false; } } diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart index 05d03583..1b6e72a4 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart @@ -5,6 +5,7 @@ import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_model.dart'; import 'package:sige_ie/main.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart'; class FireAlarmEquipmentService { final Logger _logger = Logger('FireAlarmEquipmentService'); @@ -54,4 +55,46 @@ class FireAlarmEquipmentService { throw Exception('Failed to delete fire alarm equipment'); } } + + Future> getFireAlarmById(int id) async { + var url = Uri.parse('${baseUrl}fire-alarms/$id/'); + try { + var response = await client.get(url); + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + _logger.info( + 'Failed to load fire alarm with status code: ${response.statusCode}'); + throw Exception('Failed to load fire alarm'); + } + } catch (e) { + _logger.info('Error during get fire alarm: $e'); + throw Exception('Failed to load fire alarm'); + } + } + + Future updateFireAlarm(int id, + FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { + var url = Uri.parse('${baseUrl}fire-alarms/$id/'); + + try { + var response = await client.put( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(fireAlarmEquipmentRequestModel.toJson()), + ); + + if (response.statusCode == 200) { + _logger.info('Successfully updated fire alarm equipment with ID: $id'); + return true; + } else { + _logger.info( + 'Failed to update fire alarm equipment with status code: ${response.statusCode}'); + return false; + } + } catch (e) { + _logger.info('Error during update fire alarm equipment: $e'); + return false; + } + } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index da4f4884..afc3f3a8 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_service.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; @@ -25,36 +26,38 @@ class ImageData { List _images = []; Map> categoryImagesMap = {}; -class AddfireAlarm extends StatefulWidget { +class AddFireAlarm extends StatefulWidget { final String areaName; final String localName; final int localId; final int categoryNumber; final int areaId; - final int? equipmentId; // Add this parameter + final int? equipmentId; + final bool isEdit; - const AddfireAlarm({ + const AddFireAlarm({ super.key, required this.areaName, required this.categoryNumber, required this.localName, required this.localId, required this.areaId, - this.equipmentId, // Add this parameter + this.equipmentId, + this.isEdit = false, }); @override _AddEquipmentScreenState createState() => _AddEquipmentScreenState(); } -class _AddEquipmentScreenState extends State { +class _AddEquipmentScreenState extends State { EquipmentService equipmentService = EquipmentService(); + FireAlarmEquipmentService fireAlarmService = FireAlarmEquipmentService(); EquipmentPhotoService equipmentPhotoService = EquipmentPhotoService(); PersonalEquipmentCategoryService personalEquipmentCategoryService = PersonalEquipmentCategoryService(); GenericEquipmentCategoryService genericEquipmentCategoryService = GenericEquipmentCategoryService(); - final _equipmentQuantityController = TextEditingController(); String? _selectedType; int? _selectedGenericEquipmentCategoryId; @@ -72,36 +75,35 @@ class _AddEquipmentScreenState extends State { super.initState(); _fetchEquipmentCategory(); - if (widget.equipmentId != null) { + if (widget.isEdit && widget.equipmentId != null) { _fetchEquipmentDetails(widget.equipmentId!); } } Future _fetchEquipmentDetails(int equipmentId) async { try { - // Buscar detalhes do alarme de incêndio final fireAlarmDetails = - await equipmentService.getFireAlarmById(equipmentId); + await fireAlarmService.getFireAlarmById(equipmentId); - // Buscar detalhes do equipamento final equipmentDetails = await equipmentService .getEquipmentById(fireAlarmDetails['equipment']); setState(() { - if (equipmentDetails['personal_equipment_category'] != null) { + _isPersonalEquipmentCategorySelected = + equipmentDetails['personal_equipment_category'] != null; + + if (_isPersonalEquipmentCategorySelected) { _selectedPersonalEquipmentCategoryId = equipmentDetails['personal_equipment_category']; _selectedType = personalEquipmentTypes.firstWhere((element) => element['id'] == _selectedPersonalEquipmentCategoryId)['name'] as String; - _isPersonalEquipmentCategorySelected = true; } else { _selectedGenericEquipmentCategoryId = equipmentDetails['generic_equipment_category']; _selectedType = genericEquipmentTypes.firstWhere((element) => element['id'] == _selectedGenericEquipmentCategoryId)['name'] as String; - _isPersonalEquipmentCategorySelected = false; } }); } catch (e) { @@ -249,6 +251,90 @@ class _AddEquipmentScreenState extends State { ); } + void _updateEquipment() async { + int? genericEquipmentCategory; + int? personalEquipmentCategory; + + if (_isPersonalEquipmentCategorySelected) { + genericEquipmentCategory = null; + personalEquipmentCategory = _selectedPersonalEquipmentCategoryId; + } else { + genericEquipmentCategory = _selectedGenericEquipmentCategoryId; + personalEquipmentCategory = null; + } + + final fireAlarmDetails = + await fireAlarmService.getFireAlarmById(widget.equipmentId!); + final equipmentId = fireAlarmDetails['equipment']; + + final Map equipmentTypeUpdate = { + "generic_equipment_category": genericEquipmentCategory, + "personal_equipment_category": personalEquipmentCategory, + "place_owner": fireAlarmDetails['place_owner'], + }; + + bool typeUpdateSuccess = await equipmentService.updateEquipmentType( + equipmentId, equipmentTypeUpdate); + + if (typeUpdateSuccess) { + final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( + area: widget.areaId, + system: widget.categoryNumber, + ); + + final FireAlarmEquipmentRequestModel fireAlarmEquipmentDetail = + FireAlarmEquipmentRequestModel( + genericEquipmentCategory: genericEquipmentCategory, + personalEquipmentCategory: personalEquipmentCategory, + fireAlarmRequestModel: fireAlarmModel, + ); + + bool fireAlarmUpdateSuccess = await fireAlarmService.updateFireAlarm( + widget.equipmentId!, fireAlarmEquipmentDetail); + + if (fireAlarmUpdateSuccess) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Detalhes do equipamento atualizados com sucesso.'), + backgroundColor: Colors.green, + ), + ); + Navigator.pushReplacementNamed( + context, + '/listFireAlarms', + arguments: { + 'areaName': widget.areaName, + 'categoryNumber': widget.categoryNumber, + 'localName': widget.localName, + 'localId': widget.localId, + 'areaId': widget.areaId, + }, + ); + setState(() { + _equipmentQuantityController.clear(); + _selectedType = null; + _selectedPersonalEquipmentCategoryId = null; + _selectedGenericEquipmentCategoryId = null; + _images.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao atualizar os detalhes do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Falha ao atualizar o tipo do equipamento.'), + backgroundColor: Colors.red, + ), + ); + } + } + Future _registerPersonalEquipmentType() async { int systemId = widget.categoryNumber; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = @@ -420,7 +506,9 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: const Text('Adicionar'), + child: widget.equipmentId == null + ? const Text('Adicionar') + : const Text('Atualizar'), onPressed: () { _registerEquipment(); }, @@ -455,8 +543,16 @@ class _AddEquipmentScreenState extends State { fireAlarmRequestModel: fireAlarmModel, ); - int? equipmentId = - await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); + int? equipmentId; + if (widget.isEdit) { + equipmentId = widget.equipmentId; + bool success = await fireAlarmService.updateFireAlarm( + equipmentId!, fireAlarmEquipmentDetail); + if (!success) equipmentId = null; + } else { + equipmentId = + await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); + } if (equipmentId != null) { await Future.wait(_images.map((imageData) async { @@ -464,7 +560,7 @@ class _AddEquipmentScreenState extends State { EquipmentPhotoRequestModel( photo: imageData.imageFile, description: imageData.description, - equipment: equipmentId, + equipment: equipmentId!, ), ); })); @@ -550,9 +646,12 @@ class _AddEquipmentScreenState extends State { borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), ), - child: const Center( - child: Text('Adicionar equipamentos ', - style: TextStyle( + child: Center( + child: Text( + widget.equipmentId == null + ? 'Adicionar Equipamento' + : 'Editar Equipamento', + style: const TextStyle( fontSize: 26, fontWeight: FontWeight.bold, color: Colors.white)), @@ -721,10 +820,14 @@ class _AddEquipmentScreenState extends State { MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ))), - onPressed: _showConfirmationDialog, - child: const Text( - 'ADICIONAR EQUIPAMENTO', - style: TextStyle( + onPressed: widget.isEdit + ? _updateEquipment + : _showConfirmationDialog, + child: Text( + widget.isEdit + ? 'ATUALIZAR EQUIPAMENTO' + : 'ADICIONAR EQUIPAMENTO', + style: const TextStyle( fontSize: 17, fontWeight: FontWeight.bold), ), ), diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index fc3ce24e..7d94f76c 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -43,7 +43,7 @@ class _ListFireAlarmsState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => AddfireAlarm( + builder: (context) => AddFireAlarm( areaName: widget.areaName, categoryNumber: widget.categoryNumber, localName: widget.localName, @@ -59,13 +59,14 @@ class _ListFireAlarmsState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => AddfireAlarm( + builder: (context) => AddFireAlarm( areaName: widget.areaName, categoryNumber: widget.categoryNumber, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, - equipmentId: equipmentId, // Passando o ID do equipamento + equipmentId: equipmentId, + isEdit: true, ), ), ); From 1ab76f02f2345f1a8a61b73965bbcd4198ddd17d Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 8 Jul 2024 12:41:29 -0300 Subject: [PATCH 312/351] =?UTF-8?q?Fun=C3=A7=C3=A3o=20de=20gerar=20relat?= =?UTF-8?q?=C3=B3rio=20iniciada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire_alarm/list_fire_alarms.dart | 1 - .../sige_ie/lib/facilities/ui/facilities.dart | 50 ++++++++++++++++++- .../Flutter/GeneratedPluginRegistrant.swift | 2 + frontend/sige_ie/pubspec.lock | 24 +++++++++ frontend/sige_ie/pubspec.yaml | 4 +- 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 7d94f76c..5aa5dba4 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -29,7 +29,6 @@ class _ListFireAlarmsState extends State { final FireAlarmEquipmentService _fireAlarmService = FireAlarmEquipmentService(); - // Chave global para ScaffoldMessenger final GlobalKey _scaffoldMessengerKey = GlobalKey(); diff --git a/frontend/sige_ie/lib/facilities/ui/facilities.dart b/frontend/sige_ie/lib/facilities/ui/facilities.dart index dfddaf9c..5595f6d6 100644 --- a/frontend/sige_ie/lib/facilities/ui/facilities.dart +++ b/frontend/sige_ie/lib/facilities/ui/facilities.dart @@ -6,6 +6,10 @@ import 'package:sige_ie/places/data/place_response_model.dart'; import 'package:sige_ie/places/data/place_service.dart'; import '../../config/app_styles.dart'; import '../../areas/data/area_service.dart'; +import 'dart:io'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as path; class FacilitiesPage extends StatefulWidget { const FacilitiesPage({super.key}); @@ -365,6 +369,31 @@ class _FacilitiesPageState extends State { ); } + Future _exportToPDF(int placeId) async { + final response = await http + .get(Uri.parse('http://10.0.2.2:8000/api/places/$placeId/report')); + + if (response.statusCode == 200) { + final directory = await getApplicationDocumentsDirectory(); + final filePath = path.join(directory.path, 'report_$placeId.pdf'); + final file = File(filePath); + await file.writeAsBytes(response.bodyBytes); + + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( + SnackBar(content: Text('PDF baixado com sucesso: $filePath')), + ); + } else { + ScaffoldMessenger.of(_scaffoldContext).showSnackBar( + const SnackBar(content: Text('Falha ao baixar o PDF')), + ); + } + } + + void _exportToExcel(int placeId) { + // Implement your Excel export logic here + print("Export to Excel for place $placeId"); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -442,10 +471,27 @@ class _FacilitiesPageState extends State { onPressed: () => _confirmDelete(_scaffoldContext, place), ), - IconButton( + PopupMenuButton( icon: const Icon(Icons.description, color: AppColors.lightText), - onPressed: () {}, + onSelected: (value) { + if (value == 'pdf') { + _exportToPDF(place.id); + } else if (value == 'excel') { + _exportToExcel(place.id); + } + }, + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + value: 'pdf', + child: Text('Exportar para PDF'), + ), + const PopupMenuItem( + value: 'excel', + child: Text('Exportar para Excel'), + ), + ], ), ], ), diff --git a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift index af3162f6..797a88c0 100644 --- a/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/sige_ie/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,14 @@ import Foundation import file_selector_macos import geolocator_apple +import path_provider_foundation import shared_preferences_foundation import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index e985cef1..2508e2d9 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -480,6 +480,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + url: "https://pub.dev" + source: hosted + version: "2.2.4" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" path_provider_linux: dependency: transitive description: diff --git a/frontend/sige_ie/pubspec.yaml b/frontend/sige_ie/pubspec.yaml index 813747ff..d53f4531 100644 --- a/frontend/sige_ie/pubspec.yaml +++ b/frontend/sige_ie/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: sdk: flutter shared_preferences: ^2.0.15 video_player: ^2.2.14 - http: ^0.13.3 + http: ^0.13.4 cookie_jar: ^4.0.8 http_interceptor: ^1.0.1 image_picker: ^0.8.5 @@ -44,6 +44,8 @@ dependencies: geolocator: ^9.0.2 logger: ^1.0.0 logging: ^1.2.0 + path_provider: ^2.0.2 + dev_dependencies: flutter_test: From a6b4f966e49eb3b989d4edffb0f085038a67229a Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 8 Jul 2024 12:46:26 -0300 Subject: [PATCH 313/351] =?UTF-8?q?Adicionando=20detalhes=20=C3=A0=20tela?= =?UTF-8?q?=20de=20edi=C3=A7=C3=A3o=20de=20fire=20alarm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire_alarm/add_fire_alarm.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index afc3f3a8..e889ac9d 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -506,11 +506,16 @@ class _AddEquipmentScreenState extends State { }, ), TextButton( - child: widget.equipmentId == null - ? const Text('Adicionar') - : const Text('Atualizar'), + child: widget.isEdit + ? const Text('Atualizar') + : const Text('Adicionar'), onPressed: () { - _registerEquipment(); + if (widget.isEdit) { + _updateEquipment(); + } else { + _registerEquipment(); + } + Navigator.of(context).pop(); }, ), ], @@ -820,9 +825,7 @@ class _AddEquipmentScreenState extends State { MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ))), - onPressed: widget.isEdit - ? _updateEquipment - : _showConfirmationDialog, + onPressed: _showConfirmationDialog, child: Text( widget.isEdit ? 'ATUALIZAR EQUIPAMENTO' From 0371fdbcb8a10ac448891192e2146b1138811639 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 8 Jul 2024 13:28:20 -0300 Subject: [PATCH 314/351] =?UTF-8?q?Adi=C3=A7=C3=A3o=20de=20prints=20para?= =?UTF-8?q?=20verificar=20se=20fotos=20tem=20descri=C3=A7=C3=A3o=20opciona?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipments/feature/fire_alarm/add_fire_alarm.dart | 4 +++- .../equipment-photo/equipment_photo_request_model.dart | 6 +++++- .../data/equipment-photo/equipment_photo_service.dart | 9 ++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index e889ac9d..d3bbcea6 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -560,11 +560,13 @@ class _AddEquipmentScreenState extends State { } if (equipmentId != null) { + print('Registering photos for equipment ID: $equipmentId'); await Future.wait(_images.map((imageData) async { + print('Creating photo with description: "${imageData.description}"'); await equipmentPhotoService.createPhoto( EquipmentPhotoRequestModel( photo: imageData.imageFile, - description: imageData.description, + description: imageData.description, // Pode ser nulo ou vazio equipment: equipmentId!, ), ); diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart index 63cbb1da..55d6d15d 100644 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart @@ -13,13 +13,17 @@ class EquipmentPhotoRequestModel { }) { String base64Image = base64Encode(photo.readAsBytesSync()); photoBase64 = 'data:image/jpeg;base64,$base64Image'; + print( + 'Created EquipmentPhotoRequestModel: photoBase64: $photoBase64, description: $description, equipment: $equipment'); } Map toJson() { - return { + Map json = { 'photo': photoBase64, 'description': description, 'equipment': equipment, }; + print('EquipmentPhotoRequestModel toJson: $json'); + return json; } } diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart index c3acca04..b2b960a1 100644 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart @@ -17,6 +17,9 @@ class EquipmentPhotoService { EquipmentPhotoRequestModel equipmentPhotoRequestModel) async { var url = Uri.parse(baseUrl); + print( + 'Sending request to: $url with body: ${jsonEncode(equipmentPhotoRequestModel.toJson())}'); + try { var response = await client.post( url, @@ -32,13 +35,17 @@ class EquipmentPhotoService { if (response.statusCode == 201 || response.statusCode == 200) { Map responseData = jsonDecode(response.body); _logger.info('Request successful, received ID: ${responseData['id']}'); + print('Request successful, received ID: ${responseData['id']}'); return true; } else { - _logger.info('Failed to register equipment photo: ${response.statusCode}'); + _logger + .info('Failed to register equipment photo: ${response.statusCode}'); + print('Failed to register equipment photo: ${response.statusCode}'); return false; } } catch (e) { _logger.info('Error during register: $e'); + print('Error during register: $e'); return false; } } From f838d2f164a30ff68edcb9c72cfa89e67b7cbb37 Mon Sep 17 00:00:00 2001 From: EngDann Date: Mon, 8 Jul 2024 14:53:00 -0300 Subject: [PATCH 315/351] =?UTF-8?q?Fotos=20com=20descri=C3=A7=C3=A3o=20nul?= =?UTF-8?q?a=20e=20fun=C3=A7=C3=A3o=20para=20pegar=20fotos=20pelo=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire_alarm/add_fire_alarm.dart | 30 ++++++++++-- .../equipment_photo_request_model.dart | 6 +-- .../equipment_photo_service.dart | 48 +++++++++++++++++++ 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index d3bbcea6..2bc8b7a8 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -77,6 +77,7 @@ class _AddEquipmentScreenState extends State { if (widget.isEdit && widget.equipmentId != null) { _fetchEquipmentDetails(widget.equipmentId!); + _fetchExistingPhotos(widget.equipmentId!); } } @@ -111,6 +112,25 @@ class _AddEquipmentScreenState extends State { } } + void _fetchExistingPhotos(int fireAlarmId) async { + try { + List> photos = + await equipmentPhotoService.getPhotosByEquipmentId(fireAlarmId); + + setState(() { + _images = photos.map((photo) { + return ImageData( + File(photo['imagePath']), + photo['description'] ?? '', + ); + }).toList(); + categoryImagesMap[widget.categoryNumber] = _images; + }); + } catch (e) { + print('Erro ao buscar fotos existentes: $e'); + } + } + Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService @@ -184,12 +204,15 @@ class _AddEquipmentScreenState extends State { child: const Text('Salvar'), onPressed: () { setState(() { + String? description = descriptionController.text.isEmpty + ? null + : descriptionController.text; if (existingImage != null) { - existingImage.description = descriptionController.text; + existingImage.description = description ?? ''; } else { final imageData = ImageData( imageFile, - descriptionController.text, + description ?? '', ); final categoryNumber = widget.categoryNumber; if (!categoryImagesMap.containsKey(categoryNumber)) { @@ -566,7 +589,8 @@ class _AddEquipmentScreenState extends State { await equipmentPhotoService.createPhoto( EquipmentPhotoRequestModel( photo: imageData.imageFile, - description: imageData.description, // Pode ser nulo ou vazio + description: + imageData.description.isEmpty ? null : imageData.description, equipment: equipmentId!, ), ); diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart index 55d6d15d..63cbb1da 100644 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_request_model.dart @@ -13,17 +13,13 @@ class EquipmentPhotoRequestModel { }) { String base64Image = base64Encode(photo.readAsBytesSync()); photoBase64 = 'data:image/jpeg;base64,$base64Image'; - print( - 'Created EquipmentPhotoRequestModel: photoBase64: $photoBase64, description: $description, equipment: $equipment'); } Map toJson() { - Map json = { + return { 'photo': photoBase64, 'description': description, 'equipment': equipment, }; - print('EquipmentPhotoRequestModel toJson: $json'); - return json; } } diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart index b2b960a1..d0a98586 100644 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart @@ -49,4 +49,52 @@ class EquipmentPhotoService { return false; } } + + Future>> getPhotosByEquipmentId( + int fireAlarmId) async { + try { + // Fetch the equipment ID from the fire alarm endpoint + var fireAlarmUrl = + Uri.parse('http://10.0.2.2:8000/api/fire-alarms/$fireAlarmId/'); + print('Fetching fire alarm details from: $fireAlarmUrl'); + var fireAlarmResponse = await client.get(fireAlarmUrl); + + if (fireAlarmResponse.statusCode != 200) { + _logger.warning( + 'Failed to fetch fire alarm details: ${fireAlarmResponse.statusCode}'); + print( + 'Failed to fetch fire alarm details: ${fireAlarmResponse.statusCode}'); + return []; + } + + Map fireAlarmData = jsonDecode(fireAlarmResponse.body); + print('Fire alarm data: $fireAlarmData'); + int equipmentId = fireAlarmData['equipment']; + + // Fetch the photos using the equipment ID + var photosUrl = Uri.parse( + 'http://10.0.2.2:8000/api/equipment-photos/by-equipment/$equipmentId/'); + print('Fetching photos from: $photosUrl'); + var photosResponse = await client.get(photosUrl); + + if (photosResponse.statusCode != 200) { + _logger.warning( + 'Failed to fetch equipment photos: ${photosResponse.statusCode}'); + print('Failed to fetch equipment photos: ${photosResponse.statusCode}'); + return []; + } + + List photosData = jsonDecode(photosResponse.body); + print('Photos data: $photosData'); + + _logger.info('Fetched equipment photos successfully'); + print('Fetched equipment photos successfully'); + + return List>.from(photosData); + } catch (e) { + _logger.severe('Error fetching photos by equipment ID: $e'); + print('Error fetching photos by equipment ID: $e'); + return []; + } + } } From c4d2e1ea15e9c1431f801ef4a25a0c1bb6f686de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Tue, 9 Jul 2024 15:44:23 -0300 Subject: [PATCH 316/351] backend: cria campos especificos de equipmentos --- api/equipments/models.py | 21 +++++++++++++++++++++ api/equipments/serializers.py | 8 ++++---- frontend/sige_ie/pubspec.lock | 24 ++++++++++++------------ 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/api/equipments/models.py b/api/equipments/models.py index fcb8bd7c..b42ee8f2 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -59,6 +59,11 @@ class IluminationEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="ilumination_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=1) + power = models.IntegerField(default=1) + tecnology = models.CharField(max_length=30, default=None) + format = models.CharField(max_length=30, default=None) + quantity = models.IntegerField(default=0) + class Meta: db_table = 'equipments_illuminations' @@ -68,6 +73,11 @@ class ElectricalLoadEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_load_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=2) + brand = models.CharField(max_length=30, default=None) + model = models.CharField(max_length=50, default=None) + power = models.IntegerField(default=0) + quantity = models.IntegerField(default=0) + class Meta: db_table = 'equipments_electrical_loads' @@ -86,6 +96,8 @@ class ElectricalCircuitEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name='electrical_circuit_equipment') equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=4) + size = models.IntegerField(default=0) + isolament = models.CharField(max_length=30, default=None) class Meta: db_table = 'equipments_electrical_circuits' @@ -95,6 +107,13 @@ class DistributionBoardEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="distribution_board_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=5) + power = models.IntegerField(default=0) + dr = models.BooleanField(default=False) + dps = models.BooleanField(default=False) + grounding = models.BooleanField(default=False) + type_material = models.CharField(max_length=30, null=True) + method_installation = models.CharField(max_length=50, null=True) + class Meta: db_table = 'equipments_distribution_boards' @@ -131,6 +150,8 @@ class RefrigerationEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="refrigeration_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=9) + power = models.IntegerField(default=0) + quantity = models.IntegerField(default=0) class Meta: db_table = 'equipments_refrigeration' diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index b2fc78fa..60a1a7b6 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -120,8 +120,8 @@ class DistributionBoardEquipmentResponseSerializer(EquipmentCategoryMixin, seria equipment_category = serializers.SerializerMethodField() class Meta: - model = StructuredCablingEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + model = DistributionBoardEquipment + fields = ['id', 'area', 'equipment_category', 'system', 'power', 'dr', 'dps', 'grounding', 'type_material', 'method_installation'] class ElectricalCircuitEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -129,7 +129,7 @@ class ElectricalCircuitEquipmentResponseSerializer(EquipmentCategoryMixin, seria class Meta: model = ElectricalCircuitEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + fields = ['id', 'area', 'equipment_category', 'system', 'size', 'isolament'] class ElectricalLineEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -161,7 +161,7 @@ class RefrigerationEquipmentResponseSerializer(EquipmentCategoryMixin, serialize class Meta: model = RefrigerationEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + fields = ['id', 'area', 'equipment_category', 'system', 'power', 'quantity'] class EquipmentSerializer(serializers.ModelSerializer): diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index e985cef1..8f9c048e 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -396,26 +396,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -460,10 +460,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mime: dependency: "direct main" description: @@ -649,10 +649,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" typed_data: dependency: transitive description: @@ -729,10 +729,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" web: dependency: transitive description: From 4084bd4a682e01b5af407e2cca541d26d4b5c0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kauan=20Jos=C3=A9?= Date: Tue, 9 Jul 2024 15:45:11 -0300 Subject: [PATCH 317/351] migrations --- ...distributionboardequipment_dps_and_more.py | 103 ++++++++++++++++++ .../0032_alter_place_place_owner.py | 20 ++++ 2 files changed, 123 insertions(+) create mode 100644 api/equipments/migrations/0033_distributionboardequipment_dps_and_more.py create mode 100644 api/places/migrations/0032_alter_place_place_owner.py diff --git a/api/equipments/migrations/0033_distributionboardequipment_dps_and_more.py b/api/equipments/migrations/0033_distributionboardequipment_dps_and_more.py new file mode 100644 index 00000000..cdd2e893 --- /dev/null +++ b/api/equipments/migrations/0033_distributionboardequipment_dps_and_more.py @@ -0,0 +1,103 @@ +# Generated by Django 4.2 on 2024-07-09 18:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0032_alter_atmosphericdischargeequipment_table_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='distributionboardequipment', + name='dps', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='distributionboardequipment', + name='dr', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='distributionboardequipment', + name='grounding', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='distributionboardequipment', + name='method_installation', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='distributionboardequipment', + name='power', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='distributionboardequipment', + name='type_material', + field=models.CharField(max_length=30, null=True), + ), + migrations.AddField( + model_name='electricalcircuitequipment', + name='isolament', + field=models.CharField(default=None, max_length=30), + ), + migrations.AddField( + model_name='electricalcircuitequipment', + name='size', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='electricalloadequipment', + name='brand', + field=models.CharField(default=None, max_length=30), + ), + migrations.AddField( + model_name='electricalloadequipment', + name='model', + field=models.CharField(default=None, max_length=50), + ), + migrations.AddField( + model_name='electricalloadequipment', + name='power', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='electricalloadequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='iluminationequipment', + name='format', + field=models.CharField(default=None, max_length=30), + ), + migrations.AddField( + model_name='iluminationequipment', + name='power', + field=models.IntegerField(default=1), + ), + migrations.AddField( + model_name='iluminationequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='iluminationequipment', + name='tecnology', + field=models.CharField(default=None, max_length=30), + ), + migrations.AddField( + model_name='refrigerationequipment', + name='power', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='refrigerationequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + ] diff --git a/api/places/migrations/0032_alter_place_place_owner.py b/api/places/migrations/0032_alter_place_place_owner.py new file mode 100644 index 00000000..f11ae6f0 --- /dev/null +++ b/api/places/migrations/0032_alter_place_place_owner.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2024-07-09 18:42 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_alter_placeowner_user'), + ('places', '0031_remove_place_access_code'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='place_owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='places', to='users.placeowner'), + ), + ] From 9b673815c8115c647143b337f23ecd75639cd286 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 10 Jul 2024 13:44:16 -0300 Subject: [PATCH 318/351] backend: Ajusta retorno do campo de photo --- api/equipments/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 60a1a7b6..3e116d86 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -19,7 +19,7 @@ class Meta: class EquipmentPhotoSerializer(serializers.ModelSerializer): - photo = serializers.CharField(write_only=True) + photo = serializers.CharField() class Meta: model = EquipmentPhoto From e71ff1014c1828678c0d3b319d82dbac451d4546 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 12 Jul 2024 16:16:25 -0300 Subject: [PATCH 319/351] =?UTF-8?q?frontend:=20Ajusta=20funcionalidade=20d?= =?UTF-8?q?e=20visualizar=20a=20foto=20na=20parte=20de=20edi=C3=A7=C3=A3o?= =?UTF-8?q?=20e=20corrige=20outras=20p=C3=A1ginas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipments/data/equipment_service.dart | 10 +- .../fire_alarm_response_by_area_model.dart | 23 +++ .../fire_alarm/fire_alarm_response_model.dart | 6 +- .../data/fire_alarm/fire_alarm_service.dart | 26 ++-- .../structured_cabling_service.dart | 6 +- .../add_atmospheric_discharges_equipment.dart | 32 ++-- .../atmospheric_discharges_list.dart | 6 +- .../add_distribuition_board.dart | 28 ++-- .../distribuition_board_equipment_list.dart | 6 +- .../add_electrical_circuit.dart | 28 ++-- .../electrical_circuit_list.dart | 6 +- .../electrical_line/add_electrical_line.dart | 28 ++-- .../electrical_line/electrical_line_list.dart | 6 +- .../electrical_load/add_electrical_load.dart | 28 ++-- .../electrical_load/eletrical_load_list.dart | 6 +- .../feature/fire_alarm/add_fire_alarm.dart | 143 ++++++++++-------- .../feature/fire_alarm/list_fire_alarms.dart | 40 ++--- .../add_ilumination_equipment.dart | 28 ++-- .../ilumination_equipment_list.dart | 6 +- .../refrigerations/add_refrigeration.dart | 28 ++-- .../refrigeration_equipment_list.dart | 6 +- .../add_structured_cabling.dart | 28 ++-- .../structured_cabling_equipment_list.dart | 6 +- .../feature/system_configuration.dart | 20 +-- frontend/sige_ie/lib/main.dart | 36 ++--- .../equipment_photo_response_model.dart | 36 +++++ .../equipment_photo_service.dart | 50 ++---- .../personal_equipment_category_service.dart | 5 +- 28 files changed, 374 insertions(+), 303 deletions(-) create mode 100644 frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart create mode 100644 frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_response_model.dart diff --git a/frontend/sige_ie/lib/equipments/data/equipment_service.dart b/frontend/sige_ie/lib/equipments/data/equipment_service.dart index 5e9a9f3b..be56b2f0 100644 --- a/frontend/sige_ie/lib/equipments/data/equipment_service.dart +++ b/frontend/sige_ie/lib/equipments/data/equipment_service.dart @@ -21,8 +21,8 @@ class EquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getEquipmentById(int id) async { - var url = Uri.parse('http://10.0.2.2:8000/api/equipments/$id/'); + Future> getEquipmentById(int equipmentId) async { + var url = Uri.parse('http://10.0.2.2:8000/api/equipments/$equipmentId/'); try { var response = await client.get(url); if (response.statusCode == 200) { @@ -38,7 +38,7 @@ class EquipmentService { } } - Future updateEquipmentType(int id, Map data) async { + Future updateEquipment(int id, Map data) async { var url = Uri.parse('http://10.0.2.2:8000/api/equipments/$id/'); try { @@ -273,14 +273,14 @@ class EquipmentService { Future createRefrigerations( RefrigerationsEquipmentRequestModel - RefrigerationsEquipmentRequestModel) async { + refrigerationsEquipmentRequestModel) async { var url = Uri.parse(baseUrl); try { var response = await client.post( url, headers: {'Content-Type': 'application/json'}, - body: jsonEncode(RefrigerationsEquipmentRequestModel.toJson()), + body: jsonEncode(refrigerationsEquipmentRequestModel.toJson()), ); _logger.info('Response status code: ${response.statusCode}'); diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart new file mode 100644 index 00000000..dbaf9b5d --- /dev/null +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart @@ -0,0 +1,23 @@ +class FireAlarmEquipmentResponseByAreaModel { + int id; + int area; + String equipmentCategory; + int system; + + FireAlarmEquipmentResponseByAreaModel({ + required this.id, + required this.area, + required this.equipmentCategory, + required this.system, + }); + + factory FireAlarmEquipmentResponseByAreaModel.fromJson( + Map json) { + return FireAlarmEquipmentResponseByAreaModel( + id: json['id'], + area: json['area'], + equipmentCategory: json['equipment_category'], + system: json['system'], + ); + } +} diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart index fd9275a2..ef863133 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart @@ -1,13 +1,13 @@ class FireAlarmEquipmentResponseModel { int id; int area; - String equipmentCategory; + int equipment; int system; FireAlarmEquipmentResponseModel({ required this.id, required this.area, - required this.equipmentCategory, + required this.equipment, required this.system, }); @@ -15,7 +15,7 @@ class FireAlarmEquipmentResponseModel { return FireAlarmEquipmentResponseModel( id: json['id'], area: json['area'], - equipmentCategory: json['equipment_category'], + equipment: json['equipment'], system: json['system'], ); } diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart index 1b6e72a4..c2111176 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart @@ -3,6 +3,7 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_model.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart'; @@ -14,7 +15,7 @@ class FireAlarmEquipmentService { interceptors: [AuthInterceptor(cookieJar)], ); - Future> getFireAlarmListByArea( + Future> getFireAlarmListByArea( int areaId) async { var url = Uri.parse('${baseUrl}fire-alarms/by-area/$areaId/'); try { @@ -23,7 +24,7 @@ class FireAlarmEquipmentService { if (response.statusCode == 200) { List dataList = jsonDecode(response.body); return dataList - .map((data) => FireAlarmEquipmentResponseModel.fromJson(data)) + .map((data) => FireAlarmEquipmentResponseByAreaModel.fromJson(data)) .toList(); } else { _logger.info( @@ -37,13 +38,13 @@ class FireAlarmEquipmentService { } } - Future deleteFireAlarm(int equipmentId) async { - var url = Uri.parse('${baseUrl}fire-alarms/$equipmentId/'); + Future deleteFireAlarm(int fireAlarmId) async { + var url = Uri.parse('${baseUrl}fire-alarms/$fireAlarmId/'); try { var response = await client.delete(url); if (response.statusCode == 204) { _logger.info( - 'Successfully deleted fire alarm equipment with ID: $equipmentId'); + 'Successfully deleted fire alarm equipment with ID: $fireAlarmId'); } else { _logger.info( 'Failed to delete fire alarm equipment with status code: ${response.statusCode}'); @@ -56,12 +57,14 @@ class FireAlarmEquipmentService { } } - Future> getFireAlarmById(int id) async { - var url = Uri.parse('${baseUrl}fire-alarms/$id/'); + Future getFireAlarmById( + int fireAlarmId) async { + var url = Uri.parse('${baseUrl}fire-alarms/$fireAlarmId/'); try { var response = await client.get(url); if (response.statusCode == 200) { - return jsonDecode(response.body); + var jsonResponse = jsonDecode(response.body); + return FireAlarmEquipmentResponseModel.fromJson(jsonResponse); } else { _logger.info( 'Failed to load fire alarm with status code: ${response.statusCode}'); @@ -73,9 +76,9 @@ class FireAlarmEquipmentService { } } - Future updateFireAlarm(int id, + Future updateFireAlarm(int fireAlarmId, FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { - var url = Uri.parse('${baseUrl}fire-alarms/$id/'); + var url = Uri.parse('${baseUrl}fire-alarms/$fireAlarmId/'); try { var response = await client.put( @@ -85,7 +88,8 @@ class FireAlarmEquipmentService { ); if (response.statusCode == 200) { - _logger.info('Successfully updated fire alarm equipment with ID: $id'); + _logger.info( + 'Successfully updated fire alarm equipment with ID: $fireAlarmId'); return true; } else { _logger.info( diff --git a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart index 490734cd..59d278a1 100644 --- a/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart +++ b/frontend/sige_ie/lib/equipments/data/structured_cabling/structured_cabling_service.dart @@ -37,13 +37,13 @@ class StructuredCablingEquipmentService { } } - Future deleteStructuredCabling(int equipmentId) async { - var url = Uri.parse('${baseUrl}structured-cabling/$equipmentId/'); + Future deleteStructuredCabling(int structuredCablingId) async { + var url = Uri.parse('${baseUrl}structured-cabling/$structuredCablingId/'); try { var response = await client.delete(url); if (response.statusCode == 204) { _logger.info( - 'Successfully deleted structured cabling equipment with ID: $equipmentId'); + 'Successfully deleted structured cabling equipment with ID: $structuredCablingId'); } else { _logger.info( 'Failed to delete structured cabling equipment with status code: ${response.statusCode}'); diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart index 9941bb46..9d27d064 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/add_atmospheric_discharges_equipment.dart @@ -29,13 +29,13 @@ class AddAtmosphericEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; const AddAtmosphericEquipmentScreen({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -80,11 +80,11 @@ class _AddEquipmentScreenState extends State { Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); if (mounted) { setState(() { @@ -105,7 +105,7 @@ class _AddEquipmentScreenState extends State { @override void dispose() { _equipmentQuantityController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -156,12 +156,12 @@ class _AddEquipmentScreenState extends State { } else { final imageData = ImageData(imageFile, descriptionController.text); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -217,7 +217,7 @@ class _AddEquipmentScreenState extends State { } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -271,7 +271,7 @@ class _AddEquipmentScreenState extends State { return; } - int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + int personalCategoryId = personalEquipmentMap[_selectedTypeToDelete]!; showDialog( context: context, @@ -292,7 +292,7 @@ class _AddEquipmentScreenState extends State { onPressed: () async { Navigator.of(context).pop(); bool success = await personalEquipmentCategoryService - .deletePersonalEquipmentCategory(equipmentId); + .deletePersonalEquipmentCategory(personalCategoryId); if (success) { setState(() { @@ -410,7 +410,7 @@ class _AddEquipmentScreenState extends State { final AtmosphericRequestModel atmosphericModel = AtmosphericRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final AtmosphericEquipmentRequestModel atmosphericEquipmentDetail = @@ -445,7 +445,7 @@ class _AddEquipmentScreenState extends State { '/listatmosphericEquipment', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -494,7 +494,7 @@ class _AddEquipmentScreenState extends State { '/listatmosphericEquipment', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart index 9b6efb52..67a461b8 100644 --- a/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/atmospheric-discharges/atmospheric_discharges_list.dart @@ -6,14 +6,14 @@ import 'package:sige_ie/equipments/feature/atmospheric-discharges/add_atmospheri class ListAtmosphericEquipment extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListAtmosphericEquipment({ Key? key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -61,7 +61,7 @@ class _ListAtmosphericEquipmentState extends State { MaterialPageRoute( builder: (context) => AddAtmosphericEquipmentScreen( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart index 7b8488d7..c994ef40 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart @@ -29,13 +29,13 @@ class AddDistribuitionBoard extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; const AddDistribuitionBoard({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -81,11 +81,11 @@ class _AddDistribuitionBoardState extends State { Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); if (mounted) { setState(() { @@ -107,7 +107,7 @@ class _AddDistribuitionBoardState extends State { void dispose() { _equipmentChargeController.dispose(); _equipmentQuantityController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -158,12 +158,12 @@ class _AddDistribuitionBoardState extends State { } else { final imageData = ImageData(imageFile, descriptionController.text); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -219,7 +219,7 @@ class _AddDistribuitionBoardState extends State { } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -417,7 +417,7 @@ class _AddDistribuitionBoardState extends State { final DistributionRequestModel distributionModel = DistributionRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final DistributionEquipmentRequestModel distributionEquipmentDetail = @@ -452,7 +452,7 @@ class _AddDistribuitionBoardState extends State { '/listDistribuitionBoard', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -503,7 +503,7 @@ class _AddDistribuitionBoardState extends State { '/listDistribuitionBoard', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart index a39d6e80..07cf41c2 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/distribuition_board_equipment_list.dart @@ -6,14 +6,14 @@ import 'package:sige_ie/equipments/feature/distribuition_board/add_distribuition class ListDistributionBoard extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListDistributionBoard({ Key? key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -60,7 +60,7 @@ class _ListDistributionBoardState extends State { MaterialPageRoute( builder: (context) => AddDistribuitionBoard( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart index ebd27a86..3dfe3dd3 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/add_electrical_circuit.dart @@ -29,13 +29,13 @@ class AddElectricalCircuitEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; const AddElectricalCircuitEquipmentScreen({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -86,11 +86,11 @@ class _AddElectricalCircuitEquipmentScreenState Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); if (mounted) { setState(() { @@ -115,7 +115,7 @@ class _AddElectricalCircuitEquipmentScreenState _breakerStateController.dispose(); _wireTypeController.dispose(); _dimensionController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -166,12 +166,12 @@ class _AddElectricalCircuitEquipmentScreenState } else { final imageData = ImageData(imageFile, descriptionController.text); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -227,7 +227,7 @@ class _AddElectricalCircuitEquipmentScreenState } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -437,7 +437,7 @@ class _AddElectricalCircuitEquipmentScreenState final EletricalCircuitRequestModel electricalCircuitModel = EletricalCircuitRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final EletricalCircuitEquipmentRequestModel @@ -473,7 +473,7 @@ class _AddElectricalCircuitEquipmentScreenState '/electricalCircuitList', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -519,7 +519,7 @@ class _AddElectricalCircuitEquipmentScreenState '/electricalCircuitList', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart index 8b07d0f2..83429d43 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_circuit/electrical_circuit_list.dart @@ -6,14 +6,14 @@ import 'package:sige_ie/equipments/feature/electrical_circuit/add_electrical_cir class ListCircuitEquipment extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListCircuitEquipment({ Key? key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -61,7 +61,7 @@ class _ListCircuitEquipmentState extends State { MaterialPageRoute( builder: (context) => AddElectricalCircuitEquipmentScreen( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart index 6d3b3ed6..ffad9bab 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_line/add_electrical_line.dart @@ -30,13 +30,13 @@ class AddElectricalLineScreen extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; const AddElectricalLineScreen({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -81,11 +81,11 @@ class _AddEquipmentScreenState extends State { Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); if (mounted) { setState(() { @@ -106,7 +106,7 @@ class _AddEquipmentScreenState extends State { @override void dispose() { _equipmentQuantityController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -157,12 +157,12 @@ class _AddEquipmentScreenState extends State { } else { final imageData = ImageData(imageFile, descriptionController.text); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -218,7 +218,7 @@ class _AddEquipmentScreenState extends State { } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -411,7 +411,7 @@ class _AddEquipmentScreenState extends State { final EletricalLineRequestModel electricalLineModel = EletricalLineRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final EletricalLineEquipmentRequestModel electricalLineEquipmentDetail = @@ -446,7 +446,7 @@ class _AddEquipmentScreenState extends State { '/electricalLineList', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -495,7 +495,7 @@ class _AddEquipmentScreenState extends State { '/electricalLineList', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart index 91e602cc..7d159643 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_line/electrical_line_list.dart @@ -6,14 +6,14 @@ import 'package:sige_ie/equipments/feature/electrical_line/add_electrical_line.d class ListElectricalLineEquipment extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListElectricalLineEquipment({ Key? key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -63,7 +63,7 @@ class _ListElectricalLineEquipmentState MaterialPageRoute( builder: (context) => AddElectricalLineScreen( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart index 3d6bcf31..5d8ac559 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/add_electrical_load.dart @@ -29,13 +29,13 @@ class AddElectricalLoadEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; const AddElectricalLoadEquipmentScreen({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -85,11 +85,11 @@ class _AddElectricalLoadEquipmentScreenState Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); if (mounted) { setState(() { @@ -113,7 +113,7 @@ class _AddElectricalLoadEquipmentScreenState _equipmentModelController.dispose(); _equipmentQuantityController.dispose(); _equipmentLoadController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -164,12 +164,12 @@ class _AddElectricalLoadEquipmentScreenState } else { final imageData = ImageData(imageFile, descriptionController.text); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -225,7 +225,7 @@ class _AddElectricalLoadEquipmentScreenState } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -434,7 +434,7 @@ class _AddElectricalLoadEquipmentScreenState final EletricalLoadRequestModel EletricalLoadModel = EletricalLoadRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final EletricalLoadEquipmentRequestModel EletricalLoadEquipmentDetail = @@ -469,7 +469,7 @@ class _AddElectricalLoadEquipmentScreenState '/listelectricalLoadEquipment', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -524,7 +524,7 @@ class _AddElectricalLoadEquipmentScreenState '/listelectricalLoadEquipment', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart index b016ab99..97eaee52 100644 --- a/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/electrical_load/eletrical_load_list.dart @@ -6,14 +6,14 @@ import 'package:sige_ie/equipments/feature/electrical_load/add_electrical_load.d class ListElectricalLoadEquipment extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListElectricalLoadEquipment({ Key? key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -72,7 +72,7 @@ class _ListElectricalLoadEquipmentState MaterialPageRoute( builder: (context) => AddElectricalLoadEquipmentScreen( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index 2bc8b7a8..4138ffc1 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -4,8 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:sige_ie/config/app_styles.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_model.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_service.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_response_model.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_service.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_response_model.dart'; import 'package:sige_ie/shared/data/generic-equipment-category/generic_equipment_category_service.dart'; @@ -20,7 +22,11 @@ class ImageData { File imageFile; String description; - ImageData(this.imageFile, this.description) : id = Random().nextInt(1000000); + ImageData({ + int? id, + required this.imageFile, + required this.description, + }) : id = id ?? Random().nextInt(1000000); } List _images = []; @@ -30,19 +36,19 @@ class AddFireAlarm extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; - final int? equipmentId; + final int? fireAlarmId; final bool isEdit; const AddFireAlarm({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, - this.equipmentId, + this.fireAlarmId, this.isEdit = false, }); @@ -65,29 +71,47 @@ class _AddEquipmentScreenState extends State { bool _isPersonalEquipmentCategorySelected = false; String? _newEquipmentTypeName; String? _selectedTypeToDelete; - + int? equipmentId; List> genericEquipmentTypes = []; List> personalEquipmentTypes = []; Map personalEquipmentMap = {}; + FireAlarmEquipmentResponseModel? fireAlarmEquipmentResponseModel; @override void initState() { super.initState(); _fetchEquipmentCategory(); + if (widget.isEdit && widget.fireAlarmId != null) { + _initializeData(widget.fireAlarmId!); + } + } + + Future _initializeData(int fireAlarmId) async { + try { + await _fetchFireAlarmEquipment(fireAlarmId); + + if (fireAlarmEquipmentResponseModel != null) { + setState(() { + equipmentId = fireAlarmEquipmentResponseModel!.equipment; + }); - if (widget.isEdit && widget.equipmentId != null) { - _fetchEquipmentDetails(widget.equipmentId!); - _fetchExistingPhotos(widget.equipmentId!); + _fetchEquipmentDetails(fireAlarmEquipmentResponseModel!.equipment); + _fetchExistingPhotos(fireAlarmEquipmentResponseModel!.equipment); + } + } catch (error) { + print('Error: $error'); } } + Future _fetchFireAlarmEquipment(int fireAlarmId) async { + fireAlarmEquipmentResponseModel = + await fireAlarmService.getFireAlarmById(fireAlarmId); + } + Future _fetchEquipmentDetails(int equipmentId) async { try { - final fireAlarmDetails = - await fireAlarmService.getFireAlarmById(equipmentId); - - final equipmentDetails = await equipmentService - .getEquipmentById(fireAlarmDetails['equipment']); + final equipmentDetails = + await equipmentService.getEquipmentById(equipmentId); setState(() { _isPersonalEquipmentCategorySelected = @@ -112,19 +136,25 @@ class _AddEquipmentScreenState extends State { } } - void _fetchExistingPhotos(int fireAlarmId) async { + void _fetchExistingPhotos(int equipmentId) async { try { - List> photos = - await equipmentPhotoService.getPhotosByEquipmentId(fireAlarmId); + List photos = + await equipmentPhotoService.getPhotosByEquipmentId(equipmentId); + + List imageList = []; + + for (var photo in photos) { + File imageFile = await photo.toFile(); + imageList.add(ImageData( + id: photo.id, + imageFile: imageFile, + description: photo.description ?? '', + )); + } setState(() { - _images = photos.map((photo) { - return ImageData( - File(photo['imagePath']), - photo['description'] ?? '', - ); - }).toList(); - categoryImagesMap[widget.categoryNumber] = _images; + _images = imageList; + categoryImagesMap[widget.systemId] = _images; }); } catch (e) { print('Erro ao buscar fotos existentes: $e'); @@ -134,11 +164,11 @@ class _AddEquipmentScreenState extends State { Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); setState(() { genericEquipmentTypes = genericEquipmentCategoryList @@ -157,7 +187,7 @@ class _AddEquipmentScreenState extends State { @override void dispose() { _equipmentQuantityController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -211,15 +241,15 @@ class _AddEquipmentScreenState extends State { existingImage.description = description ?? ''; } else { final imageData = ImageData( - imageFile, - description ?? '', + imageFile: imageFile, + description: description ?? '', ); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -286,23 +316,18 @@ class _AddEquipmentScreenState extends State { personalEquipmentCategory = null; } - final fireAlarmDetails = - await fireAlarmService.getFireAlarmById(widget.equipmentId!); - final equipmentId = fireAlarmDetails['equipment']; - final Map equipmentTypeUpdate = { "generic_equipment_category": genericEquipmentCategory, "personal_equipment_category": personalEquipmentCategory, - "place_owner": fireAlarmDetails['place_owner'], }; - bool typeUpdateSuccess = await equipmentService.updateEquipmentType( - equipmentId, equipmentTypeUpdate); + bool typeUpdateSuccess = await equipmentService.updateEquipment( + equipmentId!, equipmentTypeUpdate); if (typeUpdateSuccess) { final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final FireAlarmEquipmentRequestModel fireAlarmEquipmentDetail = @@ -313,7 +338,7 @@ class _AddEquipmentScreenState extends State { ); bool fireAlarmUpdateSuccess = await fireAlarmService.updateFireAlarm( - widget.equipmentId!, fireAlarmEquipmentDetail); + widget.fireAlarmId!, fireAlarmEquipmentDetail); if (fireAlarmUpdateSuccess) { ScaffoldMessenger.of(context).showSnackBar( @@ -327,7 +352,7 @@ class _AddEquipmentScreenState extends State { '/listFireAlarms', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -359,7 +384,7 @@ class _AddEquipmentScreenState extends State { } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -415,7 +440,7 @@ class _AddEquipmentScreenState extends State { return; } - int equipmentId = personalEquipmentMap[_selectedTypeToDelete]!; + int personalCategoryId = personalEquipmentMap[_selectedTypeToDelete]!; showDialog( context: context, @@ -436,7 +461,7 @@ class _AddEquipmentScreenState extends State { onPressed: () async { Navigator.of(context).pop(); bool success = await personalEquipmentCategoryService - .deletePersonalEquipmentCategory(equipmentId); + .deletePersonalEquipmentCategory(personalCategoryId); if (success) { setState(() { @@ -561,7 +586,7 @@ class _AddEquipmentScreenState extends State { final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final FireAlarmEquipmentRequestModel fireAlarmEquipmentDetail = @@ -571,19 +596,19 @@ class _AddEquipmentScreenState extends State { fireAlarmRequestModel: fireAlarmModel, ); - int? equipmentId; + int? fireAlarmId; if (widget.isEdit) { - equipmentId = widget.equipmentId; + fireAlarmId = widget.fireAlarmId; bool success = await fireAlarmService.updateFireAlarm( - equipmentId!, fireAlarmEquipmentDetail); - if (!success) equipmentId = null; + fireAlarmId!, fireAlarmEquipmentDetail); + if (!success) fireAlarmId = null; } else { - equipmentId = + fireAlarmId = await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); } - if (equipmentId != null) { - print('Registering photos for equipment ID: $equipmentId'); + if (equipmentId! != 0) { + print('Registering photos for equipment ID: $fireAlarmId'); await Future.wait(_images.map((imageData) async { print('Creating photo with description: "${imageData.description}"'); await equipmentPhotoService.createPhoto( @@ -591,7 +616,7 @@ class _AddEquipmentScreenState extends State { photo: imageData.imageFile, description: imageData.description.isEmpty ? null : imageData.description, - equipment: equipmentId!, + equipment: equipmentId, ), ); })); @@ -607,7 +632,7 @@ class _AddEquipmentScreenState extends State { '/listFireAlarms', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -656,7 +681,7 @@ class _AddEquipmentScreenState extends State { '/listFireAlarms', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -679,7 +704,7 @@ class _AddEquipmentScreenState extends State { ), child: Center( child: Text( - widget.equipmentId == null + widget.fireAlarmId == null ? 'Adicionar Equipamento' : 'Editar Equipamento', style: const TextStyle( diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart index 5aa5dba4..61b8b48f 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/list_fire_alarms.dart @@ -1,20 +1,20 @@ import 'package:flutter/material.dart'; import 'package:sige_ie/config/app_styles.dart'; -import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_model.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_service.dart'; import 'package:sige_ie/equipments/feature/fire_alarm/add_fire_alarm.dart'; class ListFireAlarms extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListFireAlarms({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -25,7 +25,7 @@ class ListFireAlarms extends StatefulWidget { } class _ListFireAlarmsState extends State { - late Future> _fireAlarmList; + late Future> _fireAlarmList; final FireAlarmEquipmentService _fireAlarmService = FireAlarmEquipmentService(); @@ -38,42 +38,42 @@ class _ListFireAlarmsState extends State { _fireAlarmList = _fireAlarmService.getFireAlarmListByArea(widget.areaId); } - void navigateToAddEquipment(BuildContext context) { + void navigateToAddFireAlarm(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddFireAlarm( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, - equipmentId: null, + fireAlarmId: null, ), ), ); } - void _editEquipment(BuildContext context, int equipmentId) { + void _editFireAlarm(BuildContext context, int fireAlarmId) { Navigator.push( context, MaterialPageRoute( builder: (context) => AddFireAlarm( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, - equipmentId: equipmentId, + fireAlarmId: fireAlarmId, isEdit: true, ), ), ); } - Future _deleteEquipment(BuildContext context, int equipmentId) async { + Future _deleteFireAlarm(BuildContext context, int fireAlarmId) async { try { - await _fireAlarmService.deleteFireAlarm(equipmentId); + await _fireAlarmService.deleteFireAlarm(fireAlarmId); setState(() { _fireAlarmList = _fireAlarmService.getFireAlarmListByArea(widget.areaId); @@ -94,7 +94,7 @@ class _ListFireAlarmsState extends State { } } - void _confirmDelete(BuildContext context, int equipmentId) { + void _confirmDelete(BuildContext context, int fireAlarmId) { showDialog( context: context, builder: (BuildContext context) { @@ -113,7 +113,7 @@ class _ListFireAlarmsState extends State { child: const Text('Excluir'), onPressed: () { Navigator.of(context).pop(); - _deleteEquipment(context, equipmentId); + _deleteFireAlarm(context, fireAlarmId); }, ), ], @@ -172,7 +172,7 @@ class _ListFireAlarmsState extends State { ), ), const SizedBox(height: 20), - FutureBuilder>( + FutureBuilder>( future: _fireAlarmList, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { @@ -185,7 +185,7 @@ class _ListFireAlarmsState extends State { physics: const NeverScrollableScrollPhysics(), itemCount: snapshot.data!.length, itemBuilder: (context, index) { - var equipment = snapshot.data![index]; + var fireAlarmEquipment = snapshot.data![index]; return Container( margin: const EdgeInsets.symmetric( horizontal: 10, vertical: 5), @@ -197,7 +197,7 @@ class _ListFireAlarmsState extends State { contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 10), title: Text( - equipment.equipmentCategory, + fireAlarmEquipment.equipmentCategory, style: const TextStyle( color: AppColors.lightText, fontWeight: FontWeight.bold), @@ -209,13 +209,13 @@ class _ListFireAlarmsState extends State { icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () => - _editEquipment(context, equipment.id), + _editFireAlarm(context, fireAlarmEquipment.id), ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => - _confirmDelete(context, equipment.id), + _confirmDelete(context, fireAlarmEquipment.id), ), ], ), @@ -243,7 +243,7 @@ class _ListFireAlarmsState extends State { ), ), floatingActionButton: FloatingActionButton( - onPressed: () => navigateToAddEquipment(context), + onPressed: () => navigateToAddFireAlarm(context), backgroundColor: AppColors.sigeIeYellow, child: const Icon(Icons.add, color: AppColors.sigeIeBlue), ), diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart index c90f21ac..f031fa27 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/add_ilumination_equipment.dart @@ -30,13 +30,13 @@ class AddIluminationEquipmentScreen extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; const AddIluminationEquipmentScreen({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -82,11 +82,11 @@ class _AddIlluminationScreenState extends State { Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); if (mounted) { setState(() { @@ -108,7 +108,7 @@ class _AddIlluminationScreenState extends State { void dispose() { _equipmentQuantityController.dispose(); _equipmentPowerController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -159,12 +159,12 @@ class _AddIlluminationScreenState extends State { } else { final imageData = ImageData(imageFile, descriptionController.text); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -220,7 +220,7 @@ class _AddIlluminationScreenState extends State { } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -418,7 +418,7 @@ class _AddIlluminationScreenState extends State { final IluminationRequestModel iluminationModel = IluminationRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final IluminationEquipmentRequestModel iluminationEquipmentDetail = @@ -453,7 +453,7 @@ class _AddIlluminationScreenState extends State { '/listIluminationEquipment', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -504,7 +504,7 @@ class _AddIlluminationScreenState extends State { '/listIluminationEquipment', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart index 7ac5cf72..45910396 100644 --- a/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/iluminations/ilumination_equipment_list.dart @@ -6,14 +6,14 @@ import 'package:sige_ie/equipments/feature/iluminations/add_ilumination_equipmen class ListIluminationEquipment extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListIluminationEquipment({ Key? key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -61,7 +61,7 @@ class _ListIluminationEquipmentState extends State { MaterialPageRoute( builder: (context) => AddIluminationEquipmentScreen( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart b/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart index e516880d..2d2f7472 100644 --- a/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart +++ b/frontend/sige_ie/lib/equipments/feature/refrigerations/add_refrigeration.dart @@ -29,13 +29,13 @@ class AddRefrigeration extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; const AddRefrigeration({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -81,11 +81,11 @@ class _AddRefrigerationState extends State { Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); if (mounted) { setState(() { @@ -107,7 +107,7 @@ class _AddRefrigerationState extends State { void dispose() { _equipmentQuantityController.dispose(); _equipmentPowerController.dispose(); - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -158,12 +158,12 @@ class _AddRefrigerationState extends State { } else { final imageData = ImageData(imageFile, descriptionController.text); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -219,7 +219,7 @@ class _AddRefrigerationState extends State { } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -418,7 +418,7 @@ class _AddRefrigerationState extends State { final RefrigerationsRequestModel refrigerationsModel = RefrigerationsRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final RefrigerationsEquipmentRequestModel refrigerationsEquipmentDetail = @@ -453,7 +453,7 @@ class _AddRefrigerationState extends State { '/listRefrigerationEquipment', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -504,7 +504,7 @@ class _AddRefrigerationState extends State { '/listRefrigerationEquipment', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart index fd9eedda..059546c0 100644 --- a/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/refrigerations/refrigeration_equipment_list.dart @@ -6,14 +6,14 @@ import 'package:sige_ie/equipments/feature/refrigerations/add_refrigeration.dart class ListRefrigerationEquipment extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListRefrigerationEquipment({ Key? key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -63,7 +63,7 @@ class _ListRefrigerationEquipmentState MaterialPageRoute( builder: (context) => AddRefrigeration( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart index b8cc926c..30e7bb43 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/add_structured_cabling.dart @@ -29,13 +29,13 @@ class AddStructuredCabling extends StatefulWidget { final String areaName; final String localName; final int localId; - final int categoryNumber; + final int systemId; final int areaId; const AddStructuredCabling({ super.key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -83,11 +83,11 @@ class _AddStructuredCablingScreenState extends State { Future _fetchEquipmentCategory() async { List genericEquipmentCategoryList = await genericEquipmentCategoryService - .getAllGenericEquipmentCategoryBySystem(widget.categoryNumber); + .getAllGenericEquipmentCategoryBySystem(widget.systemId); List personalEquipmentCategoryList = await personalEquipmentCategoryService - .getAllPersonalEquipmentCategoryBySystem(widget.categoryNumber); + .getAllPersonalEquipmentCategoryBySystem(widget.systemId); if (mounted) { setState(() { @@ -109,7 +109,7 @@ class _AddStructuredCablingScreenState extends State { void dispose() { _equipmentQuantityController.dispose(); _equipmentDimensionController.dispose(); // Adicionado para a dimensão - categoryImagesMap[widget.categoryNumber]?.clear(); + categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -160,12 +160,12 @@ class _AddStructuredCablingScreenState extends State { } else { final imageData = ImageData(imageFile, descriptionController.text); - final categoryNumber = widget.categoryNumber; - if (!categoryImagesMap.containsKey(categoryNumber)) { - categoryImagesMap[categoryNumber] = []; + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - categoryImagesMap[categoryNumber]!.add(imageData); - _images = categoryImagesMap[categoryNumber]!; + categoryImagesMap[systemId]!.add(imageData); + _images = categoryImagesMap[systemId]!; } }); Navigator.of(context).pop(); @@ -221,7 +221,7 @@ class _AddStructuredCablingScreenState extends State { } Future _registerPersonalEquipmentType() async { - int systemId = widget.categoryNumber; + int systemId = widget.systemId; PersonalEquipmentCategoryRequestModel personalEquipmentTypeRequestModel = PersonalEquipmentCategoryRequestModel( name: _newEquipmentTypeName ?? '', system: systemId); @@ -422,7 +422,7 @@ class _AddStructuredCablingScreenState extends State { final StructuredCablingRequestModel structuredCablingModel = StructuredCablingRequestModel( area: widget.areaId, - system: widget.categoryNumber, + system: widget.systemId, ); final StructuredCablingEquipmentRequestModel @@ -458,7 +458,7 @@ class _AddStructuredCablingScreenState extends State { '/listStruturedCabling', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, @@ -510,7 +510,7 @@ class _AddStructuredCablingScreenState extends State { '/listStruturedCabling', arguments: { 'areaName': widget.areaName, - 'categoryNumber': widget.categoryNumber, + 'systemId': widget.systemId, 'localName': widget.localName, 'localId': widget.localId, 'areaId': widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart index 29b36243..8f65b074 100644 --- a/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart +++ b/frontend/sige_ie/lib/equipments/feature/structured_cabling/structured_cabling_equipment_list.dart @@ -7,14 +7,14 @@ import 'package:sige_ie/equipments/feature/structured_cabling/add_structured_cab class ListStructuredCabling extends StatefulWidget { final String areaName; final String localName; - final int categoryNumber; + final int systemId; final int localId; final int areaId; const ListStructuredCabling({ Key? key, required this.areaName, - required this.categoryNumber, + required this.systemId, required this.localName, required this.localId, required this.areaId, @@ -45,7 +45,7 @@ class _ListStructuredCablingState extends State { MaterialPageRoute( builder: (context) => AddStructuredCabling( areaName: widget.areaName, - categoryNumber: widget.categoryNumber, + systemId: widget.systemId, localName: widget.localName, localId: widget.localId, areaId: widget.areaId, diff --git a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart index fcf1af7c..9fa29789 100644 --- a/frontend/sige_ie/lib/equipments/feature/system_configuration.dart +++ b/frontend/sige_ie/lib/equipments/feature/system_configuration.dart @@ -30,7 +30,7 @@ class SystemConfiguration extends StatefulWidget { class _SystemConfigurationState extends State { void navigateTo(String routeName, String areaName, String localName, - int localId, int areaId, int category) { + int localId, int areaId, int systemId) { Navigator.push( context, MaterialPageRoute( @@ -41,7 +41,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); case '/atmosphericDischarges': @@ -49,7 +49,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); case '/fireAlarm': @@ -57,7 +57,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); case '/electricLoads': @@ -65,7 +65,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); case '/electricLines': @@ -73,7 +73,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); case '/circuits': @@ -81,7 +81,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); case '/distributionBoard': @@ -89,7 +89,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); case '/cooling': @@ -97,7 +97,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); case '/lighting': @@ -105,7 +105,7 @@ class _SystemConfigurationState extends State { areaName: areaName, localName: localName, localId: localId, - categoryNumber: category, + systemId: systemId, areaId: areaId, ); default: diff --git a/frontend/sige_ie/lib/main.dart b/frontend/sige_ie/lib/main.dart index cf29fea5..b3098a2a 100644 --- a/frontend/sige_ie/lib/main.dart +++ b/frontend/sige_ie/lib/main.dart @@ -111,7 +111,7 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; final int? areaId = args['areaId']; if (areaName != null && @@ -121,7 +121,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListIluminationEquipment( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, @@ -140,7 +140,7 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; final int? areaId = args['areaId']; if (areaName != null && @@ -150,7 +150,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListCircuitEquipment( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, @@ -169,7 +169,7 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; final int? areaId = args['areaId']; if (areaName != null && @@ -179,7 +179,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListElectricalLineEquipment( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, @@ -198,7 +198,7 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; final int? areaId = args['areaId']; if (areaName != null && @@ -208,7 +208,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListAtmosphericEquipment( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, @@ -227,7 +227,7 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; final int? areaId = args['areaId']; if (areaName != null && @@ -237,7 +237,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListDistributionBoard( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, @@ -257,7 +257,7 @@ class MyApp extends StatelessWidget { final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int? areaId = args['areaId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; if (areaName != null && localName != null && @@ -266,7 +266,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListFireAlarms( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, @@ -287,7 +287,7 @@ class MyApp extends StatelessWidget { final String? localName = args['localName']?.toString(); final int? localId = args['localId']; final int? areaId = args['areaId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; if (areaName != null && localName != null && @@ -296,7 +296,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListRefrigerationEquipment( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, @@ -317,7 +317,7 @@ class MyApp extends StatelessWidget { final int? localId = args['localId']; final int? areaId = args['areaId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; if (areaName != null && localName != null && @@ -326,7 +326,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListStructuredCabling( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, @@ -345,7 +345,7 @@ class MyApp extends StatelessWidget { final String? areaName = args['areaName']?.toString(); final String? localName = args['localName']?.toString(); final int? localId = args['localId']; - final int categoryNumber = args['categoryNumber'] ?? 0; + final int systemId = args['systemId'] ?? 0; final int? areaId = args['areaId']; if (areaName != null && @@ -355,7 +355,7 @@ class MyApp extends StatelessWidget { return MaterialPageRoute( builder: (context) => ListElectricalLoadEquipment( areaName: areaName, - categoryNumber: categoryNumber, + systemId: systemId, localName: localName, localId: localId, areaId: areaId, diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_response_model.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_response_model.dart new file mode 100644 index 00000000..a3c0a2b3 --- /dev/null +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_response_model.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:path_provider/path_provider.dart'; + +class EquipmentPhotoResponseModel { + int id; + String? photoBase64; + String? description; + int? equipment; + + EquipmentPhotoResponseModel({ + required this.id, + required this.photoBase64, + required this.description, + required this.equipment, + }); + + factory EquipmentPhotoResponseModel.fromJson(Map json) { + return EquipmentPhotoResponseModel( + id: json['id'], + photoBase64: json['photo_base64'], + description: json['description'], + equipment: json['equipment'], + ); + } + + Future toFile() async { + Uint8List bytes = base64Decode(photoBase64!.split(',')[1]); + final tempDir = await getTemporaryDirectory(); + final filePath = '${tempDir.path}/photo_$id.jpg'; + File file = File(filePath); + await file.writeAsBytes(bytes); + return file; + } +} diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart index d0a98586..03af2ac6 100644 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart @@ -5,6 +5,7 @@ import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; import 'package:sige_ie/main.dart'; import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_request_model.dart'; +import 'package:sige_ie/shared/data/equipment-photo/equipment_photo_response_model.dart'; class EquipmentPhotoService { final Logger _logger = Logger('EquipmentPhotoService'); @@ -17,7 +18,7 @@ class EquipmentPhotoService { EquipmentPhotoRequestModel equipmentPhotoRequestModel) async { var url = Uri.parse(baseUrl); - print( + _logger.info( 'Sending request to: $url with body: ${jsonEncode(equipmentPhotoRequestModel.toJson())}'); try { @@ -35,65 +36,46 @@ class EquipmentPhotoService { if (response.statusCode == 201 || response.statusCode == 200) { Map responseData = jsonDecode(response.body); _logger.info('Request successful, received ID: ${responseData['id']}'); - print('Request successful, received ID: ${responseData['id']}'); return true; } else { _logger .info('Failed to register equipment photo: ${response.statusCode}'); - print('Failed to register equipment photo: ${response.statusCode}'); return false; } } catch (e) { _logger.info('Error during register: $e'); - print('Error during register: $e'); return false; } } - Future>> getPhotosByEquipmentId( - int fireAlarmId) async { + Future> getPhotosByEquipmentId( + int equipmentId) async { try { - // Fetch the equipment ID from the fire alarm endpoint - var fireAlarmUrl = - Uri.parse('http://10.0.2.2:8000/api/fire-alarms/$fireAlarmId/'); - print('Fetching fire alarm details from: $fireAlarmUrl'); - var fireAlarmResponse = await client.get(fireAlarmUrl); - - if (fireAlarmResponse.statusCode != 200) { - _logger.warning( - 'Failed to fetch fire alarm details: ${fireAlarmResponse.statusCode}'); - print( - 'Failed to fetch fire alarm details: ${fireAlarmResponse.statusCode}'); - return []; - } - - Map fireAlarmData = jsonDecode(fireAlarmResponse.body); - print('Fire alarm data: $fireAlarmData'); - int equipmentId = fireAlarmData['equipment']; - - // Fetch the photos using the equipment ID - var photosUrl = Uri.parse( + var url = Uri.parse( 'http://10.0.2.2:8000/api/equipment-photos/by-equipment/$equipmentId/'); - print('Fetching photos from: $photosUrl'); - var photosResponse = await client.get(photosUrl); + _logger.info('Fetching photos from: $url'); + var photosResponse = await client.get(url); if (photosResponse.statusCode != 200) { _logger.warning( 'Failed to fetch equipment photos: ${photosResponse.statusCode}'); - print('Failed to fetch equipment photos: ${photosResponse.statusCode}'); return []; } List photosData = jsonDecode(photosResponse.body); - print('Photos data: $photosData'); + + _logger.info('Photos data: $photosData'); + + List photoModels = + photosData.map((photoData) { + return EquipmentPhotoResponseModel.fromJson(photoData); + }).toList(); _logger.info('Fetched equipment photos successfully'); - print('Fetched equipment photos successfully'); - return List>.from(photosData); + return photoModels; } catch (e) { - _logger.severe('Error fetching photos by equipment ID: $e'); - print('Error fetching photos by equipment ID: $e'); + _logger.severe('Error fetching and converting photos: $e'); return []; } } diff --git a/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart b/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart index 8f78a7cd..7d415cfc 100644 --- a/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart +++ b/frontend/sige_ie/lib/shared/data/personal-equipment-category/personal_equipment_category_service.dart @@ -71,8 +71,9 @@ class PersonalEquipmentCategoryService { } } - Future deletePersonalEquipmentCategory(int id) async { - var url = Uri.parse('${baseUrl}personal-equipment-types/$id/'); + Future deletePersonalEquipmentCategory(int personalCategoryId) async { + var url = + Uri.parse('${baseUrl}personal-equipment-types/$personalCategoryId/'); try { _logger.info('Sending DELETE request to $url'); From b379b029933972d8e68b2a3a75b413bd17dfdac7 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Fri, 12 Jul 2024 16:17:01 -0300 Subject: [PATCH 320/351] backend: corrige serializer de fotos --- api/equipments/serializers.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 3e116d86..8742c3ab 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -19,11 +19,12 @@ class Meta: class EquipmentPhotoSerializer(serializers.ModelSerializer): - photo = serializers.CharField() + photo = serializers.CharField(write_only=True) + photo_base64 = serializers.SerializerMethodField() class Meta: model = EquipmentPhoto - fields = '__all__' + fields = ['id', 'photo', 'photo_base64', 'description', 'equipment'] def create(self, validated_data): photo_data = validated_data.pop('photo') @@ -37,6 +38,12 @@ def create(self, validated_data): equipment_photo = EquipmentPhoto.objects.create(photo=photo, **validated_data) return equipment_photo + def get_photo_base64(self, obj): + if obj.photo: + with obj.photo.open('rb') as image_file: + return 'data:image/jpeg;base64,' + base64.b64encode(image_file.read()).decode('utf-8') + return None + class FireAlarmEquipmentSerializer(ValidateAreaMixin, serializers.ModelSerializer): class Meta: From 18b8e7f217f1839cb2fc337c3ae8319085a4d7ea Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 16 Jul 2024 10:45:40 -0300 Subject: [PATCH 321/351] =?UTF-8?q?Fun=C3=A7=C3=A3o=20de=20deletar=20foto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire_alarm/add_fire_alarm.dart | 100 +++++++++++++----- .../equipment_photo_service.dart | 58 ++++++++++ 2 files changed, 130 insertions(+), 28 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index 4138ffc1..ad5ff00c 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -21,11 +21,13 @@ class ImageData { int id; File imageFile; String description; + bool toDelete; ImageData({ int? id, required this.imageFile, required this.description, + this.toDelete = false, }) : id = id ?? Random().nextInt(1000000); } @@ -232,26 +234,43 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Salvar'), - onPressed: () { - setState(() { - String? description = descriptionController.text.isEmpty - ? null - : descriptionController.text; - if (existingImage != null) { - existingImage.description = description ?? ''; - } else { - final imageData = ImageData( - imageFile: imageFile, - description: description ?? '', - ); - final systemId = widget.systemId; - if (!categoryImagesMap.containsKey(systemId)) { - categoryImagesMap[systemId] = []; - } - categoryImagesMap[systemId]!.add(imageData); - _images = categoryImagesMap[systemId]!; + onPressed: () async { + String? description = descriptionController.text.isEmpty + ? null + : descriptionController.text; + if (existingImage != null) { + existingImage.description = description ?? ''; + bool success = await equipmentPhotoService.updatePhoto( + existingImage.id, + EquipmentPhotoRequestModel( + photo: existingImage.imageFile, + description: existingImage.description, + equipment: equipmentId!, + ), + ); + if (success) { + setState(() { + final index = _images + .indexWhere((image) => image.id == existingImage.id); + if (index != -1) { + _images[index] = existingImage; + } + }); + } + } else { + final imageData = ImageData( + imageFile: imageFile, + description: description ?? '', + ); + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; } - }); + categoryImagesMap[systemId]!.add(imageData); + setState(() { + _images = categoryImagesMap[systemId]!; + }); + } Navigator.of(context).pop(); }, ), @@ -341,6 +360,26 @@ class _AddEquipmentScreenState extends State { widget.fireAlarmId!, fireAlarmEquipmentDetail); if (fireAlarmUpdateSuccess) { + await Future.wait(_images + .where((imageData) => imageData.toDelete) + .map((imageData) async { + await equipmentPhotoService.deletePhoto(imageData.id); + })); + + await Future.wait(_images + .where((imageData) => !imageData.toDelete) + .map((imageData) async { + await equipmentPhotoService.updatePhoto( + imageData.id, + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: + imageData.description.isEmpty ? null : imageData.description, + equipment: equipmentId!, + ), + ); + })); + ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Detalhes do equipamento atualizados com sucesso.'), @@ -607,16 +646,15 @@ class _AddEquipmentScreenState extends State { await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); } - if (equipmentId! != 0) { + if (fireAlarmId != null) { print('Registering photos for equipment ID: $fireAlarmId'); await Future.wait(_images.map((imageData) async { print('Creating photo with description: "${imageData.description}"'); await equipmentPhotoService.createPhoto( EquipmentPhotoRequestModel( photo: imageData.imageFile, - description: - imageData.description.isEmpty ? null : imageData.description, - equipment: equipmentId, + description: imageData.description, + equipment: fireAlarmId!, ), ); })); @@ -655,6 +693,13 @@ class _AddEquipmentScreenState extends State { } } + Future _deletePhoto(int photoId) async { + setState(() { + _images.firstWhere((imageData) => imageData.id == photoId).toDelete = + true; + }); + } + @override Widget build(BuildContext context) { List> combinedTypes = [ @@ -831,7 +876,9 @@ class _AddEquipmentScreenState extends State { onPressed: _pickImage, ), Wrap( - children: _images.map((imageData) { + children: _images + .where((imageData) => !imageData.toDelete) + .map((imageData) { return Stack( alignment: Alignment.topRight, children: [ @@ -852,10 +899,7 @@ class _AddEquipmentScreenState extends State { icon: const Icon(Icons.remove_circle, color: AppColors.warn), onPressed: () { - setState(() { - _images.removeWhere( - (element) => element.id == imageData.id); - }); + _deletePhoto(imageData.id); }, ), ], diff --git a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart index 03af2ac6..a30d546c 100644 --- a/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart +++ b/frontend/sige_ie/lib/shared/data/equipment-photo/equipment_photo_service.dart @@ -79,4 +79,62 @@ class EquipmentPhotoService { return []; } } + + Future deletePhoto(int photoId) async { + var url = Uri.parse('$baseUrl$photoId/'); + + _logger.info('Sending DELETE request to: $url'); + + try { + var response = await client.delete(url, headers: { + 'Content-Type': 'application/json', + }); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 204 || response.statusCode == 200) { + _logger.info('Photo deleted successfully'); + return true; + } else { + _logger.info('Failed to delete photo: ${response.statusCode}'); + return false; + } + } catch (e) { + _logger.severe('Error during delete: $e'); + return false; + } + } + + Future updatePhoto(int photoId, + EquipmentPhotoRequestModel equipmentPhotoRequestModel) async { + var url = Uri.parse('$baseUrl$photoId/'); + + _logger.info( + 'Sending PUT request to: $url with body: ${jsonEncode(equipmentPhotoRequestModel.toJson())}'); + + try { + var response = await client.put( + url, + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode(equipmentPhotoRequestModel.toJson()), + ); + + _logger.info('Response status code: ${response.statusCode}'); + _logger.info('Response body: ${response.body}'); + + if (response.statusCode == 200) { + _logger.info('Photo updated successfully'); + return true; + } else { + _logger.info('Failed to update photo: ${response.statusCode}'); + return false; + } + } catch (e) { + _logger.severe('Error during update: $e'); + return false; + } + } } From d12fc65c2baaa89c2a42cfd5e1683096cfad3b07 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 16 Jul 2024 10:54:40 -0300 Subject: [PATCH 322/351] =?UTF-8?q?Vers=C3=A3o=20anterior=20do=20c=C3=B3di?= =?UTF-8?q?go=20sem=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire_alarm/add_fire_alarm.dart | 69 ++++++++----------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index ad5ff00c..9c8f4404 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -234,43 +234,26 @@ class _AddEquipmentScreenState extends State { ), TextButton( child: const Text('Salvar'), - onPressed: () async { - String? description = descriptionController.text.isEmpty - ? null - : descriptionController.text; - if (existingImage != null) { - existingImage.description = description ?? ''; - bool success = await equipmentPhotoService.updatePhoto( - existingImage.id, - EquipmentPhotoRequestModel( - photo: existingImage.imageFile, - description: existingImage.description, - equipment: equipmentId!, - ), - ); - if (success) { - setState(() { - final index = _images - .indexWhere((image) => image.id == existingImage.id); - if (index != -1) { - _images[index] = existingImage; - } - }); - } - } else { - final imageData = ImageData( - imageFile: imageFile, - description: description ?? '', - ); - final systemId = widget.systemId; - if (!categoryImagesMap.containsKey(systemId)) { - categoryImagesMap[systemId] = []; - } - categoryImagesMap[systemId]!.add(imageData); - setState(() { + onPressed: () { + setState(() { + String? description = descriptionController.text.isEmpty + ? null + : descriptionController.text; + if (existingImage != null) { + existingImage.description = description ?? ''; + } else { + final imageData = ImageData( + imageFile: imageFile, + description: description ?? '', + ); + final systemId = widget.systemId; + if (!categoryImagesMap.containsKey(systemId)) { + categoryImagesMap[systemId] = []; + } + categoryImagesMap[systemId]!.add(imageData); _images = categoryImagesMap[systemId]!; - }); - } + } + }); Navigator.of(context).pop(); }, ), @@ -360,12 +343,14 @@ class _AddEquipmentScreenState extends State { widget.fireAlarmId!, fireAlarmEquipmentDetail); if (fireAlarmUpdateSuccess) { + // Delete photos marked for deletion await Future.wait(_images .where((imageData) => imageData.toDelete) .map((imageData) async { await equipmentPhotoService.deletePhoto(imageData.id); })); + // Update photo descriptions await Future.wait(_images .where((imageData) => !imageData.toDelete) .map((imageData) async { @@ -644,17 +629,21 @@ class _AddEquipmentScreenState extends State { } else { fireAlarmId = await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); + setState(() { + equipmentId = fireAlarmId; + }); } - if (fireAlarmId != null) { - print('Registering photos for equipment ID: $fireAlarmId'); + if (equipmentId != null && equipmentId != 0) { + print('Registering photos for equipment ID: $equipmentId'); await Future.wait(_images.map((imageData) async { print('Creating photo with description: "${imageData.description}"'); await equipmentPhotoService.createPhoto( EquipmentPhotoRequestModel( photo: imageData.imageFile, - description: imageData.description, - equipment: fireAlarmId!, + description: + imageData.description.isEmpty ? null : imageData.description, + equipment: equipmentId!, ), ); })); From 6d104f9c33a85d81e55ae1397640b605546b1e97 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 16 Jul 2024 11:06:17 -0300 Subject: [PATCH 323/351] Resolvendo bugs visuais --- .../lib/equipments/feature/fire_alarm/add_fire_alarm.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index 9c8f4404..61bceac7 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -492,6 +492,8 @@ class _AddEquipmentScreenState extends State { personalEquipmentTypes.removeWhere( (element) => element['name'] == _selectedTypeToDelete); _selectedTypeToDelete = null; + _selectedType = + null; // Limpando a seleção no dropdown principal _fetchEquipmentCategory(); }); ScaffoldMessenger.of(context).showSnackBar( @@ -546,7 +548,9 @@ class _AddEquipmentScreenState extends State { const Text('Imagens:', style: TextStyle(fontWeight: FontWeight.bold)), Wrap( - children: _images.map((imageData) { + children: _images + .where((imageData) => !imageData.toDelete) + .map((imageData) { return Padding( padding: const EdgeInsets.all(4.0), child: GestureDetector( From 229d20b743ade37fee2ee491828c11fb993d8c25 Mon Sep 17 00:00:00 2001 From: EngDann Date: Tue, 16 Jul 2024 12:34:38 -0300 Subject: [PATCH 324/351] =?UTF-8?q?Quadro=20de=20distribui=C3=A7=C3=A3o=20?= =?UTF-8?q?visualmente=20concluida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../distribution_equipment_request_model.dart | 18 +++ .../add_distribuition_board.dart | 134 ++++++++++++++++-- 2 files changed, 141 insertions(+), 11 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/distribution/distribution_equipment_request_model.dart b/frontend/sige_ie/lib/equipments/data/distribution/distribution_equipment_request_model.dart index fbbaf814..eda6a289 100644 --- a/frontend/sige_ie/lib/equipments/data/distribution/distribution_equipment_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/distribution/distribution_equipment_request_model.dart @@ -4,11 +4,23 @@ class DistributionEquipmentRequestModel { int? genericEquipmentCategory; int? personalEquipmentCategory; DistributionRequestModel? distributionRequestModel; + String? power; + bool dr; + bool dps; + bool grounding; + String? typeMaterial; + String? methodInstallation; DistributionEquipmentRequestModel({ required this.genericEquipmentCategory, required this.personalEquipmentCategory, required this.distributionRequestModel, + this.power, + this.dr = false, + this.dps = false, + this.grounding = false, + this.typeMaterial, + this.methodInstallation, }); Map toJson() { @@ -16,6 +28,12 @@ class DistributionEquipmentRequestModel { 'generic_equipment_category': genericEquipmentCategory, 'personal_equipment_category': personalEquipmentCategory, 'distribution_board_equipment': distributionRequestModel?.toJson(), + 'power': power, + 'dr': dr, + 'dps': dps, + 'grounding': grounding, + 'type_material': typeMaterial, + 'method_installation': methodInstallation, }; } } diff --git a/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart index c994ef40..9af8f245 100644 --- a/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart +++ b/frontend/sige_ie/lib/equipments/feature/distribuition_board/add_distribuition_board.dart @@ -55,6 +55,12 @@ class _AddDistribuitionBoardState extends State { final _equipmentChargeController = TextEditingController(); final _equipmentQuantityController = TextEditingController(); + final _powerController = TextEditingController(); + final _typeMaterialController = TextEditingController(); + final _methodInstallationController = TextEditingController(); + bool _dr = false; + bool _dps = false; + bool _grounding = false; String? _selectedType; String? _selectedTypeToDelete; String? _newEquipmentTypeName; @@ -107,6 +113,9 @@ class _AddDistribuitionBoardState extends State { void dispose() { _equipmentChargeController.dispose(); _equipmentQuantityController.dispose(); + _powerController.dispose(); + _typeMaterialController.dispose(); + _methodInstallationController.dispose(); categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -425,6 +434,12 @@ class _AddDistribuitionBoardState extends State { genericEquipmentCategory: genericEquipmentCategory, personalEquipmentCategory: personalEquipmentCategory, distributionRequestModel: distributionModel, + power: _powerController.text, + dr: _dr, + dps: _dps, + grounding: _grounding, + typeMaterial: _typeMaterialController.text, + methodInstallation: _methodInstallationController.text, ); int? equipmentId = @@ -461,6 +476,12 @@ class _AddDistribuitionBoardState extends State { setState(() { _equipmentChargeController.clear(); _equipmentQuantityController.clear(); + _powerController.clear(); + _typeMaterialController.clear(); + _methodInstallationController.clear(); + _dr = false; + _dps = false; + _grounding = false; _selectedType = null; _selectedPersonalEquipmentCategoryId = null; _selectedGenericEquipmentCategoryId = null; @@ -493,6 +514,12 @@ class _AddDistribuitionBoardState extends State { setState(() { _equipmentChargeController.clear(); _equipmentQuantityController.clear(); + _powerController.clear(); + _typeMaterialController.clear(); + _methodInstallationController.clear(); + _dr = false; + _dps = false; + _grounding = false; _selectedType = null; _selectedPersonalEquipmentCategoryId = null; _selectedGenericEquipmentCategoryId = null; @@ -610,7 +637,6 @@ class _AddDistribuitionBoardState extends State { ), ], ), - const SizedBox(height: 8), TextButton( onPressed: () { setState(() { @@ -619,8 +645,8 @@ class _AddDistribuitionBoardState extends State { }, child: const Text('Limpar seleção'), ), - const SizedBox(height: 30), - const Text('Especificação', + const SizedBox(height: 8), + const Text('Quantidade', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -630,7 +656,11 @@ class _AddDistribuitionBoardState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentChargeController, + controller: _equipmentQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], decoration: const InputDecoration( border: InputBorder.none, contentPadding: @@ -638,8 +668,8 @@ class _AddDistribuitionBoardState extends State { ), ), ), - const SizedBox(height: 30), - const Text('Quantidade', + const SizedBox(height: 15), + const Text('Potência', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(height: 8), @@ -649,11 +679,93 @@ class _AddDistribuitionBoardState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentQuantityController, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + controller: _powerController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('DR', style: TextStyle(fontSize: 16)), + Checkbox( + value: _dr, + onChanged: (bool? newValue) { + setState(() { + _dr = newValue ?? false; + }); + }, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('DPS', style: TextStyle(fontSize: 16)), + Checkbox( + value: _dps, + onChanged: (bool? newValue) { + setState(() { + _dps = newValue ?? false; + }); + }, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Aterramento', + style: TextStyle(fontSize: 16)), + Checkbox( + value: _grounding, + onChanged: (bool? newValue) { + setState(() { + _grounding = newValue ?? false; + }); + }, + ), + ], + ), + ], + ), + const Text('Tipo de Material', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _typeMaterialController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 15), + ), + ), + ), + const SizedBox(height: 15), + const Text('Método de Instalação', + style: + TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + child: TextField( + controller: _methodInstallationController, decoration: const InputDecoration( border: InputBorder.none, contentPadding: From 6069fecc4772cbb3b023667db581b3022c77ae1c Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 17 Jul 2024 14:20:05 -0300 Subject: [PATCH 325/351] =?UTF-8?q?backend:=20arruma=20o=20m=C3=A9todo=20p?= =?UTF-8?q?ut=20no=20serializer=20de=20fotos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/equipments/serializers.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index 8742c3ab..c3662fc1 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -38,6 +38,22 @@ def create(self, validated_data): equipment_photo = EquipmentPhoto.objects.create(photo=photo, **validated_data) return equipment_photo + def update(self, instance, validated_data): + photo_data = validated_data.pop('photo', None) + if photo_data: + try: + format, imgstr = photo_data.split(';base64,') + ext = format.split('/')[-1] + photo = ContentFile(base64.b64decode(imgstr), name='temp.' + ext) + instance.photo = photo + except ValueError: + raise serializers.ValidationError("Invalid image data") + + instance.description = validated_data.get('description', instance.description) + instance.equipment = validated_data.get('equipment', instance.equipment) + instance.save() + return instance + def get_photo_base64(self, obj): if obj.photo: with obj.photo.open('rb') as image_file: From d011f8e7cdb7e65a40eb81f8c0a731407ee36d37 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Wed, 17 Jul 2024 14:31:37 -0300 Subject: [PATCH 326/351] backend: adiciona campo de quantidade em todos os equipamentos --- ...ricdischargeequipment_quantity_and_more.py | 43 +++++++++++++++++++ api/equipments/models.py | 6 +++ api/equipments/serializers.py | 16 +++---- 3 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 api/equipments/migrations/0034_atmosphericdischargeequipment_quantity_and_more.py diff --git a/api/equipments/migrations/0034_atmosphericdischargeequipment_quantity_and_more.py b/api/equipments/migrations/0034_atmosphericdischargeequipment_quantity_and_more.py new file mode 100644 index 00000000..6affde09 --- /dev/null +++ b/api/equipments/migrations/0034_atmosphericdischargeequipment_quantity_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 4.2 on 2024-07-17 17:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('equipments', '0033_distributionboardequipment_dps_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='atmosphericdischargeequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='distributionboardequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='electricalcircuitequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='electricallineequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='firealarmequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='structuredcablingequipment', + name='quantity', + field=models.IntegerField(default=0), + ), + ] diff --git a/api/equipments/models.py b/api/equipments/models.py index b42ee8f2..b74bdcf0 100644 --- a/api/equipments/models.py +++ b/api/equipments/models.py @@ -87,6 +87,7 @@ class ElectricalLineEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="electrical_line_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=3) + quantity = models.IntegerField(default=0) class Meta: db_table = 'equipments_electrical_lines' @@ -98,6 +99,7 @@ class ElectricalCircuitEquipment(models.Model): system = models.ForeignKey(System, on_delete=models.CASCADE, default=4) size = models.IntegerField(default=0) isolament = models.CharField(max_length=30, default=None) + quantity = models.IntegerField(default=0) class Meta: db_table = 'equipments_electrical_circuits' @@ -113,6 +115,7 @@ class DistributionBoardEquipment(models.Model): grounding = models.BooleanField(default=False) type_material = models.CharField(max_length=30, null=True) method_installation = models.CharField(max_length=50, null=True) + quantity = models.IntegerField(default=0) class Meta: @@ -123,6 +126,7 @@ class StructuredCablingEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="structured_cabling_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=6) + quantity = models.IntegerField(default=0) class Meta: db_table = 'equipments_structured_cabling' @@ -132,6 +136,7 @@ class AtmosphericDischargeEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="atmospheric_discharge_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True) system = models.ForeignKey(System, on_delete=models.CASCADE, default=7) + quantity = models.IntegerField(default=0) class Meta: db_table = 'equipments_atmospheric_discharges' @@ -141,6 +146,7 @@ class FireAlarmEquipment(models.Model): area = models.ForeignKey(Area, on_delete=models.CASCADE, null=True, related_name="fire_alarm_equipment") equipment = models.OneToOneField(Equipment, on_delete=models.CASCADE, null=True, related_name="fire_alarm") system = models.ForeignKey(System, on_delete=models.CASCADE, default=8) + quantity = models.IntegerField(default=0) class Meta: db_table = 'equipments_fire_alarms' diff --git a/api/equipments/serializers.py b/api/equipments/serializers.py index c3662fc1..0ded426a 100644 --- a/api/equipments/serializers.py +++ b/api/equipments/serializers.py @@ -120,7 +120,7 @@ class FireAlarmEquipmentResponseSerializer(ValidateAreaMixin, EquipmentCategoryM class Meta: model = FireAlarmEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + fields = ['id', 'area', 'equipment_category', 'system', 'quantity'] class AtmosphericDischargeEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -128,7 +128,7 @@ class AtmosphericDischargeEquipmentResponseSerializer(EquipmentCategoryMixin, se class Meta: model = AtmosphericDischargeEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + fields = ['id', 'area', 'equipment_category', 'system', 'quantity'] class StructuredCablingEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -136,7 +136,7 @@ class StructuredCablingEquipmentResponseSerializer(EquipmentCategoryMixin, seria class Meta: model = StructuredCablingEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + fields = ['id', 'area', 'equipment_category', 'system', 'quantity'] class DistributionBoardEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -144,7 +144,7 @@ class DistributionBoardEquipmentResponseSerializer(EquipmentCategoryMixin, seria class Meta: model = DistributionBoardEquipment - fields = ['id', 'area', 'equipment_category', 'system', 'power', 'dr', 'dps', 'grounding', 'type_material', 'method_installation'] + fields = ['id', 'area', 'equipment_category', 'system', 'power', 'dr', 'dps', 'grounding', 'type_material', 'method_installation', 'quantity'] class ElectricalCircuitEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -152,7 +152,7 @@ class ElectricalCircuitEquipmentResponseSerializer(EquipmentCategoryMixin, seria class Meta: model = ElectricalCircuitEquipment - fields = ['id', 'area', 'equipment_category', 'system', 'size', 'isolament'] + fields = ['id', 'area', 'equipment_category', 'system', 'size', 'isolament', 'quantity'] class ElectricalLineEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -160,7 +160,7 @@ class ElectricalLineEquipmentResponseSerializer(EquipmentCategoryMixin, serializ class Meta: model = ElectricalLineEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + fields = ['id', 'area', 'equipment_category', 'system', 'quantity'] class ElectricalLoadEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -168,7 +168,7 @@ class ElectricalLoadEquipmentResponseSerializer(EquipmentCategoryMixin, serializ class Meta: model = ElectricalLoadEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + fields = ['id', 'area', 'equipment_category', 'system', 'quantity'] class IluminationEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): @@ -176,7 +176,7 @@ class IluminationEquipmentResponseSerializer(EquipmentCategoryMixin, serializers class Meta: model = IluminationEquipment - fields = ['id', 'area', 'equipment_category', 'system'] + fields = ['id', 'area', 'equipment_category', 'system', 'quantity'] class RefrigerationEquipmentResponseSerializer(EquipmentCategoryMixin, serializers.ModelSerializer): From 2b2780944c0968dc9de983669ed89bc4613dd7f1 Mon Sep 17 00:00:00 2001 From: EngDann Date: Wed, 17 Jul 2024 15:44:05 -0300 Subject: [PATCH 327/351] =?UTF-8?q?Alarme=20de=20inc=C3=AAndio=20integrado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fire_alarm/fire_alarm_request_model.dart | 3 +++ .../fire_alarm_response_by_area_model.dart | 3 +++ .../fire_alarm/fire_alarm_response_model.dart | 3 +++ .../feature/fire_alarm/add_fire_alarm.dart | 24 +++++++++---------- frontend/sige_ie/pubspec.lock | 24 +++++++++---------- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_request_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_request_model.dart index 52f7e86c..b84ecd16 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_request_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_request_model.dart @@ -1,16 +1,19 @@ class FireAlarmRequestModel { int? area; int? system; + int? quantity; FireAlarmRequestModel({ required this.area, required this.system, + this.quantity, }); Map toJson() { return { 'area': area, 'system': system, + 'quantity': quantity, }; } } diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart index dbaf9b5d..ef320836 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart @@ -3,12 +3,14 @@ class FireAlarmEquipmentResponseByAreaModel { int area; String equipmentCategory; int system; + int quantity; FireAlarmEquipmentResponseByAreaModel({ required this.id, required this.area, required this.equipmentCategory, required this.system, + required this.quantity, }); factory FireAlarmEquipmentResponseByAreaModel.fromJson( @@ -18,6 +20,7 @@ class FireAlarmEquipmentResponseByAreaModel { area: json['area'], equipmentCategory: json['equipment_category'], system: json['system'], + quantity: json['quantity'], ); } } diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart index ef863133..3f8689e3 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart @@ -3,12 +3,14 @@ class FireAlarmEquipmentResponseModel { int area; int equipment; int system; + int quantity; FireAlarmEquipmentResponseModel({ required this.id, required this.area, required this.equipment, required this.system, + required this.quantity, }); factory FireAlarmEquipmentResponseModel.fromJson(Map json) { @@ -17,6 +19,7 @@ class FireAlarmEquipmentResponseModel { area: json['area'], equipment: json['equipment'], system: json['system'], + quantity: json['quantity'], ); } } diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index 61bceac7..94343f95 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -66,7 +66,7 @@ class _AddEquipmentScreenState extends State { PersonalEquipmentCategoryService(); GenericEquipmentCategoryService genericEquipmentCategoryService = GenericEquipmentCategoryService(); - final _equipmentQuantityController = TextEditingController(); + final TextEditingController _quantity = TextEditingController(); String? _selectedType; int? _selectedGenericEquipmentCategoryId; int? _selectedPersonalEquipmentCategoryId; @@ -95,6 +95,7 @@ class _AddEquipmentScreenState extends State { if (fireAlarmEquipmentResponseModel != null) { setState(() { equipmentId = fireAlarmEquipmentResponseModel!.equipment; + _quantity.text = fireAlarmEquipmentResponseModel!.quantity.toString(); }); _fetchEquipmentDetails(fireAlarmEquipmentResponseModel!.equipment); @@ -188,7 +189,7 @@ class _AddEquipmentScreenState extends State { @override void dispose() { - _equipmentQuantityController.dispose(); + _quantity.dispose(); categoryImagesMap[widget.systemId]?.clear(); super.dispose(); } @@ -330,6 +331,7 @@ class _AddEquipmentScreenState extends State { final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( area: widget.areaId, system: widget.systemId, + quantity: int.tryParse(_quantity.text), ); final FireAlarmEquipmentRequestModel fireAlarmEquipmentDetail = @@ -343,14 +345,12 @@ class _AddEquipmentScreenState extends State { widget.fireAlarmId!, fireAlarmEquipmentDetail); if (fireAlarmUpdateSuccess) { - // Delete photos marked for deletion await Future.wait(_images .where((imageData) => imageData.toDelete) .map((imageData) async { await equipmentPhotoService.deletePhoto(imageData.id); })); - // Update photo descriptions await Future.wait(_images .where((imageData) => !imageData.toDelete) .map((imageData) async { @@ -383,7 +383,7 @@ class _AddEquipmentScreenState extends State { }, ); setState(() { - _equipmentQuantityController.clear(); + _quantity.clear(); _selectedType = null; _selectedPersonalEquipmentCategoryId = null; _selectedGenericEquipmentCategoryId = null; @@ -492,8 +492,7 @@ class _AddEquipmentScreenState extends State { personalEquipmentTypes.removeWhere( (element) => element['name'] == _selectedTypeToDelete); _selectedTypeToDelete = null; - _selectedType = - null; // Limpando a seleção no dropdown principal + _selectedType = null; _fetchEquipmentCategory(); }); ScaffoldMessenger.of(context).showSnackBar( @@ -519,7 +518,7 @@ class _AddEquipmentScreenState extends State { } void _showConfirmationDialog() { - if (_equipmentQuantityController.text.isEmpty || + if (_quantity.text.isEmpty || (_selectedType == null && _newEquipmentTypeName == null)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -543,7 +542,7 @@ class _AddEquipmentScreenState extends State { const SizedBox(height: 10), const Text('Quantidade:', style: TextStyle(fontWeight: FontWeight.bold)), - Text(_equipmentQuantityController.text), + Text(_quantity.text), const SizedBox(height: 10), const Text('Imagens:', style: TextStyle(fontWeight: FontWeight.bold)), @@ -615,6 +614,7 @@ class _AddEquipmentScreenState extends State { final FireAlarmRequestModel fireAlarmModel = FireAlarmRequestModel( area: widget.areaId, system: widget.systemId, + quantity: int.tryParse(_quantity.text), ); final FireAlarmEquipmentRequestModel fireAlarmEquipmentDetail = @@ -670,7 +670,7 @@ class _AddEquipmentScreenState extends State { }, ); setState(() { - _equipmentQuantityController.clear(); + _quantity.clear(); _selectedType = null; _selectedPersonalEquipmentCategoryId = null; _selectedGenericEquipmentCategoryId = null; @@ -708,7 +708,7 @@ class _AddEquipmentScreenState extends State { icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { setState(() { - _equipmentQuantityController.clear(); + _quantity.clear(); _selectedType = null; _selectedPersonalEquipmentCategoryId = null; _selectedGenericEquipmentCategoryId = null; @@ -851,7 +851,7 @@ class _AddEquipmentScreenState extends State { borderRadius: BorderRadius.circular(10), ), child: TextField( - controller: _equipmentQuantityController, + controller: _quantity, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 5fde215d..2508e2d9 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -396,26 +396,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.0" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "2.0.1" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.0.1" lints: dependency: transitive description: @@ -460,10 +460,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.0" mime: dependency: "direct main" description: @@ -673,10 +673,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -753,10 +753,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "13.0.0" web: dependency: transitive description: From 6452e91c9b25713209247fc5d5643ea9d8ebd8d9 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 18 Jul 2024 11:32:28 -0300 Subject: [PATCH 328/351] =?UTF-8?q?C=C3=B3digo=20est=C3=A1vel=20de=20firea?= =?UTF-8?q?larm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../equipments/feature/fire_alarm/add_fire_alarm.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index 94343f95..50c28ca8 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -96,6 +96,7 @@ class _AddEquipmentScreenState extends State { setState(() { equipmentId = fireAlarmEquipmentResponseModel!.equipment; _quantity.text = fireAlarmEquipmentResponseModel!.quantity.toString(); + print('Loaded quantity: ${_quantity.text}'); }); _fetchEquipmentDetails(fireAlarmEquipmentResponseModel!.equipment); @@ -324,6 +325,9 @@ class _AddEquipmentScreenState extends State { "personal_equipment_category": personalEquipmentCategory, }; + print('Quantity: ${_quantity.text}'); + print('Equipment Type Update: $equipmentTypeUpdate'); + bool typeUpdateSuccess = await equipmentService.updateEquipment( equipmentId!, equipmentTypeUpdate); @@ -341,6 +345,8 @@ class _AddEquipmentScreenState extends State { fireAlarmRequestModel: fireAlarmModel, ); + print('Fire Alarm Model: ${fireAlarmModel.toJson()}'); + bool fireAlarmUpdateSuccess = await fireAlarmService.updateFireAlarm( widget.fireAlarmId!, fireAlarmEquipmentDetail); @@ -861,6 +867,9 @@ class _AddEquipmentScreenState extends State { contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 15), ), + onChanged: (value) { + print('Quantity changed: $value'); + }, ), ), const SizedBox(height: 15), From 4ecd372273bc0bf91905681ada5b83df817bb579 Mon Sep 17 00:00:00 2001 From: EngDann Date: Thu, 18 Jul 2024 11:45:41 -0300 Subject: [PATCH 329/351] =?UTF-8?q?retirando=20categoryImagesMap=20e=20sim?= =?UTF-8?q?plificando=20o=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/fire_alarm/add_fire_alarm.dart | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index 50c28ca8..ad956fd2 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; @@ -28,11 +27,10 @@ class ImageData { required this.imageFile, required this.description, this.toDelete = false, - }) : id = id ?? Random().nextInt(1000000); + }) : id = id ?? -1; // Valor padrão indicando ID inexistente } List _images = []; -Map> categoryImagesMap = {}; class AddFireAlarm extends StatefulWidget { final String areaName; @@ -158,7 +156,6 @@ class _AddEquipmentScreenState extends State { setState(() { _images = imageList; - categoryImagesMap[widget.systemId] = _images; }); } catch (e) { print('Erro ao buscar fotos existentes: $e'); @@ -191,7 +188,7 @@ class _AddEquipmentScreenState extends State { @override void dispose() { _quantity.dispose(); - categoryImagesMap[widget.systemId]?.clear(); + _images.clear(); super.dispose(); } @@ -248,12 +245,7 @@ class _AddEquipmentScreenState extends State { imageFile: imageFile, description: description ?? '', ); - final systemId = widget.systemId; - if (!categoryImagesMap.containsKey(systemId)) { - categoryImagesMap[systemId] = []; - } - categoryImagesMap[systemId]!.add(imageData); - _images = categoryImagesMap[systemId]!; + _images.add(imageData); } }); Navigator.of(context).pop(); @@ -360,15 +352,29 @@ class _AddEquipmentScreenState extends State { await Future.wait(_images .where((imageData) => !imageData.toDelete) .map((imageData) async { - await equipmentPhotoService.updatePhoto( - imageData.id, - EquipmentPhotoRequestModel( - photo: imageData.imageFile, - description: - imageData.description.isEmpty ? null : imageData.description, - equipment: equipmentId!, - ), - ); + if (imageData.id == -1) { + // Verifique se o ID é -1 (não atribuído) + await equipmentPhotoService.createPhoto( + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description.isEmpty + ? null + : imageData.description, + equipment: equipmentId!, + ), + ); + } else { + await equipmentPhotoService.updatePhoto( + imageData.id, + EquipmentPhotoRequestModel( + photo: imageData.imageFile, + description: imageData.description.isEmpty + ? null + : imageData.description, + equipment: equipmentId!, + ), + ); + } })); ScaffoldMessenger.of(context).showSnackBar( From 94296f866efc5b1a6b42cf222cb2b875bf2193d1 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 18 Jul 2024 14:34:04 -0300 Subject: [PATCH 330/351] =?UTF-8?q?frontend:=20termina=20edi=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20fire=20alarm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fire_alarm/fire_alarm_response_model.dart | 8 ++-- .../data/fire_alarm/fire_alarm_service.dart | 13 +++--- .../feature/fire_alarm/add_fire_alarm.dart | 40 ++++++------------- 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart index 3f8689e3..c6430edf 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_response_model.dart @@ -1,11 +1,11 @@ -class FireAlarmEquipmentResponseModel { +class FireAlarmResponseModel { int id; int area; int equipment; int system; int quantity; - FireAlarmEquipmentResponseModel({ + FireAlarmResponseModel({ required this.id, required this.area, required this.equipment, @@ -13,8 +13,8 @@ class FireAlarmEquipmentResponseModel { required this.quantity, }); - factory FireAlarmEquipmentResponseModel.fromJson(Map json) { - return FireAlarmEquipmentResponseModel( + factory FireAlarmResponseModel.fromJson(Map json) { + return FireAlarmResponseModel( id: json['id'], area: json['area'], equipment: json['equipment'], diff --git a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart index c2111176..579ca036 100644 --- a/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart +++ b/frontend/sige_ie/lib/equipments/data/fire_alarm/fire_alarm_service.dart @@ -3,10 +3,10 @@ import 'package:http/http.dart' as http; import 'package:http_interceptor/http_interceptor.dart'; import 'package:logging/logging.dart'; import 'package:sige_ie/core/data/auth_interceptor.dart'; +import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_request_model.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_by_area_model.dart'; import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_response_model.dart'; import 'package:sige_ie/main.dart'; -import 'package:sige_ie/equipments/data/fire_alarm/fire_alarm_equipment_request_model.dart'; class FireAlarmEquipmentService { final Logger _logger = Logger('FireAlarmEquipmentService'); @@ -57,14 +57,13 @@ class FireAlarmEquipmentService { } } - Future getFireAlarmById( - int fireAlarmId) async { + Future getFireAlarmById(int fireAlarmId) async { var url = Uri.parse('${baseUrl}fire-alarms/$fireAlarmId/'); try { var response = await client.get(url); if (response.statusCode == 200) { var jsonResponse = jsonDecode(response.body); - return FireAlarmEquipmentResponseModel.fromJson(jsonResponse); + return FireAlarmResponseModel.fromJson(jsonResponse); } else { _logger.info( 'Failed to load fire alarm with status code: ${response.statusCode}'); @@ -76,15 +75,15 @@ class FireAlarmEquipmentService { } } - Future updateFireAlarm(int fireAlarmId, - FireAlarmEquipmentRequestModel fireAlarmEquipmentRequestModel) async { + Future updateFireAlarm( + int fireAlarmId, FireAlarmRequestModel fireAlarmRequestModel) async { var url = Uri.parse('${baseUrl}fire-alarms/$fireAlarmId/'); try { var response = await client.put( url, headers: {'Content-Type': 'application/json'}, - body: jsonEncode(fireAlarmEquipmentRequestModel.toJson()), + body: jsonEncode(fireAlarmRequestModel.toJson()), ); if (response.statusCode == 200) { diff --git a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart index ad956fd2..80692949 100644 --- a/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart +++ b/frontend/sige_ie/lib/equipments/feature/fire_alarm/add_fire_alarm.dart @@ -75,7 +75,7 @@ class _AddEquipmentScreenState extends State { List> genericEquipmentTypes = []; List> personalEquipmentTypes = []; Map personalEquipmentMap = {}; - FireAlarmEquipmentResponseModel? fireAlarmEquipmentResponseModel; + FireAlarmResponseModel? fireAlarmResponseModel; @override void initState() { @@ -90,15 +90,15 @@ class _AddEquipmentScreenState extends State { try { await _fetchFireAlarmEquipment(fireAlarmId); - if (fireAlarmEquipmentResponseModel != null) { + if (fireAlarmResponseModel != null) { setState(() { - equipmentId = fireAlarmEquipmentResponseModel!.equipment; - _quantity.text = fireAlarmEquipmentResponseModel!.quantity.toString(); + equipmentId = fireAlarmResponseModel!.equipment; + _quantity.text = fireAlarmResponseModel!.quantity.toString(); print('Loaded quantity: ${_quantity.text}'); }); - _fetchEquipmentDetails(fireAlarmEquipmentResponseModel!.equipment); - _fetchExistingPhotos(fireAlarmEquipmentResponseModel!.equipment); + _fetchEquipmentDetails(fireAlarmResponseModel!.equipment); + _fetchExistingPhotos(fireAlarmResponseModel!.equipment); } } catch (error) { print('Error: $error'); @@ -106,7 +106,7 @@ class _AddEquipmentScreenState extends State { } Future _fetchFireAlarmEquipment(int fireAlarmId) async { - fireAlarmEquipmentResponseModel = + fireAlarmResponseModel = await fireAlarmService.getFireAlarmById(fireAlarmId); } @@ -330,17 +330,10 @@ class _AddEquipmentScreenState extends State { quantity: int.tryParse(_quantity.text), ); - final FireAlarmEquipmentRequestModel fireAlarmEquipmentDetail = - FireAlarmEquipmentRequestModel( - genericEquipmentCategory: genericEquipmentCategory, - personalEquipmentCategory: personalEquipmentCategory, - fireAlarmRequestModel: fireAlarmModel, - ); - print('Fire Alarm Model: ${fireAlarmModel.toJson()}'); bool fireAlarmUpdateSuccess = await fireAlarmService.updateFireAlarm( - widget.fireAlarmId!, fireAlarmEquipmentDetail); + widget.fireAlarmId!, fireAlarmModel); if (fireAlarmUpdateSuccess) { await Future.wait(_images @@ -636,19 +629,10 @@ class _AddEquipmentScreenState extends State { fireAlarmRequestModel: fireAlarmModel, ); - int? fireAlarmId; - if (widget.isEdit) { - fireAlarmId = widget.fireAlarmId; - bool success = await fireAlarmService.updateFireAlarm( - fireAlarmId!, fireAlarmEquipmentDetail); - if (!success) fireAlarmId = null; - } else { - fireAlarmId = - await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); - setState(() { - equipmentId = fireAlarmId; - }); - } + int? id = await equipmentService.createFireAlarm(fireAlarmEquipmentDetail); + setState(() { + equipmentId = id; + }); if (equipmentId != null && equipmentId != 0) { print('Registering photos for equipment ID: $equipmentId'); From 96cab20d993cd5887b7f2cfdd0bec6bd4a4b1efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Lucas=20Garcia=20=D7=90=D7=9E=D7=AA?= Date: Mon, 22 Jul 2024 16:13:33 -0300 Subject: [PATCH 331/351] Atualiza fase em README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29e46e65..fdc10c32 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ SIGE IE ## Fase Release 1 ✔️ Ir para milestone da release 1 -Release 2 (atual) Ir para milestone da release 2 +Release 2 ✔️Ir para milestone da release 2 +Release 3 (atual) Ir para milestone da release 3 ## Visão geral do produto ### Sobre From 4b1125d036235113dc40f42aa0f890d458a7e5c2 Mon Sep 17 00:00:00 2001 From: OscarDeBrito Date: Tue, 30 Jul 2024 15:44:43 -0300 Subject: [PATCH 332/351] . --- frontend/sige_ie/pubspec.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/sige_ie/pubspec.lock b/frontend/sige_ie/pubspec.lock index 5fde215d..2508e2d9 100644 --- a/frontend/sige_ie/pubspec.lock +++ b/frontend/sige_ie/pubspec.lock @@ -396,26 +396,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.0" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "2.0.1" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.0.1" lints: dependency: transitive description: @@ -460,10 +460,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.0" mime: dependency: "direct main" description: @@ -673,10 +673,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -753,10 +753,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "13.0.0" web: dependency: transitive description: From 229be9e4a952087e038de2c486bce36495885ee9 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 1 Aug 2024 09:05:55 -0300 Subject: [PATCH 333/351] backend: altera host do banco --- api/sigeie/docker-compose.yml | 19 ------------------- api/sigeie/settings.py | 2 +- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 api/sigeie/docker-compose.yml diff --git a/api/sigeie/docker-compose.yml b/api/sigeie/docker-compose.yml deleted file mode 100644 index 2645fb73..00000000 --- a/api/sigeie/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3.9" -services: - db: - image: mysql:8.1.0 - restart: always - environment: - MYSQL_DATABASE: 'sigeie_db' - MYSQL_ROOT_PASSWORD: 'root' - container_name: sigeie_mysql_db - ports: - - "3306:3306" - volumes: - - ./data/mysql/db:/var/lib/mysql - redis: - image: redis:7.2 - restart: always - container_name: sigeie_redis_db - ports: - - "6379:6379" diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index ce84ca99..fa63a638 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -61,7 +61,7 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', - 'HOST': '127.0.0.1', + 'HOST': 'db', 'PORT': '3306', 'USER': 'root', 'PASSWORD': 'root', From cbf52e10d8dbd96eca89c4b5b84c6a8471af7975 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 1 Aug 2024 09:06:28 -0300 Subject: [PATCH 334/351] backend: configura docker para usar a imagem do django --- api/docker-compose.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 api/docker-compose.yml diff --git a/api/docker-compose.yml b/api/docker-compose.yml new file mode 100644 index 00000000..64954f82 --- /dev/null +++ b/api/docker-compose.yml @@ -0,0 +1,37 @@ +version: "3.9" +services: + db: + image: mysql:8.1.0 + restart: always + environment: + MYSQL_DATABASE: 'sigeie_db' + MYSQL_ROOT_PASSWORD: 'root' + container_name: sigeie_mysql_db + ports: + - "3306:3306" + volumes: + - ./data/mysql/db:/var/lib/mysql + redis: + image: redis:7.2 + restart: always + container_name: sigeie_redis_db + ports: + - "6379:6379" + web: + build: . + restart: always + container_name: sigeie_web + command: ["python", "manage.py", "runserver", "0.0.0.0:8000"] + volumes: + - .:/app + ports: + - "8000:8000" + depends_on: + - db + - redis + environment: + - DB_HOST=db + - DB_NAME=sigeie_db + - DB_USER=root + - DB_PASSWORD=root + - REDIS_HOST=redis From f6e1f63d2a1f118023c87e61ed0824d1b593ad63 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 1 Aug 2024 09:06:49 -0300 Subject: [PATCH 335/351] backend: cria Dockerfile para a imagem do django --- api/Dockerfile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 api/Dockerfile diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 00000000..3d6b28de --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.11 + +WORKDIR /app + +COPY requirements.txt /app/ + +RUN pip install --no-cache-dir -r requirements.txt + +COPY . /app/ + +EXPOSE 8000 From 236cffde04f1c83cc56b3461e2a5249744714b90 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 1 Aug 2024 09:07:19 -0300 Subject: [PATCH 336/351] backend: cria dockerignore --- api/.dockerignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 api/.dockerignore diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 00000000..354168f2 --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1 @@ +data/mysql/db From 30f161c9f873dd590cb16c28e65915f2e93a37a0 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 1 Aug 2024 09:22:16 -0300 Subject: [PATCH 337/351] backend: fix bug do nome do host --- api/sigeie/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index fa63a638..ce84ca99 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -61,7 +61,7 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', - 'HOST': 'db', + 'HOST': '127.0.0.1', 'PORT': '3306', 'USER': 'root', 'PASSWORD': 'root', From f3d0727c1c22d4416e5758d5f77203924318f040 Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 1 Aug 2024 09:37:04 -0300 Subject: [PATCH 338/351] backend: fix docker-compose e settings.py Co-authored-by: Kauan --- api/docker-compose.yml | 8 +------- api/sigeie/settings.py | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/api/docker-compose.yml b/api/docker-compose.yml index 64954f82..e38f4b8a 100644 --- a/api/docker-compose.yml +++ b/api/docker-compose.yml @@ -28,10 +28,4 @@ services: - "8000:8000" depends_on: - db - - redis - environment: - - DB_HOST=db - - DB_NAME=sigeie_db - - DB_USER=root - - DB_PASSWORD=root - - REDIS_HOST=redis + - redis \ No newline at end of file diff --git a/api/sigeie/settings.py b/api/sigeie/settings.py index ce84ca99..fa63a638 100644 --- a/api/sigeie/settings.py +++ b/api/sigeie/settings.py @@ -61,7 +61,7 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', - 'HOST': '127.0.0.1', + 'HOST': 'db', 'PORT': '3306', 'USER': 'root', 'PASSWORD': 'root', From 4e28734e1a05a03b2153a22adbe1b4e5cb54a1fe Mon Sep 17 00:00:00 2001 From: Pedro Lucas Garcia <190115548@aluno.unb.br> Date: Thu, 1 Aug 2024 09:44:04 -0300 Subject: [PATCH 339/351] atualiza como subir o projeto em README.md Co-authored-by: Kauan --- README.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fdc10c32..ebdcb4be 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ SIGE IE ## Fase Release 1 ✔️ Ir para milestone da release 1 -Release 2 ✔️Ir para milestone da release 2 +Release 2 ✔️ Ir para milestone da release 2 Release 3 (atual) Ir para milestone da release 3 ## Visão geral do produto @@ -172,13 +172,13 @@ Vá para dentro da pasta raiz `api`: pip install -r requirements.txt ``` -3. Inicie o Docker, depois vá para o diretório `api/sigeie` e crie a imagem do banco de dados pela primeira vez: +3. Inicie o Docker, depois vá para o diretório `api` e crie a imagem do banco de dados e da api pela primeira vez: ``` - docker-compose up -d + docker-compose up --build ``` -4. Ainda no mesmo terminal, retorne para o diretório raiz `api` e aplique as migrações: +4. Em outro terminal, retorne para o diretório raiz `api` e aplique as migrações: ``` python manage.py makemigrations @@ -188,12 +188,6 @@ Vá para dentro da pasta raiz `api`: python manage.py migrate ``` -5. Inicie o servidor: - - ``` - python manage.py runserver - ``` - Pronto, o servidor já está rodando com o banco de dados configurado. ##### Pela segunda vez @@ -210,7 +204,7 @@ Garanta que não haja nenhum processo que use o porto 8080, 3306 e 6379. Por fim - Atualizar as dependências, fazer as migrações e iniciar o servidor: ``` - source venv/bin/activate && pip install -r requirements.txt && python manage.py makemigrations && python manage.py migrate && python manage.py runserver + source venv/bin/activate && python manage.py makemigrations && python manage.py migrate ``` Isso é tudo, pessoal. From 3cab21d0c432d109862b5b40483d85510302f75b Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 2 Aug 2024 11:40:36 -0300 Subject: [PATCH 340/351] imagem logo e realese atualizada Co-authored-by: pedro --- docs/index.html | 3 ++- docs/info/historiasDeUsuarios.md | 0 docs/info/home.md | 4 +++- docs/releases/{Release1.md => release1.md} | 0 mkdocs.yml | 3 ++- 5 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 docs/info/historiasDeUsuarios.md rename docs/releases/{Release1.md => release1.md} (100%) diff --git a/docs/index.html b/docs/index.html index 2070e36e..43075a9e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -447,8 +447,9 @@

    -
  • • Desenvolvedor backend
  • +
  • • Desenvolvedor Frontend
  • • Desenvolvedor Infraestrutura
  • +
  • • Desenvolvedor backend
diff --git a/docs/info/historiasDeUsuarios.md b/docs/info/historiasDeUsuarios.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/info/home.md b/docs/info/home.md index a81c3947..0a344da4 100644 --- a/docs/info/home.md +++ b/docs/info/home.md @@ -1,5 +1,7 @@

SIGE IE

- +

+ Logo +


diff --git a/docs/releases/Release1.md b/docs/releases/release1.md similarity index 100% rename from docs/releases/Release1.md rename to docs/releases/release1.md diff --git a/mkdocs.yml b/mkdocs.yml index ac95b4b3..28acca25 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,13 +39,14 @@ nav: - Cronograma: - Cronograma: info/cronograma.md - Releases: - - Release 1: releases/Release1.md + - Release 1: releases/release1.md - Release 2: releases/release2.md - Release 3: releases/release3.md - Release 4: releases/release4.md - Requisitos de Software: - Épicos: info/requisitosEpicos.md - Features: info/requisitosFeatures.md + - Histórias de Usuário: info/historiasDeUsuarios.md plugins: - search \ No newline at end of file From c9577b795f169697de5c705e6b9bf0398459e55f Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 2 Aug 2024 12:16:25 -0300 Subject: [PATCH 341/351] =?UTF-8?q?Historia=20de=20Usu=C3=A1rio,=20criar?= =?UTF-8?q?=20conta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pedro Co-authored-by: OscarDeBrito Co-authored-by: Kauan Jose --- docs/info/historiasDeUsuarios.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/info/historiasDeUsuarios.md b/docs/info/historiasDeUsuarios.md index e69de29b..27a39978 100644 --- a/docs/info/historiasDeUsuarios.md +++ b/docs/info/historiasDeUsuarios.md @@ -0,0 +1,10 @@ +# História de Usuário + +Aqui estão as histórias de usuários mapeadas pelos épicos e features conforme o método SAFe. + +| Épico | Feature | Número | Prioridade | Título | Descrição | Critérios de aceitação | +| --- | --- | --- | --- | --- | --- | --- | +| E1 | F1 | US1 | Alta | Cadastrar Usuário | Como usuário quero cadastrar no sistema para utilizar seus recursos e funcionalidades disponíveis | Atributos obrigatórios: id, nome, nome de usuário, e-mail, senha, está ativo, data de criação, grupos;

Os atributos nome de usuário e senha devem ter no mínimo 6 caracteres, e no máximo 23 e 200 caracteres, respectivamente;

Os atributos está ativo, data de criação são automáticos; A senha deve ser criptografada;

Os grupos são: criador, editor, usuário;

Observações:

Um usuário pertence ao grupo usuário quando cria uma nova conta;

Um usuário pertence ao grupo criador quando cria um lugar;

Um usuário pertence ao grupo editor quando edita um lugar;

| + + + From c7926b8ca0b5d0512da65b7c766826105d9b0002 Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 2 Aug 2024 12:31:43 -0300 Subject: [PATCH 342/351] =?UTF-8?q?Historia=20de=20Usu=C3=A1rio,=20visuali?= =?UTF-8?q?zar=20=20conta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pedro Co-authored-by: OscarDeBrito Co-authored-by: Kauan Jose --- docs/info/historiasDeUsuarios.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/info/historiasDeUsuarios.md b/docs/info/historiasDeUsuarios.md index 27a39978..27cca7b8 100644 --- a/docs/info/historiasDeUsuarios.md +++ b/docs/info/historiasDeUsuarios.md @@ -5,6 +5,7 @@ Aqui estão as histórias de usuários mapeadas pelos épicos e features conform | Épico | Feature | Número | Prioridade | Título | Descrição | Critérios de aceitação | | --- | --- | --- | --- | --- | --- | --- | | E1 | F1 | US1 | Alta | Cadastrar Usuário | Como usuário quero cadastrar no sistema para utilizar seus recursos e funcionalidades disponíveis | Atributos obrigatórios: id, nome, nome de usuário, e-mail, senha, está ativo, data de criação, grupos;

Os atributos nome de usuário e senha devem ter no mínimo 6 caracteres, e no máximo 23 e 200 caracteres, respectivamente;

Os atributos está ativo, data de criação são automáticos; A senha deve ser criptografada;

Os grupos são: criador, editor, usuário;

Observações:

Um usuário pertence ao grupo usuário quando cria uma nova conta;

Um usuário pertence ao grupo criador quando cria um lugar;

Um usuário pertence ao grupo editor quando edita um lugar;

| +| E1 | F1 | US2 | Alta | Visualizar Usuário |Como um usuário registrado, quero visualizar minhas informações de conta para que eu possa revisar e atualizar meus dados conforme necessário | Critérios de aceitação:

Deve-se visualizar as informações nome, nome de usuário, e-mail;

Não deve ser permitida a visualização da senha, em nenhuma hipótese;

| From 775ad83904a57c437bfd8ae179250cbc9bc650df Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 2 Aug 2024 12:37:42 -0300 Subject: [PATCH 343/351] =?UTF-8?q?Historia=20de=20Usu=C3=A1rio,=20editar?= =?UTF-8?q?=20conta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pedro Co-authored-by: OscarDeBrito Co-authored-by: Kauan Jose --- docs/info/historiasDeUsuarios.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/info/historiasDeUsuarios.md b/docs/info/historiasDeUsuarios.md index 27cca7b8..0342bd9e 100644 --- a/docs/info/historiasDeUsuarios.md +++ b/docs/info/historiasDeUsuarios.md @@ -6,6 +6,6 @@ Aqui estão as histórias de usuários mapeadas pelos épicos e features conform | --- | --- | --- | --- | --- | --- | --- | | E1 | F1 | US1 | Alta | Cadastrar Usuário | Como usuário quero cadastrar no sistema para utilizar seus recursos e funcionalidades disponíveis | Atributos obrigatórios: id, nome, nome de usuário, e-mail, senha, está ativo, data de criação, grupos;

Os atributos nome de usuário e senha devem ter no mínimo 6 caracteres, e no máximo 23 e 200 caracteres, respectivamente;

Os atributos está ativo, data de criação são automáticos; A senha deve ser criptografada;

Os grupos são: criador, editor, usuário;

Observações:

Um usuário pertence ao grupo usuário quando cria uma nova conta;

Um usuário pertence ao grupo criador quando cria um lugar;

Um usuário pertence ao grupo editor quando edita um lugar;

| | E1 | F1 | US2 | Alta | Visualizar Usuário |Como um usuário registrado, quero visualizar minhas informações de conta para que eu possa revisar e atualizar meus dados conforme necessário | Critérios de aceitação:

Deve-se visualizar as informações nome, nome de usuário, e-mail;

Não deve ser permitida a visualização da senha, em nenhuma hipótese;

| - +| E1| F1 | US3 | Alta | Editar Usuário |Como usuário quero editar o meu perfil no sistema para atualizar meus dados.|Critérios de aceitação:

Editar atributos obrigatórios: Nome, e-mail | From 705a49b7643cf446f17fded0b8ed4ea743655405 Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 2 Aug 2024 12:54:35 -0300 Subject: [PATCH 344/351] =?UTF-8?q?Historia=20de=20Usu=C3=A1rio,=20excluir?= =?UTF-8?q?=20conta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pedro Co-authored-by: OscarDeBrito Co-authored-by: Kauan Jose --- docs/info/historiasDeUsuarios.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/info/historiasDeUsuarios.md b/docs/info/historiasDeUsuarios.md index 0342bd9e..24ed7133 100644 --- a/docs/info/historiasDeUsuarios.md +++ b/docs/info/historiasDeUsuarios.md @@ -6,6 +6,7 @@ Aqui estão as histórias de usuários mapeadas pelos épicos e features conform | --- | --- | --- | --- | --- | --- | --- | | E1 | F1 | US1 | Alta | Cadastrar Usuário | Como usuário quero cadastrar no sistema para utilizar seus recursos e funcionalidades disponíveis | Atributos obrigatórios: id, nome, nome de usuário, e-mail, senha, está ativo, data de criação, grupos;

Os atributos nome de usuário e senha devem ter no mínimo 6 caracteres, e no máximo 23 e 200 caracteres, respectivamente;

Os atributos está ativo, data de criação são automáticos; A senha deve ser criptografada;

Os grupos são: criador, editor, usuário;

Observações:

Um usuário pertence ao grupo usuário quando cria uma nova conta;

Um usuário pertence ao grupo criador quando cria um lugar;

Um usuário pertence ao grupo editor quando edita um lugar;

| | E1 | F1 | US2 | Alta | Visualizar Usuário |Como um usuário registrado, quero visualizar minhas informações de conta para que eu possa revisar e atualizar meus dados conforme necessário | Critérios de aceitação:

Deve-se visualizar as informações nome, nome de usuário, e-mail;

Não deve ser permitida a visualização da senha, em nenhuma hipótese;

| -| E1| F1 | US3 | Alta | Editar Usuário |Como usuário quero editar o meu perfil no sistema para atualizar meus dados.|Critérios de aceitação:

Editar atributos obrigatórios: Nome, e-mail | +| E1 | F1 | US3 | Alta | Editar Usuário |Como usuário quero editar o meu perfil no sistema para atualizar meus dados.|Critérios de aceitação:

Editar atributos obrigatórios: Nome, e-mail

| +| E1 | F1 | US4 | Alta | Excluir Conta | Como um usuário do aplicativo, quero excluir minha conta, para não disponibilizar mais minhas informações no sistema nem utilizar mais seus serviços.|Critérios de aceitação:

Ao selecionar a opção de exclusão de conta, o usuário deve ser solicitado a confirmar sua escolha;

Após a confirmação da exclusão da conta, todos os dados pessoais do usuário devem ser removidos do sistema;

| From 5642000761ab9368a4625edd9d892f96448c7ffe Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 2 Aug 2024 13:05:24 -0300 Subject: [PATCH 345/351] =?UTF-8?q?Historia=20de=20Usu=C3=A1rio,=20realiza?= =?UTF-8?q?r=20conta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pedro Co-authored-by: OscarDeBrito Co-authored-by: Kauan Jose --- docs/info/historiasDeUsuarios.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/info/historiasDeUsuarios.md b/docs/info/historiasDeUsuarios.md index 24ed7133..d5a293fe 100644 --- a/docs/info/historiasDeUsuarios.md +++ b/docs/info/historiasDeUsuarios.md @@ -8,5 +8,6 @@ Aqui estão as histórias de usuários mapeadas pelos épicos e features conform | E1 | F1 | US2 | Alta | Visualizar Usuário |Como um usuário registrado, quero visualizar minhas informações de conta para que eu possa revisar e atualizar meus dados conforme necessário | Critérios de aceitação:

Deve-se visualizar as informações nome, nome de usuário, e-mail;

Não deve ser permitida a visualização da senha, em nenhuma hipótese;

| | E1 | F1 | US3 | Alta | Editar Usuário |Como usuário quero editar o meu perfil no sistema para atualizar meus dados.|Critérios de aceitação:

Editar atributos obrigatórios: Nome, e-mail

| | E1 | F1 | US4 | Alta | Excluir Conta | Como um usuário do aplicativo, quero excluir minha conta, para não disponibilizar mais minhas informações no sistema nem utilizar mais seus serviços.|Critérios de aceitação:

Ao selecionar a opção de exclusão de conta, o usuário deve ser solicitado a confirmar sua escolha;

Após a confirmação da exclusão da conta, todos os dados pessoais do usuário devem ser removidos do sistema;

| +| E1 | F1 | US5 | Alta | Realizar Login | Como usuário, quero poder fazer login com segurança para ter acesso as informações no aplicativo.| Critérios de aceitação:

O login é feito com nome de usuário, senha;

Caso o usuário insira credenciais inválidas, deve ser exibida uma mensagem de erro indicando que o login falhou;

Após o login bem-sucedido, o usuário deve ser redirecionado para a página inicial;

| From a973825c081bf785dde28ce8758c46b85af7be81 Mon Sep 17 00:00:00 2001 From: ramires Date: Fri, 2 Aug 2024 13:16:13 -0300 Subject: [PATCH 346/351] =?UTF-8?q?Historia=20de=20Usu=C3=A1rio,=20realiza?= =?UTF-8?q?r=20logout=20da=20=20conta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pedro Co-authored-by: OscarDeBrito Co-authored-by: Kauan Jose --- docs/info/historiasDeUsuarios.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/info/historiasDeUsuarios.md b/docs/info/historiasDeUsuarios.md index d5a293fe..6fb5f483 100644 --- a/docs/info/historiasDeUsuarios.md +++ b/docs/info/historiasDeUsuarios.md @@ -9,5 +9,7 @@ Aqui estão as histórias de usuários mapeadas pelos épicos e features conform | E1 | F1 | US3 | Alta | Editar Usuário |Como usuário quero editar o meu perfil no sistema para atualizar meus dados.|Critérios de aceitação:

Editar atributos obrigatórios: Nome, e-mail

| | E1 | F1 | US4 | Alta | Excluir Conta | Como um usuário do aplicativo, quero excluir minha conta, para não disponibilizar mais minhas informações no sistema nem utilizar mais seus serviços.|Critérios de aceitação:

Ao selecionar a opção de exclusão de conta, o usuário deve ser solicitado a confirmar sua escolha;

Após a confirmação da exclusão da conta, todos os dados pessoais do usuário devem ser removidos do sistema;

| | E1 | F1 | US5 | Alta | Realizar Login | Como usuário, quero poder fazer login com segurança para ter acesso as informações no aplicativo.| Critérios de aceitação:

O login é feito com nome de usuário, senha;

Caso o usuário insira credenciais inválidas, deve ser exibida uma mensagem de erro indicando que o login falhou;

Após o login bem-sucedido, o usuário deve ser redirecionado para a página inicial;

| +| E1 | F1 | US6 | Alta | Realizar Logout | Como um usuário autenticado no sistema,quero fazer logout para encerrar minha sessão no sistema; | Critérios de aceitação:

Ao clicar no botão de logout, o sistema deve encerrar a sessão atual do usuário;

Após fazer logout, o usuário deve ser redirecionado para a página de login;

O logout deve limpar todas as informações de autenticação e sessão do usuário, garantindo que não haja acesso não autorizado à conta após o logout;

| + From a76da81a0ee8c90669f67ded83b55a200b3d0c25 Mon Sep 17 00:00:00 2001 From: ramires Date: Mon, 5 Aug 2024 17:47:26 -0300 Subject: [PATCH 347/351] historia de usuarios realese 2 Co-authored-by: Danilo melo Co-authored-by: Ramires rocha Co-authored-by: Kauan Jose Co-authored-by: Oscar DeBrito Co-authored-by: Pedro Lucas --- mkdocs.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 28acca25..c44a62d4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,7 +46,11 @@ nav: - Requisitos de Software: - Épicos: info/requisitosEpicos.md - Features: info/requisitosFeatures.md - - Histórias de Usuário: info/historiasDeUsuarios.md + - Histórias de Usuário: + - realese 1: info/historiasDeUsuarios.md + - realese 2: info/historiasDeUsuarios2.md + - realese 3: info/historiaDeUsuarios3.md + - realese 4: info/historiaDeUsuarios4.md plugins: - search \ No newline at end of file From 808c438f8bae1ed0279f10e0e5aa06121c0b14b9 Mon Sep 17 00:00:00 2001 From: ramires Date: Mon, 5 Aug 2024 17:48:24 -0300 Subject: [PATCH 348/351] historia de usuarios 2 Co-authored-by: Danilo melo Co-authored-by: Ramires rocha Co-authored-by: Kauan Jose Co-authored-by: Oscar DeBrito Co-authored-by: Pedro Lucas --- docs/info/historiasDeUsuarios2.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 docs/info/historiasDeUsuarios2.md diff --git a/docs/info/historiasDeUsuarios2.md b/docs/info/historiasDeUsuarios2.md new file mode 100644 index 00000000..18d5ab07 --- /dev/null +++ b/docs/info/historiasDeUsuarios2.md @@ -0,0 +1,12 @@ +# História de Usuário + +Aqui estão as histórias de usuários mapeadas pelos épicos e features conforme o método SAFe. + +| Épico | Feature | Número | Prioridade | Título | Descrição | Critérios de aceitação | +| --- | --- | --- | --- | --- | --- | --- | +| E2 | F2 | US1 | Alta | Cadastrar Local | Como usuário autenticado, quero poder registrar um novo local no aplicativo para poder gerenciá-lo.| Atributos obrigatórios: nome, longitude, latitude, dono do lugar;O dono deve ser definido automaticamente como o usuário autenticado no sistema.

| +| E2 | F2 | US2 | Alta | Visualizar Local | Como usuário, desejo visualizar um local que criei para revisar seu status e detalhes.| Deve-se visualizar as informações nome, longitude, latitude.

| +| E2 | F2 | US3 | Alta | Editar Local | Como usuário, quero poder editar informações de locais caso haja necessidade de atualização.| Editar atributos obrigatórios: nome, longitude, latitude ;

| +| E2 | F2 | US4 | Alta | Cadastrar area | Como usuário, quero poder adicionar uma area dentro de cada local para registrar as instalações elétricas daquela area.| Atributos obrigatórios: id, nome, andar, local;

| +| E2 | F2 | US5 | Alta | Visualizar area | Como usuário, desejo visualizar as áreas que eu criei para revisar seu status e detalhes.| Deve-se visualizar as informações: nome, andar, lugar.

| +| E2 | F2 | US6 | Alta | Editar area | Como usuário, quero poder editar informações da area de um lugar, caso haja necessidade de atualização.| Editar atributos obrigatórios: nome, andar;

| \ No newline at end of file From 40b2a18e1c3778c046ef9c546aad69fe1ab05161 Mon Sep 17 00:00:00 2001 From: ramires Date: Mon, 5 Aug 2024 17:48:56 -0300 Subject: [PATCH 349/351] historia de usuarios 3 Co-authored-by: Danilo melo Co-authored-by: Ramires rocha Co-authored-by: Kauan Jose Co-authored-by: Oscar DeBrito Co-authored-by: Pedro Lucas --- docs/info/historiaDeUsuarios3.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/info/historiaDeUsuarios3.md diff --git a/docs/info/historiaDeUsuarios3.md b/docs/info/historiaDeUsuarios3.md new file mode 100644 index 00000000..e69de29b From 9af36bea99eb156dfa8c2b116bcd9afe33eb6abd Mon Sep 17 00:00:00 2001 From: ramires Date: Mon, 5 Aug 2024 17:49:23 -0300 Subject: [PATCH 350/351] historia de usuarios 4 Co-authored-by: Danilo melo Co-authored-by: Ramires rocha Co-authored-by: Kauan Jose Co-authored-by: Oscar DeBrito Co-authored-by: Pedro Lucas --- docs/info/historiaDeUsuarios4.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/info/historiaDeUsuarios4.md diff --git a/docs/info/historiaDeUsuarios4.md b/docs/info/historiaDeUsuarios4.md new file mode 100644 index 00000000..e69de29b From 2706a1045b2c127f2f977b0147bf16962dd12d43 Mon Sep 17 00:00:00 2001 From: ramires Date: Mon, 5 Aug 2024 17:49:47 -0300 Subject: [PATCH 351/351] historia de usuarios 5 Co-authored-by: Danilo melo Co-authored-by: Ramires rocha Co-authored-by: Kauan Jose Co-authored-by: Oscar DeBrito Co-authored-by: Pedro Lucas --- docs/info/historiasDeUsuarios.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/info/historiasDeUsuarios.md b/docs/info/historiasDeUsuarios.md index 6fb5f483..9febc9c7 100644 --- a/docs/info/historiasDeUsuarios.md +++ b/docs/info/historiasDeUsuarios.md @@ -4,12 +4,13 @@ Aqui estão as histórias de usuários mapeadas pelos épicos e features conform | Épico | Feature | Número | Prioridade | Título | Descrição | Critérios de aceitação | | --- | --- | --- | --- | --- | --- | --- | -| E1 | F1 | US1 | Alta | Cadastrar Usuário | Como usuário quero cadastrar no sistema para utilizar seus recursos e funcionalidades disponíveis | Atributos obrigatórios: id, nome, nome de usuário, e-mail, senha, está ativo, data de criação, grupos;

Os atributos nome de usuário e senha devem ter no mínimo 6 caracteres, e no máximo 23 e 200 caracteres, respectivamente;

Os atributos está ativo, data de criação são automáticos; A senha deve ser criptografada;

Os grupos são: criador, editor, usuário;

Observações:

Um usuário pertence ao grupo usuário quando cria uma nova conta;

Um usuário pertence ao grupo criador quando cria um lugar;

Um usuário pertence ao grupo editor quando edita um lugar;

| -| E1 | F1 | US2 | Alta | Visualizar Usuário |Como um usuário registrado, quero visualizar minhas informações de conta para que eu possa revisar e atualizar meus dados conforme necessário | Critérios de aceitação:

Deve-se visualizar as informações nome, nome de usuário, e-mail;

Não deve ser permitida a visualização da senha, em nenhuma hipótese;

| -| E1 | F1 | US3 | Alta | Editar Usuário |Como usuário quero editar o meu perfil no sistema para atualizar meus dados.|Critérios de aceitação:

Editar atributos obrigatórios: Nome, e-mail

| -| E1 | F1 | US4 | Alta | Excluir Conta | Como um usuário do aplicativo, quero excluir minha conta, para não disponibilizar mais minhas informações no sistema nem utilizar mais seus serviços.|Critérios de aceitação:

Ao selecionar a opção de exclusão de conta, o usuário deve ser solicitado a confirmar sua escolha;

Após a confirmação da exclusão da conta, todos os dados pessoais do usuário devem ser removidos do sistema;

| -| E1 | F1 | US5 | Alta | Realizar Login | Como usuário, quero poder fazer login com segurança para ter acesso as informações no aplicativo.| Critérios de aceitação:

O login é feito com nome de usuário, senha;

Caso o usuário insira credenciais inválidas, deve ser exibida uma mensagem de erro indicando que o login falhou;

Após o login bem-sucedido, o usuário deve ser redirecionado para a página inicial;

| -| E1 | F1 | US6 | Alta | Realizar Logout | Como um usuário autenticado no sistema,quero fazer logout para encerrar minha sessão no sistema; | Critérios de aceitação:

Ao clicar no botão de logout, o sistema deve encerrar a sessão atual do usuário;

Após fazer logout, o usuário deve ser redirecionado para a página de login;

O logout deve limpar todas as informações de autenticação e sessão do usuário, garantindo que não haja acesso não autorizado à conta após o logout;

| +| E1 | F1 | US1 | Alta | Cadastrar Usuário | Como usuário quero cadastrar no sistema para utilizar seus recursos e funcionalidades disponíveis | Atributos obrigatórios: id, nome, nome de usuário, e-mail, senha, está ativo, data de criação, grupos;

Os atributos nome de usuário e senha devem ter no mínimo 6 caracteres, e no máximo 23 e 200 caracteres, respectivamente;

Os atributos está ativo, data de criação são automáticos; A senha deve ser criptografada;

Os grupos são: criador, editor, usuário;

| +| E1 | F1 | US2 | Alta | Visualizar Usuário |Como um usuário registrado, quero visualizar minhas informações de conta para que eu possa revisar e atualizar meus dados conforme necessário | Deve-se visualizar as informações nome, nome de usuário, e-mail;

Não deve ser permitida a visualização da senha, em nenhuma hipótese;

| +| E1 | F1 | US3 | Alta | Editar Usuário |Como usuário quero editar o meu perfil no sistema para atualizar meus dados.|Editar atributos obrigatórios: Nome, e-mail

| +| E1 | F1 | US4 | Alta | Excluir Conta | Como um usuário do aplicativo, quero excluir minha conta, para não disponibilizar mais minhas informações no sistema nem utilizar mais seus serviços.|Ao selecionar a opção de exclusão de conta, o usuário deve ser solicitado a confirmar sua escolha;

Após a confirmação da exclusão da conta, todos os dados pessoais do usuário devem ser removidos do sistema;

| +| E1 | F1 | US5 | Alta | Realizar Login | Como usuário, quero poder fazer login com segurança para ter acesso as informações no aplicativo.| O login é feito com nome de usuário, senha;

Caso o usuário insira credenciais inválidas, deve ser exibida uma mensagem de erro indicando que o login falhou;

Após o login bem-sucedido, o usuário deve ser redirecionado para a página inicial;

| +| E1 | F1 | US6 | Alta | Realizar Logout | Como um usuário autenticado no sistema,quero fazer logout para encerrar minha sessão no sistema; | Ao clicar no botão de logout, o sistema deve encerrar a sessão atual do usuário;

Após fazer logout, o usuário deve ser redirecionado para a página de login;

O logout deve limpar todas as informações de autenticação e sessão do usuário, garantindo que não haja acesso não autorizado à conta após o logout;

| +

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 z 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 z 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