From 6c055346d95a8d0c53c4f110fb22c4ffcb76ed98 Mon Sep 17 00:00:00 2001 From: mtrznadel24 Date: Sat, 23 May 2026 16:09:47 +0200 Subject: [PATCH 1/2] feat(risks): implement 5x5 risk matrix and fix persistence bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Logic: Implement Probability × Impact (1-5 scale) for risk assessment. API/DTO: Update backend DTOs and Zod schemas to support the new risk model. Frontend: Refactor ProjectRisks to display matrix-based risk levels and add impact input to the project creation wizard. --- .../dto/project/ProjectCreationRequest.java | 5 +- .../controller/dto/project/RiskResponse.java | 21 --- .../ProjectRiskRequest.java} | 17 +- .../projectrisk/ProjectRiskResponse.java | 25 +++ .../project/ProjectRiskController.java | 20 +-- .../domain/entity/project/ProjectRisk.java | 14 +- .../service/command/project/RiskCommand.java | 5 +- .../service/project/ProjectRiskService.java | 19 +- .../service/project/ProjectService.java | 1 + .../project/ProjectRiskServiceTest.java | 20 ++- .../service/project/ProjectServiceTest.java | 2 +- .../project/components/CreateProjectForm.tsx | 9 +- .../components/CreateProjectForm.view.tsx | 164 ++++++++---------- .../project/components/ProjectRisks.tsx | 146 ++++++++++------ .../components/StepMilestoneAndRisk.tsx | 38 ++-- .../src/features/project/project.schema.ts | 3 +- .../src/features/project/project.types.ts | 2 + 17 files changed, 288 insertions(+), 223 deletions(-) delete mode 100644 backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/RiskResponse.java rename backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/{RiskRequest.java => projectrisk/ProjectRiskRequest.java} (58%) create mode 100644 backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/projectrisk/ProjectRiskResponse.java diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/ProjectCreationRequest.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/ProjectCreationRequest.java index 7497fe7e..aab2b214 100644 --- a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/ProjectCreationRequest.java +++ b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/ProjectCreationRequest.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import pl.edu.agh.project_manager.controller.dto.milestone.MilestoneRequest; +import pl.edu.agh.project_manager.controller.dto.project.projectrisk.ProjectRiskRequest; import pl.edu.agh.project_manager.service.command.project.ProjectCreationCommand; import java.time.LocalDate; @@ -33,7 +34,7 @@ public record ProjectCreationRequest( List committee, @Valid - List risks, + List risks, @Valid List milestones @@ -50,7 +51,7 @@ public ProjectCreationCommand toCommand(UUID creatorId) { this.startDate, this.endDate, this.projectGroupId, - this.risks.stream().map(RiskRequest::toCommand).toList(), + this.risks.stream().map(ProjectRiskRequest::toCommand).toList(), this.milestones.stream().map(MilestoneRequest::toCommand).toList(), this.sponsors, this.committee diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/RiskResponse.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/RiskResponse.java deleted file mode 100644 index 391f6beb..00000000 --- a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/RiskResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package pl.edu.agh.project_manager.controller.dto.project; - -import pl.edu.agh.project_manager.domain.entity.project.ProjectRisk; - -import java.util.UUID; - -public record RiskResponse( - UUID id, - String name, - String description, - Integer probability -) { - public static RiskResponse from(ProjectRisk risk) { - return new RiskResponse( - risk.getId(), - risk.getName(), - risk.getDescription(), - risk.getProbability() - ); - } -} diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/RiskRequest.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/projectrisk/ProjectRiskRequest.java similarity index 58% rename from backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/RiskRequest.java rename to backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/projectrisk/ProjectRiskRequest.java index 7450f52e..0ab48d9d 100644 --- a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/RiskRequest.java +++ b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/projectrisk/ProjectRiskRequest.java @@ -1,11 +1,11 @@ -package pl.edu.agh.project_manager.controller.dto.project; +package pl.edu.agh.project_manager.controller.dto.project.projectrisk; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import org.hibernate.validator.constraints.Range; import pl.edu.agh.project_manager.service.command.project.RiskCommand; -public record RiskRequest( +public record ProjectRiskRequest( @NotBlank(message = "Nazwa ryzyka nie może być pusta!") String name, @@ -13,14 +13,19 @@ public record RiskRequest( String description, @NotNull(message = "Poziom prawdopodobieństwa jest wymagany!") - @Range(min = 0, max = 100, message = "Prawdopodobieństwo musi być w skali 0-100%!") - Integer probability + @Range(min = 1, max = 5, message = "Prawdopodobieństwo musi być w skali 1-5!") + Integer probability, + + @NotNull(message = "Poziom wpływu jest wymagany!") + @Range(min = 1, max = 5, message = "Wpływ musi być w skali 1-5!") + Integer impact ) { public RiskCommand toCommand() { return new RiskCommand( this.name, this.description, - this.probability + this.probability, + this.impact ); } -} +} \ No newline at end of file diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/projectrisk/ProjectRiskResponse.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/projectrisk/ProjectRiskResponse.java new file mode 100644 index 00000000..2a32fcb0 --- /dev/null +++ b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/dto/project/projectrisk/ProjectRiskResponse.java @@ -0,0 +1,25 @@ +package pl.edu.agh.project_manager.controller.dto.project.projectrisk; + +import pl.edu.agh.project_manager.domain.entity.project.ProjectRisk; + +import java.util.UUID; + +public record ProjectRiskResponse( + UUID id, + String name, + String description, + Integer probability, + Integer impact, + Integer value +) { + public static ProjectRiskResponse from(ProjectRisk risk) { + return new ProjectRiskResponse( + risk.getId(), + risk.getName(), + risk.getDescription(), + risk.getProbability(), + risk.getImpact(), + risk.getProbability() * risk.getImpact() + ); + } +} diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/project/ProjectRiskController.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/project/ProjectRiskController.java index 60e0846b..80403751 100644 --- a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/project/ProjectRiskController.java +++ b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/controller/project/ProjectRiskController.java @@ -6,8 +6,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import pl.edu.agh.project_manager.controller.dto.project.RiskRequest; -import pl.edu.agh.project_manager.controller.dto.project.RiskResponse; +import pl.edu.agh.project_manager.controller.dto.project.projectrisk.ProjectRiskRequest; +import pl.edu.agh.project_manager.controller.dto.project.projectrisk.ProjectRiskResponse; import pl.edu.agh.project_manager.service.project.ProjectRiskService; import java.util.List; @@ -21,11 +21,11 @@ public class ProjectRiskController { @PostMapping @PreAuthorize("@projectAccess.isProjectManagerForProject(#projectId, authentication.principal)") - public ResponseEntity createProjectRisk( + public ResponseEntity createProjectRisk( @PathVariable UUID projectId, - @Valid @RequestBody RiskRequest riskRequest + @Valid @RequestBody ProjectRiskRequest riskRequest ) { - RiskResponse createdRisk = riskService.createProjectRisk( + ProjectRiskResponse createdRisk = riskService.createProjectRisk( riskRequest.toCommand(), projectId ); @@ -34,21 +34,21 @@ public ResponseEntity createProjectRisk( @GetMapping @PreAuthorize("hasAnyRole('ADMINISTRATOR', 'AUTHORITY') or @projectAccess.canAccessProject(#projectId, authentication.principal)") - public ResponseEntity> getRisks( + public ResponseEntity> getRisks( @PathVariable UUID projectId ) { - List risks = riskService.getProjectRisks(projectId); + List risks = riskService.getProjectRisks(projectId); return ResponseEntity.ok(risks); } @PatchMapping("/{riskId}") @PreAuthorize("@projectAccess.isProjectManagerForProject(#projectId, authentication.principal)") - public ResponseEntity updateProjectRisk( + public ResponseEntity updateProjectRisk( @PathVariable UUID projectId, @PathVariable UUID riskId, - @Valid @RequestBody RiskRequest riskRequest + @Valid @RequestBody ProjectRiskRequest riskRequest ) { - RiskResponse updatedRisk = riskService.updateProjectRisk( + ProjectRiskResponse updatedRisk = riskService.updateProjectRisk( projectId, riskId, riskRequest.toCommand() diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/domain/entity/project/ProjectRisk.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/domain/entity/project/ProjectRisk.java index 78059b6e..27aa14ec 100644 --- a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/domain/entity/project/ProjectRisk.java +++ b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/domain/entity/project/ProjectRisk.java @@ -26,13 +26,17 @@ public class ProjectRisk { @Column(name = "description", nullable = false, length = 500) private String description; - // TODO: Przejscie na model od 1 do 5 w skali prawdopodobienstwa wystapienia i skali ryzyka dla projektu - @Column(name = "probability", nullable = false, columnDefinition = "integer check (probability >= 0 and probability <= 100)") - @Min(value = 0, message = "Prawdopodobieństwo musi być większe bądź równe 0") - @Max(value = 100, message = "Prawdopodobieństwo musi być mniejsze bądź równe 100") + @Column(name = "probability", nullable = false, columnDefinition = "integer check (probability >= 1 and probability <= 5)") + @Min(value = 1, message = "Prawdopodobieństwo musi być w skali od 1 do 5") + @Max(value = 5, message = "Prawdopodobieństwo musi być w skali od 1 do 5") private Integer probability; + @Column(name = "impact", nullable = false, columnDefinition = "integer check (impact >= 1 and impact <= 5)") + @Min(value = 1, message = "Wpływ musi być w skali od 1 do 5") + @Max(value = 5, message = "Wpływ musi być w skali od 1 do 5") + private Integer impact; + @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "project_id", nullable = false) private Project project; -} +} \ No newline at end of file diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/command/project/RiskCommand.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/command/project/RiskCommand.java index e85e8b8d..4aad7a67 100644 --- a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/command/project/RiskCommand.java +++ b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/command/project/RiskCommand.java @@ -3,6 +3,7 @@ public record RiskCommand( String name, String description, - Integer probability + Integer probability, + Integer impact ) { -} +} \ No newline at end of file diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/project/ProjectRiskService.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/project/ProjectRiskService.java index 68578438..a431a6d1 100644 --- a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/project/ProjectRiskService.java +++ b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/project/ProjectRiskService.java @@ -3,12 +3,11 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import pl.edu.agh.project_manager.controller.dto.project.RiskResponse; +import pl.edu.agh.project_manager.controller.dto.project.projectrisk.ProjectRiskResponse; import pl.edu.agh.project_manager.domain.entity.project.Project; import pl.edu.agh.project_manager.domain.entity.project.ProjectRisk; import pl.edu.agh.project_manager.domain.exception.ApiErrorCode; import pl.edu.agh.project_manager.domain.exception.ApplicationException; -import pl.edu.agh.project_manager.repository.project.ProjectRepository; import pl.edu.agh.project_manager.repository.project.RiskRepository; import pl.edu.agh.project_manager.service.command.project.RiskCommand; @@ -23,22 +22,22 @@ public class ProjectRiskService { private final ProjectService projectService; @Transactional - public RiskResponse createProjectRisk(RiskCommand command, UUID projectId) { + public ProjectRiskResponse createProjectRisk(RiskCommand command, UUID projectId) { Project project = projectService.getProjectEntityOrThrow(projectId); ProjectRisk risk = buildRisk(command); risk.setProject(project); ProjectRisk savedRisk = riskRepository.save(risk); - return RiskResponse.from(savedRisk); + return ProjectRiskResponse.from(savedRisk); } - public List getProjectRisks(UUID projectId) { + public List getProjectRisks(UUID projectId) { projectService.checkProjectExistsOrThrow(projectId); return riskRepository.findAllByProjectId(projectId) .stream() - .map(RiskResponse::from) + .map(ProjectRiskResponse::from) .toList(); } @@ -54,7 +53,7 @@ public void deleteProjectRisk(UUID projectId, UUID riskId) { } @Transactional - public RiskResponse updateProjectRisk(UUID projectId, UUID riskId, RiskCommand command) { + public ProjectRiskResponse updateProjectRisk(UUID projectId, UUID riskId, RiskCommand command) { ProjectRisk risk = getRiskOrThrow(riskId); if (!risk.getProject().getId().equals(projectId)) { @@ -64,8 +63,9 @@ public RiskResponse updateProjectRisk(UUID projectId, UUID riskId, RiskCommand c if (command.name() != null) risk.setName(command.name()); if (command.description() != null) risk.setDescription(command.description()); if (command.probability() != null) risk.setProbability(command.probability()); + if (command.impact() != null) risk.setImpact(command.impact()); - return RiskResponse.from(risk); + return ProjectRiskResponse.from(risk); } private ProjectRisk getRiskOrThrow(UUID riskId) { @@ -78,6 +78,7 @@ private ProjectRisk buildRisk(RiskCommand command) { .name(command.name()) .description(command.description()) .probability(command.probability()) + .impact(command.impact()) .build(); } -} +} \ No newline at end of file diff --git a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/project/ProjectService.java b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/project/ProjectService.java index c0efdca2..c2cd03af 100644 --- a/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/project/ProjectService.java +++ b/backend/project-manager/src/main/java/pl/edu/agh/project_manager/service/project/ProjectService.java @@ -140,6 +140,7 @@ private void addRisksToProject(Project project, List risks) { .name(riskRequest.name()) .description(riskRequest.description()) .probability(riskRequest.probability()) + .impact(riskRequest.impact()) .build(); project.addRisk(risk); diff --git a/backend/project-manager/src/test/java/pl/edu/agh/project_manager/service/project/ProjectRiskServiceTest.java b/backend/project-manager/src/test/java/pl/edu/agh/project_manager/service/project/ProjectRiskServiceTest.java index f6754167..78765b28 100644 --- a/backend/project-manager/src/test/java/pl/edu/agh/project_manager/service/project/ProjectRiskServiceTest.java +++ b/backend/project-manager/src/test/java/pl/edu/agh/project_manager/service/project/ProjectRiskServiceTest.java @@ -6,7 +6,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import pl.edu.agh.project_manager.controller.dto.project.RiskResponse; +import pl.edu.agh.project_manager.controller.dto.project.projectrisk.ProjectRiskResponse; import pl.edu.agh.project_manager.domain.entity.project.Project; import pl.edu.agh.project_manager.domain.entity.project.ProjectRisk; import pl.edu.agh.project_manager.repository.project.RiskRepository; @@ -57,24 +57,28 @@ void updateProjectRisk_Success() { // Given UUID projectId = UUID.randomUUID(); UUID riskId = UUID.randomUUID(); - RiskCommand command = new RiskCommand("New Name", "New Desc", 90); + RiskCommand command = new RiskCommand("New Name", "New Desc", 4, 5); Project project = Project.builder().id(projectId).build(); ProjectRisk risk = ProjectRisk.builder() .id(riskId) .name("Old") .project(project) + .probability(1) + .impact(1) .build(); when(riskRepository.findById(riskId)).thenReturn(Optional.of(risk)); // When - RiskResponse response = riskService.updateProjectRisk(projectId, riskId, command); + ProjectRiskResponse response = riskService.updateProjectRisk(projectId, riskId, command); // Then assertThat(response.name()).isEqualTo("New Name"); assertThat(risk.getName()).isEqualTo("New Name"); - assertThat(risk.getProbability()).isEqualTo(90); + assertThat(risk.getProbability()).isEqualTo(4); + assertThat(risk.getImpact()).isEqualTo(5); + assertThat(response.value()).isEqualTo(20); } @Test @@ -82,12 +86,15 @@ void updateProjectRisk_Success() { void createProjectRisk_Success() { // Given UUID projectId = UUID.randomUUID(); - RiskCommand command = new RiskCommand("Title", "Desc", 50); + RiskCommand command = new RiskCommand("Title", "Desc", 3, 4); Project project = Project.builder().id(projectId).build(); ProjectRisk savedRisk = ProjectRisk.builder() .id(UUID.randomUUID()) .name("Title") + .description("Desc") + .probability(3) + .impact(4) .project(project) .build(); @@ -95,10 +102,11 @@ void createProjectRisk_Success() { when(riskRepository.save(any(ProjectRisk.class))).thenReturn(savedRisk); // When - RiskResponse response = riskService.createProjectRisk(command, projectId); + ProjectRiskResponse response = riskService.createProjectRisk(command, projectId); // Then assertThat(response.id()).isEqualTo(savedRisk.getId()); + assertThat(response.value()).isEqualTo(12); verify(riskRepository).save(any(ProjectRisk.class)); } } \ No newline at end of file diff --git a/backend/project-manager/src/test/java/pl/edu/agh/project_manager/service/project/ProjectServiceTest.java b/backend/project-manager/src/test/java/pl/edu/agh/project_manager/service/project/ProjectServiceTest.java index d0e98452..c78096ee 100644 --- a/backend/project-manager/src/test/java/pl/edu/agh/project_manager/service/project/ProjectServiceTest.java +++ b/backend/project-manager/src/test/java/pl/edu/agh/project_manager/service/project/ProjectServiceTest.java @@ -56,7 +56,7 @@ void createProject_Success() { LocalDate.now(), null, null, - new ArrayList<>(List.of(new RiskCommand("Risk", "Desc", 50))), + new ArrayList<>(List.of(new RiskCommand("Risk", "Desc", 5, 3))), new ArrayList<>(List.of(new MilestoneCommand("Start", "Start desc", LocalDate.now()))), new ArrayList<>(), // Sponsors new ArrayList<>() // Committee diff --git a/frontend/src/features/project/components/CreateProjectForm.tsx b/frontend/src/features/project/components/CreateProjectForm.tsx index 1b5cd195..d57cc714 100644 --- a/frontend/src/features/project/components/CreateProjectForm.tsx +++ b/frontend/src/features/project/components/CreateProjectForm.tsx @@ -29,7 +29,8 @@ export const CreateProjectForm = () => { }, }); - const { data: groups = [] } = useProjectGroups(); + const { data: groupsData = [] } = useProjectGroups(); + const groups = groupsData.map(g => ({ id: g.id, name: g.name })); const [sponsorsQuery, setSponsorsQuery] = useState(""); const [sponsorsQueryValue] = useDebounce(sponsorsQuery, 300); @@ -44,11 +45,7 @@ export const CreateProjectForm = () => { const navigate = useNavigate(); const onSubmit = methods.handleSubmit((data) => { - const payload = { - ...data, - }; - - mutation.mutate(payload, { + mutation.mutate(data, { onSuccess: (newProjectId) => { methods.reset(); navigate(PATHS.PROJECT(newProjectId)); diff --git a/frontend/src/features/project/components/CreateProjectForm.view.tsx b/frontend/src/features/project/components/CreateProjectForm.view.tsx index 2e4ef2a3..dbd46c50 100644 --- a/frontend/src/features/project/components/CreateProjectForm.view.tsx +++ b/frontend/src/features/project/components/CreateProjectForm.view.tsx @@ -3,10 +3,9 @@ import type { ProjectCreationRequest } from "../project.types.ts"; import { useState, useCallback } from "react"; import type { SimpleUserResponse } from "@/features/user-management"; import { UserAutocomplete } from "./UserAutocomplete.tsx"; -import { StepProgressTracker } from "./StepProgressTracker.tsx"; import { StepBasicInformation } from "./StepBasicInformation.view.tsx"; import { StepMilestonesAndRisks } from "./StepMilestoneAndRisk.tsx"; -import { StepNavigation } from "./StepNavigation.tsx"; +import { Button } from "@/components/ui/button"; interface CreateProjectViewProps { onSubmitProject: () => Promise; @@ -19,19 +18,28 @@ interface CreateProjectViewProps { message?: string; } -export const CreateProjectView = ({ onSubmitProject, isPending, groups, foundSponsors, foundCommittee, onSponsorSearch, onCommitteeSearch, message }: CreateProjectViewProps) => { +export const CreateProjectView = ({ + onSubmitProject, + isPending, + groups, + foundSponsors, + foundCommittee, + onSponsorSearch, + onCommitteeSearch, + message + }: CreateProjectViewProps) => { const { register, control, - formState: { errors }, + formState: { errors, isValid }, clearErrors, watch, setValue, getValues, - trigger, } = useFormContext(); const milestones = watch("milestones"); + const { fields: riskFields, append: appendRisk, @@ -40,6 +48,7 @@ export const CreateProjectView = ({ onSubmitProject, isPending, groups, foundSpo control, name: "risks", }); + const { fields: milestonesFields, append: appendMilestone, @@ -49,39 +58,12 @@ export const CreateProjectView = ({ onSubmitProject, isPending, groups, foundSpo name: "milestones", }); - - // const { - // fields: roleFields, - // append: appendRole, - // remove: removeRole, - // } = useFieldArray({ - // control, - // name: "roles", - // }); - const [usersById, setUsersById] = useState>({}); const rememberUser = useCallback((user: SimpleUserResponse) => { setUsersById((prev) => ({ ...prev, [user.id]: user })); }, []); - const [currentStep, setCurrentStep] = useState(1); - const totalSteps = 2; - - const handleNextStep = async () => { - if (currentStep !== 1) return; - - const isValid = await trigger(["title", "description", "startDate", "endDate", "sponsors", "committee", "milestones"]); - - if (isValid) { - setCurrentStep((prev) => prev + 1); - } - }; - - const handlePrevStep = () => { - setCurrentStep((prev) => prev - 1); - }; - return (

Utwórz Nowy Projekt

@@ -92,34 +74,37 @@ export const CreateProjectView = ({ onSubmitProject, isPending, groups, foundSpo
)} - - -
- {currentStep === 1 && ( - <> - - -
-
- - - {errors.startDate?.message &&

{errors.startDate.message as string}

} -
-
- - - {errors.endDate?.message &&

{errors.endDate.message as string}

} -
+ { e.preventDefault(); onSubmitProject(); }}> + +
+ + +
+
+ + + {errors.startDate?.message &&

{errors.startDate.message as string}

} +
+
+ + + {errors.endDate?.message &&

{errors.endDate.message as string}

}
+
+
+ +
+

Uczestnicy Główni

+
- {errors.sponsors?.message &&

{errors.sponsors.message}

} +
+
- {errors.committee?.message &&

{errors.committee.message}

} - - - - )} - - {currentStep === 2 && ( -
-

Role i alokacja czasu (Wkrótce)

-

- Moduł składania wniosków i doboru ról zostanie uruchomiony w późniejszym etapie. - Możesz zapisać projekt bez przypisywania zasobów. -

- {/* Wyciszony komponent - zakomentowany na wszelki wypadek */} - {/* */}
- )} - - +
+ +
+ +
+ +
+ +
); diff --git a/frontend/src/features/project/components/ProjectRisks.tsx b/frontend/src/features/project/components/ProjectRisks.tsx index 63996bd9..0dfc1fca 100644 --- a/frontend/src/features/project/components/ProjectRisks.tsx +++ b/frontend/src/features/project/components/ProjectRisks.tsx @@ -1,32 +1,74 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; import { Skeleton } from "@/components/ui/skeleton"; -import { AlertTriangle } from "lucide-react"; +import { AlertTriangle, Activity } from "lucide-react"; import { useProjectRisks } from "@/features/project/project.hooks.ts"; interface ProjectRisksProps { projectId: string; } -const getRiskColorClass = (probability: number) => { - if (probability < 30) return "bg-green-100 text-green-800 hover:bg-green-200"; - if (probability < 70) return "bg-yellow-100 text-yellow-800 hover:bg-yellow-200"; - return "bg-red-100 text-red-800 hover:bg-red-200"; +const getRiskDetails = (value: number) => { + if (value >= 15) return { + label: "Krytyczne", + borderLine: "bg-rose-500", + badge: "bg-rose-100 text-rose-700 border-rose-200 hover:bg-rose-200", + barColor: "bg-rose-500" + }; + if (value >= 10) return { + label: "Wysokie", + borderLine: "bg-orange-500", + badge: "bg-orange-100 text-orange-700 border-orange-200 hover:bg-orange-200", + barColor: "bg-orange-500" + }; + if (value >= 5) return { + label: "Średnie", + borderLine: "bg-amber-400", + badge: "bg-amber-100 text-amber-800 border-amber-200 hover:bg-amber-200", + barColor: "bg-amber-400" + }; + return { + label: "Niskie", + borderLine: "bg-emerald-500", + badge: "bg-emerald-100 text-emerald-700 border-emerald-200 hover:bg-emerald-200", + barColor: "bg-emerald-500" + }; }; +const MetricBar = ({ label, value, activeColor }: { label: string; value: number; activeColor: string }) => ( +
+
+ {label} + {value}/5 +
+
+ {[1, 2, 3, 4, 5].map((i) => ( +
+ ))} +
+
+); + export const ProjectRisks = ({ projectId }: ProjectRisksProps) => { const { data: risks, isLoading, isError } = useProjectRisks(projectId); if (isLoading) { return ( - - - Ryzyka projektowe + + + + + Ryzyka projektowe + - - - + + + ); @@ -34,61 +76,67 @@ export const ProjectRisks = ({ projectId }: ProjectRisksProps) => { if (isError) { return ( - - + + Nie udało się pobrać ryzyk dla tego projektu. ); } + const sortedRisks = risks ? [...risks].sort((a, b) => b.value - a.value) : []; + return ( - - -
- - - Ryzyka projektowe + + +
+ + + Rejestr Ryzyk - - Zidentyfikowane zagrożenia i prawdopodobieństwo ich wystąpienia. - +

+ Zidentyfikowane zagrożenia (Prawdopodobieństwo × Wpływ). +

- - {!risks || risks.length === 0 ? ( -
- Brak zarejestrowanych ryzyk w tym projekcie. + + {sortedRisks.length === 0 ? ( +
+ +

Brak zarejestrowanych ryzyk w tym projekcie.

) : ( -
- {risks.map((risk) => ( -
+
+ {sortedRisks.map((risk) => { + const { label, borderLine, badge, barColor } = getRiskDetails(risk.value); + + return ( +
+
-
-
-

{risk.name}

-

+

+
+

{risk.name}

+ + {label} (Wartość: {risk.value}) + +
+

{risk.description}

- - {risk.probability}% - -
-
- 0% - - 100% +
+ + +
- -
- ))} + ); + })}
)} diff --git a/frontend/src/features/project/components/StepMilestoneAndRisk.tsx b/frontend/src/features/project/components/StepMilestoneAndRisk.tsx index a41729b9..f6692a40 100644 --- a/frontend/src/features/project/components/StepMilestoneAndRisk.tsx +++ b/frontend/src/features/project/components/StepMilestoneAndRisk.tsx @@ -133,7 +133,7 @@ export const StepMilestonesAndRisks = ({

Ryzyka Projektu

-

Dodaj ryzyka projektu.

+

Dodaj ryzyka na skali 1-5 (Prawdopodobieństwo x Wpływ).

-
-
+
+
{errors.risks[index].name.message} )}
+
- - Prawdopodobieństwo (1-5) + {errors.risks?.[index]?.probability && ( {errors.risks[index].probability.message} )}
+ +
+ + + {errors.risks?.[index]?.impact && ( + {errors.risks[index].impact.message} + )} +
+