From 7215cc263a57e00ee3fc737efcbd5d7fb27c6415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Ara=C3=BAjo?= Date: Wed, 16 Sep 2026 14:15:51 +0100 Subject: [PATCH] dashboard --- medmap/Network/MedMapAPI.swift | 7 +++ medmap/ViewModels/DashboardViewModel.swift | 18 +++++++ medmap/Views/DashboardView.swift | 31 +++++++++++- medmap/Views/PatientMonthlyHistorySheet.swift | 48 +++++++++---------- .../map02/controllers/RestAuthController.java | 13 +++++ 5 files changed, 90 insertions(+), 27 deletions(-) diff --git a/medmap/Network/MedMapAPI.swift b/medmap/Network/MedMapAPI.swift index 39a02c3..c394f0e 100644 --- a/medmap/Network/MedMapAPI.swift +++ b/medmap/Network/MedMapAPI.swift @@ -30,6 +30,11 @@ class MedMapAPI { return try await apiClient.request(url, method: .post, parameters: SignupRequest(email: email, password: password, role: role)) } + func logout() async throws { + let url = "\(baseURL)/api/auth/logout" + try await apiClient.requestVoid(url, method: .post, parameters: LogoutRequest()) + } + // MARK: - Companies func createCompany(request: CreateCompanyRequest) async throws -> CompanyDTO { @@ -113,3 +118,5 @@ class MedMapAPI { return try await apiClient.request(url, method: .get) } } + +private struct LogoutRequest: Encodable {} diff --git a/medmap/ViewModels/DashboardViewModel.swift b/medmap/ViewModels/DashboardViewModel.swift index 074b4e9..6955401 100644 --- a/medmap/ViewModels/DashboardViewModel.swift +++ b/medmap/ViewModels/DashboardViewModel.swift @@ -9,6 +9,7 @@ final class DashboardViewModel: ViewModel { @Published private(set) var prescriptions: [PrescriptionWithSchedulesResponse] = [] @Published private(set) var medications: [String: MedicationDTO] = [:] @Published private(set) var errorMessage: String? + @Published private(set) var logoutErrorMessage: String? private let api: MedMapAPI @@ -73,6 +74,23 @@ final class DashboardViewModel: ViewModel { } } + func logout() async { + guard !isLoading else { return } + + isLoading = true + logoutErrorMessage = nil + defer { isLoading = false } + + do { + try await api.logout() + appStateManager.resetNavigation() + appStateManager.navigateTo(state: .login) + } catch { + logoutErrorMessage = "Nao foi possivel terminar a sessao. Tente novamente." + fluxlog("Patient logout failed: \(error.localizedDescription)") + } + } + private var currentTime: String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") diff --git a/medmap/Views/DashboardView.swift b/medmap/Views/DashboardView.swift index c8e29a0..e6335c1 100644 --- a/medmap/Views/DashboardView.swift +++ b/medmap/Views/DashboardView.swift @@ -4,6 +4,7 @@ struct DashboardView: View { @StateObject private var viewModel = DashboardViewModel() @State private var isShowingMonthlyHistory = false + @State private var isShowingLogoutConfirmation = false var body: some View { ZStack { @@ -44,7 +45,15 @@ struct DashboardView: View { } .sheet(isPresented: $isShowingMonthlyHistory) { PatientMonthlyHistorySheet() - .presentationBackground(.clear) + .presentationBackground(.ultraThinMaterial) + } + .alert("Terminar sessao?", isPresented: $isShowingLogoutConfirmation) { + Button("Cancelar", role: .cancel) {} + Button("Terminar sessao", role: .destructive) { + Task { await viewModel.logout() } + } + } message: { + Text("Terá de iniciar sessao novamente para consultar a sua medicacao.") } } @@ -66,8 +75,28 @@ struct DashboardView: View { .fill(.green) .frame(width: 16, height: 16) } + + Button { + isShowingLogoutConfirmation = true + } label: { + Image(systemName: "power") + .font(.system(size: 15, weight: .semibold)) + .foregroundStyle(Color.appTextPrimary.opacity(0.52)) + .frame(width: 32, height: 32) + .background(.white.opacity(0.45)) + .clipShape(Circle()) + } + .buttonStyle(.plain) + .accessibilityLabel("Terminar sessao") } .padding(.horizontal, 32) + + if let logoutErrorMessage = viewModel.logoutErrorMessage { + Text(logoutErrorMessage) + .font(.jakartaRegular12) + .foregroundStyle(.red) + .frame(maxWidth: .infinity) + } } // MARK: - Patient header diff --git a/medmap/Views/PatientMonthlyHistorySheet.swift b/medmap/Views/PatientMonthlyHistorySheet.swift index 014217d..ed2bc97 100644 --- a/medmap/Views/PatientMonthlyHistorySheet.swift +++ b/medmap/Views/PatientMonthlyHistorySheet.swift @@ -13,32 +13,28 @@ struct PatientMonthlyHistorySheet: View { private let columns = Array(repeating: GridItem(.flexible(), spacing: 7), count: 7) var body: some View { - ZStack { - Color.appBackgroundLight.opacity(0.22).ignoresSafeArea() + VStack(spacing: 0) { + Spacer(minLength: 0) - VStack(spacing: 0) { - Spacer(minLength: 0) - - VStack(spacing: 20) { - header - content - } - .padding(20) - .background(.white) - .clipShape(RoundedRectangle(cornerRadius: 22)) - .shadow(color: .black.opacity(0.18), radius: 15, y: 8) - .padding(.horizontal, 32) - - Spacer(minLength: 0) - - Button(action: dismiss.callAsFunction) { - Image(systemName: "xmark") - .font(.system(size: 21, weight: .medium)) - .foregroundStyle(Color.appAccent) - .frame(width: 44, height: 44) - } - .padding(.bottom, 48) + VStack(spacing: 24) { + header + content } + .padding(24) + .background(.white) + .clipShape(RoundedRectangle(cornerRadius: 22)) + .shadow(color: .black.opacity(0.18), radius: 15, y: 8) + .padding(.horizontal, 20) + + Spacer(minLength: 0) + + Button(action: dismiss.callAsFunction) { + Image(systemName: "xmark") + .font(.system(size: 21, weight: .medium)) + .foregroundStyle(Color.appAccent) + .frame(width: 44, height: 44) + } + .padding(.bottom, 48) } .task { await viewModel.loadHistory() @@ -64,7 +60,7 @@ struct PatientMonthlyHistorySheet: View { Text(viewModel.history?.periodLabel ?? "Histórico") .font(.jakartaBold16) .foregroundStyle(Color.appAccent) - .frame(minWidth: 94) + .frame(minWidth: 124) Button { Task { await viewModel.showNextMonth() } @@ -102,7 +98,7 @@ struct PatientMonthlyHistorySheet: View { } else if let history = viewModel.history { LazyVGrid(columns: columns, spacing: 11) { ForEach(0.. logout(HttpServletRequest request, HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + new SecurityContextLogoutHandler().logout(request, response, authentication); + new CookieClearingLogoutHandler("JSESSIONID").logout(request, response, authentication); + return ResponseEntity.noContent().build(); + } }