From e40e9ceed9a81083767a4ea477ea75f92b648d42 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 4 Jun 2025 11:00:37 +0100 Subject: [PATCH] Display status changes on the project page --- app/controllers/StatusController.scala | 9 ++ app/models/StatusChange.scala | 5 + conf/routes | 1 + .../ProjectEntryEditComponent.tsx | 97 ++++++++++++++++++- frontend/app/ProjectEntryList/helpers.ts | 21 ++++ frontend/app/StatusChanges/StatusChanges.tsx | 4 +- 6 files changed, 134 insertions(+), 3 deletions(-) diff --git a/app/controllers/StatusController.scala b/app/controllers/StatusController.scala index 2f3c81a4..20ea1725 100644 --- a/app/controllers/StatusController.scala +++ b/app/controllers/StatusController.scala @@ -75,4 +75,13 @@ class StatusController @Inject()(cc:ControllerComponents, override val bearerTok }) }} + def recordsForProject(projectId:Int) = IsAuthenticatedAsync {uid=>{request=> + StatusChangeDAO.getRecordsForProject(projectId).map({ + case Success(results)=>Ok(Json.obj("status"->"ok","result"->results)) + case Failure(error)=> + logger.error("Could not list status changes: ", error) + InternalServerError(Json.obj("status"->"error","detail"->error.toString)) + }) + }} + } \ No newline at end of file diff --git a/app/models/StatusChange.scala b/app/models/StatusChange.scala index dc44670c..dec6e537 100644 --- a/app/models/StatusChange.scala +++ b/app/models/StatusChange.scala @@ -68,6 +68,11 @@ object StatusChangeDAO extends ((Option[Int], Int, Timestamp, String, String, St db.run( TableQuery[StatusChange].sortBy(_.id.desc).drop(startAt).take(limit).result.asTry ) + + def getRecordsForProject(projectId:Int)(implicit db:slick.jdbc.PostgresProfile#Backend#Database) = + db.run( + TableQuery[StatusChange].filter(_.projectId===projectId).result.asTry + ) } class StatusChange(tag:Tag) extends Table[StatusChangeDAO](tag, "StatusChange") { diff --git a/conf/routes b/conf/routes index 9931c882..7d8e2586 100644 --- a/conf/routes +++ b/conf/routes @@ -71,6 +71,7 @@ GET /api/project/:id/fileDownload @controllers.ProjectEntryController. PUT /api/project/:id/restore/:version @controllers.ProjectEntryController.restoreBackup(id:Int, version:Int) PUT /api/project/:id/restoreForAssetFolder @controllers.ProjectEntryController.restoreAssetFolderBackup(id:Int) PUT /api/project/:id/statusChange @controllers.StatusController.record(id: Int) +GET /api/project/:id/statusChanges @controllers.StatusController.recordsForProject(id:Int) GET /api/statusChanges @controllers.StatusController.records(startAt:Int ?=0,length:Int ?=100) GET /api/valid-users @controllers.ProjectEntryController.queryUsersForAutocomplete(prefix:String ?= "", limit:Option[Int]) diff --git a/frontend/app/ProjectEntryList/ProjectEntryEditComponent.tsx b/frontend/app/ProjectEntryList/ProjectEntryEditComponent.tsx index 1f7c3179..c53363df 100644 --- a/frontend/app/ProjectEntryList/ProjectEntryEditComponent.tsx +++ b/frontend/app/ProjectEntryList/ProjectEntryEditComponent.tsx @@ -18,6 +18,13 @@ import { styled, Select, MenuItem, + Table, + TableHead, + TableRow, + TableCell, + TableSortLabel, + TableBody, + TableContainer, } from "@material-ui/core"; import { getProject, @@ -29,6 +36,7 @@ import { getMissingFiles, downloadProjectFile, recordStatusChange, + getStatusChanges, } from "./helpers"; import { SystemNotification, @@ -54,7 +62,7 @@ import ProjectFileUpload from "./ProjectFileUpload"; import FolderIcon from "@material-ui/icons/Folder"; import BuildIcon from "@material-ui/icons/Build"; import LaunchIcon from "@material-ui/icons/Launch"; -import { sortListByOrder } from "../utils/lists"; +import { SortDirection, sortListByOrder } from "../utils/lists"; declare var deploymentRootPath: string; @@ -114,6 +122,12 @@ const EMPTY_FILE: FileEntry = { premiereVersion: 0, }; +const tableHeaderTitles: HeaderTitle[] = [ + { label: "User", key: "user" }, + { label: "Time", key: "time" }, + { label: "Status", key: "status" }, +]; + const ProjectEntryEditComponent: React.FC = ( props ) => { @@ -139,6 +153,9 @@ const ProjectEntryEditComponent: React.FC = ( const [fileData, setFileData] = useState(EMPTY_FILE); const [premiereProVersion, setPremiereProVersion] = useState(1); const [userName, setUserName] = useState(""); + const [statusChanges, setStatusChanges] = useState([]); + const [order, setOrder] = useState("desc"); + const [orderBy, setOrderBy] = useState("id"); const getProjectTypeData = async (projectTypeId: number) => { try { @@ -165,6 +182,16 @@ const ProjectEntryEditComponent: React.FC = ( } }; + const fetchStatusChanges = async () => { + try { + const id = Number(props.match.params.itemid); + const statusChanges = await getStatusChanges(id); + setStatusChanges(statusChanges); + } catch { + console.log("Could not load status changes."); + } + }; + // Fetch project from URL path useEffect(() => { // No need to fetch data if we navigated from the project list. @@ -218,6 +245,8 @@ const ProjectEntryEditComponent: React.FC = ( getPremiereVersionData(); + fetchStatusChanges(); + return () => { isMounted = false; }; @@ -526,6 +555,14 @@ const ProjectEntryEditComponent: React.FC = ( } }; + const sortByColumn = (property: keyof StatusChange) => ( + _event: React.MouseEvent + ) => { + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); + setOrderBy(property); + }; + return ( <> {userAllowedBoolean ? ( @@ -956,6 +993,64 @@ const ProjectEntryEditComponent: React.FC = ( )} + {statusChanges.length === 0 ? null : ( + + + Status Changes + + + + + + {tableHeaderTitles.map((title, index) => ( + + {title.key ? ( + + {title.label} + {orderBy === title.key && ( + + {order === "desc" + ? "sorted descending" + : "sorted ascending"} + + )} + + ) : ( + title.label + )} + + ))} + + + + {sortListByOrder(statusChanges, orderBy, order).map( + ({ id, projectId, time, user, status, title }) => ( + + {user} + + + {moment(time).format("DD/MM/YYYY HH:mm")} + + + {status} + + ) + )} + +
+
+
+ )} => { + try { + const { + status, + data: { result }, + } = await Axios.get>( + `${API_PROJECTS}/${id}/statusChanges` + ); + + if (status === 200) { + console.log(result); + return result; + } + + throw new Error(`Could not retrieve status changes. ${status}`); + } catch (error) { + console.error(error); + throw error; + } +}; diff --git a/frontend/app/StatusChanges/StatusChanges.tsx b/frontend/app/StatusChanges/StatusChanges.tsx index 1456f0c5..7498e3d4 100644 --- a/frontend/app/StatusChanges/StatusChanges.tsx +++ b/frontend/app/StatusChanges/StatusChanges.tsx @@ -41,7 +41,7 @@ const StatusChanges: React.FC = (props) => { const [orderBy, setOrderBy] = useState("id"); useEffect(() => { - const fetchDeletionRecordsOnPage = async () => { + const fetchStatusChangesOnPage = async () => { const statusChanges = await getStatusChangesOnPage({ page, pageSize }); setStatusChanges(statusChanges); }; @@ -57,7 +57,7 @@ const StatusChanges: React.FC = (props) => { fetchWhoIsLoggedIn(); - fetchDeletionRecordsOnPage(); + fetchStatusChangesOnPage(); }, [page, pageSize]); const handleChangePage = (