diff --git a/backend/app/api/__pycache__/activities.cpython-312.pyc b/backend/app/api/__pycache__/activities.cpython-312.pyc index 5bdda4e..a3e601c 100644 Binary files a/backend/app/api/__pycache__/activities.cpython-312.pyc and b/backend/app/api/__pycache__/activities.cpython-312.pyc differ diff --git a/backend/app/api/__pycache__/projects.cpython-312.pyc b/backend/app/api/__pycache__/projects.cpython-312.pyc index 00467cf..812ab69 100644 Binary files a/backend/app/api/__pycache__/projects.cpython-312.pyc and b/backend/app/api/__pycache__/projects.cpython-312.pyc differ diff --git a/backend/app/api/projects.py b/backend/app/api/projects.py index 48b7249..928e972 100644 --- a/backend/app/api/projects.py +++ b/backend/app/api/projects.py @@ -64,7 +64,7 @@ def add_project_member(project_id: int, payload: ProjectMember, session: Session entity_type="project_member", entity_id=member.id, verb="added", - payload={"project_id": project_id, "employee_id": member.employee_id}, + payload={"project_id": project_id, "employee_id": member.employee_id, "role": member.role}, ) session.commit() return member @@ -87,3 +87,27 @@ def remove_project_member(project_id: int, member_id: int, session: Session = De ) session.commit() return {"ok": True} + + +@router.patch("/{project_id}/members/{member_id}", response_model=ProjectMember) +def update_project_member(project_id: int, member_id: int, payload: ProjectMember, session: Session = Depends(get_session)): + member = session.get(ProjectMember, member_id) + if not member or member.project_id != project_id: + raise HTTPException(status_code=404, detail="Project member not found") + + if payload.role is not None: + member.role = payload.role + + session.add(member) + session.commit() + session.refresh(member) + log_activity( + session, + actor_employee_id=None, + entity_type="project_member", + entity_id=member.id, + verb="updated", + payload={"project_id": project_id, "role": member.role}, + ) + session.commit() + return member diff --git a/frontend/src/api/generated/projects/projects.ts b/frontend/src/api/generated/projects/projects.ts index 440a31f..ac9835a 100644 --- a/frontend/src/api/generated/projects/projects.ts +++ b/frontend/src/api/generated/projects/projects.ts @@ -960,3 +960,162 @@ export const useRemoveProjectMemberProjectsProjectIdMembersMemberIdDelete = < queryClient, ); }; +/** + * @summary Update Project Member + */ +export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse200 = + { + data: ProjectMember; + status: 200; + }; + +export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponseSuccess = + updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse200 & { + headers: Headers; + }; +export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponseError = + updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse422 & { + headers: Headers; + }; + +export type updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponse = + | updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponseSuccess + | updateProjectMemberProjectsProjectIdMembersMemberIdPatchResponseError; + +export const getUpdateProjectMemberProjectsProjectIdMembersMemberIdPatchUrl = ( + projectId: number, + memberId: number, +) => { + return `/projects/${projectId}/members/${memberId}`; +}; + +export const updateProjectMemberProjectsProjectIdMembersMemberIdPatch = async ( + projectId: number, + memberId: number, + projectMember: ProjectMember, + options?: RequestInit, +): Promise => { + return customFetch( + getUpdateProjectMemberProjectsProjectIdMembersMemberIdPatchUrl( + projectId, + memberId, + ), + { + ...options, + method: "PATCH", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(projectMember), + }, + ); +}; + +export const getUpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch + > + >, + TError, + { projectId: number; memberId: number; data: ProjectMember }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType< + typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch + > + >, + TError, + { projectId: number; memberId: number; data: ProjectMember }, + TContext + > => { + const mutationKey = [ + "updateProjectMemberProjectsProjectIdMembersMemberIdPatch", + ]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType< + typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch + > + >, + { projectId: number; memberId: number; data: ProjectMember } + > = (props) => { + const { projectId, memberId, data } = props ?? {}; + + return updateProjectMemberProjectsProjectIdMembersMemberIdPatch( + projectId, + memberId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type UpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationResult = + NonNullable< + Awaited< + ReturnType< + typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch + > + > + >; +export type UpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationBody = + ProjectMember; +export type UpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationError = + HTTPValidationError; + +/** + * @summary Update Project Member + */ +export const useUpdateProjectMemberProjectsProjectIdMembersMemberIdPatch = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof updateProjectMemberProjectsProjectIdMembersMemberIdPatch + > + >, + TError, + { projectId: number; memberId: number; data: ProjectMember }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { projectId: number; memberId: number; data: ProjectMember }, + TContext +> => { + return useMutation( + getUpdateProjectMemberProjectsProjectIdMembersMemberIdPatchMutationOptions( + options, + ), + queryClient, + ); +}; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 63ed4a8..7cb935e 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,66 +1,165 @@ "use client"; +import { useState } from "react"; + import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Select } from "@/components/ui/select"; -import { useListProjectsProjectsGet } from "@/api/generated/projects/projects"; +import { useCreateProjectProjectsPost, useListProjectsProjectsGet } from "@/api/generated/projects/projects"; +import { useCreateDepartmentDepartmentsPost, useListDepartmentsDepartmentsGet } from "@/api/generated/org/org"; +import { useCreateEmployeeEmployeesPost, useListEmployeesEmployeesGet } from "@/api/generated/org/org"; +import { useListActivitiesActivitiesGet } from "@/api/generated/activities/activities"; export default function Home() { const projects = useListProjectsProjectsGet(); + const departments = useListDepartmentsDepartmentsGet(); + const employees = useListEmployeesEmployeesGet(); + const activities = useListActivitiesActivitiesGet({ limit: 20 }); + + const [projectName, setProjectName] = useState(""); + const [deptName, setDeptName] = useState(""); + const [personName, setPersonName] = useState(""); + const [personType, setPersonType] = useState<"human" | "agent">("human"); + + const createProject = useCreateProjectProjectsPost({ + mutation: { onSuccess: () => { setProjectName(""); projects.refetch(); } }, + }); + const createDepartment = useCreateDepartmentDepartmentsPost({ + mutation: { onSuccess: () => { setDeptName(""); departments.refetch(); } }, + }); + const createEmployee = useCreateEmployeeEmployeesPost({ + mutation: { onSuccess: () => { setPersonName(""); employees.refetch(); } }, + }); return ( -
+

Company Mission Control

- Orval-generated client + React Query + shadcn-style components. + Dashboard overview + quick create. No-auth v1.

-
-
+
- Projects - GET /projects + Quick create project + Projects drive all tasks - - {projects.isLoading ?
Loading…
: null} - {projects.error ? ( -
{(projects.error as Error).message}
- ) : null} - {!projects.isLoading && !projects.error ? ( -
    - {projects.data?.map((p) => ( -
  • -
    {p.name}
    -
    {p.status}
    -
  • - ))} - {(projects.data?.length ?? 0) === 0 ? ( -
  • No projects yet.
  • - ) : null} -
- ) : null} + + setProjectName(e.target.value)} /> +
- API - Docs & health + Quick create department + Organization structure - -
- Docs: /docs -
-
- Set NEXT_PUBLIC_API_URL in .env.local (example: http://192.168.1.101:8000). -
+ + setDeptName(e.target.value)} /> + + +
+ + + + Quick add person + Employees & agents + + + setPersonName(e.target.value)} /> + + + + +
+ +
+ + + Projects + {(projects.data ?? []).length} total + + +
    + {(projects.data ?? []).slice(0, 8).map((p) => ( +
  • + {p.name} + {p.status} +
  • + ))} + {(projects.data ?? []).length === 0 ? ( +
  • No projects yet.
  • + ) : null} +
+
+
+ + + + Departments + {(departments.data ?? []).length} total + + +
    + {(departments.data ?? []).slice(0, 8).map((d) => ( +
  • + {d.name} + id {d.id} +
  • + ))} + {(departments.data ?? []).length === 0 ? ( +
  • No departments yet.
  • + ) : null} +
+
+
+ + + + Activity + Latest actions + + +
    + {(activities.data ?? []).map((a) => ( +
  • +
    {a.entity_type} · {a.verb}
    +
    id {a.entity_id ?? "—"}
    +
  • + ))} + {(activities.data ?? []).length === 0 ? ( +
  • No activity yet.
  • + ) : null} +
diff --git a/frontend/src/app/projects/[id]/page.tsx b/frontend/src/app/projects/[id]/page.tsx index 9bdfd7e..30acfe9 100644 --- a/frontend/src/app/projects/[id]/page.tsx +++ b/frontend/src/app/projects/[id]/page.tsx @@ -23,16 +23,10 @@ import { useListProjectMembersProjectsProjectIdMembersGet, useAddProjectMemberProjectsProjectIdMembersPost, useRemoveProjectMemberProjectsProjectIdMembersMemberIdDelete, + useUpdateProjectMemberProjectsProjectIdMembersMemberIdPatch, } from "@/api/generated/projects/projects"; -const STATUSES = [ - "backlog", - "ready", - "in_progress", - "review", - "done", - "blocked", -] as const; +const STATUSES = ["backlog", "ready", "in_progress", "review", "done", "blocked"] as const; export default function ProjectDetailPage() { const params = useParams(); @@ -50,6 +44,9 @@ export default function ProjectDetailPage() { const removeMember = useRemoveProjectMemberProjectsProjectIdMembersMemberIdDelete({ mutation: { onSuccess: () => members.refetch() }, }); + const updateMember = useUpdateProjectMemberProjectsProjectIdMembersMemberIdPatch({ + mutation: { onSuccess: () => members.refetch() }, + }); const tasks = useListTasksTasksGet({ projectId }); const createTask = useCreateTaskTasksPost({ @@ -161,7 +158,7 @@ export default function ProjectDetailPage() {
    {projectMembers.map((m) => ( -
  • -
    {employeeName(m.employee_id)}
    - +
  • +
    +
    {employeeName(m.employee_id)}
    + +
    +
    + + updateMember.mutate({ + projectId, + memberId: Number(m.id), + data: { project_id: projectId, employee_id: m.employee_id, role: e.currentTarget.value || null }, + }) + } + /> +
  • ))} {projectMembers.length === 0 ?
  • No members yet.
  • : null}