dashboard
This commit is contained in:
parent
ba34d0e59b
commit
7215cc263a
@ -30,6 +30,11 @@ class MedMapAPI {
|
|||||||
return try await apiClient.request(url, method: .post, parameters: SignupRequest(email: email, password: password, role: role))
|
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
|
// MARK: - Companies
|
||||||
|
|
||||||
func createCompany(request: CreateCompanyRequest) async throws -> CompanyDTO {
|
func createCompany(request: CreateCompanyRequest) async throws -> CompanyDTO {
|
||||||
@ -113,3 +118,5 @@ class MedMapAPI {
|
|||||||
return try await apiClient.request(url, method: .get)
|
return try await apiClient.request(url, method: .get)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct LogoutRequest: Encodable {}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ final class DashboardViewModel: ViewModel {
|
|||||||
@Published private(set) var prescriptions: [PrescriptionWithSchedulesResponse] = []
|
@Published private(set) var prescriptions: [PrescriptionWithSchedulesResponse] = []
|
||||||
@Published private(set) var medications: [String: MedicationDTO] = [:]
|
@Published private(set) var medications: [String: MedicationDTO] = [:]
|
||||||
@Published private(set) var errorMessage: String?
|
@Published private(set) var errorMessage: String?
|
||||||
|
@Published private(set) var logoutErrorMessage: String?
|
||||||
|
|
||||||
private let api: MedMapAPI
|
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 {
|
private var currentTime: String {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
|||||||
@ -4,6 +4,7 @@ struct DashboardView: View {
|
|||||||
|
|
||||||
@StateObject private var viewModel = DashboardViewModel()
|
@StateObject private var viewModel = DashboardViewModel()
|
||||||
@State private var isShowingMonthlyHistory = false
|
@State private var isShowingMonthlyHistory = false
|
||||||
|
@State private var isShowingLogoutConfirmation = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@ -44,7 +45,15 @@ struct DashboardView: View {
|
|||||||
}
|
}
|
||||||
.sheet(isPresented: $isShowingMonthlyHistory) {
|
.sheet(isPresented: $isShowingMonthlyHistory) {
|
||||||
PatientMonthlyHistorySheet()
|
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)
|
.fill(.green)
|
||||||
.frame(width: 16, height: 16)
|
.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)
|
.padding(.horizontal, 32)
|
||||||
|
|
||||||
|
if let logoutErrorMessage = viewModel.logoutErrorMessage {
|
||||||
|
Text(logoutErrorMessage)
|
||||||
|
.font(.jakartaRegular12)
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Patient header
|
// MARK: - Patient header
|
||||||
|
|||||||
@ -13,32 +13,28 @@ struct PatientMonthlyHistorySheet: View {
|
|||||||
private let columns = Array(repeating: GridItem(.flexible(), spacing: 7), count: 7)
|
private let columns = Array(repeating: GridItem(.flexible(), spacing: 7), count: 7)
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
VStack(spacing: 0) {
|
||||||
Color.appBackgroundLight.opacity(0.22).ignoresSafeArea()
|
Spacer(minLength: 0)
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 24) {
|
||||||
Spacer(minLength: 0)
|
header
|
||||||
|
content
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
.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 {
|
.task {
|
||||||
await viewModel.loadHistory()
|
await viewModel.loadHistory()
|
||||||
@ -64,7 +60,7 @@ struct PatientMonthlyHistorySheet: View {
|
|||||||
Text(viewModel.history?.periodLabel ?? "Histórico")
|
Text(viewModel.history?.periodLabel ?? "Histórico")
|
||||||
.font(.jakartaBold16)
|
.font(.jakartaBold16)
|
||||||
.foregroundStyle(Color.appAccent)
|
.foregroundStyle(Color.appAccent)
|
||||||
.frame(minWidth: 94)
|
.frame(minWidth: 124)
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Task { await viewModel.showNextMonth() }
|
Task { await viewModel.showNextMonth() }
|
||||||
@ -102,7 +98,7 @@ struct PatientMonthlyHistorySheet: View {
|
|||||||
} else if let history = viewModel.history {
|
} else if let history = viewModel.history {
|
||||||
LazyVGrid(columns: columns, spacing: 11) {
|
LazyVGrid(columns: columns, spacing: 11) {
|
||||||
ForEach(0..<history.leadingDays, id: \.self) { _ in
|
ForEach(0..<history.leadingDays, id: \.self) { _ in
|
||||||
Color.clear.frame(height: 32)
|
Color.clear.frame(height: 38)
|
||||||
}
|
}
|
||||||
ForEach(history.days) { day in
|
ForEach(history.days) { day in
|
||||||
CalendarDayView(day: day)
|
CalendarDayView(day: day)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
|||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@ -21,6 +22,8 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
|
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
|
||||||
|
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -98,4 +101,14 @@ public class RestAuthController {
|
|||||||
|
|
||||||
return ResponseEntity.status(201).headers(headers).body(LoginResponse.from(created));
|
return ResponseEntity.status(201).headers(headers).body(LoginResponse.from(created));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Log out", description = "Invalidates the authenticated session and clears its session cookie.")
|
||||||
|
@ApiResponse(responseCode = "204", description = "Session invalidated")
|
||||||
|
@PostMapping("/logout")
|
||||||
|
public ResponseEntity<Void> 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user