diff --git a/.gitignore b/.gitignore index 851ad2c..f594556 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,6 @@ logs/ # ========================= coverage/ +# Claude Code local settings +.claude/settings.local.json + 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 660c7e8..883a502 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 @@ -2,7 +2,6 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; -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_risk.ProjectRiskRequest; @@ -27,10 +26,8 @@ public record ProjectCreationRequest( UUID projectGroupId, - @NotEmpty(message = "Lista sponsorów nie może być pusta") List sponsors, - @NotEmpty(message = "Lista członków komitetu sterującego nie może być pusta") List committee, @Valid @@ -41,6 +38,8 @@ public record ProjectCreationRequest( ) { public ProjectCreationRequest { if (risks == null || risks.isEmpty()) risks = List.of(); + if (sponsors == null) sponsors = List.of(); + if (committee == null) committee = List.of(); } public ProjectCreationCommand toCommand(UUID creatorId) { diff --git a/frontend/src/features/project/components/CreateProjectForm.tsx b/frontend/src/features/project/components/CreateProjectForm.tsx index d57cc71..eeb277a 100644 --- a/frontend/src/features/project/components/CreateProjectForm.tsx +++ b/frontend/src/features/project/components/CreateProjectForm.tsx @@ -1,4 +1,4 @@ -import { useForm, FormProvider } from "react-hook-form"; +import { useForm } from "react-hook-form"; import { CreateProjectView } from "./CreateProjectForm.view.tsx"; import type { ProjectCreationRequest } from "../project.types.ts"; import { useCreateProject } from "../project.hooks.ts"; @@ -11,6 +11,7 @@ import { useNavigate } from "react-router-dom"; import { zodResolver } from "@hookform/resolvers/zod"; import { CreateProjectFormSchema } from "../project.schema.ts"; import { getNextDateFromToday } from "../project.utils.ts"; +import { Form } from "@/components/ui/form"; export const CreateProjectForm = () => { const methods = useForm({ @@ -29,8 +30,7 @@ export const CreateProjectForm = () => { }, }); - const { data: groupsData = [] } = useProjectGroups(); - const groups = groupsData.map(g => ({ id: g.id, name: g.name })); + const { data: groups = [] } = useProjectGroups(); const [sponsorsQuery, setSponsorsQuery] = useState(""); const [sponsorsQueryValue] = useDebounce(sponsorsQuery, 300); @@ -54,7 +54,7 @@ export const CreateProjectForm = () => { }); return ( - +
{ onSponsorSearch={setSponsorsQuery} onCommitteeSearch={setCommitteeQuery} /> - + ); }; diff --git a/frontend/src/features/project/components/CreateProjectForm.view.tsx b/frontend/src/features/project/components/CreateProjectForm.view.tsx index dbd46c5..3caeefb 100644 --- a/frontend/src/features/project/components/CreateProjectForm.view.tsx +++ b/frontend/src/features/project/components/CreateProjectForm.view.tsx @@ -1,21 +1,25 @@ import { useFieldArray, useFormContext } from "react-hook-form"; import type { ProjectCreationRequest } from "../project.types.ts"; -import { useState, useCallback } from "react"; import type { SimpleUserResponse } from "@/features/user-management"; +import type { GroupBasicResponse } from "@/features/project_group/project_group.types.ts"; import { UserAutocomplete } from "./UserAutocomplete.tsx"; import { StepBasicInformation } from "./StepBasicInformation.view.tsx"; import { StepMilestonesAndRisks } from "./StepMilestoneAndRisk.tsx"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form"; +import { getNextDateFromToday } from "../project.utils.ts"; interface CreateProjectViewProps { onSubmitProject: () => Promise; isPending: boolean; - groups: { id: string; name: string }[]; + groups: GroupBasicResponse[]; foundSponsors: SimpleUserResponse[]; foundCommittee: SimpleUserResponse[]; onSponsorSearch: (query: string) => void; onCommitteeSearch: (query: string) => void; - message?: string; } export const CreateProjectView = ({ @@ -26,139 +30,129 @@ export const CreateProjectView = ({ foundCommittee, onSponsorSearch, onCommitteeSearch, - message }: CreateProjectViewProps) => { const { register, control, formState: { errors, isValid }, - clearErrors, watch, setValue, - getValues, } = useFormContext(); - const milestones = watch("milestones"); + const startDate = watch("startDate"); + const endDate = watch("endDate"); + const today = getNextDateFromToday(0); const { fields: riskFields, append: appendRisk, remove: removeRisk, - } = useFieldArray({ - control, - name: "risks", - }); + } = useFieldArray({ control, name: "risks" }); const { fields: milestonesFields, append: appendMilestone, remove: removeMilestone, - } = useFieldArray({ - control, - name: "milestones", - }); - - const [usersById, setUsersById] = useState>({}); - - const rememberUser = useCallback((user: SimpleUserResponse) => { - setUsersById((prev) => ({ ...prev, [user.id]: user })); - }, []); + } = useFieldArray({ control, name: "milestones" }); return ( -
-

Utwórz Nowy Projekt

- - {message && ( -
- {message} -
- )} - -
{ e.preventDefault(); onSubmitProject(); }}> - -
- +
+ + + Utwórz Nowy Projekt + + + { + e.preventDefault(); + onSubmitProject(); + }} + > + + +
+ ( + + Data rozpoczęcia * + + + + + + )} + /> -
-
- - ( + + Data zakończenia * + + + + + + )} /> - {errors.startDate?.message &&

{errors.startDate.message as string}

}
-
- - + +
+

Uczestnicy Główni

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

{errors.endDate.message as string}

}
-
-
- -
-

Uczestnicy Główni

- -
- - {errors.sponsors?.message &&

{errors.sponsors.message}

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

{errors.committee.message}

} -
-
-
- -
+ + +
+ +
-
- -
- +
+ +
+ + +
); }; \ No newline at end of file diff --git a/frontend/src/features/project/components/StepBasicInformation.view.tsx b/frontend/src/features/project/components/StepBasicInformation.view.tsx index e2f8fdd..0f2f61e 100644 --- a/frontend/src/features/project/components/StepBasicInformation.view.tsx +++ b/frontend/src/features/project/components/StepBasicInformation.view.tsx @@ -3,74 +3,61 @@ import type { FieldErrors, UseFormRegister } from "react-hook-form"; import type { ProjectCreationRequest } from "../project.types"; import { ChevronDownIcon, CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils"; - -// Import Twoich komponentów DropdownMenu +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import type {GroupBasicResponse} from "@/features/project_group/project_group.types.ts"; interface StepBasicInformationViewProps { register: UseFormRegister; errors: FieldErrors; - groups: { id: string; name: string }[]; + groups: GroupBasicResponse[]; } -export const StepBasicInformation = ({ - register, - errors, - groups +export const StepBasicInformation = ({ + register, + errors, + groups, }: StepBasicInformationViewProps) => { const { control, setValue } = useFormContext(); return (
- {/* --- NAZWA PROJEKTU --- */} -
- - + + {errors.title && ( - {errors.title.message} +

{errors.title.message}

)}
- {/* --- OPIS PROJEKTU --- */} -
- -