From 7efe2429ed830e1d9a98522c63c05d0e551b0f7d Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sun, 1 Feb 2026 23:51:40 +0530 Subject: [PATCH] Add dashboard activity feed and project member role editing --- .../__pycache__/activities.cpython-312.pyc | Bin 1725 -> 1725 bytes .../api/__pycache__/projects.cpython-312.pyc | Bin 5177 -> 6101 bytes backend/app/api/projects.py | 26 ++- .../src/api/generated/projects/projects.ts | 159 +++++++++++++++++ frontend/src/app/page.tsx | 167 ++++++++++++++---- frontend/src/app/projects/[id]/page.tsx | 46 +++-- 6 files changed, 346 insertions(+), 52 deletions(-) diff --git a/backend/app/api/__pycache__/activities.cpython-312.pyc b/backend/app/api/__pycache__/activities.cpython-312.pyc index 5bdda4efbb77531f9c9ce9322aa7b5d4f45fd0bc..a3e601ced052c7f1ba3356f8a1cfcd995059b6f8 100644 GIT binary patch delta 19 ZcmdnXyO)>iG%qg~0}wFH+{m?=4FEA41f2i? delta 19 ZcmdnXyO)>iG%qg~0}#Yc+{m?=4FEG01n>X= diff --git a/backend/app/api/__pycache__/projects.cpython-312.pyc b/backend/app/api/__pycache__/projects.cpython-312.pyc index 00467cf493ebc31c9355d3dc6fd3f16f5431456b..812ab690ecdd3563b3ce42e7509116d1d190b5b7 100644 GIT binary patch delta 851 zcmZvZOK1~O6o&7aOdceeNhV2SG_O*`G>S@FiPSE-h$32SO@x9Wnzorl3{4HwxTuWO zh|n$ylAc8o3a(tGf)YUm5%;B%P^7gB?aG}H4Q|AR_h!@%(!=o0Kj(etzV7{SQd}1V zE1?#6G=KA{qbm9!UyZy4U-e5I4&qUW!y%l33*g6BR0r@E3`c@;*8^jXRZ&ZYa_+)_ zruCFP_z8mUknGjvhUX4M!}U>a&}y~OWCJ14K802L*CVQ-!0XF3EyJo|jjx%sFCh5P z;l&A~4O#t|;EgMxrBm3+jn^w6s^WJa!GbzqrAyLby z(notJ7a95)&N0L&qMUMu>9Y(y%wbhdGi_(+giOm~&xldeqs$v&@H3pJm^D&#GgAUo z?R4EP$&=+=rli~|VF;|?*DN2II(k%TkZaD|MyJm4gt*8xic-tFXnXXPRnwD(4 zxG2U1?{+x)EcSBf`B2k2j2ogKQ_=_|u_SpoC5dmO@UbLqq;z)asLn2C>Wf;mHFa^C z?}KjqA$_)8VpU@d3EXi7t=Fk!H#3{AgZl);TATxXt;2+~ECecnhS>M+_`Sd;T>h9Z ZHfH8F^LN_B1Fp7#a0&RvQK^Jz^>6GAq*nj{ delta 247 zcmcbrzf*(nG%qg~0}vdZUY}XQGm%e%QE8(3K1T70&zu=0CTlS|GfGa*WAtQ{n!E$Z zmY)2F(Oy_4MYM%=HAoi_M9Ec(Y07S%#AM0E1u}`5fr0U}+~lh~Z`o2<<}j^coqUg1 zpOI}cAD<`Ga)(f(S568pL7;5}NXp6GZQ``GEL-lfA@bxuStg YMj$RW*jy;4$H?`OiJwuTNDe3h02abJS^xk5 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}