diff --git a/package-lock.json b/package-lock.json index 730f4e7c..2e186c4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "react-dom": "^18.2.0", "react-draggable": "^4.4.5", "react-joyride": "^2.5.3", + "react-json-tree": "^0.19.0", "react-material-ui-carousel": "^3.4.2", "react-mui-dropzone": "^4.0.7", "react-paginate": "^8.1.4", @@ -4655,6 +4656,11 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/lodash": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==" + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -6518,6 +6524,18 @@ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6534,6 +6552,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -12713,6 +12740,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.assignwith": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", @@ -15312,6 +15344,17 @@ "react-dom": ">=16.2.0" } }, + "node_modules/react-base16-styling": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.10.0.tgz", + "integrity": "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==", + "dependencies": { + "@types/lodash": "^4.17.0", + "color": "^4.2.3", + "csstype": "^3.1.3", + "lodash-es": "^4.17.21" + } + }, "node_modules/react-contextmenu": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/react-contextmenu/-/react-contextmenu-2.14.0.tgz", @@ -15528,6 +15571,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-json-tree": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz", + "integrity": "sha512-PqT1WRVcWP+RROsZPQfNEKIC1iM/ZMfY4g5jN6oDnXp5593PPRAYgoHcgYCDjflAHQMtxl8XGdlTwIBdEGUXvw==", + "dependencies": { + "@types/lodash": "^4.17.0", + "react-base16-styling": "^0.10.0" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -16783,6 +16839,19 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -22247,6 +22316,11 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "@types/lodash": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==" + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -23673,6 +23747,15 @@ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" }, + "color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "requires": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -23686,6 +23769,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -28374,6 +28466,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "lodash.assignwith": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", @@ -30086,6 +30183,17 @@ "react-audio-visualize": "^1.1.3" } }, + "react-base16-styling": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.10.0.tgz", + "integrity": "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==", + "requires": { + "@types/lodash": "^4.17.0", + "color": "^4.2.3", + "csstype": "^3.1.3", + "lodash-es": "^4.17.21" + } + }, "react-contextmenu": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/react-contextmenu/-/react-contextmenu-2.14.0.tgz", @@ -30264,6 +30372,15 @@ } } }, + "react-json-tree": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz", + "integrity": "sha512-PqT1WRVcWP+RROsZPQfNEKIC1iM/ZMfY4g5jN6oDnXp5593PPRAYgoHcgYCDjflAHQMtxl8XGdlTwIBdEGUXvw==", + "requires": { + "@types/lodash": "^4.17.0", + "react-base16-styling": "^0.10.0" + } + }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -31166,6 +31283,21 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", diff --git a/package.json b/package.json index 209ef1d4..e2b5760a 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-dom": "^18.2.0", "react-draggable": "^4.4.5", "react-joyride": "^2.5.3", + "react-json-tree": "^0.19.0", "react-material-ui-carousel": "^3.4.2", "react-mui-dropzone": "^4.0.7", "react-paginate": "^8.1.4", diff --git a/src/common/Header.jsx b/src/common/Header.jsx index 4a2ff3e3..131dcdd5 100644 --- a/src/common/Header.jsx +++ b/src/common/Header.jsx @@ -230,7 +230,8 @@ const Header = () => { Tasks - {userData?.role == "ADMIN" && + {(userData?.role === "ADMIN" || userData?.role==="ORG_OWNER") && ( + @@ -241,7 +242,8 @@ const Header = () => { > Admin - } + + )} {/* { + return {moment(value).format("DD/MM/YYYY HH:mm:ss")}; + }, + }, + }, { name: "status", label: "Status", @@ -338,6 +347,20 @@ export const taskListColumns = [ }, }, }, + { + name: "eta", + label: "ETA", + options: { + customBodyRender: (value) => { + if (value) { + const formattedETA = new Date(value).toLocaleString(); + return {formattedETA}; + } + return N/A; + }, + }, + }, + { name: "time_spent", label: "Time Spent", diff --git a/src/containers/Admin/Dashboard.jsx b/src/containers/Admin/Dashboard.jsx index 7ecac147..a326b154 100644 --- a/src/containers/Admin/Dashboard.jsx +++ b/src/containers/Admin/Dashboard.jsx @@ -1,23 +1,27 @@ -import { useState } from "react"; +import React, { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; -//styles +// Styles import { DatasetStyle } from "styles"; -//Components -import { Box, Card, Grid, Tab, Tabs, Button } from "@mui/material"; +// Components +import { Box, Card, Grid, Tab, Tabs, Button, Typography, Paper } from "@mui/material"; import OrganizationList from "./OrganizationList"; import MemberList from "./MemberList"; import { AddNewMember, AddOrganizationMember } from "common"; import AdminLevelReport from "./AdminLevelReport"; - -//Apis -import { APITransport, AddOrganizationMemberAPI } from "redux/actions"; import NewsLetter from "./NewsLetterTemplate"; import OnboardingRequests from "./OnboardingRequests"; +import VideoTaskDetails from "./VideoTaskDetails"; +import VideoDetails from "./VideoDetails"; +import TaskDetails from "./TaskDetails"; + +// APIs +import { APITransport, AddOrganizationMemberAPI, FetchLoggedInUserDetailsAPI } from "redux/actions"; -const TabPanel = (props) => { +// Tab Panel Component +function TabPanel(props) { const { children, value, index, ...other } = props; return ( @@ -31,18 +35,30 @@ const TabPanel = (props) => { {value === index && {children}} ); -}; +} const DashBoard = () => { const classes = DatasetStyle(); const navigate = useNavigate(); const dispatch = useDispatch(); - const [value, setValue] = useState(0); + // Fetch userData from Redux + const userData = useSelector((state) => state.getLoggedInUserDetails.data); + + const [value, setValue] = useState(0); const [addUserDialog, setAddUserDialog] = useState(false); const [newMemberEmail, setNewMemberEmail] = useState(""); const [openMemberDialog, setOpenMemberDialog] = useState(false); + // Fetch user data on component mount + useEffect(() => { + const fetchUserData = () => { + const loggedInUserObj = new FetchLoggedInUserDetailsAPI(); + dispatch(APITransport(loggedInUserObj)); + }; + fetchUserData(); + }, [dispatch]); + const addNewMemberHandler = async () => { const data = { role: "ORG_OWNER", @@ -55,146 +71,142 @@ const DashBoard = () => { setNewMemberEmail(""); }; + const handleTabChange = (event, newValue) => { + setValue(newValue); + }; + + const adminTabs = [ + { label: "Organizations", component: }, + { label: "Members", component: }, + { label: "Reports", component: }, + { label: "Newsletter", component: }, + { label: "Onboarding Requests", component: }, + ]; + + const orgOwnerTabs = [ + { label: "Video Details", component: }, + { label: "Video Task Details", component: }, + { label: "Task Details", component: }, + ]; + + const isAdmin = userData?.role === "ADMIN"; + const isOrgOwner = userData?.role === "ORG_OWNER"; + return ( - setValue(newValue)} - aria-label="basic tabs example" - > - - - - - + + {isAdmin && + adminTabs.map((tab, index) => ( + + ))} + + {isOrgOwner && + orgOwnerTabs.map((tab, index) => ( + + ))} - - - - - - - - - - - -
- -
-
-
-
- - - - - -
- -
-
-
- - - -
- -
-
-
- - - -
- -
-
-
- - - - -
- -
-
-
-
+ + {isAdmin && ( + <> + + + + + + + + + + + +
+ +
+
+
+
+ + + + + +
+ +
+
+
+ + + +
+ +
+
+
+ + + +
+ +
+
+
+ + + + +
+ +
+
+
+
+ + )} + + {isOrgOwner && ( + <> + + + + + + + + + + + + + + + + + + + )} +
+ {/* Dialogs */} {addUserDialog && ( { /> )} - {openMemberDialog && ( - setOpenMemberDialog(false)} - /> - )} + {openMemberDialog && setOpenMemberDialog(false)} />}
); }; -export default DashBoard; +export default DashBoard; \ No newline at end of file diff --git a/src/containers/Admin/TaskDetails.jsx b/src/containers/Admin/TaskDetails.jsx new file mode 100644 index 00000000..cc73ab1c --- /dev/null +++ b/src/containers/Admin/TaskDetails.jsx @@ -0,0 +1,281 @@ +import React, { useState } from 'react'; +import { Grid, TextField, Button, Box, Typography, CircularProgress, Tabs, Tab } from '@mui/material'; +import { JSONTree } from 'react-json-tree'; +import GetTaskDetailsAPI from "redux/actions/api/Admin/GetTaskDetails.js"; +import GetAllTranscriptionsAPI from "redux/actions/api/Admin/GetAllTranscriptions.js"; +import GetAllTranslationsAPI from "redux/actions/api/Admin/GetAllTranslations.js"; +import { snakeToTitleCase } from '../../utils/utils.js'; + +function TaskDetails() { + const [taskId, setTaskId] = useState(''); + const [tabValue, setTabValue] = useState(0); + const [taskDetails, setTaskDetails] = useState(null); + const [transcriptions, setTranscriptions] = useState(null); + const [translations, setTranslations] = useState(null); + const [loading, setLoading] = useState(false); + const [loadingTranscriptions, setLoadingTranscriptions] = useState(false); + const [loadingTranslations, setLoadingTranslations] = useState(false); + + const fetchTaskDetails = async () => { + setLoading(true); + setTaskDetails(null); + setTranscriptions(null); + setTranslations(null); + + const apiObj = new GetTaskDetailsAPI(taskId); + try { + const res = await fetch(apiObj.apiEndPoint(), apiObj.getHeaders()); + let data; + if (res.status === 200) { + data = await res.json(); + } else if (res.status === 404) { + data = { error: 'Task not found' }; + } else { + data = { error: 'Something went wrong' }; + } + + setLoading(false); + if (data.error) { + setTaskDetails({ error: data.error }); + return; + } + + setTaskDetails(data); + const videoId = data.video; + + setTabValue(0); + + if (["TRANSCRIPTION_EDIT", "TRANSCRIPTION_REVIEW"].includes(data.task_type)) { + fetchTranscriptions(videoId); + } else if (["TRANSLATION_EDIT", "TRANSLATION_REVIEW", "TRANSLATION_VOICEOVER_EDIT", "TRANSLATION_VOICEOVER_REVIEW"].includes(data.task_type)) { + fetchTranslations(videoId); + } + + } catch (error) { + setLoading(false); + setTaskDetails({ error: 'Network error' }); + console.error(error); + } + }; + + const fetchTranscriptions = async (videoId) => { + setLoadingTranscriptions(true); + const apiObj = new GetAllTranscriptionsAPI(videoId); + try { + const res = await fetch(apiObj.apiEndPoint(), apiObj.getHeaders()); + let data; + if (res.status === 200) { + data = await res.json(); + } else { + data = { error: 'Failed to fetch transcriptions' }; + } + + if (data.error) { + setTranscriptions({ error: data.error }); + } else { + setTranscriptions(data.transcripts); + } + } catch (error) { + setTranscriptions({ error: 'Network error' }); + console.error(error); + } + setLoadingTranscriptions(false); + }; + + const fetchTranslations = async (videoId) => { + setLoadingTranslations(true); + const apiObj = new GetAllTranslationsAPI(videoId); + try { + const res = await fetch(apiObj.apiEndPoint(), apiObj.getHeaders()); + let data; + if (res.status === 200) { + data = await res.json(); + } else { + data = { error: 'Failed to fetch translations' }; + } + + if (data.error) { + setTranslations({ error: data.error }); + } else { + setTranslations(data); + } + } catch (error) { + setTranslations({ error: 'Network error' }); + console.error(error); + } + setLoadingTranslations(false); + }; + + const theme = { + extend: { + base00: '#000', + base01: '#383830', + base02: '#49483e', + base03: '#75715e', + base04: '#a59f85', + base05: '#f8f8f2', + base06: '#f5f4f1', + base07: '#f9f8f5', + base08: '#f92672', + base09: '#fd971f', + base0A: '#f4bf75', + base0B: '#a6e22e', + base0C: '#a1efe4', + base0D: '#66d9ef', + base0E: '#ae81ff', + base0F: '#cc6633', + }, + value: ({ style }, nodeType, keyPath) => ({ + style: { + ...style, + borderLeft: '2px solid #ccc', + marginLeft: '1.375em', + paddingLeft: '2em', + }, + }), + nestedNode: ({ style }, nodeType, keyPath) => ({ + style: { + ...style, + borderLeft: '2px solid #ccc', + marginLeft: keyPath.length > 1 ? '1.375em' : 0, + textIndent: '-0.375em', + }, + }), + arrowContainer: ({ style }, arrowStyle) => ({ + style: { + ...style, + paddingRight: '1.375rem', + textIndent: '0rem', + backgroundColor: 'white', + }, + }), + }; + + function TabPanel(props) { + const { children, value, index, ...other } = props; + + return ( + + ); + } + + return ( + + + + setTaskId(event.target.value)} + /> + + + + {loading && ( + + + + )} + {taskDetails && ( + <> + + setTabValue(v)} aria-label="task-details-tabs"> + + {["TRANSCRIPTION_EDIT", "TRANSCRIPTION_REVIEW"].includes(taskDetails.task_type) && ( + + )} + {["TRANSLATION_EDIT", "TRANSLATION_REVIEW", "TRANSLATION_VOICEOVER_EDIT", "TRANSLATION_VOICEOVER_REVIEW"].includes(taskDetails.task_type) && ( + + )} + + + + + + {taskDetails.error ? ( + {taskDetails.error} + ) : ( + {typeof key === "string" ? snakeToTitleCase(key) : key}} + valueRenderer={(raw) => {typeof raw === "string" && raw.match(/^"(.*)"$/) ? raw.slice(1, -1) : raw}} + theme={theme} + /> + )} + + + {["TRANSCRIPTION_EDIT", "TRANSCRIPTION_REVIEW"].includes(taskDetails.task_type) && ( + + {loadingTranscriptions ? ( + + + + ) : transcriptions ? ( + transcriptions.error ? ( + {transcriptions.error} + ) : ( + {typeof key === "string" ? snakeToTitleCase(key) : key}} + valueRenderer={(raw) => {typeof raw === "string" && raw.match(/^"(.*)"$/) ? raw.slice(1, -1) : raw}} + theme={theme} + /> + ) + ) : ( + No transcriptions available. + )} + + )} + + {["TRANSLATION_EDIT", "TRANSLATION_REVIEW", "TRANSLATION_VOICEOVER_EDIT", "TRANSLATION_VOICEOVER_REVIEW"].includes(taskDetails.task_type) && ( + + {loadingTranslations ? ( + + + + ) : translations ? ( + translations.error ? ( + {translations.error} + ) : ( + {typeof key === "string" ? snakeToTitleCase(key) : key}} + valueRenderer={(raw) => {typeof raw === "string" && raw.match(/^"(.*)"$/) ? raw.slice(1, -1) : raw}} + theme={theme} + /> + ) + ) : ( + No translations available. + )} + + )} + + + )} + + ); +} + +export default TaskDetails; diff --git a/src/containers/Admin/VideoDetails.jsx b/src/containers/Admin/VideoDetails.jsx new file mode 100644 index 00000000..93c01395 --- /dev/null +++ b/src/containers/Admin/VideoDetails.jsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import { Grid, TextField, Button, Tab, Tabs, Box, Typography, CircularProgress } from '@mui/material'; +import { JSONTree } from 'react-json-tree'; +import GetVideoDetailsAPI from "redux/actions/api/Admin/GetVideoDetails.js"; +import { snakeToTitleCase } from '../../utils/utils.js'; + +function VideoDetails() { + const [videoUrl, setVideoUrl] = useState(''); + const [tabValue, setTabValue] = useState(0); + const [taskDetails, setTaskDetails] = useState(null); + const [loading, setLoading] = useState(false); + + const fetchVideoDetails = async () => { + setLoading(true); + setTaskDetails(null); + + const apiObj = new GetVideoDetailsAPI(videoUrl); + fetch(apiObj.apiEndPoint(), apiObj.getHeaders()) + .then(async (res) => { + if (res.status === 200) { + const data = await res.json(); + return data; + } else if (res.status === 404) { + return { error: 'Task not found' }; + } else { + return { error: 'Something went wrong' }; + } + }) + .then(data => { + setLoading(false); + setTaskDetails(data); + }); + }; + + const theme = { + extend: { + base00: '#000', + base01: '#383830', + base02: '#49483e', + base03: '#75715e', + base04: '#a59f85', + base05: '#f8f8f2', + base06: '#f5f4f1', + base07: '#f9f8f5', + base08: '#f92672', + base09: '#fd971f', + base0A: '#f4bf75', + base0B: '#a6e22e', + base0C: '#a1efe4', + base0D: '#66d9ef', + base0E: '#ae81ff', + base0F: '#cc6633', + }, + value: ({ style }, nodeType, keyPath) => ({ + style: { + ...style, + borderLeft: '2px solid #ccc', + marginLeft: '1.375em', + paddingLeft: '2em', + }, + }), + nestedNode: ({ style }, nodeType, keyPath) => ({ + style: { + ...style, + borderLeft: '2px solid #ccc', + marginLeft: keyPath.length > 1 ? '1.375em' : 0, + textIndent: '-0.375em', + }, + }), + arrowContainer: ({ style }, arrowStyle) => ({ + style: { + ...style, + paddingRight: '1.375rem', + textIndent: '0rem', + backgroundColor: 'white', + }, + }), + }; + + function TabPanel(props) { + const { children, value, index, ...other } = props; + + return ( + + ); + } + + return ( + + + + setVideoUrl(event.target.value)} + /> + + + + {loading && ( + + + + )} + {taskDetails && ( + <> + + setTabValue(v)} aria-label="video-task-details-tabs"> + + + + + + + {typeof key === "string" ? snakeToTitleCase(key) : key}} + valueRenderer={(raw) => {typeof raw === "string" && raw.match(/^"(.*)"$/) ? raw.slice(1, -1) : raw}} + theme={theme} + /> + + + + )} + + ); +} + +export default VideoDetails; diff --git a/src/containers/Admin/VideoTaskDetails.jsx b/src/containers/Admin/VideoTaskDetails.jsx new file mode 100644 index 00000000..572c422f --- /dev/null +++ b/src/containers/Admin/VideoTaskDetails.jsx @@ -0,0 +1,231 @@ +import React, { useState } from 'react'; +import { Grid, TextField, Button, Tab, Tabs, Box, Typography, CircularProgress } from '@mui/material'; +import { JSONTree } from 'react-json-tree'; +import GetAllTranscriptionsAPI from "redux/actions/api/Admin/GetAllTranscriptions.js"; +import GetAllTranslationsAPI from "redux/actions/api/Admin/GetAllTranslations.js"; +import GetVideoTaskDetailsAPI from "redux/actions/api/Admin/GetVideoTaskDetails.js"; +import { snakeToTitleCase } from '../../utils/utils.js'; + +function VideoTaskDetails() { + const [videoId, setVideoId] = useState(''); + const [tabValue, setTabValue] = useState(0); + const [taskDetails, setTaskDetails] = useState(null); + const [transcriptions, setTranscriptions] = useState(null); + const [translations, setTranslations] = useState(null); + const [loading, setLoading] = useState(false); + const [loadingTranscriptions, setLoadingTranscriptions] = useState(false); + const [loadingTranslations, setLoadingTranslations] = useState(false); + + const fetchVideoTaskDetails = async () => { + setLoading(true); + setTaskDetails(null); + setTranscriptions(null); + setTranslations(null); + + const apiObj = new GetVideoTaskDetailsAPI(videoId); + fetch(apiObj.apiEndPoint(), apiObj.getHeaders()) + .then(async (res) => { + if (res.status === 200) { + const data = await res.json(); + return data; + } else if (res.status === 404) { + return { error: 'Task not found' }; + } else { + return { error: 'Something went wrong' }; + } + }) + .then(data => { + setLoading(false); + setTaskDetails(data); + fetchTranscriptions(); + fetchTranslations(); + }); + }; + + const fetchTranscriptions = async () => { + setLoadingTranscriptions(true); + const apiObj = new GetAllTranscriptionsAPI(videoId); + fetch(apiObj.apiEndPoint(), apiObj.getHeaders()) + .then(async (res) => { + if (res.status === 200) { + const data = await res.json(); + return data; + } else { + return { error: 'Failed to fetch transcriptions' }; + } + }) + .then(data => { + setTranscriptions(data.transcripts); + setLoadingTranscriptions(false); + }); + }; + + const fetchTranslations = async () => { + setLoadingTranslations(true); + const apiObj = new GetAllTranslationsAPI(videoId); + fetch(apiObj.apiEndPoint(), apiObj.getHeaders()) + .then(async (res) => { + if (res.status === 200) { + const data = await res.json(); + return data; + } else { + return { error: 'Failed to fetch translations' }; + } + }) + .then(data => { + setTranslations(data); + setLoadingTranslations(false); + }); + }; + + const theme = { + extend: { + base00: '#000', + base01: '#383830', + base02: '#49483e', + base03: '#75715e', + base04: '#a59f85', + base05: '#f8f8f2', + base06: '#f5f4f1', + base07: '#f9f8f5', + base08: '#f92672', + base09: '#fd971f', + base0A: '#f4bf75', + base0B: '#a6e22e', + base0C: '#a1efe4', + base0D: '#66d9ef', + base0E: '#ae81ff', + base0F: '#cc6633', + }, + value: ({ style }, nodeType, keyPath) => ({ + style: { + ...style, + borderLeft: '2px solid #ccc', + marginLeft: '1.375em', + paddingLeft: '2em', + }, + }), + nestedNode: ({ style }, nodeType, keyPath) => ({ + style: { + ...style, + borderLeft: '2px solid #ccc', + marginLeft: keyPath.length > 1 ? '1.375em' : 0, + textIndent: '-0.375em', + }, + }), + arrowContainer: ({ style }, arrowStyle) => ({ + style: { + ...style, + paddingRight: '1.375rem', + textIndent: '0rem', + backgroundColor: 'white', + }, + }), + }; + + function TabPanel(props) { + const { children, value, index, ...other } = props; + + return ( + + ); + } + + return ( + + + + setVideoId(event.target.value)} + /> + + + + {loading && ( + + + + )} + {taskDetails && ( + <> + + setTabValue(v)} aria-label="video-task-details-tabs"> + + + + + + + + + {typeof key === "string" ? snakeToTitleCase(key) : key}} + valueRenderer={(raw) => {typeof raw === "string" && raw.match(/^"(.*)"$/) ? raw.slice(1, -1) : raw}} + theme={theme} + /> + + + {loadingTranscriptions ? ( + + + + ) : transcriptions ? ( + {typeof key === "string" ? snakeToTitleCase(key) : key}} + valueRenderer={(raw) => {typeof raw === "string" && raw.match(/^"(.*)"$/) ? raw.slice(1, -1) : raw}} + theme={theme} + /> + ) : ( + No transcriptions available. + )} + + + {loadingTranslations ? ( + + + + ) : translations ? ( + {typeof key === "string" ? snakeToTitleCase(key) : key}} + valueRenderer={(raw) => {typeof raw === "string" && raw.match(/^"(.*)"$/) ? raw.slice(1, -1) : raw}} + theme={theme} + /> + ) : ( + No translations available. + )} + + + + )} + + ); +} + +export default VideoTaskDetails; diff --git a/src/containers/Organization/MyOrganization.jsx b/src/containers/Organization/MyOrganization.jsx index e515a833..6099e564 100644 --- a/src/containers/Organization/MyOrganization.jsx +++ b/src/containers/Organization/MyOrganization.jsx @@ -142,11 +142,11 @@ const MyOrganization = () => { const { organization: { organization_owners }, } = userData; - + if (organization_owners && organization_owners?.length > 0) { - const ownerIds = organization_owners.map(owner => owner.id); + const ownerIds = organization_owners.map((owner) => owner.id); setOrgOwnerId(ownerIds); - + if (ownerIds.includes(userData.id)) { setIsUserOrgOwner(true); } else { @@ -173,7 +173,16 @@ const MyOrganization = () => { } return ( - + {organizationDetails?.title} ); @@ -192,31 +201,81 @@ const MyOrganization = () => { }; return ( - + {renderOrgDetails()} setValue(newValue)} + variant="fullWidth" aria-label="basic tabs example" + TabIndicatorProps={{ + style: { display: "none" }, + }} > - - - { roles.filter((role) => role.value === userData?.role)[0] + + + {roles.filter((role) => role.value === userData?.role)[0] ?.canAddMembers && ( - + )} - {(isUserOrgOwner|| userData?.role==="ADMIN") &&( - + {(isUserOrgOwner || userData?.role === "ADMIN") && ( + )} - {(isUserOrgOwner || userData?.role==="ADMIN")&&( + {(isUserOrgOwner || userData?.role === "ADMIN") && ( )} @@ -234,7 +293,9 @@ const MyOrganization = () => { alignItems="center" > - {(isUserOrgOwner|| userData?.role==="ADMIN") && ( + {(isUserOrgOwner || + userData?.role === "ADMIN" || + userData?.role === "PROJECT_MANAGER") && (