diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/OrganizationUseCaseConfig.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/OrganizationUseCaseConfig.java index 4f8cdd7..e7e43b8 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/OrganizationUseCaseConfig.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/OrganizationUseCaseConfig.java @@ -6,6 +6,7 @@ import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationUs import net.beetechgroup.beetask.usecase.organization.search.SearchOrganizationsUseCase; import net.beetechgroup.beetask.usecase.organization.join.RequestJoinOrganizationUseCase; import net.beetechgroup.beetask.usecase.organization.requests.ListPendingRequestsUseCase; +import net.beetechgroup.beetask.usecase.organization.requests.ListUserJoinRequestsUseCase; import net.beetechgroup.beetask.usecase.organization.requests.HandleJoinRequestUseCase; import net.beetechgroup.beetask.usecase.organization.auth.AuthorizeOrganizationAdminUseCase; import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; @@ -51,4 +52,10 @@ public class OrganizationUseCaseConfig { UserOrganizationRepository userOrganizationRepository) { return new HandleJoinRequestUseCase(authorizer, userOrganizationRepository); } + + @Produces + public ListUserJoinRequestsUseCase listUserJoinRequestsUseCase(UserOrganizationRepository userOrganizationRepository, + UserRepository userRepository) { + return new ListUserJoinRequestsUseCase(userOrganizationRepository, userRepository); + } } \ No newline at end of file diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/organization/OrganizationController.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/organization/OrganizationController.java index ca5b125..345d315 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/organization/OrganizationController.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/organization/OrganizationController.java @@ -10,7 +10,6 @@ import jakarta.ws.rs.PATCH; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Response; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationInput; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationOutput; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationUseCase; @@ -18,6 +17,8 @@ import net.beetechgroup.beetask.usecase.organization.search.SearchOrganizationsU import net.beetechgroup.beetask.usecase.organization.search.SearchOrganizationOutput; import net.beetechgroup.beetask.usecase.organization.join.RequestJoinOrganizationUseCase; import net.beetechgroup.beetask.usecase.organization.requests.ListPendingRequestsUseCase; +import net.beetechgroup.beetask.usecase.organization.requests.ListUserJoinRequestsUseCase; +import net.beetechgroup.beetask.usecase.organization.requests.UserJoinRequestOutput; import net.beetechgroup.beetask.usecase.organization.requests.JoinRequestOutput; import net.beetechgroup.beetask.usecase.organization.requests.HandleJoinRequestUseCase; @@ -38,6 +39,9 @@ public class OrganizationController { @Inject ListPendingRequestsUseCase listPendingRequestsUseCase; + @Inject + ListUserJoinRequestsUseCase listUserJoinRequestsUseCase; + @Inject HandleJoinRequestUseCase handleJoinRequestUseCase; @@ -97,4 +101,13 @@ public class OrganizationController { String userEmail = securityIdentity.getPrincipal().getName(); handleJoinRequestUseCase.execute(userEmail, organizationId, new HandleJoinRequestUseCase.Input(userId, false)); } + + @GET + @Path("/my-requests") + @Authenticated + @Operation(summary = "List my requests", description = "List all join requests made by the current user") + public List listMyRequests() { + String userEmail = securityIdentity.getPrincipal().getName(); + return listUserJoinRequestsUseCase.execute(userEmail); + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/ListUserJoinRequestsUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/ListUserJoinRequestsUseCase.java new file mode 100644 index 0000000..65b76ad --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/ListUserJoinRequestsUseCase.java @@ -0,0 +1,30 @@ +package net.beetechgroup.beetask.usecase.organization.requests; + +import net.beetechgroup.beetask.entities.User; +import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; +import net.beetechgroup.beetask.usecase.repository.UserRepository; + +import java.util.List; + +public class ListUserJoinRequestsUseCase { + private final UserOrganizationRepository userOrganizationRepository; + private final UserRepository userRepository; + + public ListUserJoinRequestsUseCase(UserOrganizationRepository userOrganizationRepository, UserRepository userRepository) { + this.userOrganizationRepository = userOrganizationRepository; + this.userRepository = userRepository; + } + + public List execute(String userEmail) { + User user = userRepository.findByEmail(userEmail) + .orElseThrow(() -> new RuntimeException("User not found")); + + return userOrganizationRepository.findByUserId(user.getId()).stream() + .map(uo -> new UserJoinRequestOutput( + uo.getOrganization().getId(), + uo.getOrganization().getName(), + uo.getStatus() + )) + .toList(); + } +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/UserJoinRequestOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/UserJoinRequestOutput.java new file mode 100644 index 0000000..db8be5f --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/UserJoinRequestOutput.java @@ -0,0 +1,5 @@ +package net.beetechgroup.beetask.usecase.organization.requests; + +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; + +public record UserJoinRequestOutput(Long organizationId, String organizationName, UserOrganizationStatus status) {} diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx index 8dc56d3..dce2094 100644 --- a/frontend/src/components/AppShell.tsx +++ b/frontend/src/components/AppShell.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { Layout, ListTodo, PlusCircle, Columns, LogOut, Settings, Building2 } from 'lucide-react' +import { Layout, ListTodo, PlusCircle, Columns, LogOut, Settings, Building2, Clock } from 'lucide-react' import { NavLink, Outlet, Navigate } from 'react-router-dom' import { useAuth } from '../contexts/AuthContext' import { OnboardingModal } from './OnboardingModal' @@ -59,6 +59,10 @@ export function AppShell() { Quadro + + + Solicitações + Nova Tarefa @@ -123,6 +127,10 @@ export function AppShell() { Quadro + + + Pedidos + Criar diff --git a/frontend/src/pages/MyRequestsPage.tsx b/frontend/src/pages/MyRequestsPage.tsx new file mode 100644 index 0000000..d99be7d --- /dev/null +++ b/frontend/src/pages/MyRequestsPage.tsx @@ -0,0 +1,140 @@ +import React, { useEffect, useState } from "react"; +import { motion } from "framer-motion"; +import { Clock, CheckCircle2, XCircle, Building2, ArrowLeft } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { organizationService, type UserJoinRequest } from "../services/organizationService"; + +const StatusBadge: React.FC<{ status: UserJoinRequest["status"] }> = ({ status }) => { + const configs = { + PENDING: { + label: "Pendente", + icon: Clock, + className: "bg-amber-500/10 text-amber-500 border-amber-500/20", + }, + ACTIVE: { + label: "Aprovado", + icon: CheckCircle2, + className: "bg-emerald-500/10 text-emerald-500 border-emerald-500/20", + }, + REJECTED: { + label: "Recusado", + icon: XCircle, + className: "bg-rose-500/10 text-rose-500 border-rose-500/20", + }, + }; + + const config = configs[status]; + const Icon = config.icon; + + return ( +
+ + {config.label} +
+ ); +}; + +export const MyRequestsPage: React.FC = () => { + const [requests, setRequests] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const navigate = useNavigate(); + + useEffect(() => { + const fetchRequests = async () => { + try { + const data = await organizationService.getUserRequests(); + setRequests(data); + } catch (error) { + console.error("Failed to fetch requests", error); + } finally { + setIsLoading(false); + } + }; + + fetchRequests(); + }, []); + + return ( +
+ {/* Background Orbs */} +
+
+ + +
+
+ +
+

+ Minhas Solicitações +

+

+ Acompanhe o status dos seus pedidos para entrar em organizações +

+
+
+ + {isLoading ? ( +
+ {[1, 2, 3].map((i) => ( +
+ ))} +
+ ) : requests.length === 0 ? ( +
+
+ +
+

Nenhuma solicitação encontrada

+

+ Você ainda não solicitou participar de nenhuma organização. +

+ +
+ ) : ( +
+ {requests.map((request, index) => ( + +
+
+ +
+
+

+ {request.organizationName} +

+ + ID: {request.organizationId} + +
+
+ +
+ ))} +
+ )} +
+ +
+ ); +}; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 59ec700..ff23164 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -9,6 +9,7 @@ import { RegisterPage } from "./pages/RegisterPage"; import { CreateOrganizationPage } from "./pages/CreateOrganizationPage"; import { JoinOrganizationPage } from "./pages/JoinOrganizationPage"; import { OrganizationAdminPage } from "./pages/OrganizationAdminPage"; +import { MyRequestsPage } from "./pages/MyRequestsPage"; export const router = createBrowserRouter([ { @@ -47,6 +48,10 @@ export const router = createBrowserRouter([ path: "admin", element: , }, + { + path: "requests", + element: , + }, ], }, ]); diff --git a/frontend/src/services/organizationService.ts b/frontend/src/services/organizationService.ts index 7fe975b..cab8dbc 100644 --- a/frontend/src/services/organizationService.ts +++ b/frontend/src/services/organizationService.ts @@ -12,6 +12,12 @@ export interface JoinRequest { photo?: string } +export interface UserJoinRequest { + organizationId: number + organizationName: string + status: 'PENDING' | 'ACTIVE' | 'REJECTED' +} + export const organizationService = { create: async (name: string): Promise => { return apiFetch('/organizations', { @@ -38,5 +44,9 @@ export const organizationService = { rejectRequest: async (organizationId: number, userId: number): Promise => { return apiFetch(`/organizations/${organizationId}/requests/${userId}/reject`, { method: 'PATCH' }) + }, + + getUserRequests: async (): Promise => { + return apiFetch('/organizations/my-requests') } }