From cca191ad5c10362b2cf3cc6edf5aeb7936ccbb4b Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 31 Dec 2023 14:34:55 +0700 Subject: [PATCH 01/24] Change ID from sequence number to random uuid4 --- src/App.tsx | 2 +- .../CreateCollectionForm/ManageProblems.tsx | 22 +++++----- .../Forms/CreateCollectionForm/index.tsx | 2 +- .../CreateCourseForm/ManageCollections.tsx | 22 +++++----- .../Forms/CreateCourseForm/index.tsx | 8 ++-- .../Forms/CreateGroupForm/ManageMembers.tsx | 20 ++++----- .../Forms/CreateGroupForm/index.tsx | 8 ++-- .../Forms/CreateProblemForm/Requirement.tsx | 2 +- .../Forms/CreateProblemForm/index.tsx | 8 ++-- src/components/MyProblemMiniCard.tsx | 4 +- .../NavbarCollectionsProblemsAccordion.tsx | 2 +- .../NavigationBar/CourseNavSidebar.tsx | 4 +- .../NavigationBar/ProfileDropdown.tsx | 2 +- src/components/ProblemViewLayout.tsx | 4 +- src/services/Group.service.ts | 4 +- src/types/apis/Account.api.ts | 2 +- src/types/apis/Auth.api.ts | 4 +- src/types/apis/Collection.api.ts | 14 +++--- src/types/apis/Group.api.ts | 12 ++--- src/types/apis/Problem.api.ts | 12 ++--- src/types/apis/Submission.api.ts | 14 +++--- src/types/apis/Topic.api.ts | 12 ++--- src/types/models/Account.model.ts | 6 +-- src/types/models/Collection.model.ts | 8 ++-- src/types/models/Group.model.ts | 6 +-- src/types/models/Problem.model.ts | 10 ++--- src/types/models/Submission.model.ts | 14 +++--- src/types/models/Topic.model.ts | 8 ++-- src/utilities/DeleteProblem.ts | 2 +- src/views/Dashboard.tsx | 2 +- src/views/ExploreCourses.tsx | 4 +- src/views/My/Collections/CreateCollection.tsx | 44 +++++++------------ src/views/My/Collections/EditCollection.tsx | 4 +- src/views/My/Collections/MyCollections.tsx | 2 +- src/views/My/Courses/CreateCourse.tsx | 15 +++---- src/views/My/Courses/EditCourse.tsx | 17 +++---- src/views/My/Courses/MyCourses.tsx | 2 +- src/views/My/Groups/CreateGroup.tsx | 4 +- src/views/My/Groups/EditGroup.tsx | 8 ++-- src/views/My/Groups/MyGroups.tsx | 2 +- src/views/My/Problems/CreateProblem.tsx | 39 ++++++---------- src/views/My/Problems/EditProblem.tsx | 12 ++--- src/views/My/Problems/MyProblems.tsx | 2 +- src/views/ViewCourse.tsx | 4 +- src/views/ViewCourseProblem.tsx | 16 +++---- src/views/ViewProblem.tsx | 12 ++--- 46 files changed, 195 insertions(+), 232 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 1d534b0..d13b264 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,7 +21,7 @@ function App() { useEffect(() => { const token = localStorage.getItem("token"); - const account_id = Number(localStorage.getItem("account_id")); + const account_id = String(localStorage.getItem("account_id")); if (!token || !account_id) { return; diff --git a/src/components/Forms/CreateCollectionForm/ManageProblems.tsx b/src/components/Forms/CreateCollectionForm/ManageProblems.tsx index 58a521a..450de45 100644 --- a/src/components/Forms/CreateCollectionForm/ManageProblems.tsx +++ b/src/components/Forms/CreateCollectionForm/ManageProblems.tsx @@ -30,7 +30,7 @@ const ManageProblems = ({ >; }) => { - const accountId = Number(localStorage.getItem("account_id")); + const accountId = String(localStorage.getItem("account_id")); const [allProblemsSortable, setAllProblemsSortable] = useState< ItemInterface[] @@ -43,13 +43,13 @@ const ManageProblems = ({ >({}); const [initial, setInitial] = useState(true); - const [selectedProblemSortableIds, setSelectedProblemSortableIds] = useState([]); + const [selectedProblemSortableIds, setSelectedProblemSortableIds] = useState([]); useEffect(() => { - setSelectedProblemSortableIds(selectedProblemsSortable.map((item) => item.id as number)); + setSelectedProblemSortableIds(selectedProblemsSortable.map((item) => item.id as string)); },[selectedProblemsSortable]) - const handleRemoveSelectedProblem = (id: number) => { + const handleRemoveSelectedProblem = (id: string) => { setSelectedProblemsSortable( [...selectedProblemsSortable.filter((item) => item.id !== id)] ); @@ -58,14 +58,14 @@ const ManageProblems = ({ const handleQuickToggleSelectedProblem = (item: ItemInterface) => { // if (selectedProblemsSortable.find((item1) => item1.id === item.id)) { // console.log("Remove"); - // handleRemoveSelectedProblem(item.id as number); + // handleRemoveSelectedProblem(item.id as string); // } else { // console.log("Add"); // setSelectedProblemsSortable([...selectedProblemsSortable, item]); // } - if (selectedProblemSortableIds.includes(item.id as number)) { - handleRemoveSelectedProblem(item.id as number); + if (selectedProblemSortableIds.includes(item.id as string)) { + handleRemoveSelectedProblem(item.id as string); } else { setSelectedProblemsSortable([...selectedProblemsSortable, item]); @@ -123,10 +123,10 @@ const ManageProblems = ({ {selectedProblemsSortable?.map((item) => ( handleRemoveSelectedProblem(item.id as number)} + onClick={() => handleRemoveSelectedProblem(item.id as string)} key={item.id} problem={ - allProblems[item.id as number] + allProblems[item.id as string] } /> ))} @@ -160,10 +160,10 @@ const ManageProblems = ({
handleQuickToggleSelectedProblem(item)} - disabled={selectedProblemSortableIds.includes(item.id as number)} + disabled={selectedProblemSortableIds.includes(item.id as string)} key={item.id} problem={ - allProblems[item.id as number] + allProblems[item.id as string] } />
diff --git a/src/components/Forms/CreateCollectionForm/index.tsx b/src/components/Forms/CreateCollectionForm/index.tsx index acdb164..7fc5115 100644 --- a/src/components/Forms/CreateCollectionForm/index.tsx +++ b/src/components/Forms/CreateCollectionForm/index.tsx @@ -30,7 +30,7 @@ const TabList = [ export type OnCollectionSavedCallback = { setLoading?: React.Dispatch> - collectionId?: number + collectionid?: string setCollectionId?: React.Dispatch> createRequest?: CreateCollectionRequestForm } diff --git a/src/components/Forms/CreateCourseForm/ManageCollections.tsx b/src/components/Forms/CreateCourseForm/ManageCollections.tsx index c046a93..7001071 100644 --- a/src/components/Forms/CreateCourseForm/ManageCollections.tsx +++ b/src/components/Forms/CreateCourseForm/ManageCollections.tsx @@ -35,7 +35,7 @@ const ManageCollections = ({ >; }) => { - const accountId = Number(localStorage.getItem("account_id")); + const accountId = String(localStorage.getItem("account_id")); const [allCollectionsSortable, setAllCollectionsSortable] = useState< ItemInterface[] @@ -48,13 +48,13 @@ const ManageCollections = ({ >({}); const [initial, setInitial] = useState(true); - const [selectedCollectionsSortableIds, setSelectedCollectionsSortableIds] = useState([]); + const [selectedCollectionsSortableIds, setSelectedCollectionsSortableIds] = useState([]); useEffect(() => { - setSelectedCollectionsSortableIds(selectedCollectionsSortable.map((item) => item.id as number)); + setSelectedCollectionsSortableIds(selectedCollectionsSortable.map((item) => item.id as string)); },[selectedCollectionsSortable]) - const handleRemoveSelectedCollection = (id: number) => { + const handleRemoveSelectedCollection = (id: string) => { setSelectedCollectionsSortable( [...selectedCollectionsSortable.filter((item) => item.id !== id)] ); @@ -63,14 +63,14 @@ const ManageCollections = ({ const handleQuickToggleSelectedCollection = (item: ItemInterface) => { // if (selectedCollectionsSortable.find((item1) => item1.id === item.id)) { // console.log("Remove"); - // handleRemoveSelectedCollection(item.id as number); + // handleRemoveSelectedCollection(item.id as string); // } else { // console.log("Add"); // setSelectedCollectionsSortable([...selectedCollectionsSortable, item]); // } - if (selectedCollectionsSortableIds.includes(item.id as number)) { - handleRemoveSelectedCollection(item.id as number); + if (selectedCollectionsSortableIds.includes(item.id as string)) { + handleRemoveSelectedCollection(item.id as string); } else { setSelectedCollectionsSortable([...selectedCollectionsSortable, item]); @@ -138,9 +138,9 @@ const ManageCollections = ({ {selectedCollectionsSortable?.map((item) => ( handleRemoveSelectedCollection(item.id as number)} + onClick={() => handleRemoveSelectedCollection(item.id as string)} key={item.id} - collection={allCollections[item.id as number] as CollectionPopulateProblemSecureModel} + collection={allCollections[item.id as string] as CollectionPopulateProblemSecureModel} /> ))} @@ -173,9 +173,9 @@ const ManageCollections = ({
handleQuickToggleSelectedCollection(item)} - disabled={selectedCollectionsSortableIds.includes(item.id as number)} + disabled={selectedCollectionsSortableIds.includes(item.id as string)} key={item.id} - collection={allCollections[item.id as number] as CollectionPopulateProblemSecureModel} + collection={allCollections[item.id as string] as CollectionPopulateProblemSecureModel} />
))} diff --git a/src/components/Forms/CreateCourseForm/index.tsx b/src/components/Forms/CreateCourseForm/index.tsx index 634bc78..97f2d79 100644 --- a/src/components/Forms/CreateCourseForm/index.tsx +++ b/src/components/Forms/CreateCourseForm/index.tsx @@ -30,8 +30,8 @@ const TabList = [ export type OnCourseSavedCallback = { setLoading: React.Dispatch>; - courseId: number; - setCourseId: React.Dispatch>; + // courseId: string; + // setCourseId: React.Dispatch>; createRequest: CreateCourseRequestForm; } @@ -58,8 +58,8 @@ const CreateCourseForm = ({ onCourseSave({ setLoading, createRequest, - courseId, - setCourseId, + // courseId, + // setCourseId, }); }; diff --git a/src/components/Forms/CreateGroupForm/ManageMembers.tsx b/src/components/Forms/CreateGroupForm/ManageMembers.tsx index bdaae0e..74e86d6 100644 --- a/src/components/Forms/CreateGroupForm/ManageMembers.tsx +++ b/src/components/Forms/CreateGroupForm/ManageMembers.tsx @@ -44,7 +44,7 @@ const ManageMembers = ({ React.SetStateAction >; }) => { - const accountId = Number(localStorage.getItem("account_id")); + const accountId = String(localStorage.getItem("account_id")); const [allAccountsSortable, setAllAccountsSortable] = useState< ItemInterface[] @@ -56,23 +56,23 @@ const ManageMembers = ({ const [initial, setInitial] = useState(true); const [selectedAccountsSortableIds, setSelectedAccountsSortableIds] = - useState([]); + useState([]); useEffect(() => { setSelectedAccountsSortableIds( - selectedAccountsSortable?.map((item) => item.id as number) + selectedAccountsSortable?.map((item) => item.id as string) ); }, [selectedAccountsSortable]); - const handleRemoveSelectedCollection = (id: number) => { + const handleRemoveSelectedCollection = (id: string) => { setSelectedAccountsSortable([ ...selectedAccountsSortable.filter((item) => item.id !== id), ]); }; const handleQuickToggleSelectedCollection = (item: ItemInterface) => { - if (selectedAccountsSortableIds.includes(item.id as number)) { - handleRemoveSelectedCollection(item.id as number); + if (selectedAccountsSortableIds.includes(item.id as string)) { + handleRemoveSelectedCollection(item.id as string); } else { setSelectedAccountsSortable([...selectedAccountsSortable, item]); } @@ -131,9 +131,9 @@ const ManageMembers = ({ {selectedAccountsSortable?.map((item) => ( handleRemoveSelectedCollection(item.id as number)} + onClick={() => handleRemoveSelectedCollection(item.id as string)} key={item.id} - account={allAccounts[item.id as number] as AccountSecureModel} + account={allAccounts[item.id as string] as AccountSecureModel} /> ))} @@ -171,10 +171,10 @@ const ManageMembers = ({ } > handleQuickToggleSelectedCollection(item)} key={item.id} - account={allAccounts[item.id as number] as AccountSecureModel} + account={allAccounts[item.id as string] as AccountSecureModel} /> ))} diff --git a/src/components/Forms/CreateGroupForm/index.tsx b/src/components/Forms/CreateGroupForm/index.tsx index e32d892..2121558 100644 --- a/src/components/Forms/CreateGroupForm/index.tsx +++ b/src/components/Forms/CreateGroupForm/index.tsx @@ -32,8 +32,8 @@ const TabList = [ export type OnGroupSavedCallback = { setLoading: React.Dispatch>; - groupId: number; - setGroupId: React.Dispatch>; + // groupId: string; + // setGroupId: React.Dispatch>; createRequest: CreateGroupRequestForm; } @@ -60,8 +60,8 @@ const CreateGroupForm = ({ onCourseSave({ setLoading, createRequest, - groupId, - setGroupId, + // groupId, + // setGroupId, }); }; diff --git a/src/components/Forms/CreateProblemForm/Requirement.tsx b/src/components/Forms/CreateProblemForm/Requirement.tsx index 4a3e6dd..f8e9ad1 100644 --- a/src/components/Forms/CreateProblemForm/Requirement.tsx +++ b/src/components/Forms/CreateProblemForm/Requirement.tsx @@ -19,7 +19,7 @@ const Requirement = ({ onChange={(e) => setCreateRequest({ ...createRequest, - time_limit: Number(e.target.value), + time_limit: String(e.target.value), }) } /> diff --git a/src/components/Forms/CreateProblemForm/index.tsx b/src/components/Forms/CreateProblemForm/index.tsx index 7ebbfd8..32e451e 100644 --- a/src/components/Forms/CreateProblemForm/index.tsx +++ b/src/components/Forms/CreateProblemForm/index.tsx @@ -58,8 +58,8 @@ const transformCreateProblemRequestForm2CreateProblemRequest = ( export type OnProblemSaveCallback = ( setLoading: React.Dispatch>, - problemId: number, - setProblemId: React.Dispatch>, + // problemid: string, + // setProblemId: React.Dispatch>, createRequest: CreateProblemRequestForm ) => void; @@ -72,7 +72,7 @@ const CreateProblemForm = ({ onProblemSave: OnProblemSaveCallback; validatedTestcases?: TestcaseModel[]; }) => { - const accountId = Number(localStorage.getItem("account_id")); + const accountId = String(localStorage.getItem("account_id")); const navigate = useNavigate(); const [loading, setLoading] = useState(false); @@ -84,7 +84,7 @@ const CreateProblemForm = ({ const [problemId, setProblemId] = useState(-1); const handleSave = () => { - onProblemSave(setLoading, problemId, setProblemId, createRequest); + onProblemSave(setLoading, createRequest); }; useEffect(() => { diff --git a/src/components/MyProblemMiniCard.tsx b/src/components/MyProblemMiniCard.tsx index dae3ef9..cbebd5d 100644 --- a/src/components/MyProblemMiniCard.tsx +++ b/src/components/MyProblemMiniCard.tsx @@ -119,7 +119,7 @@ const MyProblemMiniCard = ({ // className={`pt-6 px-5 ${disabled ? "opacity-50" : }`}`} > - + {/* */}
@@ -149,7 +149,7 @@ const MyProblemMiniCard = ({
-
+ {/*
*/} ) diff --git a/src/components/NavbarCollectionsProblemsAccordion.tsx b/src/components/NavbarCollectionsProblemsAccordion.tsx index 6f5140a..1c0dc82 100644 --- a/src/components/NavbarCollectionsProblemsAccordion.tsx +++ b/src/components/NavbarCollectionsProblemsAccordion.tsx @@ -31,7 +31,7 @@ const NavbarCollectionProblemCard = ({ result += "hover:bg-blue-100 " } - if (problem.problem_id === Number(problemId)) { + if (problem.problem_id === String(problemId)) { if (problem.best_submission?.is_passed) { result += "bg-green-100 " } diff --git a/src/components/NavigationBar/CourseNavSidebar.tsx b/src/components/NavigationBar/CourseNavSidebar.tsx index e5ec43a..fcddf23 100644 --- a/src/components/NavigationBar/CourseNavSidebar.tsx +++ b/src/components/NavigationBar/CourseNavSidebar.tsx @@ -10,7 +10,7 @@ import { ScrollArea } from "../shadcn/ScrollArea"; const CourseNavSidebar = () => { const navigate = useNavigate(); - const accountId = Number(localStorage.getItem("account_id")); + const accountId = String(localStorage.getItem("account_id")); const { courseId } = useParams(); const courseNavSidebarContext = useContext(CourseNavSidebarContext); @@ -19,7 +19,7 @@ const CourseNavSidebar = () => { useEffect(() => { - TopicService.getPublicByAccount(accountId, Number(courseId)).then( + TopicService.getPublicByAccount(accountId, String(courseId)).then( (response) => { console.log(response.data); setCourse(response.data); diff --git a/src/components/NavigationBar/ProfileDropdown.tsx b/src/components/NavigationBar/ProfileDropdown.tsx index 246a8c0..3c4e816 100644 --- a/src/components/NavigationBar/ProfileDropdown.tsx +++ b/src/components/NavigationBar/ProfileDropdown.tsx @@ -42,7 +42,7 @@ const ProfileDropdown = ({ children }: { children: ReactNode }) => { const navigate = useNavigate() const username = localStorage.getItem('username') - const account_id = Number(localStorage.getItem('account_id')) + const account_id = String(localStorage.getItem('account_id')) const token = localStorage.getItem('token') const handleLogout = async () => { diff --git a/src/components/ProblemViewLayout.tsx b/src/components/ProblemViewLayout.tsx index a510655..ec139a9 100644 --- a/src/components/ProblemViewLayout.tsx +++ b/src/components/ProblemViewLayout.tsx @@ -48,7 +48,7 @@ const ProblemViewLayout = ({ onSubmit({setGrading, setLastedSubmission,selectedLanguage,submitCodeValue}) } - const handleSelectPreviousSubmission = (submissionId: number) => { + const handleSelectPreviousSubmission = (submissionId: string) => { let submission = null; if ( submissionId === previousSubmissions?.best_submission?.submission_id @@ -173,7 +173,7 @@ const ProblemViewLayout = ({ previousSubmissions?.submissions as SubmissionPopulateSubmissionTestcasesSecureModel[] } onSelect={(submissionId) => - handleSelectPreviousSubmission(Number(submissionId)) + handleSelectPreviousSubmission(String(submissionId)) } /> + + + ); +}; + +export default PublicCourseCard; diff --git a/src/components/TestcasesGradingIndicator.tsx b/src/components/TestcasesGradingIndicator.tsx index a3913da..551147b 100644 --- a/src/components/TestcasesGradingIndicator.tsx +++ b/src/components/TestcasesGradingIndicator.tsx @@ -73,9 +73,9 @@ const TestcasesGradingIndicator = ({submissionTestcases,disableHover=false,class sizeX?: number; sizeY?: number; }) => { - useEffect(()=>{ - console.log(submissionTestcases) - },[submissionTestcases]) + // useEffect(()=>{ + // console.log(submissionTestcases) + // },[submissionTestcases]) return (
{ diff --git a/src/services/Collection.service.ts b/src/services/Collection.service.ts index 4e6125b..be55615 100644 --- a/src/services/Collection.service.ts +++ b/src/services/Collection.service.ts @@ -12,7 +12,7 @@ export const CollectionService: CollectionServiceAPI = { return axios.get(`${BASE_URL}/api/collections/${collectionId}`); }, - getAllByAccount: (accountId) => { + getAllAsCreator: (accountId) => { return axios.get(`${BASE_URL}/api/accounts/${accountId}/collections`); }, diff --git a/src/services/Group.service.ts b/src/services/Group.service.ts index f0c95cc..8e1e31e 100644 --- a/src/services/Group.service.ts +++ b/src/services/Group.service.ts @@ -11,7 +11,7 @@ export const GroupService: GroupSerivceAPI = { return response; }, - getAllByAccount: async (accountId:string,query?:any) => { + getAllAsCreator: async (accountId:string,query?:any) => { const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/groups`,{ params: query }) diff --git a/src/services/Problem.service.ts b/src/services/Problem.service.ts index 0a86abb..916160d 100644 --- a/src/services/Problem.service.ts +++ b/src/services/Problem.service.ts @@ -13,7 +13,7 @@ export const ProblemService: ProblemServiceAPI = { return axios.get(`${BASE_URL}/api/problems`); }, - getAllByAccount: async (accountId) => { + getAllAsCreator: async (accountId) => { return axios.get(`${BASE_URL}/api/accounts/${accountId}/problems`); }, diff --git a/src/services/Topic.service.ts b/src/services/Topic.service.ts index 4eea157..51d5a4a 100644 --- a/src/services/Topic.service.ts +++ b/src/services/Topic.service.ts @@ -14,12 +14,17 @@ export const TopicService: TopicSerivceAPI = { return response; }, + getAllAccessibleByAccount: async (accountId) => { + const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/access/topics`); + return response; + }, + update: async (topicId, request) => { const response = await axios.put(`${BASE_URL}/api/topics/${topicId}`, request); return response; }, - getAllByAccount: async (accountId) => { + getAllAsCreator: async (accountId) => { const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/topics`); return response; }, diff --git a/src/types/apis/Collection.api.ts b/src/types/apis/Collection.api.ts index d4b9958..ede0367 100644 --- a/src/types/apis/Collection.api.ts +++ b/src/types/apis/Collection.api.ts @@ -4,7 +4,7 @@ import { CollectionCreateRequest, CollectionModel, CollectionPopulateProblemSecu export type CollectionServiceAPI = { create: (accountId:string,request:CollectionCreateRequest) => Promise>; get: (collectionId:string) => Promise>; - getAllByAccount: (accountId:string) => Promise>; + getAllAsCreator: (accountId:string) => Promise>; update: (collectionId:string,request:CollectionUpdateRequest) => Promise>; addProblem: (collectionId:string,problemIds:string[]) => Promise>; removeProblem: (collectionId:string,problemIds:string[]) => Promise>; diff --git a/src/types/apis/Group.api.ts b/src/types/apis/Group.api.ts index 8a86f74..181926d 100644 --- a/src/types/apis/Group.api.ts +++ b/src/types/apis/Group.api.ts @@ -7,7 +7,7 @@ export type GroupCreateRequest = { color?: string | null; } -export type GroupGetAllByAccountResponse = { +export type GroupgetAllAsCreatorResponse = { groups: GroupModel[] | GroupPopulateGroupMemberPopulateAccountSecureModel[]; } @@ -17,7 +17,7 @@ export type GroupGetQuery = { export type GroupSerivceAPI = { get: (groupId:string,query?:GroupGetQuery) => Promise>; - getAllByAccount: (accountId:string,query?:GroupGetQuery) => Promise>; + getAllAsCreator: (accountId:string,query?:GroupGetQuery) => Promise>; create: (accountId:string,request:GroupCreateRequest) => Promise>; update: (groupId:string,request:GroupCreateRequest) => Promise>; delete: (groupId:string) => Promise>; diff --git a/src/types/apis/Problem.api.ts b/src/types/apis/Problem.api.ts index 9004231..4d07ba3 100644 --- a/src/types/apis/Problem.api.ts +++ b/src/types/apis/Problem.api.ts @@ -51,7 +51,7 @@ export type GetAllProblemsResponse = { export type ProblemServiceAPI = { create: (accountId:string,request: CreateProblemRequest) => Promise>; getAll: () => Promise>; - getAllByAccount: (accountId:string) => Promise>; + getAllAsCreator: (accountId:string) => Promise>; get: (problemId:string) => Promise>; update: (problemId:string, request: UpdateProblemRequest | CreateProblemRequest) => Promise>; // deleteMultiple: (problemIds:string[]) => Promise>; diff --git a/src/types/apis/Topic.api.ts b/src/types/apis/Topic.api.ts index f37a17f..2572919 100644 --- a/src/types/apis/Topic.api.ts +++ b/src/types/apis/Topic.api.ts @@ -5,11 +5,16 @@ export type GetAllTopicsByAccountResponse = { topics: TopicPopulateTopicCollectionPopulateCollectionModel[]; } +export type GetAllTopicsByAccessibleAccountResponse = { + topics: TopicModel[]; +} + export type TopicSerivceAPI = { create: (accountid: string,request: FormData) => Promise>; get: (accountid: string,courseId:string) => Promise>; + getAllAsCreator: (accountId:string) => Promise>; + getAllAccessibleByAccount: (accountId:string) => Promise>; + getPublicByAccount: (accountId:string,courseId:string) => Promise>; update: (courseId:string,request: FormData) => Promise>; - getAllByAccount: (accountId:string) => Promise>; updateCollections: (topicId:string,collectionIds:string[]) => Promise>; - getPublicByAccount: (accountId:string,courseId:string) => Promise>; } \ No newline at end of file diff --git a/src/views/Dashboard.tsx b/src/views/Dashboard.tsx index f857f6f..a2cb3c8 100644 --- a/src/views/Dashboard.tsx +++ b/src/views/Dashboard.tsx @@ -4,15 +4,22 @@ import NavbarMenuLayout from "../layout/NavbarMenuLayout"; import { SubmissionService } from "../services/Submission.service"; import { SubmissionPopulateSubmissionTestcaseAndProblemSecureModel } from "../types/models/Submission.model"; import SubmissionCard from "../components/SubmissionCard"; +import { TopicModel } from "../types/models/Topic.model"; +import { TopicService } from "../services/Topic.service"; +import PublicCourseCard from "../components/PublicCourseCard"; const Dashboard = () => { const accountId = String(localStorage.getItem("account_id")); - const username = localStorage.getItem("username"); + const username = localStorage.getItem("username"); const [previousAttemptedProblems, setPreviousAttemptedProblems] = useState< SubmissionPopulateSubmissionTestcaseAndProblemSecureModel[] >([]); + const [accessibleCourses, setAccessibleCourses] = useState( + [] + ); + useEffect(() => { SubmissionService.getAll({ account_id: accountId, @@ -21,8 +28,14 @@ const Dashboard = () => { end: 4, }).then((response) => { setPreviousAttemptedProblems(response.data.submissions); + // console.log(response.data.submissions); + }); + + TopicService.getAllAccessibleByAccount(accountId).then((response) => { + console.log("result", response.data.topics); + setAccessibleCourses(response.data.topics); }); - }, []); + }, [accountId]); return ( @@ -36,16 +49,20 @@ const Dashboard = () => {
{previousAttemptedProblems.map((submission, index) => ( -
- -
+ ))}
-

Courses

+

Courses

+
+ + { + accessibleCourses.map((course) => ( + + )) + } + +
); diff --git a/src/views/My/Collections/MyCollections.tsx b/src/views/My/Collections/MyCollections.tsx index 97c5823..04fda75 100644 --- a/src/views/My/Collections/MyCollections.tsx +++ b/src/views/My/Collections/MyCollections.tsx @@ -19,7 +19,7 @@ const MyCollections = () => { useEffect(() => { setSection("COLLECTIONS") - CollectionService.getAllByAccount(accountId).then((response => { + CollectionService.getAllAsCreator(accountId).then((response => { setCollections(response.data.collections) })) }, []); diff --git a/src/views/My/Courses/MyCourses.tsx b/src/views/My/Courses/MyCourses.tsx index e9aa8ce..b342415 100644 --- a/src/views/My/Courses/MyCourses.tsx +++ b/src/views/My/Courses/MyCourses.tsx @@ -20,7 +20,7 @@ const MyCourses = () => { useEffect(( )=> { setSection("COURSES") - TopicService.getAllByAccount(accountId).then((response) => { + TopicService.getAllAsCreator(accountId).then((response) => { setTopics(response.data.topics) }) },[]) diff --git a/src/views/My/Groups/MyGroups.tsx b/src/views/My/Groups/MyGroups.tsx index 22b22c8..dc21edf 100644 --- a/src/views/My/Groups/MyGroups.tsx +++ b/src/views/My/Groups/MyGroups.tsx @@ -23,7 +23,7 @@ const MyGroups = () => { useEffect(( )=> { setSection("GROUPS") - GroupService.getAllByAccount(accountId,{ + GroupService.getAllAsCreator(accountId,{ populate_members: true, }).then((response) => { setGroups(response.data.groups as GroupPopulateGroupMemberPopulateAccountSecureModel[]); diff --git a/src/views/My/Problems/MyProblems.tsx b/src/views/My/Problems/MyProblems.tsx index bc86799..d7e7cf6 100644 --- a/src/views/My/Problems/MyProblems.tsx +++ b/src/views/My/Problems/MyProblems.tsx @@ -20,7 +20,7 @@ const MyProblems = () => { const {section,setSection} = useContext(NavSidebarContext) useEffect(() => { - ProblemService.getAllByAccount(accountId).then((response) => { + ProblemService.getAllAsCreator(accountId).then((response) => { setProblems(response.data.problems); console.log(response.data.problems); }); From 9bb406ae33a751d2f307169a3a4c3310d645c26e Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 31 Dec 2023 20:27:49 +0700 Subject: [PATCH 03/24] Better card directories --- src/components/AddProblemDialog.tsx | 39 ------------------- .../AccountCards}/AccountCheckboxCard.tsx | 6 +-- .../AccountCards}/AccountMiniCard.tsx | 20 +++++----- .../CollectionCards}/MyCollectionCard.tsx | 12 +++--- .../CollectionCards}/MyCollectionMiniCard.tsx | 18 ++++----- .../TopicCollectionAccordionCard.tsx | 18 ++++----- .../{ => Cards/CourseCards}/MyCourseCard.tsx | 8 ++-- .../CourseCards}/PublicCourseCard.tsx | 6 +-- src/components/{ => Cards}/MyGroupCard.tsx | 6 +-- .../ProblemCards}/MyProblemCard.tsx | 14 +++---- .../ProblemCards}/MyProblemMiniCard.tsx | 16 ++++---- .../ProblemCards}/PublicProblemCard.tsx | 12 +++--- .../ProblemCards}/PublicProblemMiniCard.tsx | 10 ++--- src/components/{ => Cards}/SubmissionCard.tsx | 10 ++--- .../CreateCollectionForm/ManageProblems.tsx | 5 +-- .../CreateCourseForm/ManageCollections.tsx | 7 ++-- .../Forms/CreateGroupForm/ManageMembers.tsx | 11 +++--- src/components/TopicCollectionsAccordion.tsx | 4 +- src/views/Dashboard.tsx | 4 +- src/views/ExploreProblems.tsx | 2 +- src/views/My/Collections/MyCollections.tsx | 2 +- src/views/My/Courses/MyCourses.tsx | 2 +- src/views/My/Groups/MyGroups.tsx | 4 +- src/views/My/Problems/MyProblems.tsx | 2 +- src/views/ViewCourse.tsx | 2 +- 25 files changed, 99 insertions(+), 141 deletions(-) delete mode 100644 src/components/AddProblemDialog.tsx rename src/components/{ => Cards/AccountCards}/AccountCheckboxCard.tsx (80%) rename src/components/{ => Cards/AccountCards}/AccountMiniCard.tsx (78%) rename src/components/{ => Cards/CollectionCards}/MyCollectionCard.tsx (86%) rename src/components/{ => Cards/CollectionCards}/MyCollectionMiniCard.tsx (87%) rename src/components/{ => Cards/CollectionCards}/TopicCollectionAccordionCard.tsx (83%) rename src/components/{ => Cards/CourseCards}/MyCourseCard.tsx (89%) rename src/components/{ => Cards/CourseCards}/PublicCourseCard.tsx (81%) rename src/components/{ => Cards}/MyGroupCard.tsx (91%) rename src/components/{ => Cards/ProblemCards}/MyProblemCard.tsx (90%) rename src/components/{ => Cards/ProblemCards}/MyProblemMiniCard.tsx (89%) rename src/components/{ => Cards/ProblemCards}/PublicProblemCard.tsx (86%) rename src/components/{ => Cards/ProblemCards}/PublicProblemMiniCard.tsx (83%) rename src/components/{ => Cards}/SubmissionCard.tsx (83%) diff --git a/src/components/AddProblemDialog.tsx b/src/components/AddProblemDialog.tsx deleted file mode 100644 index a4b8711..0000000 --- a/src/components/AddProblemDialog.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Dialog, DialogTrigger, DialogContent } from "./shadcn/Dialog"; -import { Input } from "./shadcn/Input"; -import MyProblemCard from "./MyProblemCard"; -import { - ProblemModel, - ProblemSecureModel, -} from "../types/models/Problem.model"; -import { ProblemService } from "../services/Problem.service"; - -const AddProblemDialog = ({ children }: { children: React.ReactNode }) => { - const [problems, setProblems] = useState< - ProblemSecureModel[] | ProblemModel[] - >([]); - - useEffect(() => { - ProblemService.getAllAsCreator(4).then((response) => { - setProblems(response.data.problems); - }); - }); - - return ( - - {children} - -

Add Problem

- - -
- {problems.map((problem) => ( - - ))} -
-
-
- ); -}; - -export default AddProblemDialog; diff --git a/src/components/AccountCheckboxCard.tsx b/src/components/Cards/AccountCards/AccountCheckboxCard.tsx similarity index 80% rename from src/components/AccountCheckboxCard.tsx rename to src/components/Cards/AccountCards/AccountCheckboxCard.tsx index 4fb7237..b3a6ecd 100644 --- a/src/components/AccountCheckboxCard.tsx +++ b/src/components/Cards/AccountCards/AccountCheckboxCard.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { Card } from "./shadcn/Card"; -import { Checkbox } from "./shadcn/Checkbox"; +import { Card } from "../../shadcn/Card"; +import { Checkbox } from "../../shadcn/Checkbox"; import { useState } from "react"; -import { AccountSecureModel } from "../types/models/Account.model"; +import { AccountSecureModel } from "../../../types/models/Account.model"; export type AccountCheckboxCardOnClickCallback = { checked: boolean; diff --git a/src/components/AccountMiniCard.tsx b/src/components/Cards/AccountCards/AccountMiniCard.tsx similarity index 78% rename from src/components/AccountMiniCard.tsx rename to src/components/Cards/AccountCards/AccountMiniCard.tsx index 8288edf..2e41167 100644 --- a/src/components/AccountMiniCard.tsx +++ b/src/components/Cards/AccountCards/AccountMiniCard.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; -import { Card, CardContent, CardTitle } from "./shadcn/Card"; -import { Button } from "./shadcn/Button"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import { Button } from "../../shadcn/Button"; import { Check, CheckCircle2, @@ -19,19 +19,19 @@ import { ProblemPopulateTestcases, ProblemSecureModel, TestcaseModel, -} from "../types/models/Problem.model"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; +} from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, -} from "./shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "./DeleteProblemConfirmationDialog"; -import Checkmark from "./Checkmark"; -import { Tooltip, TooltipContent, TooltipTrigger } from "./shadcn/Tooltip"; -import { CollectionModel, CollectionPopulateProblemSecureModel } from "../types/models/Collection.model"; -import { AccountModel, AccountSecureModel } from "../types/models/Account.model"; +} from "../../shadcn/ContextMenu"; +import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import Checkmark from "../../Checkmark"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; +import { CollectionModel, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; +import { AccountModel, AccountSecureModel } from "../../../types/models/Account.model"; const AccountMiniCard = ({ // problem, diff --git a/src/components/MyCollectionCard.tsx b/src/components/Cards/CollectionCards/MyCollectionCard.tsx similarity index 86% rename from src/components/MyCollectionCard.tsx rename to src/components/Cards/CollectionCards/MyCollectionCard.tsx index c5ecc06..c630191 100644 --- a/src/components/MyCollectionCard.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionCard.tsx @@ -1,12 +1,12 @@ import React, { useState } from "react"; -import { Card, CardContent, CardTitle } from "./shadcn/Card"; -import { Button } from "./shadcn/Button"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import { Button } from "../../shadcn/Button"; import { Check, CheckCircle2, FileSpreadsheet, Folder, X } from "lucide-react"; import { useNavigate } from "react-router-dom"; -import { ProblemModel } from "../types/models/Problem.model"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; -import Checkmark from "./Checkmark"; -import { CollectionProblemModel, CollectionProblemPopulateProblemSecureModel, GetCollectionByAccountResponse } from "../types/models/Collection.model"; +import { ProblemModel } from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; +import Checkmark from "../../Checkmark"; +import { CollectionProblemModel, CollectionProblemPopulateProblemSecureModel, GetCollectionByAccountResponse } from "../../../types/models/Collection.model"; const MyCollectionCard = ({ collection diff --git a/src/components/MyCollectionMiniCard.tsx b/src/components/Cards/CollectionCards/MyCollectionMiniCard.tsx similarity index 87% rename from src/components/MyCollectionMiniCard.tsx rename to src/components/Cards/CollectionCards/MyCollectionMiniCard.tsx index 77a29e3..f064771 100644 --- a/src/components/MyCollectionMiniCard.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionMiniCard.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; -import { Card, CardContent, CardTitle } from "./shadcn/Card"; -import { Button } from "./shadcn/Button"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import { Button } from "../../shadcn/Button"; import { Check, CheckCircle2, @@ -17,18 +17,18 @@ import { ProblemPopulateTestcases, ProblemSecureModel, TestcaseModel, -} from "../types/models/Problem.model"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; +} from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, -} from "./shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "./DeleteProblemConfirmationDialog"; -import Checkmark from "./Checkmark"; -import { Tooltip, TooltipContent, TooltipTrigger } from "./shadcn/Tooltip"; -import { CollectionModel, CollectionPopulateProblemSecureModel } from "../types/models/Collection.model"; +} from "../../shadcn/ContextMenu"; +import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import Checkmark from "../../Checkmark"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; +import { CollectionModel, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; const checkRuntimeStatus = (testcases: TestcaseModel[]) => { for (const testcase of testcases) { diff --git a/src/components/TopicCollectionAccordionCard.tsx b/src/components/Cards/CollectionCards/TopicCollectionAccordionCard.tsx similarity index 83% rename from src/components/TopicCollectionAccordionCard.tsx rename to src/components/Cards/CollectionCards/TopicCollectionAccordionCard.tsx index 0b77310..a2a35bc 100644 --- a/src/components/TopicCollectionAccordionCard.tsx +++ b/src/components/Cards/CollectionCards/TopicCollectionAccordionCard.tsx @@ -1,19 +1,19 @@ import React from "react"; -import { Card } from "./shadcn/Card"; +import { Card } from "../../shadcn/Card"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, -} from "./shadcn/Accordion"; +} from "../../shadcn/Accordion"; import { Check, FileCheck, FileSpreadsheet, Folder } from "lucide-react"; -import PublicProblemCard from "./PublicProblemCard"; -import { CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Collection.model"; -import CardContainer from "./CardContainer"; -import { ScrollArea } from "./shadcn/ScrollArea"; -import PublicProblemMiniCard from "./PublicProblemMiniCard"; -import ReadOnlyPlate from "./ReadOnlyPlate"; -import { Badge } from "./shadcn/Badge"; +import PublicProblemCard from "../ProblemCards/PublicProblemCard"; +import { CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../../../types/models/Collection.model"; +import CardContainer from "../../CardContainer"; +import { ScrollArea } from "../../shadcn/ScrollArea"; +import PublicProblemMiniCard from "../ProblemCards/PublicProblemMiniCard"; +import ReadOnlyPlate from "../../ReadOnlyPlate"; +import { Badge } from "../../shadcn/Badge"; const isPassed = (collection: CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel):boolean => { return collection.problems.filter( diff --git a/src/components/MyCourseCard.tsx b/src/components/Cards/CourseCards/MyCourseCard.tsx similarity index 89% rename from src/components/MyCourseCard.tsx rename to src/components/Cards/CourseCards/MyCourseCard.tsx index fe5289d..b3c5c90 100644 --- a/src/components/MyCourseCard.tsx +++ b/src/components/Cards/CourseCards/MyCourseCard.tsx @@ -1,9 +1,9 @@ import React, { useState } from "react"; -import { Card, CardContent } from "./shadcn/Card"; +import { Card, CardContent } from "../../shadcn/Card"; import { Folder, LibraryBig } from "lucide-react"; -import { TopicPopulateTopicCollectionPopulateCollectionModel } from "../types/models/Topic.model"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; -import { BASE_URL } from "../constants/BackendBaseURL"; +import { TopicPopulateTopicCollectionPopulateCollectionModel } from "../../../types/models/Topic.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; +import { BASE_URL } from "../../../constants/BackendBaseURL"; import { useNavigate } from "react-router-dom"; const MyCourseCard = ({ diff --git a/src/components/PublicCourseCard.tsx b/src/components/Cards/CourseCards/PublicCourseCard.tsx similarity index 81% rename from src/components/PublicCourseCard.tsx rename to src/components/Cards/CourseCards/PublicCourseCard.tsx index b24cc49..8d88fc1 100644 --- a/src/components/PublicCourseCard.tsx +++ b/src/components/Cards/CourseCards/PublicCourseCard.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { Card } from "./shadcn/Card"; +import { Card } from "../../shadcn/Card"; import { LibraryBig, StepForward } from "lucide-react"; -import { Button } from "./shadcn/Button"; -import { TopicModel } from "../types/models/Topic.model"; +import { Button } from "../../shadcn/Button"; +import { TopicModel } from "../../../types/models/Topic.model"; import { useNavigate } from "react-router-dom"; const PublicCourseCard = ({ course }: { course: TopicModel }) => { diff --git a/src/components/MyGroupCard.tsx b/src/components/Cards/MyGroupCard.tsx similarity index 91% rename from src/components/MyGroupCard.tsx rename to src/components/Cards/MyGroupCard.tsx index 3ce4f49..8cee522 100644 --- a/src/components/MyGroupCard.tsx +++ b/src/components/Cards/MyGroupCard.tsx @@ -1,9 +1,9 @@ import React, { useState } from "react"; -import { Card, CardContent } from "./shadcn/Card"; +import { Card, CardContent } from "../shadcn/Card"; import { useNavigate } from "react-router-dom"; import { User, Users } from "lucide-react"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; -import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../types/models/Group.model"; +import { readableDateFormat } from "../../utilities/ReadableDateFormat"; +import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../../types/models/Group.model"; const MyGroupCard = ({ group diff --git a/src/components/MyProblemCard.tsx b/src/components/Cards/ProblemCards/MyProblemCard.tsx similarity index 90% rename from src/components/MyProblemCard.tsx rename to src/components/Cards/ProblemCards/MyProblemCard.tsx index b4492e0..c600ba4 100644 --- a/src/components/MyProblemCard.tsx +++ b/src/components/Cards/ProblemCards/MyProblemCard.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import { Card, CardContent, CardTitle } from "./shadcn/Card"; -import { Button } from "./shadcn/Button"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import { Button } from "../../shadcn/Button"; import { Check, CheckCircle2, @@ -16,16 +16,16 @@ import { ProblemPopulateTestcases, ProblemSecureModel, TestcaseModel, -} from "../types/models/Problem.model"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; +} from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, -} from "./shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "./DeleteProblemConfirmationDialog"; -import Checkmark from "./Checkmark"; +} from "../../shadcn/ContextMenu"; +import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import Checkmark from "../../Checkmark"; const checkRuntimeStatus = (testcases: TestcaseModel[]) => { for (const testcase of testcases) { diff --git a/src/components/MyProblemMiniCard.tsx b/src/components/Cards/ProblemCards/MyProblemMiniCard.tsx similarity index 89% rename from src/components/MyProblemMiniCard.tsx rename to src/components/Cards/ProblemCards/MyProblemMiniCard.tsx index cbebd5d..3bd8579 100644 --- a/src/components/MyProblemMiniCard.tsx +++ b/src/components/Cards/ProblemCards/MyProblemMiniCard.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; -import { Card, CardContent, CardTitle } from "./shadcn/Card"; -import { Button } from "./shadcn/Button"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import { Button } from "../../shadcn/Button"; import { Check, CheckCircle2, @@ -16,17 +16,17 @@ import { ProblemPopulateTestcases, ProblemSecureModel, TestcaseModel, -} from "../types/models/Problem.model"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; +} from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, -} from "./shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "./DeleteProblemConfirmationDialog"; -import Checkmark from "./Checkmark"; -import { Tooltip, TooltipContent, TooltipTrigger } from "./shadcn/Tooltip"; +} from "../../shadcn/ContextMenu"; +import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import Checkmark from "../../Checkmark"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; const checkRuntimeStatus = (testcases: TestcaseModel[]) => { for (const testcase of testcases) { diff --git a/src/components/PublicProblemCard.tsx b/src/components/Cards/ProblemCards/PublicProblemCard.tsx similarity index 86% rename from src/components/PublicProblemCard.tsx rename to src/components/Cards/ProblemCards/PublicProblemCard.tsx index bb3e807..b5e54d0 100644 --- a/src/components/PublicProblemCard.tsx +++ b/src/components/Cards/ProblemCards/PublicProblemCard.tsx @@ -1,10 +1,10 @@ import React, { useState } from "react"; -import { Card, CardContent, CardTitle } from "./shadcn/Card"; -import TestcasesGradingIndicator from "./TestcasesGradingIndicator"; -import { ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Problem.model"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; -import { Button } from "./shadcn/Button"; -import { Label } from "./shadcn/Label"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import TestcasesGradingIndicator from "../../TestcasesGradingIndicator"; +import { ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; +import { Button } from "../../shadcn/Button"; +import { Label } from "../../shadcn/Label"; import { useNavigate } from "react-router-dom"; import { FileSpreadsheet, Puzzle } from "lucide-react"; diff --git a/src/components/PublicProblemMiniCard.tsx b/src/components/Cards/ProblemCards/PublicProblemMiniCard.tsx similarity index 83% rename from src/components/PublicProblemMiniCard.tsx rename to src/components/Cards/ProblemCards/PublicProblemMiniCard.tsx index 87d46ee..5dc2d8c 100644 --- a/src/components/PublicProblemMiniCard.tsx +++ b/src/components/Cards/ProblemCards/PublicProblemMiniCard.tsx @@ -1,10 +1,10 @@ import React from "react"; -import { Card } from "./shadcn/Card"; -import { ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Problem.model"; +import { Card } from "../../shadcn/Card"; +import { ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../../../types/models/Problem.model"; import { FileSpreadsheet, Puzzle } from "lucide-react"; -import TestcasesGradingIndicator from "./TestcasesGradingIndicator"; -import { Button } from "./shadcn/Button"; -import TestcasesGradingMiniIndicator from "./TestcasesGradingMiniIndicator"; +import TestcasesGradingIndicator from "../../TestcasesGradingIndicator"; +import { Button } from "../../shadcn/Button"; +import TestcasesGradingMiniIndicator from "../../TestcasesGradingMiniIndicator"; import { useNavigate } from "react-router-dom"; const PublicProblemMiniCard = ({ diff --git a/src/components/SubmissionCard.tsx b/src/components/Cards/SubmissionCard.tsx similarity index 83% rename from src/components/SubmissionCard.tsx rename to src/components/Cards/SubmissionCard.tsx index 05c1281..751232d 100644 --- a/src/components/SubmissionCard.tsx +++ b/src/components/Cards/SubmissionCard.tsx @@ -1,10 +1,10 @@ import React from "react"; -import { Card } from "./shadcn/Card"; +import { Card } from "../shadcn/Card"; import { FileSpreadsheet, Puzzle, StepForward } from "lucide-react"; -import { Button } from "./shadcn/Button"; -import { SubmissionPopulateSubmissionTestcaseAndProblemSecureModel } from "../types/models/Submission.model"; -import TestcasesGradingIndicator from "./TestcasesGradingIndicator"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; +import { Button } from "../shadcn/Button"; +import { SubmissionPopulateSubmissionTestcaseAndProblemSecureModel } from "../../types/models/Submission.model"; +import TestcasesGradingIndicator from "../TestcasesGradingIndicator"; +import { readableDateFormat } from "../../utilities/ReadableDateFormat"; import { useNavigate } from "react-router-dom"; const SubmissionCard = ({ diff --git a/src/components/Forms/CreateCollectionForm/ManageProblems.tsx b/src/components/Forms/CreateCollectionForm/ManageProblems.tsx index 2e26597..37b6203 100644 --- a/src/components/Forms/CreateCollectionForm/ManageProblems.tsx +++ b/src/components/Forms/CreateCollectionForm/ManageProblems.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; import { ReactSortable } from "react-sortablejs"; import { Button } from "../../shadcn/Button"; -import AddProblemDialog from "../../AddProblemDialog"; import { Separator } from "../../shadcn/Seperator"; import { Input } from "../../shadcn/Input"; import { ProblemService } from "../../../services/Problem.service"; @@ -12,10 +11,10 @@ import { ProblemSecureModel, } from "../../../types/models/Problem.model"; import { ItemInterface } from "./../../../../node_modules/react-sortablejs/dist/index.d"; -import MyProblemCard from "../../MyProblemCard"; +import MyProblemCard from "../../Cards/ProblemCards/MyProblemCard"; import CardContainer from "../../CardContainer"; import SortableCardContainer from "../../SortableCardContainer"; -import MyProblemMiniCard from "../../MyProblemMiniCard"; +import MyProblemMiniCard from "../../Cards/ProblemCards/MyProblemMiniCard"; import { ScrollArea } from "../../shadcn/ScrollArea"; import { Item } from "@radix-ui/react-context-menu"; import { transformProblemModel2ProblemHashedTable } from "../../../types/adapters/Problem.adapter"; diff --git a/src/components/Forms/CreateCourseForm/ManageCollections.tsx b/src/components/Forms/CreateCourseForm/ManageCollections.tsx index b8296b6..691a7c1 100644 --- a/src/components/Forms/CreateCourseForm/ManageCollections.tsx +++ b/src/components/Forms/CreateCourseForm/ManageCollections.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; import { ReactSortable } from "react-sortablejs"; import { Button } from "../../shadcn/Button"; -import AddProblemDialog from "../../AddProblemDialog"; import { Separator } from "../../shadcn/Seperator"; import { Input } from "../../shadcn/Input"; import { ProblemService } from "../../../services/Problem.service"; @@ -12,15 +11,15 @@ import { ProblemSecureModel, } from "../../../types/models/Problem.model"; import { ItemInterface } from "./../../../../node_modules/react-sortablejs/dist/index.d"; -import MyProblemCard from "../../MyProblemCard"; +import MyProblemCard from "../../Cards/ProblemCards/MyProblemCard"; import CardContainer from "../../CardContainer"; import SortableCardContainer from "../../SortableCardContainer"; -import MyProblemMiniCard from "../../MyProblemMiniCard"; +import MyProblemMiniCard from "../../Cards/ProblemCards/MyProblemMiniCard"; import { ScrollArea } from "../../shadcn/ScrollArea"; import { Item } from "@radix-ui/react-context-menu"; import { transformProblemModel2ProblemHashedTable } from "../../../types/adapters/Problem.adapter"; import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; -import MyCollectionMiniCard from "../../MyCollectionMiniCard"; +import MyCollectionMiniCard from "../../Cards/CollectionCards/MyCollectionMiniCard"; import { CollectionService } from "../../../services/Collection.service"; import { transformCollectionModel2CollectionHashedTable } from "../../../types/adapters/Collection.adapter"; import { CollectionHashedTable, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; diff --git a/src/components/Forms/CreateGroupForm/ManageMembers.tsx b/src/components/Forms/CreateGroupForm/ManageMembers.tsx index 74e86d6..b9d081d 100644 --- a/src/components/Forms/CreateGroupForm/ManageMembers.tsx +++ b/src/components/Forms/CreateGroupForm/ManageMembers.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; import { ReactSortable } from "react-sortablejs"; import { Button } from "../../shadcn/Button"; -import AddProblemDialog from "../../AddProblemDialog"; import { Separator } from "../../shadcn/Seperator"; import { Input } from "../../shadcn/Input"; import { ProblemService } from "../../../services/Problem.service"; @@ -12,28 +11,28 @@ import { ProblemSecureModel, } from "../../../types/models/Problem.model"; import { ItemInterface } from "./../../../../node_modules/react-sortablejs/dist/index.d"; -import MyProblemCard from "../../MyProblemCard"; +import MyProblemCard from "../../Cards/ProblemCards/MyProblemCard"; import CardContainer from "../../CardContainer"; import SortableCardContainer from "../../SortableCardContainer"; -import MyProblemMiniCard from "../../MyProblemMiniCard"; +import MyProblemMiniCard from "../../Cards/ProblemCards/MyProblemMiniCard"; import { ScrollArea } from "../../shadcn/ScrollArea"; import { Item } from "@radix-ui/react-context-menu"; import { transformProblemModel2ProblemHashedTable } from "../../../types/adapters/Problem.adapter"; import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestForm"; -import MyCollectionMiniCard from "../../MyCollectionMiniCard"; +import MyCollectionMiniCard from "../../Cards/CollectionCards/MyCollectionMiniCard"; import { CollectionService } from "../../../services/Collection.service"; import { transformCollectionModel2CollectionHashedTable } from "../../../types/adapters/Collection.adapter"; import { CollectionHashedTable, CollectionPopulateProblemSecureModel, } from "../../../types/models/Collection.model"; -import AccountCheckboxCard from "../../AccountCheckboxCard"; +import AccountCheckboxCard from "../../Cards/AccountCards/AccountCheckboxCard"; import { AccountHashedTable, AccountSecureModel } from "../../../types/models/Account.model"; import { GroupHashedTable } from "../../../types/models/Group.model"; import { GroupService } from "../../../services/Group.service"; import { AccountService } from "../../../services/Account.service"; import { transformAccountModels2AccountHashedTable } from "../../../types/adapters/Account.adapter"; -import AccountMiniCard from "../../AccountMiniCard"; +import AccountMiniCard from "../../Cards/AccountCards/AccountMiniCard"; const ManageMembers = ({ createRequest, diff --git a/src/components/TopicCollectionsAccordion.tsx b/src/components/TopicCollectionsAccordion.tsx index c2d0478..7b29bee 100644 --- a/src/components/TopicCollectionsAccordion.tsx +++ b/src/components/TopicCollectionsAccordion.tsx @@ -7,11 +7,11 @@ import { AccordionTrigger, } from "./shadcn/Accordion"; import { FileSpreadsheet, Folder } from "lucide-react"; -import PublicProblemCard from "./PublicProblemCard"; +import PublicProblemCard from "./Cards/ProblemCards/PublicProblemCard"; import { CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Collection.model"; import CardContainer from "./CardContainer"; import { ScrollArea } from "./shadcn/ScrollArea"; -import PublicProblemMiniCard from "./PublicProblemMiniCard"; +import PublicProblemMiniCard from "./Cards/ProblemCards/PublicProblemMiniCard"; import { TopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Topic.model"; const TopicCollectionsAccordian = ({ diff --git a/src/views/Dashboard.tsx b/src/views/Dashboard.tsx index a2cb3c8..fa95306 100644 --- a/src/views/Dashboard.tsx +++ b/src/views/Dashboard.tsx @@ -3,10 +3,10 @@ import NavbarSidebarLayout from "../layout/NavbarSidebarLayout"; import NavbarMenuLayout from "../layout/NavbarMenuLayout"; import { SubmissionService } from "../services/Submission.service"; import { SubmissionPopulateSubmissionTestcaseAndProblemSecureModel } from "../types/models/Submission.model"; -import SubmissionCard from "../components/SubmissionCard"; +import SubmissionCard from "../components/Cards/SubmissionCard"; import { TopicModel } from "../types/models/Topic.model"; import { TopicService } from "../services/Topic.service"; -import PublicCourseCard from "../components/PublicCourseCard"; +import PublicCourseCard from "../components/Cards/CourseCards/PublicCourseCard"; const Dashboard = () => { const accountId = String(localStorage.getItem("account_id")); diff --git a/src/views/ExploreProblems.tsx b/src/views/ExploreProblems.tsx index 5f74a2e..fc5e96a 100644 --- a/src/views/ExploreProblems.tsx +++ b/src/views/ExploreProblems.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import NavbarMenuLayout from "../layout/NavbarMenuLayout"; -import PublicProblemCard from "../components/PublicProblemCard"; +import PublicProblemCard from "../components/Cards/ProblemCards/PublicProblemCard"; import CardContainer from "../components/CardContainer"; import { ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Problem.model"; import { ProblemService } from "../services/Problem.service"; diff --git a/src/views/My/Collections/MyCollections.tsx b/src/views/My/Collections/MyCollections.tsx index 04fda75..0debbd2 100644 --- a/src/views/My/Collections/MyCollections.tsx +++ b/src/views/My/Collections/MyCollections.tsx @@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from "react"; import NavbarSidebarLayout from "../../../layout/NavbarSidebarLayout"; import { Button } from "../../../components/shadcn/Button"; import { Input } from "../../../components/shadcn/Input"; -import MyCollectionCard from "../../../components/MyCollectionCard"; +import MyCollectionCard from "../../../components/Cards/CollectionCards/MyCollectionCard"; import { useNavigate } from "react-router-dom"; import CardContainer from "../../../components/CardContainer"; import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; diff --git a/src/views/My/Courses/MyCourses.tsx b/src/views/My/Courses/MyCourses.tsx index b342415..ab9d5d6 100644 --- a/src/views/My/Courses/MyCourses.tsx +++ b/src/views/My/Courses/MyCourses.tsx @@ -4,7 +4,7 @@ import { Input } from "../../../components/shadcn/Input"; import { Button } from "../../../components/shadcn/Button"; import { useNavigate } from "react-router-dom"; import CardContainer from "../../../components/CardContainer"; -import MyCourseCard from "../../../components/MyCourseCard"; +import MyCourseCard from "../../../components/Cards/CourseCards/MyCourseCard"; import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; import { TopicService } from "../../../services/Topic.service"; import { TopicPopulateTopicCollectionPopulateCollectionModel } from "../../../types/models/Topic.model"; diff --git a/src/views/My/Groups/MyGroups.tsx b/src/views/My/Groups/MyGroups.tsx index dc21edf..f3b9c60 100644 --- a/src/views/My/Groups/MyGroups.tsx +++ b/src/views/My/Groups/MyGroups.tsx @@ -4,12 +4,12 @@ import { Input } from "../../../components/shadcn/Input"; import { Button } from "../../../components/shadcn/Button"; import { useNavigate } from "react-router-dom"; import CardContainer from "../../../components/CardContainer"; -import MyCourseCard from "../../../components/MyCourseCard"; +import MyCourseCard from "../../../components/Cards/CourseCards/MyCourseCard"; import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; import { TopicService } from "../../../services/Topic.service"; import { TopicPopulateTopicCollectionPopulateCollectionModel } from "../../../types/models/Topic.model"; import { LibraryBig } from "lucide-react"; -import MyGroupCard from "../../../components/MyGroupCard"; +import MyGroupCard from "../../../components/Cards/MyGroupCard"; import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../../../types/models/Group.model"; import { GroupService } from "../../../services/Group.service"; diff --git a/src/views/My/Problems/MyProblems.tsx b/src/views/My/Problems/MyProblems.tsx index d7e7cf6..76c5bbf 100644 --- a/src/views/My/Problems/MyProblems.tsx +++ b/src/views/My/Problems/MyProblems.tsx @@ -3,7 +3,7 @@ import NavbarSidebarLayout from "../../../layout/NavbarSidebarLayout"; import { Input } from "../../../components/shadcn/Input"; import { Button } from "../../../components/shadcn/Button"; import { Card, CardContent, CardTitle } from "../../../components/shadcn/Card"; -import MyProblemCard from "../../../components/MyProblemCard"; +import MyProblemCard from "../../../components/Cards/ProblemCards/MyProblemCard"; import { useNavigate } from "react-router-dom"; import { ProblemService } from "../../../services/Problem.service"; import { ProblemModel, ProblemPopulateTestcases } from "../../../types/models/Problem.model"; diff --git a/src/views/ViewCourse.tsx b/src/views/ViewCourse.tsx index 3e7bb74..66ca152 100644 --- a/src/views/ViewCourse.tsx +++ b/src/views/ViewCourse.tsx @@ -19,7 +19,7 @@ import { } from "../components/shadcn/Accordion"; import { LibraryBig } from "lucide-react"; import { Card } from "../components/shadcn/Card"; -import TopicCollectionAccordionCard from "../components/TopicCollectionAccordionCard"; +import TopicCollectionAccordionCard from "../components/Cards/CollectionCards/TopicCollectionAccordionCard"; import CardContainer from "../components/CardContainer"; import { ScrollArea } from "../components/shadcn/ScrollArea"; import TopicCollectionsAccordion from "../components/TopicCollectionsAccordion"; From fc414d59dd886c508b6cf72181cbac788230718c Mon Sep 17 00:00:00 2001 From: KanonKC Date: Mon, 1 Jan 2024 17:12:48 +0700 Subject: [PATCH 04/24] permission cards --- .../CreateGroupForm/ManagePermissions.tsx | 45 +++++++++++++++++++ .../Forms/CreateGroupForm/index.tsx | 13 ++++++ .../PermissionSwitch/PermissionSwitch.tsx | 17 +++++++ .../PermissionSwitchDescription.tsx | 11 +++++ .../PermissionSwitchTitle.tsx | 17 +++++++ src/constants/BackendBaseURL.ts | 2 +- 6 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/components/Forms/CreateGroupForm/ManagePermissions.tsx create mode 100644 src/components/PermissionSwitch/PermissionSwitch.tsx create mode 100644 src/components/PermissionSwitch/PermissionSwitchDescription.tsx create mode 100644 src/components/PermissionSwitch/PermissionSwitchTitle.tsx diff --git a/src/components/Forms/CreateGroupForm/ManagePermissions.tsx b/src/components/Forms/CreateGroupForm/ManagePermissions.tsx new file mode 100644 index 0000000..e9cb28f --- /dev/null +++ b/src/components/Forms/CreateGroupForm/ManagePermissions.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestForm"; +import { Separator } from "../../shadcn/Seperator"; +import { ScrollArea } from "../../shadcn/ScrollArea"; +import { Switch } from "../../shadcn/Switch"; +import PermissionSwitch from "../../PermissionSwitch/PermissionSwitch"; +import PermissionSwitchTitle from "../../PermissionSwitch/PermissionSwitchTitle"; +import PermissionSwitchDescription from "../../PermissionSwitch/PermissionSwitchDescription"; + +const ManagePermissions = ({ + createRequest, + setCreateRequest, +}: { + createRequest: CreateGroupRequestForm; + setCreateRequest: React.Dispatch< + React.SetStateAction + >; +}) => { + return ( +
+

Manage Permissions

+ +
+ + {/*

Course Permissions

*/} + + Course Permissions + + Can edit course name and description. + + + + + Manage Course Members + + Can manage course members. + + +
+
+
+ ); +}; + +export default ManagePermissions; diff --git a/src/components/Forms/CreateGroupForm/index.tsx b/src/components/Forms/CreateGroupForm/index.tsx index 2121558..6356038 100644 --- a/src/components/Forms/CreateGroupForm/index.tsx +++ b/src/components/Forms/CreateGroupForm/index.tsx @@ -18,6 +18,7 @@ import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollecti import FormSaveButton from "../FormSaveButton"; import GeneralDetail from "./GeneralDetail"; import ManageMembers from "./ManageMembers"; +import ManagePermissions from "./ManagePermissions"; const TabList = [ { @@ -28,6 +29,10 @@ const TabList = [ value: "members", label: "Manage Members", }, + { + value: "permissions", + label: "Permissions", + }, ]; export type OnGroupSavedCallback = { @@ -116,6 +121,14 @@ const CreateGroupForm = ({ setCreateRequest={setCreateRequest} /> )} + {currentForm === "permissions" && ( + + )} + + ); diff --git a/src/components/PermissionSwitch/PermissionSwitch.tsx b/src/components/PermissionSwitch/PermissionSwitch.tsx new file mode 100644 index 0000000..50902c1 --- /dev/null +++ b/src/components/PermissionSwitch/PermissionSwitch.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import { Separator } from '../shadcn/Seperator' + +const PermissionSwitch = ({ + children + }: { + children?: React.ReactNode +}) => { + return ( +
+ {children} + +
+ ) +} + +export default PermissionSwitch \ No newline at end of file diff --git a/src/components/PermissionSwitch/PermissionSwitchDescription.tsx b/src/components/PermissionSwitch/PermissionSwitchDescription.tsx new file mode 100644 index 0000000..cf0b9da --- /dev/null +++ b/src/components/PermissionSwitch/PermissionSwitchDescription.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +const PermissionSwitchDescription = ({ + children, +}: { + children?: React.ReactNode; +}) => { + return

{children}

; +}; + +export default PermissionSwitchDescription; diff --git a/src/components/PermissionSwitch/PermissionSwitchTitle.tsx b/src/components/PermissionSwitch/PermissionSwitchTitle.tsx new file mode 100644 index 0000000..020106d --- /dev/null +++ b/src/components/PermissionSwitch/PermissionSwitchTitle.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { Switch } from "../shadcn/Switch"; + +const PermissionSwitchTitle = ({ + children, +}: { + children?: React.ReactNode; +}) => { + return ( +
+

{children}

+ +
+ ); +}; + +export default PermissionSwitchTitle; diff --git a/src/constants/BackendBaseURL.ts b/src/constants/BackendBaseURL.ts index d6b4f29..e99071d 100644 --- a/src/constants/BackendBaseURL.ts +++ b/src/constants/BackendBaseURL.ts @@ -1 +1 @@ -export const BASE_URL = 'http://192.168.0.11:8000'; \ No newline at end of file +export const BASE_URL = 'http://localhost:8000'; \ No newline at end of file From 45636b0fbca3d4e471450e5333d31991def26a42 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Wed, 3 Jan 2024 15:27:47 +0700 Subject: [PATCH 05/24] GroupPermission on Course --- .../Forms/CreateCourseForm/ManageGroups.tsx | 22 ++ .../Forms/CreateCourseForm/index.tsx | 14 +- .../CreateGroupForm/ManagePermissions.tsx | 69 ++++-- .../Forms/CreateGroupForm/index.tsx | 1 + .../Forms/GroupAndPermissionManager.tsx | 229 ++++++++++++++++++ .../CollectionPermissionSwitchGroup.tsx | 45 ++++ .../CoursePermissionSwitchGroup.tsx | 49 ++++ .../ProblemPermissionSwitchGroup.tsx | 43 ++++ src/components/GroupCheckbox.tsx | 34 +++ .../PermissionSwitch/PermissionSwitch.tsx | 17 -- .../PermissionSwitchDescription.tsx | 11 - .../PermissionSwitchTitle.tsx | 17 -- .../Permissions/PermissionSwitch.tsx | 30 +++ .../PermissionSwitchScrollArea.tsx | 20 ++ src/services/Topic.service.ts | 11 +- .../CreateCourseRequestForm.adapter.ts | 33 ++- .../CreateGroupRequestForm.adapter.ts | 9 +- src/types/adapters/Group.adapter.ts | 26 ++ src/types/adapters/Topic.adapter.ts | 22 ++ src/types/apis/Group.api.ts | 7 + src/types/apis/Topic.api.ts | 12 +- src/types/forms/CreateCourseRequestForm.ts | 10 +- src/types/forms/CreateGroupRequestForm.ts | 18 +- src/types/models/Group.model.ts | 20 ++ src/types/models/Topic.model.ts | 6 + src/views/My/Courses/CreateCourse.tsx | 16 +- src/views/My/Courses/EditCourse.tsx | 47 ++-- src/views/My/Groups/CreateGroup.tsx | 8 + src/views/My/Groups/EditGroup.tsx | 13 +- 29 files changed, 731 insertions(+), 128 deletions(-) create mode 100644 src/components/Forms/CreateCourseForm/ManageGroups.tsx create mode 100644 src/components/Forms/GroupAndPermissionManager.tsx create mode 100644 src/components/Forms/PermissionSwitchGroups/CollectionPermissionSwitchGroup.tsx create mode 100644 src/components/Forms/PermissionSwitchGroups/CoursePermissionSwitchGroup.tsx create mode 100644 src/components/Forms/PermissionSwitchGroups/ProblemPermissionSwitchGroup.tsx create mode 100644 src/components/GroupCheckbox.tsx delete mode 100644 src/components/PermissionSwitch/PermissionSwitch.tsx delete mode 100644 src/components/PermissionSwitch/PermissionSwitchDescription.tsx delete mode 100644 src/components/PermissionSwitch/PermissionSwitchTitle.tsx create mode 100644 src/components/Permissions/PermissionSwitch.tsx create mode 100644 src/components/Permissions/PermissionSwitchScrollArea.tsx create mode 100644 src/types/adapters/Group.adapter.ts create mode 100644 src/types/adapters/Topic.adapter.ts diff --git a/src/components/Forms/CreateCourseForm/ManageGroups.tsx b/src/components/Forms/CreateCourseForm/ManageGroups.tsx new file mode 100644 index 0000000..c0b9c73 --- /dev/null +++ b/src/components/Forms/CreateCourseForm/ManageGroups.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; +import GroupAndPermissionManager from "../GroupAndPermissionManager"; + +const ManageGroups = ({ + createRequest, + setCreateRequest, +}: { + createRequest: CreateCourseRequestForm; + setCreateRequest: React.Dispatch< + React.SetStateAction + >; +}) => { + return ( + + ); +}; + +export default ManageGroups; diff --git a/src/components/Forms/CreateCourseForm/index.tsx b/src/components/Forms/CreateCourseForm/index.tsx index 97f2d79..355801e 100644 --- a/src/components/Forms/CreateCourseForm/index.tsx +++ b/src/components/Forms/CreateCourseForm/index.tsx @@ -16,6 +16,7 @@ import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollecti import GeneralDetail from "./GeneralDetail"; import ManageCollections from "./ManageCollections"; import FormSaveButton from "../FormSaveButton"; +import ManageGroups from "./ManageGroups"; const TabList = [ { @@ -26,6 +27,10 @@ const TabList = [ value: "collections", label: "Manage Collections", }, + { + value: "groups", + label: "Manage Groups & Permissions", + }, ]; export type OnCourseSavedCallback = { @@ -33,7 +38,7 @@ export type OnCourseSavedCallback = { // courseId: string; // setCourseId: React.Dispatch>; createRequest: CreateCourseRequestForm; -} +}; const CreateCourseForm = ({ createRequestInitialValue, @@ -114,6 +119,13 @@ const CreateCourseForm = ({ setCreateRequest={setCreateRequest} /> )} + + {currentForm === "groups" && ( + + )} ); diff --git a/src/components/Forms/CreateGroupForm/ManagePermissions.tsx b/src/components/Forms/CreateGroupForm/ManagePermissions.tsx index e9cb28f..b10a97a 100644 --- a/src/components/Forms/CreateGroupForm/ManagePermissions.tsx +++ b/src/components/Forms/CreateGroupForm/ManagePermissions.tsx @@ -3,9 +3,11 @@ import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestF import { Separator } from "../../shadcn/Seperator"; import { ScrollArea } from "../../shadcn/ScrollArea"; import { Switch } from "../../shadcn/Switch"; -import PermissionSwitch from "../../PermissionSwitch/PermissionSwitch"; -import PermissionSwitchTitle from "../../PermissionSwitch/PermissionSwitchTitle"; -import PermissionSwitchDescription from "../../PermissionSwitch/PermissionSwitchDescription"; +import PermissionSwitch from "../../Permissions/PermissionSwitch"; +import PermissionSwitchScrollArea from "../../Permissions/PermissionSwitchScrollArea"; +import CoursePermissionSwitchGroup from "../PermissionSwitchGroups/CoursePermissionSwitchGroup"; +import CollectionPermissionSwitchGroup from "../PermissionSwitchGroups/CollectionPermissionSwitchGroup"; +import ProblemPermissionSwitchGroup from "../PermissionSwitchGroups/ProblemPermissionSwitchGroup"; const ManagePermissions = ({ createRequest, @@ -21,22 +23,51 @@ const ManagePermissions = ({

Manage Permissions

- - {/*

Course Permissions

*/} - - Course Permissions - - Can edit course name and description. - - - - - Manage Course Members - - Can manage course members. - - -
+ +

+ Course Permission +

+ + setCreateRequest({ + ...createRequest, + manageCourses: !createRequest.manageCourses, + }) + } + onClickViewCourseLogs={() => + setCreateRequest({ + ...createRequest, + viewCourseLogs: !createRequest.viewCourseLogs, + }) + } + onClickViewCourses={() => + setCreateRequest({ + ...createRequest, + viewCourses: !createRequest.viewCourses, + }) + } + /> +

+ Collection Permission +

+ +

+ Problem Permission +

+ +
); diff --git a/src/components/Forms/CreateGroupForm/index.tsx b/src/components/Forms/CreateGroupForm/index.tsx index 6356038..640aaf5 100644 --- a/src/components/Forms/CreateGroupForm/index.tsx +++ b/src/components/Forms/CreateGroupForm/index.tsx @@ -62,6 +62,7 @@ const CreateGroupForm = ({ ); const handleSave = () => { + console.log(createRequest) onCourseSave({ setLoading, createRequest, diff --git a/src/components/Forms/GroupAndPermissionManager.tsx b/src/components/Forms/GroupAndPermissionManager.tsx new file mode 100644 index 0000000..df8d668 --- /dev/null +++ b/src/components/Forms/GroupAndPermissionManager.tsx @@ -0,0 +1,229 @@ +import React, { useEffect, useState } from "react"; +import { Separator } from "../shadcn/Seperator"; +import { ScrollArea } from "../shadcn/ScrollArea"; +import PermissionSwitchScrollArea from "../Permissions/PermissionSwitchScrollArea"; +import CoursePermissionSwitchGroup from "./PermissionSwitchGroups/CoursePermissionSwitchGroup"; +import { + CourseGroupPermissionRequestForm, + CreateCourseRequestForm, +} from "../../types/forms/CreateCourseRequestForm"; +import { PlusCircle, Users } from "lucide-react"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../shadcn/Tooltip"; +import { Dialog, DialogContent } from "../shadcn/Dialog"; +import { Input } from "../shadcn/Input"; +import GroupCheckbox from "../GroupCheckbox"; +import { Button } from "../shadcn/Button"; +import { GroupService } from "../../services/Group.service"; +import { GroupModel } from "../../types/models/Group.model"; + +const GroupListItem = ({ + hexColor = "#000000", + name = "Group Name", + hightlighted = false, +}: { + hexColor?: string; + name?: string; + hightlighted?: boolean; +}) => { + const customStyle = () => { + let style = + "font-bold text-base flex items-center cursor-pointer py-1 px-3 rounded-md "; + if (hightlighted) { + style += " bg-green-100 text-white"; + } + + return style; + }; + + return ( +
+
+
{name}
+
+ ); +}; + +const GroupAndPermissionManager = ({ + createRequest, + setCreateRequest, +}: { + createRequest: CreateCourseRequestForm; + setCreateRequest: React.Dispatch< + React.SetStateAction + >; +}) => { + + const accountId = String(localStorage.getItem("account_id")) + + const [allGroups, setAllGroups] = useState([]); + const [openAddGroupsDialog, setOpenAddGroupsDialog] = useState(false); + const [selectedGroupIds, setSelectedGroupIds] = useState([]); + + const [groupPermission, setGroupPermission] = + useState(); + const [selectedIndex, setselectedIndex] = useState(-1); + + const handleSelectGroupCheckbox = (groupId: string) => { + if (selectedGroupIds.includes(groupId)) { + setSelectedGroupIds(selectedGroupIds.filter((g) => g !== groupId)); + } else { + setSelectedGroupIds([...selectedGroupIds, groupId]); + } + } + + const getNotInPermissionGroup = () => { + const inPermissiongroupIds = createRequest.groupPermissions.map((groupPermission) => groupPermission.group.group_id) + return allGroups.filter((group) => !inPermissiongroupIds.includes(group.group_id)) + } + + const handleAddGroups = () => { + const addGroups = allGroups.filter((group) => selectedGroupIds.includes(group.group_id)) + const newGroupPermissions = addGroups.map((group) => ({ + group_id: group.group_id, + group, + manageCourses: group.permission_manage_topics, + viewCourseLogs: group.permission_view_topics_log, + viewCourses: group.permission_view_topics, + })) + + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions, + ...newGroupPermissions, + ], + }) + + setSelectedGroupIds([]) + setOpenAddGroupsDialog(false) + } + + useEffect(() => { + setGroupPermission(createRequest?.groupPermissions[selectedIndex]); + }, [selectedIndex]); + + useEffect(() => { + if (groupPermission) { + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions.slice(0, selectedIndex), + groupPermission, + ...createRequest.groupPermissions.slice(selectedIndex + 1), + ], + }); + } + }, [groupPermission]); + + useEffect(() => { + GroupService.getAllAsCreator(accountId).then((response) => { + setAllGroups(response.data.groups); + }) + },[]) + + return ( +
+
+
+

+ + Groups +

+ + + setOpenAddGroupsDialog(true)} size={20} /> + + Add Group + +
+ +
+ {createRequest.groupPermissions.map( + (groupPermission, index) => ( +
setselectedIndex(index)}> + +
+ ) + )} +
+
+
+
+ +
+
+ + {groupPermission && selectedIndex >= 0 && ( + + setGroupPermission({ + ...groupPermission, + manageCourses: + !groupPermission.manageCourses, + }) + } + onClickViewCourseLogs={() => + setGroupPermission({ + ...groupPermission, + viewCourseLogs: + !groupPermission.viewCourseLogs, + }) + } + onClickViewCourses={() => + setGroupPermission({ + ...groupPermission, + viewCourses: !groupPermission.viewCourses, + }) + } + /> + )} + +
+ + + +

Add Groups

+ +
+ { + getNotInPermissionGroup().map((group) => ( + handleSelectGroupCheckbox(group.group_id)} + checked={selectedGroupIds.includes(group.group_id)} + group={group} + /> + )) + } +
+
+
+ + +
+
+
+
+ ); +}; + +export default GroupAndPermissionManager; diff --git a/src/components/Forms/PermissionSwitchGroups/CollectionPermissionSwitchGroup.tsx b/src/components/Forms/PermissionSwitchGroups/CollectionPermissionSwitchGroup.tsx new file mode 100644 index 0000000..d09e65c --- /dev/null +++ b/src/components/Forms/PermissionSwitchGroups/CollectionPermissionSwitchGroup.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import PermissionSwitch from "../../Permissions/PermissionSwitch"; +import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestForm"; +import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; + +const CollectionPermissionSwitchGroup = ({ + createRequest, + setCreateRequest, +}: { + createRequest: CreateGroupRequestForm; + setCreateRequest: React.Dispatch< + React.SetStateAction + >; +}) => { + return ( + <> + + setCreateRequest({ + ...createRequest, + manageCollections: !createRequest.manageCollections, + }) + } + /> + + setCreateRequest({ + ...createRequest, + viewCollections: !createRequest.viewCollections, + }) + } + /> + + ); +}; + +export default CollectionPermissionSwitchGroup; diff --git a/src/components/Forms/PermissionSwitchGroups/CoursePermissionSwitchGroup.tsx b/src/components/Forms/PermissionSwitchGroups/CoursePermissionSwitchGroup.tsx new file mode 100644 index 0000000..a073d6a --- /dev/null +++ b/src/components/Forms/PermissionSwitchGroups/CoursePermissionSwitchGroup.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import PermissionSwitch from "../../Permissions/PermissionSwitch"; +import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestForm"; +import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; +import { set } from 'react-hook-form'; + +const CoursePermissionSwitchGroup = ({ + manageCoursesChecked=false, + viewCourseLogsChecked=false, + viewCoursesChecked=false, + onClickManageCourses=()=>{}, + onClickViewCourseLogs=()=>{}, + onClickViewCourses=()=>{}, +}: { + manageCoursesChecked?: boolean; + viewCourseLogsChecked?: boolean; + viewCoursesChecked?: boolean; + onClickManageCourses?: () => void | undefined + onClickViewCourseLogs?: () => void | undefined + onClickViewCourses?: () => void | undefined +}) => { + return ( + <> + onClickManageCourses()} + /> + onClickViewCourseLogs()} + /> + onClickViewCourses()} + /> + + ); +}; + +export default CoursePermissionSwitchGroup; diff --git a/src/components/Forms/PermissionSwitchGroups/ProblemPermissionSwitchGroup.tsx b/src/components/Forms/PermissionSwitchGroups/ProblemPermissionSwitchGroup.tsx new file mode 100644 index 0000000..518ddcb --- /dev/null +++ b/src/components/Forms/PermissionSwitchGroups/ProblemPermissionSwitchGroup.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import PermissionSwitch from "../../Permissions/PermissionSwitch"; +import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestForm"; +import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; + +const ProblemPermissionSwitchGroup = ({ + createRequest, + setCreateRequest, +}: { + createRequest: CreateGroupRequestForm; + setCreateRequest: React.Dispatch< + React.SetStateAction + >; +}) => { + return ( + <> + + setCreateRequest({ + ...createRequest, + manageProblems: !createRequest.manageProblems, + }) + } + /> + + setCreateRequest({ + ...createRequest, + viewProblems: !createRequest.viewProblems, + }) + } + /> + + ); +}; + +export default ProblemPermissionSwitchGroup; diff --git a/src/components/GroupCheckbox.tsx b/src/components/GroupCheckbox.tsx new file mode 100644 index 0000000..41c7071 --- /dev/null +++ b/src/components/GroupCheckbox.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { Checkbox } from "./shadcn/Checkbox"; +import { GroupModel } from "../types/models/Group.model"; + +const GroupCheckbox = ({ + group, + checked=true, + onClick=()=>{} +}:{ + group: GroupModel + checked?:boolean + onClick?:()=>void +}) => { + + const customStyle = () => { + let style = "border rounded-md p-2 flex items-center justify-between cursor-pointer " + if (checked) { + style += "bg-green-100 text-green-800 border-green-600 " + } + return style + } + + return ( +
+
+
+

{group.name}

+
+ +
+ ); +}; + +export default GroupCheckbox; diff --git a/src/components/PermissionSwitch/PermissionSwitch.tsx b/src/components/PermissionSwitch/PermissionSwitch.tsx deleted file mode 100644 index 50902c1..0000000 --- a/src/components/PermissionSwitch/PermissionSwitch.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { Separator } from '../shadcn/Seperator' - -const PermissionSwitch = ({ - children - }: { - children?: React.ReactNode -}) => { - return ( -
- {children} - -
- ) -} - -export default PermissionSwitch \ No newline at end of file diff --git a/src/components/PermissionSwitch/PermissionSwitchDescription.tsx b/src/components/PermissionSwitch/PermissionSwitchDescription.tsx deleted file mode 100644 index cf0b9da..0000000 --- a/src/components/PermissionSwitch/PermissionSwitchDescription.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -const PermissionSwitchDescription = ({ - children, -}: { - children?: React.ReactNode; -}) => { - return

{children}

; -}; - -export default PermissionSwitchDescription; diff --git a/src/components/PermissionSwitch/PermissionSwitchTitle.tsx b/src/components/PermissionSwitch/PermissionSwitchTitle.tsx deleted file mode 100644 index 020106d..0000000 --- a/src/components/PermissionSwitch/PermissionSwitchTitle.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; -import { Switch } from "../shadcn/Switch"; - -const PermissionSwitchTitle = ({ - children, -}: { - children?: React.ReactNode; -}) => { - return ( -
-

{children}

- -
- ); -}; - -export default PermissionSwitchTitle; diff --git a/src/components/Permissions/PermissionSwitch.tsx b/src/components/Permissions/PermissionSwitch.tsx new file mode 100644 index 0000000..dcdf2c6 --- /dev/null +++ b/src/components/Permissions/PermissionSwitch.tsx @@ -0,0 +1,30 @@ +import React, { ReactNode } from "react"; +import { Separator } from "../shadcn/Seperator"; +import { Switch } from "../shadcn/Switch"; + +const PermissionSwitch = ({ + title = "Title", + description = "Description", + checked = false, + onClick = () => {}, +}: { + title?: ReactNode; + description?: ReactNode; + checked?: boolean; + onClick?: () => void; +}) => { + return ( +
+
+
+

{title}

+ +
+

{description}

+
+ +
+ ); +}; + +export default PermissionSwitch; diff --git a/src/components/Permissions/PermissionSwitchScrollArea.tsx b/src/components/Permissions/PermissionSwitchScrollArea.tsx new file mode 100644 index 0000000..a84f529 --- /dev/null +++ b/src/components/Permissions/PermissionSwitchScrollArea.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { ScrollArea } from "../shadcn/ScrollArea"; + +const PermissionSwitchScrollArea = ({ + children, + className = "", + childrenClassName = "", +}: { + children: React.ReactNode; + className?: string; + childrenClassName?: string; +}) => { + return ( + +
{children}
+
+ ); +}; + +export default PermissionSwitchScrollArea; diff --git a/src/services/Topic.service.ts b/src/services/Topic.service.ts index 51d5a4a..be2bb5b 100644 --- a/src/services/Topic.service.ts +++ b/src/services/Topic.service.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { GetAllTopicsByAccountResponse, TopicSerivceAPI } from "../types/apis/Topic.api"; import { BASE_URL } from "../constants/BackendBaseURL"; -import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Topic.model"; +import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Topic.model"; export const TopicService: TopicSerivceAPI = { create: async (accountId, request) => { @@ -10,7 +10,7 @@ export const TopicService: TopicSerivceAPI = { }, get: async (accountId,topicId) => { - const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}`); + const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}`); return response; }, @@ -39,5 +39,12 @@ export const TopicService: TopicSerivceAPI = { getPublicByAccount: async (accountId,topicId) => { const response = await axios.get(`${BASE_URL}/api/topics/${topicId}?account_id=${accountId}`); return response; + }, + + updateGroupPermissions: async (topicId, groups) => { + const response = await axios.put(`${BASE_URL}/api/topics/${topicId}/groups`, { + groups: groups + }); + return response; } } \ No newline at end of file diff --git a/src/types/adapters/CreateCourseRequestForm.adapter.ts b/src/types/adapters/CreateCourseRequestForm.adapter.ts index e3d49e4..fa4243b 100644 --- a/src/types/adapters/CreateCourseRequestForm.adapter.ts +++ b/src/types/adapters/CreateCourseRequestForm.adapter.ts @@ -1,11 +1,28 @@ +import { CourseGroupPermissionCreateRequest } from "../apis/Topic.api"; import { CreateCourseRequestForm } from "../forms/CreateCourseRequestForm"; -export function transformCreateCourseRequestForm2CreateTopicRequestFormData(createRequest: CreateCourseRequestForm): FormData { - const formData = new FormData(); - formData.append("name", createRequest.title); - formData.append("description", JSON.stringify(createRequest.description)); - // formData.append("image_url", createRequest.image); - formData.append("is_private", createRequest.isPrivate ? "true" : "false"); +export function transformCreateCourseRequestForm2CreateTopicRequest( + createRequest: CreateCourseRequestForm +): { + formData: FormData; + collectionIds: string[]; + groups: CourseGroupPermissionCreateRequest[]; +} { + const formData = new FormData(); + formData.append("name", createRequest.title); + formData.append("description", JSON.stringify(createRequest.description)); + // formData.append("image_url", createRequest.image); + formData.append("is_private", createRequest.isPrivate ? "true" : "false"); - return formData; -} \ No newline at end of file + const collectionIds: string[] = createRequest.collectionsInterface.map( + (collection) => collection.id as string + ); + const groups = createRequest.groupPermissions.map((groupPermission) => ({ + group_id: groupPermission.group_id, + permission_manage_topics: groupPermission.manageCourses, + permission_view_topics: groupPermission.viewCourses, + permission_view_topics_log: groupPermission.viewCourseLogs, + })); + + return { formData, collectionIds, groups }; +} diff --git a/src/types/adapters/CreateGroupRequestForm.adapter.ts b/src/types/adapters/CreateGroupRequestForm.adapter.ts index c643dc3..9af55b9 100644 --- a/src/types/adapters/CreateGroupRequestForm.adapter.ts +++ b/src/types/adapters/CreateGroupRequestForm.adapter.ts @@ -5,6 +5,13 @@ export function transformCreateGroupRequestForm2CreateGroupRequest(createRequest return { name: createRequest.name, description: createRequest.description, - color: createRequest.color + color: createRequest.color, + permission_manage_topics: createRequest.manageCourses, + permission_view_topics: createRequest.viewCourses, + permission_view_topics_log: createRequest.viewCourseLogs, + permission_manage_collections: createRequest.manageCollections, + permission_view_collections: createRequest.viewCollections, + permission_manage_problems: createRequest.manageProblems, + permission_view_problems: createRequest.viewProblems } } \ No newline at end of file diff --git a/src/types/adapters/Group.adapter.ts b/src/types/adapters/Group.adapter.ts new file mode 100644 index 0000000..c58647d --- /dev/null +++ b/src/types/adapters/Group.adapter.ts @@ -0,0 +1,26 @@ +import CreateGroupForm from "../../components/Forms/CreateGroupForm"; +import { CreateGroupRequestForm } from "../forms/CreateGroupRequestForm"; +import { GroupModel, GroupPopulateGroupMemberPopulateAccountSecureModel } from "../models/Group.model"; + +export function transformGroupPopulateGroupMemberPopulateAccountSecureModel2CreateGroupRequestForm(group: GroupPopulateGroupMemberPopulateAccountSecureModel): CreateGroupRequestForm { + return { + name: group.name, + description: group.description, + color: group.color, + membersInterface: group.members.map(member => { + return { + id: member.account.account_id, + name: member.account.username, + group: member.group, + created_date: member.created_date + } + }), + manageCourses: group.permission_manage_topics, + viewCourseLogs: group.permission_view_topics_log, + viewCourses: group.permission_view_topics, + manageCollections: group.permission_manage_collections, + viewCollections: group.permission_view_collections, + manageProblems: group.permission_manage_problems, + viewProblems: group.permission_view_problems + } +} \ No newline at end of file diff --git a/src/types/adapters/Topic.adapter.ts b/src/types/adapters/Topic.adapter.ts new file mode 100644 index 0000000..d99b977 --- /dev/null +++ b/src/types/adapters/Topic.adapter.ts @@ -0,0 +1,22 @@ +import { CreateCourseRequestForm } from "../forms/CreateCourseRequestForm"; +import { TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; + +export function transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest(topic:TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel): CreateCourseRequestForm { + return { + title: topic.name, + description: JSON.parse(String(topic.description)), + image: topic.image_url, + isPrivate: topic.is_private, + collectionsInterface: topic.collections.map((tc) => ({ + id: tc.collection.collection_id, + name: tc.collection.name, + })), + groupPermissions: topic.group_permissions.map((gp) => ({ + group_id: gp.group.group_id, + group: gp.group, + manageCourses: gp.permission_manage_topics, + viewCourses: gp.permission_view_topics, + viewCourseLogs: gp.permission_view_topics_log, + })) + } +} \ No newline at end of file diff --git a/src/types/apis/Group.api.ts b/src/types/apis/Group.api.ts index 181926d..7e37af9 100644 --- a/src/types/apis/Group.api.ts +++ b/src/types/apis/Group.api.ts @@ -5,6 +5,13 @@ export type GroupCreateRequest = { name: string; description?: string | null; color?: string | null; + permission_manage_topics?: boolean + permission_view_topics?: boolean + permission_view_topics_log?: boolean + permission_manage_collections?: boolean + permission_view_collections?: boolean + permission_manage_problems?: boolean + permission_view_problems?: boolean } export type GroupgetAllAsCreatorResponse = { diff --git a/src/types/apis/Topic.api.ts b/src/types/apis/Topic.api.ts index 2572919..7f7052f 100644 --- a/src/types/apis/Topic.api.ts +++ b/src/types/apis/Topic.api.ts @@ -1,5 +1,5 @@ import { AxiosResponse } from "axios"; -import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../models/Topic.model"; +import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../models/Topic.model"; export type GetAllTopicsByAccountResponse = { topics: TopicPopulateTopicCollectionPopulateCollectionModel[]; @@ -9,12 +9,20 @@ export type GetAllTopicsByAccessibleAccountResponse = { topics: TopicModel[]; } +export type CourseGroupPermissionCreateRequest = { + group_id: string; + permission_manage_topics?: boolean + permission_view_topics?: boolean + permission_view_topics_log?: boolean +} + export type TopicSerivceAPI = { create: (accountid: string,request: FormData) => Promise>; - get: (accountid: string,courseId:string) => Promise>; + get: (accountid: string,courseId:string) => Promise>; getAllAsCreator: (accountId:string) => Promise>; getAllAccessibleByAccount: (accountId:string) => Promise>; getPublicByAccount: (accountId:string,courseId:string) => Promise>; update: (courseId:string,request: FormData) => Promise>; updateCollections: (topicId:string,collectionIds:string[]) => Promise>; + updateGroupPermissions: (topicId:string,groups:CourseGroupPermissionCreateRequest[]) => Promise>; } \ No newline at end of file diff --git a/src/types/forms/CreateCourseRequestForm.ts b/src/types/forms/CreateCourseRequestForm.ts index 68531ed..7dab5e8 100644 --- a/src/types/forms/CreateCourseRequestForm.ts +++ b/src/types/forms/CreateCourseRequestForm.ts @@ -1,10 +1,18 @@ import { ItemInterface } from "react-sortablejs"; import { PlateEditorValueType } from "../PlateEditorValueType"; +import { CoursePermissionRequestForm } from "./CreateGroupRequestForm"; +import { GroupModel, TopicGroupPermissionPopulateGroupModel } from "../models/Group.model"; + +export type CourseGroupPermissionRequestForm = { + group_id: string; + group: GroupModel; +} & CoursePermissionRequestForm export type CreateCourseRequestForm = { title: string; description: PlateEditorValueType; - image?: File | null; + image?: File | string | null; isPrivate?: boolean; collectionsInterface: ItemInterface[]; + groupPermissions: CourseGroupPermissionRequestForm[]; } \ No newline at end of file diff --git a/src/types/forms/CreateGroupRequestForm.ts b/src/types/forms/CreateGroupRequestForm.ts index de76c5f..28b669e 100644 --- a/src/types/forms/CreateGroupRequestForm.ts +++ b/src/types/forms/CreateGroupRequestForm.ts @@ -1,9 +1,25 @@ import { ItemInterface } from "react-sortablejs"; import { AccountSecureModel } from "../models/Account.model"; +export type CoursePermissionRequestForm = { + manageCourses: boolean; + viewCourseLogs: boolean; + viewCourses: boolean; +} + +export type CollectionPermissionRequestForm = { + manageCollections: boolean; + viewCollections: boolean; +} + +export type ProblemPermissionRequestForm = { + manageProblems: boolean; + viewProblems: boolean; +} + export type CreateGroupRequestForm = { name: string; description: string | null; color: string | null; membersInterface: ItemInterface[]; -} \ No newline at end of file +} & CoursePermissionRequestForm & CollectionPermissionRequestForm & ProblemPermissionRequestForm \ No newline at end of file diff --git a/src/types/models/Group.model.ts b/src/types/models/Group.model.ts index 16d1f40..e90b469 100644 --- a/src/types/models/Group.model.ts +++ b/src/types/models/Group.model.ts @@ -8,6 +8,13 @@ export type GroupModel = { color: string; created_date: string; updated_date: string; + permission_manage_topics: boolean; + permission_view_topics: boolean; + permission_view_topics_log: boolean; + permission_manage_collections: boolean; + permission_view_collections: boolean; + permission_manage_problems: boolean; + permission_view_problems: boolean; } export type GroupMemberModel = { @@ -27,4 +34,17 @@ export type GroupPopulateGroupMemberPopulateAccountSecureModel = GroupModel & { export type GroupHashedTable = { [id:string]: GroupModel | GroupPopulateGroupMemberPopulateAccountSecureModel +} + +export type TopicGroupPermissionModel = { + topic_group_permission_id: string + group: string + permission_manage_topics: boolean + permission_view_topics: boolean + permission_view_topics_log: boolean + topic: string +} + +export type TopicGroupPermissionPopulateGroupModel = TopicGroupPermissionModel & { + group: GroupModel } \ No newline at end of file diff --git a/src/types/models/Topic.model.ts b/src/types/models/Topic.model.ts index 0d27cb8..f3f55ff 100644 --- a/src/types/models/Topic.model.ts +++ b/src/types/models/Topic.model.ts @@ -1,4 +1,5 @@ import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "./Collection.model" +import { TopicGroupPermissionPopulateGroupModel } from "./Group.model" export type TopicModel = { topic_id: string @@ -47,4 +48,9 @@ export type TopicCollectionPopulateCollectionPopulateCollectionProblemPopulatePr export type TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel = TopicSecureModel & { collections: TopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel[] +} + +export type TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel = TopicModel & { + collections: TopicCollectionPopulateCollectionModel[] + group_permissions: TopicGroupPermissionPopulateGroupModel[] } \ No newline at end of file diff --git a/src/views/My/Courses/CreateCourse.tsx b/src/views/My/Courses/CreateCourse.tsx index 8a0cdae..7ba853d 100644 --- a/src/views/My/Courses/CreateCourse.tsx +++ b/src/views/My/Courses/CreateCourse.tsx @@ -12,22 +12,18 @@ import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseReques import CreateCourseForm, { OnCourseSavedCallback, } from "../../../components/Forms/CreateCourseForm"; -import { transformCreateCourseRequestForm2CreateTopicRequestFormData } from "../../../types/adapters/CreateCourseRequestForm.adapter"; +import { transformCreateCourseRequestForm2CreateTopicRequest } from "../../../types/adapters/CreateCourseRequestForm.adapter"; import { TopicService } from "../../../services/Topic.service"; import { useNavigate } from "react-router-dom"; +import { EmptyEditorValue } from "../../../constants/DummyEditorValue"; const formInitialValue: CreateCourseRequestForm = { title: "", - description: [ - { - id: "1", - type: ELEMENT_PARAGRAPH, - children: [{ text: "" }], - }, - ], + description: EmptyEditorValue, image: null, isPrivate: false, collectionsInterface: [], + groupPermissions: [] }; const CreateCourse = () => { @@ -44,14 +40,12 @@ const CreateCourse = () => { return; } - const formData = transformCreateCourseRequestForm2CreateTopicRequestFormData(createRequest) - const collectionIds = createRequest.collectionsInterface.map((collection) => collection.id as string) + const {formData,collectionIds} = transformCreateCourseRequestForm2CreateTopicRequest(createRequest) setLoading(true) TopicService.create(accountId, formData).then((response) => { return TopicService.updateCollections(response.data.topic_id,collectionIds) }).then((response) => { - console.log("OK!") setLoading(false) toast({ title: "Create Completed" diff --git a/src/views/My/Courses/EditCourse.tsx b/src/views/My/Courses/EditCourse.tsx index cd60ae1..503ea14 100644 --- a/src/views/My/Courses/EditCourse.tsx +++ b/src/views/My/Courses/EditCourse.tsx @@ -12,10 +12,11 @@ import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseReques import CreateCourseForm, { OnCourseSavedCallback, } from "../../../components/Forms/CreateCourseForm"; -import { transformCreateCourseRequestForm2CreateTopicRequestFormData } from "../../../types/adapters/CreateCourseRequestForm.adapter"; +import { transformCreateCourseRequestForm2CreateTopicRequest } from "../../../types/adapters/CreateCourseRequestForm.adapter"; import { TopicService } from "../../../services/Topic.service"; import { useParams } from "react-router-dom"; import { ItemInterface } from "react-sortablejs"; +import { transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest } from "../../../types/adapters/Topic.adapter"; const EditCourse = () => { const { courseId } = useParams(); @@ -28,19 +29,13 @@ const EditCourse = () => { const handleSave = ({ setLoading, createRequest, - }: OnCourseSavedCallback) => { - if ( !setLoading || !createRequest || !courseId) { + if (!setLoading || !createRequest || !courseId) { return; } - const formData = - transformCreateCourseRequestForm2CreateTopicRequestFormData( - createRequest - ); - const collectionIds = createRequest.collectionsInterface.map( - (collection) => collection.id as string - ); + const { formData, collectionIds, groups } = + transformCreateCourseRequestForm2CreateTopicRequest(createRequest); setLoading(true); TopicService.update(editCourseId, formData) @@ -51,29 +46,25 @@ const EditCourse = () => { ); }) .then(() => { - console.log("OK!"); + return TopicService.updateGroupPermissions(editCourseId, groups); + }) + .then(() => { setLoading(false); toast({ title: "Update Completed", - }) - }); + }); + }) }; useEffect(() => { - TopicService.get(accountId,editCourseId).then((response) => { + TopicService.get(accountId, editCourseId).then((response) => { const { data } = response; - setCreateRequest({ - title: data.name, - description: JSON.parse(String(data.description)), - isPrivate: data.is_private, - collectionsInterface: data.collections.map( - (topicCollection) => - ({ - id: topicCollection.collection.collection_id, - name: topicCollection.collection.name, - } as ItemInterface) - ), - }); + console.log(data) + setCreateRequest( + transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest( + data + ) + ); }); }, [editCourseId]); @@ -83,12 +74,12 @@ const EditCourse = () => { handleSave({ createRequest, - + setLoading, }) } diff --git a/src/views/My/Groups/CreateGroup.tsx b/src/views/My/Groups/CreateGroup.tsx index a0c9d8a..310c3d5 100644 --- a/src/views/My/Groups/CreateGroup.tsx +++ b/src/views/My/Groups/CreateGroup.tsx @@ -13,6 +13,13 @@ const formInitialValue: CreateGroupRequestForm = { description: "", color: "#f87171", membersInterface: [], + manageCourses: false, + viewCourseLogs: false, + viewCourses: false, + manageCollections: false, + viewCollections: false, + manageProblems: false, + viewProblems: false }; const CreateGroup = () => { @@ -30,6 +37,7 @@ const CreateGroup = () => { ); setLoading(true); + console.log(createRequest,request) GroupService.create(accountId, request) .then((response) => { return GroupService.updateMembers( diff --git a/src/views/My/Groups/EditGroup.tsx b/src/views/My/Groups/EditGroup.tsx index d7299f7..bb37c9a 100644 --- a/src/views/My/Groups/EditGroup.tsx +++ b/src/views/My/Groups/EditGroup.tsx @@ -6,6 +6,7 @@ import CreateGroupForm, { OnGroupSavedCallback } from "../../../components/Forms import { GroupService } from "../../../services/Group.service"; import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../../../types/models/Group.model"; import { transformCreateGroupRequestForm2CreateGroupRequest } from "../../../types/adapters/CreateGroupRequestForm.adapter"; +import { transformGroupPopulateGroupMemberPopulateAccountSecureModel2CreateGroupRequestForm } from "../../../types/adapters/Group.adapter"; const EditGroup = () => { const { groupId } = useParams(); @@ -21,15 +22,8 @@ const EditGroup = () => { }).then((response) => { const data = response.data as GroupPopulateGroupMemberPopulateAccountSecureModel; - setCreateRequest({ - name: data.name, - description: data.description, - color: data.color, - membersInterface: data.members.map((member) => ({ - id: member.account.account_id, - label: member.account.username, - })), - }); + setCreateRequest(transformGroupPopulateGroupMemberPopulateAccountSecureModel2CreateGroupRequestForm(data)); + console.log(data) }); }, [groupId]); @@ -38,6 +32,7 @@ const EditGroup = () => { const memberIds = createRequest.membersInterface.map((item) => item.id as string) setLoading(true) + console.log("EditGroup",request) GroupService.update(String(groupId),request).then((response) => { return GroupService.updateMembers(response.data.group_id,memberIds) }).then((response) => { From 65a03f5c78846d8233503e99fc5cb6f8dc2a5985 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Wed, 3 Jan 2024 22:45:23 +0700 Subject: [PATCH 06/24] Improve MiniCard --- .../Cards/AccountCards/AccountMiniCard2.tsx | 103 +++++++++++++ .../CollectionCards/MyCollectionMiniCard2.tsx | 143 ++++++++++++++++++ .../Cards/ProblemCards/MyProblemMiniCard2.tsx | 139 +++++++++++++++++ .../CreateCollectionForm/ManageProblems.tsx | 13 +- .../CreateCourseForm/ManageCollections.tsx | 13 +- .../Forms/CreateGroupForm/ManageMembers.tsx | 13 +- .../Forms/GroupAndPermissionManager.tsx | 134 +++++++++++----- src/components/SortableCardContainer.tsx | 2 +- src/constants/BackendBaseURL.ts | 1 + 9 files changed, 505 insertions(+), 56 deletions(-) create mode 100644 src/components/Cards/AccountCards/AccountMiniCard2.tsx create mode 100644 src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx create mode 100644 src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx diff --git a/src/components/Cards/AccountCards/AccountMiniCard2.tsx b/src/components/Cards/AccountCards/AccountMiniCard2.tsx new file mode 100644 index 0000000..6b9c8d2 --- /dev/null +++ b/src/components/Cards/AccountCards/AccountMiniCard2.tsx @@ -0,0 +1,103 @@ +import React, { useEffect, useState } from "react"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import { Button } from "../../shadcn/Button"; +import { + Check, + CheckCircle2, + FileSpreadsheet, + Folder, + Pencil, + PencilIcon, + Trash, + User, + Users, + X, +} from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { + ProblemModel, + ProblemPopulateTestcases, + ProblemSecureModel, + TestcaseModel, +} from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; +import { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, +} from "../../shadcn/ContextMenu"; +import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import Checkmark from "../../Checkmark"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; +import { CollectionModel, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; +import { AccountModel, AccountSecureModel } from "../../../types/models/Account.model"; + +const AccountMiniCard2 = ({ + // problem, + account, + disabled=false, + disabledHighlight=false, + onClick=()=>{} +}: { + // problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; + account: AccountModel | AccountSecureModel; + disabled?: boolean; + disabledHighlight?: boolean; + onClick?: () => void; +}) => { + const navigate = useNavigate(); + + const [highlightTitle, setHighlightTitle] = useState(false); + const [toolVisible, setToolVisible] = useState(true); + + const handleMouseOver = () => { + setHighlightTitle(true); + setToolVisible(true); + }; + const handleMouseOut = () => { + setHighlightTitle(false); + setToolVisible(false); + }; + + const customCardCSS = ():string => { + let className = "p-2 cursor-pointer "; + + if (disabled) { + className += "opacity-50 "; + } + else{ + if (highlightTitle && !disabledHighlight) { + className += "border-green-500 bg-green-100 "; + } + } + return className; + } + + return ( + account && ( + // + onClick()} + onMouseOver={handleMouseOver} + onMouseOut={handleMouseOut} + className={customCardCSS()} + + // className={`pt-6 px-5 ${disabled ? "opacity-50" : }`}`} + > +
+
+ +

{account.username}

+
+ {/*
+ {collection.problems.length} +
*/} +
+
+ //
+ ) + ); +}; + +export default AccountMiniCard2; diff --git a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx new file mode 100644 index 0000000..3503eab --- /dev/null +++ b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useState } from "react"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import { Button } from "../../shadcn/Button"; +import { + Check, + CheckCircle2, + FileSpreadsheet, + Folder, + Pencil, + PencilIcon, + Trash, + X, +} from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { + ProblemModel, + ProblemPopulateTestcases, + ProblemSecureModel, + TestcaseModel, +} from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; +import { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, +} from "../../shadcn/ContextMenu"; +import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import Checkmark from "../../Checkmark"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; +import { + CollectionModel, + CollectionPopulateProblemSecureModel, +} from "../../../types/models/Collection.model"; + +const checkRuntimeStatus = (testcases: TestcaseModel[]) => { + for (const testcase of testcases) { + if (testcase.runtime_status !== "OK") { + return false; + } + } + return true; +}; + +const MyCollectionContextMenu = ({ + children, + problem, +}: { + children: React.ReactNode; + problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; +}) => { + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + + return ( + + window.location.reload()} + /> + {children} + + + + Edit + + setOpenDeleteDialog(true)}> + + Delete + + + + ); +}; + +const MyCollectionMiniCard2 = ({ + // problem, + collection, + disabled = false, + disabledHighlight = false, + onClick = () => {}, +}: { + // problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; + collection: CollectionPopulateProblemSecureModel; + disabled?: boolean; + disabledHighlight?: boolean; + onClick?: () => void; +}) => { + const navigate = useNavigate(); + + const [highlightTitle, setHighlightTitle] = useState(false); + const [toolVisible, setToolVisible] = useState(true); + + const handleMouseOver = () => { + setHighlightTitle(true); + setToolVisible(true); + }; + const handleMouseOut = () => { + setHighlightTitle(false); + setToolVisible(false); + }; + + const customCardCSS = (): string => { + let className = "p-2 cursor-pointer "; + + if (disabled) { + className += "opacity-50 "; + } else { + if (highlightTitle && !disabledHighlight) { + className += "border-green-500 bg-green-100 "; + } + } + return className; + }; + + return ( + collection && ( + // + onClick()} + onMouseOver={handleMouseOver} + onMouseOut={handleMouseOut} + className={customCardCSS()} + + // className={`pt-6 px-5 ${disabled ? "opacity-50" : }`}`} + > +
+
+ +

{collection.name}

+
+
+ {collection.problems.length} +
+
+
+ //
+ ) + ); +}; + +export default MyCollectionMiniCard2; diff --git a/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx b/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx new file mode 100644 index 0000000..5ca031a --- /dev/null +++ b/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx @@ -0,0 +1,139 @@ +import React, { useEffect, useState } from "react"; +import { Card, CardContent, CardTitle } from "../../shadcn/Card"; +import { Button } from "../../shadcn/Button"; +import { + Check, + CheckCircle2, + FileSpreadsheet, + Pencil, + PencilIcon, + Trash, + X, +} from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { + ProblemModel, + ProblemPopulateTestcases, + ProblemSecureModel, + TestcaseModel, +} from "../../../types/models/Problem.model"; +import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; +import { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, +} from "../../shadcn/ContextMenu"; +import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import Checkmark from "../../Checkmark"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; + +const checkRuntimeStatus = (testcases: TestcaseModel[]) => { + for (const testcase of testcases) { + if (testcase.runtime_status !== "OK") { + return false; + } + } + return true; +}; + +const MyProblemContextMenu = ({ + children, + problem, +}: { + children: React.ReactNode; + problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; +}) => { + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + + return ( + + window.location.reload()} + /> + {children} + + + + Edit + + setOpenDeleteDialog(true)}> + + Delete + + + + ); +}; + +const MyProblemMiniCard2 = ({ + problem, + disabled = false, + disabledHighlight = false, + onClick = () => {}, +}: { + problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; + disabled?: boolean; + disabledHighlight?: boolean; + onClick?: () => void; +}) => { + const navigate = useNavigate(); + + const [highlightTitle, setHighlightTitle] = useState(false); + const [toolVisible, setToolVisible] = useState(true); + + const handleMouseOver = () => { + setHighlightTitle(true); + setToolVisible(true); + }; + const handleMouseOut = () => { + setHighlightTitle(false); + setToolVisible(false); + }; + + const customCardCSS = (): string => { + let className = "p-2 cursor-pointer "; + + if (disabled) { + className += "opacity-50 "; + } else { + if (highlightTitle && !disabledHighlight) { + className += "border-green-500 bg-green-100 "; + } + } + return className; + }; + + return ( + problem && ( + + onClick()} + onMouseOver={handleMouseOver} + onMouseOut={handleMouseOut} + className={customCardCSS()} + + // className={`pt-6 px-5 ${disabled ? "opacity-50" : }`}`} + > +
+
+ +

{problem.title}

+
+ {/*
+ {collection.problems.length} +
*/} +
+
+
+ ) + ); +}; + +export default MyProblemMiniCard2; diff --git a/src/components/Forms/CreateCollectionForm/ManageProblems.tsx b/src/components/Forms/CreateCollectionForm/ManageProblems.tsx index 37b6203..5c32cce 100644 --- a/src/components/Forms/CreateCollectionForm/ManageProblems.tsx +++ b/src/components/Forms/CreateCollectionForm/ManageProblems.tsx @@ -18,6 +18,7 @@ import MyProblemMiniCard from "../../Cards/ProblemCards/MyProblemMiniCard"; import { ScrollArea } from "../../shadcn/ScrollArea"; import { Item } from "@radix-ui/react-context-menu"; import { transformProblemModel2ProblemHashedTable } from "../../../types/adapters/Problem.adapter"; +import MyProblemMiniCard2 from "../../Cards/ProblemCards/MyProblemMiniCard2"; const ManageProblems = ({ createRequest, @@ -111,16 +112,16 @@ const ManageProblems = ({
- + {selectedProblemsSortable?.map((item) => ( - handleRemoveSelectedProblem(item.id as string)} key={item.id} @@ -141,7 +142,7 @@ const ManageProblems = ({
- + {allProblemsSortable?.map((item) => (
- handleQuickToggleSelectedProblem(item)} disabled={selectedProblemSortableIds.includes(item.id as string)} key={item.id} diff --git a/src/components/Forms/CreateCourseForm/ManageCollections.tsx b/src/components/Forms/CreateCourseForm/ManageCollections.tsx index 691a7c1..393a1df 100644 --- a/src/components/Forms/CreateCourseForm/ManageCollections.tsx +++ b/src/components/Forms/CreateCourseForm/ManageCollections.tsx @@ -23,6 +23,7 @@ import MyCollectionMiniCard from "../../Cards/CollectionCards/MyCollectionMiniCa import { CollectionService } from "../../../services/Collection.service"; import { transformCollectionModel2CollectionHashedTable } from "../../../types/adapters/Collection.adapter"; import { CollectionHashedTable, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; +import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; const ManageCollections = ({ createRequest, @@ -126,16 +127,16 @@ const ManageCollections = ({
- + {selectedCollectionsSortable?.map((item) => ( - handleRemoveSelectedCollection(item.id as string)} key={item.id} @@ -154,7 +155,7 @@ const ManageCollections = ({
- + {allCollectionsSortable?.map((item) => (
- handleQuickToggleSelectedCollection(item)} disabled={selectedCollectionsSortableIds.includes(item.id as string)} key={item.id} diff --git a/src/components/Forms/CreateGroupForm/ManageMembers.tsx b/src/components/Forms/CreateGroupForm/ManageMembers.tsx index b9d081d..6da1ecf 100644 --- a/src/components/Forms/CreateGroupForm/ManageMembers.tsx +++ b/src/components/Forms/CreateGroupForm/ManageMembers.tsx @@ -33,6 +33,7 @@ import { GroupService } from "../../../services/Group.service"; import { AccountService } from "../../../services/Account.service"; import { transformAccountModels2AccountHashedTable } from "../../../types/adapters/Account.adapter"; import AccountMiniCard from "../../Cards/AccountCards/AccountMiniCard"; +import AccountMiniCard2 from "../../Cards/AccountCards/AccountMiniCard2"; const ManageMembers = ({ createRequest, @@ -118,17 +119,17 @@ const ManageMembers = ({
- + {selectedAccountsSortable?.map((item) => ( - handleRemoveSelectedCollection(item.id as string)} key={item.id} @@ -147,7 +148,7 @@ const ManageMembers = ({
- + {allAccountsSortable?.map((item) => (
- handleQuickToggleSelectedCollection(item)} key={item.id} diff --git a/src/components/Forms/GroupAndPermissionManager.tsx b/src/components/Forms/GroupAndPermissionManager.tsx index df8d668..20c6cc3 100644 --- a/src/components/Forms/GroupAndPermissionManager.tsx +++ b/src/components/Forms/GroupAndPermissionManager.tsx @@ -7,7 +7,7 @@ import { CourseGroupPermissionRequestForm, CreateCourseRequestForm, } from "../../types/forms/CreateCourseRequestForm"; -import { PlusCircle, Users } from "lucide-react"; +import { PlusCircle, Users, X } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "../shadcn/Tooltip"; import { Dialog, DialogContent } from "../shadcn/Dialog"; import { Input } from "../shadcn/Input"; @@ -15,6 +15,13 @@ import GroupCheckbox from "../GroupCheckbox"; import { Button } from "../shadcn/Button"; import { GroupService } from "../../services/Group.service"; import { GroupModel } from "../../types/models/Group.model"; +import { Command } from "../shadcn/Command"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, +} from "../shadcn/ContextMenu"; const GroupListItem = ({ hexColor = "#000000", @@ -53,6 +60,26 @@ const GroupListItem = ({ ); }; +const GroupListItemContextMenu = ({ + children, + onClickRemove = () => {}, +}: { + children: React.ReactNode; + onClickRemove?: () => void; +}) => { + return ( + + {children} + + onClickRemove()}> + + Remove + + + + ); +}; + const GroupAndPermissionManager = ({ createRequest, setCreateRequest, @@ -62,11 +89,11 @@ const GroupAndPermissionManager = ({ React.SetStateAction >; }) => { - - const accountId = String(localStorage.getItem("account_id")) + const accountId = String(localStorage.getItem("account_id")); const [allGroups, setAllGroups] = useState([]); - const [openAddGroupsDialog, setOpenAddGroupsDialog] = useState(false); + const [openAddGroupsDialog, setOpenAddGroupsDialog] = + useState(false); const [selectedGroupIds, setSelectedGroupIds] = useState([]); const [groupPermission, setGroupPermission] = @@ -79,22 +106,38 @@ const GroupAndPermissionManager = ({ } else { setSelectedGroupIds([...selectedGroupIds, groupId]); } + }; + + const handleRemoveGroupPermission = (index: number) => { + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions.slice(0, index), + ...createRequest.groupPermissions.slice(index + 1), + ], + }); } const getNotInPermissionGroup = () => { - const inPermissiongroupIds = createRequest.groupPermissions.map((groupPermission) => groupPermission.group.group_id) - return allGroups.filter((group) => !inPermissiongroupIds.includes(group.group_id)) - } + const inPermissiongroupIds = createRequest.groupPermissions.map( + (groupPermission) => groupPermission.group.group_id + ); + return allGroups.filter( + (group) => !inPermissiongroupIds.includes(group.group_id) + ); + }; const handleAddGroups = () => { - const addGroups = allGroups.filter((group) => selectedGroupIds.includes(group.group_id)) + const addGroups = allGroups.filter((group) => + selectedGroupIds.includes(group.group_id) + ); const newGroupPermissions = addGroups.map((group) => ({ group_id: group.group_id, group, manageCourses: group.permission_manage_topics, viewCourseLogs: group.permission_view_topics_log, viewCourses: group.permission_view_topics, - })) + })); setCreateRequest({ ...createRequest, @@ -102,11 +145,11 @@ const GroupAndPermissionManager = ({ ...createRequest.groupPermissions, ...newGroupPermissions, ], - }) + }); - setSelectedGroupIds([]) - setOpenAddGroupsDialog(false) - } + setSelectedGroupIds([]); + setOpenAddGroupsDialog(false); + }; useEffect(() => { setGroupPermission(createRequest?.groupPermissions[selectedIndex]); @@ -128,12 +171,12 @@ const GroupAndPermissionManager = ({ useEffect(() => { GroupService.getAllAsCreator(accountId).then((response) => { setAllGroups(response.data.groups); - }) - },[]) + }); + }, []); return (
-
+

@@ -141,9 +184,12 @@ const GroupAndPermissionManager = ({

- setOpenAddGroupsDialog(true)} size={20} /> + setOpenAddGroupsDialog(true)} + size={20} + /> - Add Group + Add Group
@@ -151,12 +197,20 @@ const GroupAndPermissionManager = ({ {createRequest.groupPermissions.map( (groupPermission, index) => (
setselectedIndex(index)}> - + handleRemoveGroupPermission(index)} + > + +
) )} @@ -166,7 +220,7 @@ const GroupAndPermissionManager = ({
-
+
{groupPermission && selectedIndex >= 0 && (
- +

Add Groups

- { - getNotInPermissionGroup().map((group) => ( - handleSelectGroupCheckbox(group.group_id)} - checked={selectedGroupIds.includes(group.group_id)} - group={group} - /> - )) - } + {getNotInPermissionGroup().map((group) => ( + + handleSelectGroupCheckbox( + group.group_id + ) + } + checked={selectedGroupIds.includes( + group.group_id + )} + group={group} + /> + ))}
- - +
diff --git a/src/components/SortableCardContainer.tsx b/src/components/SortableCardContainer.tsx index 1f7da5a..20c9f96 100644 --- a/src/components/SortableCardContainer.tsx +++ b/src/components/SortableCardContainer.tsx @@ -9,7 +9,7 @@ const SortableCardContainer = ({ children: React.ReactNode; }) => { return ( - + {children} diff --git a/src/constants/BackendBaseURL.ts b/src/constants/BackendBaseURL.ts index e99071d..f620904 100644 --- a/src/constants/BackendBaseURL.ts +++ b/src/constants/BackendBaseURL.ts @@ -1 +1,2 @@ +// export const BASE_URL = 'http://192.168.0.11:8000'; export const BASE_URL = 'http://localhost:8000'; \ No newline at end of file From 21c82c23925861026bc4bf8efbb8556c67cf766a Mon Sep 17 00:00:00 2001 From: KanonKC Date: Wed, 3 Jan 2024 23:50:27 +0700 Subject: [PATCH 07/24] Resizable --- @/components/ui/resizable.tsx | 43 +++++++++++++++++++ package-lock.json | 10 +++++ package.json | 3 +- .../CollectionCards/MyCollectionMiniCard2.tsx | 4 +- src/components/ProblemViewLayout.tsx | 18 ++++---- src/components/shadcn/Resizable.tsx | 43 +++++++++++++++++++ 6 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 @/components/ui/resizable.tsx create mode 100644 src/components/shadcn/Resizable.tsx diff --git a/@/components/ui/resizable.tsx b/@/components/ui/resizable.tsx new file mode 100644 index 0000000..cd3cb0e --- /dev/null +++ b/@/components/ui/resizable.tsx @@ -0,0 +1,43 @@ +import { GripVertical } from "lucide-react" +import * as ResizablePrimitive from "react-resizable-panels" + +import { cn } from "@/lib/utils" + +const ResizablePanelGroup = ({ + className, + ...props +}: React.ComponentProps) => ( + +) + +const ResizablePanel = ResizablePrimitive.Panel + +const ResizableHandle = ({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean +}) => ( + div]:rotate-90", + className + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+) + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/package-lock.json b/package-lock.json index de435f8..d946822 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,6 +85,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", "react-lite-youtube-embed": "^2.3.52", + "react-resizable-panels": "^1.0.7", "react-router-dom": "^6.15.0", "react-sortablejs": "^6.1.4", "react-tweet": "^3.1.1", @@ -6174,6 +6175,15 @@ } } }, + "node_modules/react-resizable-panels": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-1.0.7.tgz", + "integrity": "sha512-CluJkHQheeNqIJly2FYDfri3ME+2h2nCXpf0Y+hTO1K1eVtNxXFA5hVp5cUD6NS70iiufswOmnku9QZiLr1hYg==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-router": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.15.0.tgz", diff --git a/package.json b/package.json index e2badf0..b69de2c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite --host", + "dev": "vite", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" @@ -87,6 +87,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", "react-lite-youtube-embed": "^2.3.52", + "react-resizable-panels": "^1.0.7", "react-router-dom": "^6.15.0", "react-sortablejs": "^6.1.4", "react-tweet": "^3.1.1", diff --git a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx index 3503eab..d4486b3 100644 --- a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx @@ -126,9 +126,9 @@ const MyCollectionMiniCard2 = ({ // className={`pt-6 px-5 ${disabled ? "opacity-50" : }`}`} >
-
+
-

{collection.name}

+

{collection.name}

{collection.problems.length} diff --git a/src/components/ProblemViewLayout.tsx b/src/components/ProblemViewLayout.tsx index ec139a9..42be497 100644 --- a/src/components/ProblemViewLayout.tsx +++ b/src/components/ProblemViewLayout.tsx @@ -14,6 +14,7 @@ import styled from "styled-components"; import { Button } from "./shadcn/Button"; import PreviousSubmissionsCombobox from "./PreviousSubmissionsCombobox"; import { Editor as MonacoEditor } from "@monaco-editor/react"; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "./shadcn/Resizable"; export type OnSubmitProblemViewLayoutCallback = { setGrading: React.Dispatch> @@ -81,8 +82,8 @@ const ProblemViewLayout = ({ },[problem]) return ( -
-
+ +
)}
-
-
+ + {/*
-
-
+
*/} + +
-
-
+
+
); }; diff --git a/src/components/shadcn/Resizable.tsx b/src/components/shadcn/Resizable.tsx new file mode 100644 index 0000000..5132aeb --- /dev/null +++ b/src/components/shadcn/Resizable.tsx @@ -0,0 +1,43 @@ +import { GripVertical } from "lucide-react" +import * as ResizablePrimitive from "react-resizable-panels" + +import { cn } from "../../lib/utils" + +const ResizablePanelGroup = ({ + className, + ...props +}: React.ComponentProps) => ( + +) + +const ResizablePanel = ResizablePrimitive.Panel + +const ResizableHandle = ({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean +}) => ( + div]:rotate-90", + className + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+) + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } From d77693ab3ff6d66311f298502d1adbeb4fb1f28c Mon Sep 17 00:00:00 2001 From: KanonKC Date: Thu, 4 Jan 2024 22:49:17 +0700 Subject: [PATCH 08/24] Now can handle personal courses and manageable courses --- src/App.tsx | 6 + .../Forms/CreateCollectionForm/index.tsx | 7 - .../CreateCourseForm/ManageCollections.tsx | 148 ++++++++++++------ src/components/NavigationBar/NavSidebar.tsx | 2 +- src/layout/NavbarSidebarLayout.tsx | 15 +- src/services/Topic.service.ts | 4 +- src/types/adapters/Collection.adapter.ts | 2 +- src/types/adapters/Topic.adapter.ts | 3 +- src/types/apis/Topic.api.ts | 3 +- src/types/forms/CreateCourseRequestForm.ts | 2 + src/types/models/Collection.model.ts | 2 +- src/views/My/Courses/EditCourse.tsx | 4 +- src/views/My/Courses/MyCourses.tsx | 26 ++- 13 files changed, 156 insertions(+), 68 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d13b264..ffe6542 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,6 +31,12 @@ function App() { if (response.data.result) { setIsLogin(true); } + else { + localStorage.removeItem("token"); + localStorage.removeItem("account_id"); + localStorage.removeItem("username"); + + } }); }, []); diff --git a/src/components/Forms/CreateCollectionForm/index.tsx b/src/components/Forms/CreateCollectionForm/index.tsx index 7fc5115..1464472 100644 --- a/src/components/Forms/CreateCollectionForm/index.tsx +++ b/src/components/Forms/CreateCollectionForm/index.tsx @@ -30,8 +30,6 @@ const TabList = [ export type OnCollectionSavedCallback = { setLoading?: React.Dispatch> - collectionid?: string - setCollectionId?: React.Dispatch> createRequest?: CreateCollectionRequestForm } @@ -46,17 +44,12 @@ const CreateCollectionForm = ({ const navigate = useNavigate(); const [currentForm, setCurrentForm] = useState("general"); const [loading, setLoading] = useState(false); - - const [collectionId, setCollectionId] = useState(-1); - const [createRequest, setCreateRequest] = useState(createRequestInitialValue) const handleSave = () => { onCollectionSave({ setLoading, createRequest, - collectionId, - setCollectionId, }) } diff --git a/src/components/Forms/CreateCourseForm/ManageCollections.tsx b/src/components/Forms/CreateCourseForm/ManageCollections.tsx index 393a1df..dff4b91 100644 --- a/src/components/Forms/CreateCourseForm/ManageCollections.tsx +++ b/src/components/Forms/CreateCourseForm/ManageCollections.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; import { ReactSortable } from "react-sortablejs"; import { Button } from "../../shadcn/Button"; @@ -22,8 +22,13 @@ import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseReques import MyCollectionMiniCard from "../../Cards/CollectionCards/MyCollectionMiniCard"; import { CollectionService } from "../../../services/Collection.service"; import { transformCollectionModel2CollectionHashedTable } from "../../../types/adapters/Collection.adapter"; -import { CollectionHashedTable, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; +import { + CollectionHashedTable, + CollectionModel, + CollectionPopulateProblemSecureModel, +} from "../../../types/models/Collection.model"; import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; +import { CourseNavSidebarContext } from "../../../contexts/CourseNavSidebarContexnt"; const ManageCollections = ({ createRequest, @@ -34,31 +39,32 @@ const ManageCollections = ({ React.SetStateAction >; }) => { - const accountId = String(localStorage.getItem("account_id")); const [allCollectionsSortable, setAllCollectionsSortable] = useState< ItemInterface[] >([]); - const [selectedCollectionsSortable, setSelectedCollectionsSortable] = useState< - ItemInterface[] - >([]); - const [allCollections, setAllCollections] = useState< - CollectionHashedTable - >({}); + const [selectedCollectionsSortable, setSelectedCollectionsSortable] = + useState([]); + const [allCollections, setAllCollections] = useState( + {} + ); const [initial, setInitial] = useState(true); - const [selectedCollectionsSortableIds, setSelectedCollectionsSortableIds] = useState([]); + const [selectedCollectionsSortableIds, setSelectedCollectionsSortableIds] = + useState([]); useEffect(() => { - setSelectedCollectionsSortableIds(selectedCollectionsSortable.map((item) => item.id as string)); - },[selectedCollectionsSortable]) + setSelectedCollectionsSortableIds( + selectedCollectionsSortable.map((item) => item.id as string) + ); + }, [selectedCollectionsSortable]); const handleRemoveSelectedCollection = (id: string) => { - setSelectedCollectionsSortable( - [...selectedCollectionsSortable.filter((item) => item.id !== id)] - ); - } + setSelectedCollectionsSortable([ + ...selectedCollectionsSortable.filter((item) => item.id !== id), + ]); + }; const handleQuickToggleSelectedCollection = (item: ItemInterface) => { // if (selectedCollectionsSortable.find((item1) => item1.id === item.id)) { @@ -71,11 +77,13 @@ const ManageCollections = ({ if (selectedCollectionsSortableIds.includes(item.id as string)) { handleRemoveSelectedCollection(item.id as string); + } else { + setSelectedCollectionsSortable([ + ...selectedCollectionsSortable, + item, + ]); } - else { - setSelectedCollectionsSortable([...selectedCollectionsSortable, item]); - } - } + }; useEffect(() => { setCreateRequest({ @@ -85,35 +93,53 @@ const ManageCollections = ({ }, [selectedCollectionsSortable]); useEffect(() => { - // ProblemService.getAllAsCreator(accountId).then((response) => { - // setAllCollections(transformProblemModel2ProblemHashedTable(response.data.problems)); - // setAllCollectionsSortable( - // response.data.problems.map((problem) => ({ - // id: problem?.problem_id, - // name: problem?.title - // })) - // ); - // }); - CollectionService.getAllAsCreator(accountId).then((response) => { - setAllCollections(transformCollectionModel2CollectionHashedTable(response.data.collections)); + setAllCollections( + transformCollectionModel2CollectionHashedTable( + response.data.collections + ) + ); setAllCollectionsSortable( response.data.collections.map((collection) => ({ id: collection.collection_id, - name: collection.name + name: collection.name, })) ); - }) + }); }, [accountId]); + useEffect(() => { + if (createRequest.course) { + // setAllCollections({ + // ...allCollections, + // ...transformCollectionModel2CollectionHashedTable( + // createRequest.course.collections.map( + // (cc) => cc.collection + // ) as CollectionModel[] + // ), + // }); + + // console.log({ + // ...allCollections, + // ...transformCollectionModel2CollectionHashedTable( + // createRequest.course.collections.map( + // (cc) => cc.collection + // ) as CollectionModel[] + // ), + // }) + + } + console.log(createRequest.course) + }, [createRequest, allCollections]); + useEffect(() => { if (initial) { - setSelectedCollectionsSortable(createRequest.collectionsInterface) + setSelectedCollectionsSortable(createRequest.collectionsInterface); setInitial(false); } console.log("Create Request", createRequest); - },[createRequest]) + }, [createRequest]); return (
@@ -135,14 +161,24 @@ const ManageCollections = ({ setList={setSelectedCollectionsSortable} className="grid gap-y-2 p-2 rounded-md" > - {selectedCollectionsSortable?.map((item) => ( - handleRemoveSelectedCollection(item.id as string)} - key={item.id} - collection={allCollections[item.id as string] as CollectionPopulateProblemSecureModel} - /> - ))} + {selectedCollectionsSortable?.map( + (item) => ( + + handleRemoveSelectedCollection( + item.id as string + ) + } + key={item.id} + collection={ + allCollections[ + item.id as string + ] as CollectionPopulateProblemSecureModel + } + /> + ) + )}
@@ -170,12 +206,30 @@ const ManageCollections = ({ className="grid grid-cols-3 gap-2 p-2 rounded-md" > {allCollectionsSortable?.map((item) => ( -
+
handleQuickToggleSelectedCollection(item)} - disabled={selectedCollectionsSortableIds.includes(item.id as string)} + onClick={() => + handleQuickToggleSelectedCollection( + item + ) + } + disabled={selectedCollectionsSortableIds.includes( + item.id as string + )} key={item.id} - collection={allCollections[item.id as string] as CollectionPopulateProblemSecureModel} + collection={ + allCollections[ + item.id as string + ] as CollectionPopulateProblemSecureModel + } />
))} diff --git a/src/components/NavigationBar/NavSidebar.tsx b/src/components/NavigationBar/NavSidebar.tsx index 3f96154..88439f7 100644 --- a/src/components/NavigationBar/NavSidebar.tsx +++ b/src/components/NavigationBar/NavSidebar.tsx @@ -30,7 +30,7 @@ const NavSidebar = () => { const navigate = useNavigate(); const { isOpen, setIsOpen } = useContext(NavSidebarContext); - + const { section } = useContext(NavSidebarContext); const customIconBehaviour = (selected: boolean) => { diff --git a/src/layout/NavbarSidebarLayout.tsx b/src/layout/NavbarSidebarLayout.tsx index 107143f..24a5960 100644 --- a/src/layout/NavbarSidebarLayout.tsx +++ b/src/layout/NavbarSidebarLayout.tsx @@ -1,23 +1,30 @@ -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { Separator } from "../components/shadcn/Seperator"; import NavbarMenuLayout from "./NavbarMenuLayout"; import { useNavigate } from "react-router-dom"; import NavSidebar from "../components/NavigationBar/NavSidebar"; +import { LoginContext } from "../contexts/LoginContext"; const NavbarSidebarLayout = ({ children }: { children: React.ReactNode }) => { - const navigate = useNavigate(); const [close, setClose] = useState(false); + const { isLogin } = useContext(LoginContext); return ( -
- + { + isLogin ? ( +
+
{children}
+ ) : ( +

No Access

+ ) + } ); }; diff --git a/src/services/Topic.service.ts b/src/services/Topic.service.ts index be2bb5b..a4a13f2 100644 --- a/src/services/Topic.service.ts +++ b/src/services/Topic.service.ts @@ -19,8 +19,8 @@ export const TopicService: TopicSerivceAPI = { return response; }, - update: async (topicId, request) => { - const response = await axios.put(`${BASE_URL}/api/topics/${topicId}`, request); + update: async (topicId,accountId, request) => { + const response = await axios.put(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}`, request); return response; }, diff --git a/src/types/adapters/Collection.adapter.ts b/src/types/adapters/Collection.adapter.ts index 5f38ebf..cd271c6 100644 --- a/src/types/adapters/Collection.adapter.ts +++ b/src/types/adapters/Collection.adapter.ts @@ -1,7 +1,7 @@ import { CollectionHashedTable, CollectionModel, CollectionPopulateProblemSecureModel } from "../models/Collection.model"; export function transformCollectionModel2CollectionHashedTable(collections: CollectionModel[] | CollectionPopulateProblemSecureModel[] ): CollectionHashedTable { - let result = []; + let result:CollectionHashedTable = {}; for (const collection of collections) { result[collection.collection_id] = collection; } diff --git a/src/types/adapters/Topic.adapter.ts b/src/types/adapters/Topic.adapter.ts index d99b977..ee68d06 100644 --- a/src/types/adapters/Topic.adapter.ts +++ b/src/types/adapters/Topic.adapter.ts @@ -17,6 +17,7 @@ export function transformTopicPopulateTopicCollectionPopulateCollectionAndTopicG manageCourses: gp.permission_manage_topics, viewCourses: gp.permission_view_topics, viewCourseLogs: gp.permission_view_topics_log, - })) + })), + course: topic } } \ No newline at end of file diff --git a/src/types/apis/Topic.api.ts b/src/types/apis/Topic.api.ts index 7f7052f..5d9def5 100644 --- a/src/types/apis/Topic.api.ts +++ b/src/types/apis/Topic.api.ts @@ -3,6 +3,7 @@ import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroup export type GetAllTopicsByAccountResponse = { topics: TopicPopulateTopicCollectionPopulateCollectionModel[]; + manageable_topics: TopicPopulateTopicCollectionPopulateCollectionModel[]; } export type GetAllTopicsByAccessibleAccountResponse = { @@ -22,7 +23,7 @@ export type TopicSerivceAPI = { getAllAsCreator: (accountId:string) => Promise>; getAllAccessibleByAccount: (accountId:string) => Promise>; getPublicByAccount: (accountId:string,courseId:string) => Promise>; - update: (courseId:string,request: FormData) => Promise>; + update: (courseId:string,accountId:string,request: FormData) => Promise>; updateCollections: (topicId:string,collectionIds:string[]) => Promise>; updateGroupPermissions: (topicId:string,groups:CourseGroupPermissionCreateRequest[]) => Promise>; } \ No newline at end of file diff --git a/src/types/forms/CreateCourseRequestForm.ts b/src/types/forms/CreateCourseRequestForm.ts index 7dab5e8..f9e98a2 100644 --- a/src/types/forms/CreateCourseRequestForm.ts +++ b/src/types/forms/CreateCourseRequestForm.ts @@ -2,6 +2,7 @@ import { ItemInterface } from "react-sortablejs"; import { PlateEditorValueType } from "../PlateEditorValueType"; import { CoursePermissionRequestForm } from "./CreateGroupRequestForm"; import { GroupModel, TopicGroupPermissionPopulateGroupModel } from "../models/Group.model"; +import { TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; export type CourseGroupPermissionRequestForm = { group_id: string; @@ -15,4 +16,5 @@ export type CreateCourseRequestForm = { isPrivate?: boolean; collectionsInterface: ItemInterface[]; groupPermissions: CourseGroupPermissionRequestForm[]; + course: TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel | null; } \ No newline at end of file diff --git a/src/types/models/Collection.model.ts b/src/types/models/Collection.model.ts index 7fd2447..9129dbe 100644 --- a/src/types/models/Collection.model.ts +++ b/src/types/models/Collection.model.ts @@ -41,7 +41,7 @@ export type GetCollectionByAccountResponse = { } export type CollectionHashedTable = { - [id:string]: CollectionModel | CollectionPopulateProblemSecureModel + [collection_id:string]: CollectionModel | CollectionPopulateProblemSecureModel } export type CollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel = { diff --git a/src/views/My/Courses/EditCourse.tsx b/src/views/My/Courses/EditCourse.tsx index 503ea14..5d19dbe 100644 --- a/src/views/My/Courses/EditCourse.tsx +++ b/src/views/My/Courses/EditCourse.tsx @@ -37,8 +37,10 @@ const EditCourse = () => { const { formData, collectionIds, groups } = transformCreateCourseRequestForm2CreateTopicRequest(createRequest); + console.log(formData.get("name")) + setLoading(true); - TopicService.update(editCourseId, formData) + TopicService.update(editCourseId,accountId, formData) .then(() => { return TopicService.updateCollections( editCourseId, diff --git a/src/views/My/Courses/MyCourses.tsx b/src/views/My/Courses/MyCourses.tsx index ab9d5d6..ba567d7 100644 --- a/src/views/My/Courses/MyCourses.tsx +++ b/src/views/My/Courses/MyCourses.tsx @@ -9,6 +9,7 @@ import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; import { TopicService } from "../../../services/Topic.service"; import { TopicPopulateTopicCollectionPopulateCollectionModel } from "../../../types/models/Topic.model"; import { LibraryBig } from "lucide-react"; +import { Tabs, TabsList, TabsTrigger } from "../../../components/shadcn/Tabs"; const MyCourses = () => { @@ -17,11 +18,15 @@ const MyCourses = () => { const {setSection} = useContext(NavSidebarContext) const [topics, setTopics] = useState([]) + const [manageableTopics, setManageableTopics] = useState([]) + + const [tabValue, setTabValue] = useState("personal") useEffect(( )=> { setSection("COURSES") TopicService.getAllAsCreator(accountId).then((response) => { setTopics(response.data.topics) + setManageableTopics(response.data.manageable_topics) }) },[]) @@ -34,9 +39,21 @@ const MyCourses = () => { My Courses
-
+
+
+ setTabValue(e)}> + + + Personal + + + Manageable + + + +
diff --git a/src/components/Forms/CreateGroupForm/ManageMembers.tsx b/src/components/Forms/CreateGroupForm/ManageMembers.tsx index 6da1ecf..9e39d48 100644 --- a/src/components/Forms/CreateGroupForm/ManageMembers.tsx +++ b/src/components/Forms/CreateGroupForm/ManageMembers.tsx @@ -21,7 +21,7 @@ import { transformProblemModel2ProblemHashedTable } from "../../../types/adapter import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestForm"; import MyCollectionMiniCard from "../../Cards/CollectionCards/MyCollectionMiniCard"; import { CollectionService } from "../../../services/Collection.service"; -import { transformCollectionModel2CollectionHashedTable } from "../../../types/adapters/Collection.adapter"; +import { transformCollectionPopulateProblemSecureModel2CollectionHashedTable } from "../../../types/adapters/Collection.adapter"; import { CollectionHashedTable, CollectionPopulateProblemSecureModel, diff --git a/src/services/Collection.service.ts b/src/services/Collection.service.ts index be55615..ab34fb3 100644 --- a/src/services/Collection.service.ts +++ b/src/services/Collection.service.ts @@ -1,6 +1,6 @@ import axios from "axios"; -import { CollectionServiceAPI } from "../types/apis/Collection.api"; -import { CollectionModel, CollectionPopulateProblemSecureModel, CollectionProblemModel, GetCollectionByAccountResponse } from "../types/models/Collection.model"; +import { CollectionServiceAPI, GetCollectionByAccountResponse } from "../types/apis/Collection.api"; +import { CollectionModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../types/models/Collection.model"; import { BASE_URL } from "../constants/BackendBaseURL"; export const CollectionService: CollectionServiceAPI = { diff --git a/src/services/Topic.service.ts b/src/services/Topic.service.ts index a4a13f2..8f4f3ae 100644 --- a/src/services/Topic.service.ts +++ b/src/services/Topic.service.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { GetAllTopicsByAccountResponse, TopicSerivceAPI } from "../types/apis/Topic.api"; import { BASE_URL } from "../constants/BackendBaseURL"; -import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Topic.model"; +import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Topic.model"; export const TopicService: TopicSerivceAPI = { create: async (accountId, request) => { @@ -10,7 +10,7 @@ export const TopicService: TopicSerivceAPI = { }, get: async (accountId,topicId) => { - const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}`); + const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}`); return response; }, diff --git a/src/types/adapters/Collection.adapter.ts b/src/types/adapters/Collection.adapter.ts index cd271c6..d1f5c1e 100644 --- a/src/types/adapters/Collection.adapter.ts +++ b/src/types/adapters/Collection.adapter.ts @@ -1,6 +1,6 @@ -import { CollectionHashedTable, CollectionModel, CollectionPopulateProblemSecureModel } from "../models/Collection.model"; +import { CollectionHashedTable, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../models/Collection.model"; -export function transformCollectionModel2CollectionHashedTable(collections: CollectionModel[] | CollectionPopulateProblemSecureModel[] ): CollectionHashedTable { +export function transformCollectionPopulateProblemSecureModel2CollectionHashedTable(collections: CollectionPopulateCollectionProblemPopulateProblemModel[] ): CollectionHashedTable { let result:CollectionHashedTable = {}; for (const collection of collections) { result[collection.collection_id] = collection; diff --git a/src/types/apis/Collection.api.ts b/src/types/apis/Collection.api.ts index ede0367..f66ca8e 100644 --- a/src/types/apis/Collection.api.ts +++ b/src/types/apis/Collection.api.ts @@ -1,5 +1,9 @@ import { AxiosResponse } from "axios" -import { CollectionCreateRequest, CollectionModel, CollectionPopulateProblemSecureModel, CollectionProblemModel, CollectionUpdateRequest, GetCollectionByAccountResponse } from "../models/Collection.model" +import { CollectionCreateRequest, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateProblemSecureModel, CollectionProblemModel, CollectionUpdateRequest } from "../models/Collection.model" + +export type GetCollectionByAccountResponse = { + collections: CollectionPopulateCollectionProblemPopulateProblemModel[]; +} export type CollectionServiceAPI = { create: (accountId:string,request:CollectionCreateRequest) => Promise>; diff --git a/src/types/apis/Topic.api.ts b/src/types/apis/Topic.api.ts index 5d9def5..8328cf3 100644 --- a/src/types/apis/Topic.api.ts +++ b/src/types/apis/Topic.api.ts @@ -1,5 +1,5 @@ import { AxiosResponse } from "axios"; -import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../models/Topic.model"; +import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../models/Topic.model"; export type GetAllTopicsByAccountResponse = { topics: TopicPopulateTopicCollectionPopulateCollectionModel[]; @@ -19,7 +19,7 @@ export type CourseGroupPermissionCreateRequest = { export type TopicSerivceAPI = { create: (accountid: string,request: FormData) => Promise>; - get: (accountid: string,courseId:string) => Promise>; + get: (accountid: string,courseId:string) => Promise>; getAllAsCreator: (accountId:string) => Promise>; getAllAccessibleByAccount: (accountId:string) => Promise>; getPublicByAccount: (accountId:string,courseId:string) => Promise>; diff --git a/src/types/forms/CreateCourseRequestForm.ts b/src/types/forms/CreateCourseRequestForm.ts index f9e98a2..e35097b 100644 --- a/src/types/forms/CreateCourseRequestForm.ts +++ b/src/types/forms/CreateCourseRequestForm.ts @@ -2,7 +2,7 @@ import { ItemInterface } from "react-sortablejs"; import { PlateEditorValueType } from "../PlateEditorValueType"; import { CoursePermissionRequestForm } from "./CreateGroupRequestForm"; import { GroupModel, TopicGroupPermissionPopulateGroupModel } from "../models/Group.model"; -import { TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; +import { TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; export type CourseGroupPermissionRequestForm = { group_id: string; @@ -16,5 +16,5 @@ export type CreateCourseRequestForm = { isPrivate?: boolean; collectionsInterface: ItemInterface[]; groupPermissions: CourseGroupPermissionRequestForm[]; - course: TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel | null; + course: TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel | null; } \ No newline at end of file diff --git a/src/types/models/Collection.model.ts b/src/types/models/Collection.model.ts index 9129dbe..418bc11 100644 --- a/src/types/models/Collection.model.ts +++ b/src/types/models/Collection.model.ts @@ -29,6 +29,13 @@ export type CollectionUpdateRequest = { description?: string; } +export type CollectionProblemPopulateProblemModel = { + id: string; + problem: ProblemModel; + order: number; + collection: number; +} + export type CollectionProblemPopulateProblemSecureModel = { id: string; problem: ProblemSecureModel; @@ -36,12 +43,10 @@ export type CollectionProblemPopulateProblemSecureModel = { collection: number; } -export type GetCollectionByAccountResponse = { - collections: CollectionProblemModel[]; -} + export type CollectionHashedTable = { - [collection_id:string]: CollectionModel | CollectionPopulateProblemSecureModel + [collection_id:string]: CollectionPopulateCollectionProblemPopulateProblemModel } export type CollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel = { @@ -53,4 +58,9 @@ export type CollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulate export type CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel = CollectionModel & { problems: CollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel[] +} + + +export type CollectionPopulateCollectionProblemPopulateProblemModel = CollectionModel & { + problems: CollectionProblemPopulateProblemModel[]; } \ No newline at end of file diff --git a/src/types/models/Topic.model.ts b/src/types/models/Topic.model.ts index f3f55ff..ba739ec 100644 --- a/src/types/models/Topic.model.ts +++ b/src/types/models/Topic.model.ts @@ -1,4 +1,4 @@ -import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "./Collection.model" +import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "./Collection.model" import { TopicGroupPermissionPopulateGroupModel } from "./Group.model" export type TopicModel = { @@ -53,4 +53,13 @@ export type TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProb export type TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel = TopicModel & { collections: TopicCollectionPopulateCollectionModel[] group_permissions: TopicGroupPermissionPopulateGroupModel[] +} + +export type TopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemModel = TopicCollectionPopulateCollectionModel & { + collection: CollectionPopulateCollectionProblemPopulateProblemModel +} + +export type TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel = TopicModel & { + collections: TopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemModel[] + group_permissions: TopicGroupPermissionPopulateGroupModel[] } \ No newline at end of file From 03608281d44323c9940d41946c0baeaa3df399fd Mon Sep 17 00:00:00 2001 From: KanonKC Date: Fri, 5 Jan 2024 01:05:07 +0700 Subject: [PATCH 10/24] Disabled manage group (for non owner) --- src/components/Forms/CreateCourseForm/index.tsx | 4 ++++ src/types/models/Collection.model.ts | 2 +- src/types/models/Group.model.ts | 2 +- src/types/models/Problem.model.ts | 2 +- src/types/models/Topic.model.ts | 4 ++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/Forms/CreateCourseForm/index.tsx b/src/components/Forms/CreateCourseForm/index.tsx index 355801e..2679038 100644 --- a/src/components/Forms/CreateCourseForm/index.tsx +++ b/src/components/Forms/CreateCourseForm/index.tsx @@ -49,6 +49,9 @@ const CreateCourseForm = ({ onCourseSave: (callback: OnCourseSavedCallback) => void; // onCollectionSave: (callback: OnCollectionSavedCallback) => void; }) => { + + const accountId = String(localStorage.getItem("account_id")); + const navigate = useNavigate(); const [currentForm, setCurrentForm] = useState("general"); const [loading, setLoading] = useState(false); @@ -89,6 +92,7 @@ const CreateCourseForm = ({ setCurrentForm(tab.value) } diff --git a/src/types/models/Collection.model.ts b/src/types/models/Collection.model.ts index 418bc11..91400ed 100644 --- a/src/types/models/Collection.model.ts +++ b/src/types/models/Collection.model.ts @@ -2,7 +2,7 @@ import { ProblemModel, ProblemPopulateAccountAndSubmissionPopulateSubmissionTest export type CollectionModel = { collection_id: string; - creator: number; + creator: string; name: string; description: string | null; is_active: boolean; diff --git a/src/types/models/Group.model.ts b/src/types/models/Group.model.ts index e90b469..18b3b5e 100644 --- a/src/types/models/Group.model.ts +++ b/src/types/models/Group.model.ts @@ -2,7 +2,7 @@ import { AccountSecureModel } from "./Account.model"; export type GroupModel = { group_id: string; - creator: number; + creator: string; name: string; description: string | null; color: string; diff --git a/src/types/models/Problem.model.ts b/src/types/models/Problem.model.ts index d7302c3..430039a 100644 --- a/src/types/models/Problem.model.ts +++ b/src/types/models/Problem.model.ts @@ -19,7 +19,7 @@ export type ProblemModel = { is_active: boolean is_private: boolean submission_regex: string - creator: number + creator: string testcases: TestcaseModel[] created_date: string; updated_date: string; diff --git a/src/types/models/Topic.model.ts b/src/types/models/Topic.model.ts index ba739ec..523df98 100644 --- a/src/types/models/Topic.model.ts +++ b/src/types/models/Topic.model.ts @@ -3,7 +3,7 @@ import { TopicGroupPermissionPopulateGroupModel } from "./Group.model" export type TopicModel = { topic_id: string - creator: number + creator: string name: string description: string | null image_url: string | null @@ -15,7 +15,7 @@ export type TopicModel = { export type TopicSecureModel = { topic_id: string - creator: number + creator: string name: string description: string | null image_url: string | null From 11d35a1f134b0d9ecc8ee97334800a9a851b066a Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 6 Jan 2024 14:07:56 +0700 Subject: [PATCH 11/24] Collection Permission --- .../CollectionCards/MyCollectionCard.tsx | 12 +- .../CreateCollectionForm/ManageGroups.tsx | 101 ++++++++++++ .../Forms/CreateCollectionForm/index.tsx | 48 +++--- .../Forms/CreateCourseForm/ManageGroups.tsx | 97 ++++++++++- .../Forms/GroupAndPermissionManager.tsx | 151 +++++++++--------- .../CollectionPermissionSwitchGroup.tsx | 32 ++-- src/services/Collection.service.ts | 10 +- src/services/Topic.service.ts | 4 +- src/types/adapters/Collection.adapter.ts | 22 ++- .../CreateCollectionRequestForm.adapter.ts | 19 ++- src/types/apis/Collection.api.ts | 11 +- src/types/apis/Topic.api.ts | 2 +- .../forms/CreateCollectionRequestForm.ts | 8 + src/types/forms/CreateCourseRequestForm.ts | 4 +- src/types/models/Collection.model.ts | 19 +++ src/views/My/Collections/CreateCollection.tsx | 10 +- src/views/My/Collections/EditCollection.tsx | 38 ++--- src/views/My/Collections/MyCollections.tsx | 4 +- src/views/My/Courses/EditCourse.tsx | 22 +-- 19 files changed, 433 insertions(+), 181 deletions(-) create mode 100644 src/components/Forms/CreateCollectionForm/ManageGroups.tsx diff --git a/src/components/Cards/CollectionCards/MyCollectionCard.tsx b/src/components/Cards/CollectionCards/MyCollectionCard.tsx index c630191..b9dde4d 100644 --- a/src/components/Cards/CollectionCards/MyCollectionCard.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionCard.tsx @@ -1,17 +1,15 @@ -import React, { useState } from "react"; -import { Card, CardContent, CardTitle } from "../../shadcn/Card"; -import { Button } from "../../shadcn/Button"; -import { Check, CheckCircle2, FileSpreadsheet, Folder, X } from "lucide-react"; +import { FileSpreadsheet, Folder } from "lucide-react"; +import { useState } from "react"; import { useNavigate } from "react-router-dom"; -import { ProblemModel } from "../../../types/models/Problem.model"; +import { CollectionPopulateCollectionProblemPopulateProblemModel, CollectionProblemModel } from "../../../types/models/Collection.model"; import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; import Checkmark from "../../Checkmark"; -import { CollectionProblemModel, CollectionProblemPopulateProblemSecureModel, GetCollectionByAccountResponse } from "../../../types/models/Collection.model"; +import { Card, CardContent } from "../../shadcn/Card"; const MyCollectionCard = ({ collection }:{ - collection: CollectionProblemModel + collection: CollectionPopulateCollectionProblemPopulateProblemModel }) => { const navigate = useNavigate(); diff --git a/src/components/Forms/CreateCollectionForm/ManageGroups.tsx b/src/components/Forms/CreateCollectionForm/ManageGroups.tsx new file mode 100644 index 0000000..964677f --- /dev/null +++ b/src/components/Forms/CreateCollectionForm/ManageGroups.tsx @@ -0,0 +1,101 @@ +import React, { useEffect, useState } from "react"; +import { + CollectionnGroupPermissionRequestForm, + CreateCollectionRequestForm, +} from "../../../types/forms/CreateCollectionRequestForm"; +import GroupAndPermissionManager, { GroupAndPermissionManagerOnAddGroupsCallback, GroupAndPermissionManagerOnRemoveGroupCallback } from "../GroupAndPermissionManager"; +import CollectionPermissionSwitchGroup from "../PermissionSwitchGroups/CollectionPermissionSwitchGroup"; + +const ManageGroups = ({ + createRequest, + setCreateRequest, +}: { + createRequest: CreateCollectionRequestForm; + setCreateRequest: React.Dispatch< + React.SetStateAction + >; +}) => { + const [groupPermission, setGroupPermission] = + useState(); + + const [selectedIndex, setSelectedIndex] = useState(-1); + + const handleAddGroups = ({ + addingGroups, + }: GroupAndPermissionManagerOnAddGroupsCallback) => { + const newGroupPermissions = addingGroups.map((group) => ({ + group_id: group.group_id, + group, + manageCollections: group.permission_manage_collections, + viewCollections: group.permission_view_collections, + })); + + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions, + ...newGroupPermissions, + ], + }); + }; + + const handleRemoveGroup = ({index}:GroupAndPermissionManagerOnRemoveGroupCallback) => { + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions.slice(0, index), + ...createRequest.groupPermissions.slice(index + 1), + ], + }); + } + + useEffect(() => { + setGroupPermission(createRequest.groupPermissions[selectedIndex]); + }, [selectedIndex]); + + useEffect(() => { + if (groupPermission) { + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions.slice(0, selectedIndex), + groupPermission, + ...createRequest.groupPermissions.slice(selectedIndex + 1), + ], + }); + } + }, [groupPermission]); + + return ( + handleAddGroups(e)} + onRemoveGroup={(e) => handleRemoveGroup(e)} + selectedIndex={selectedIndex} + setSelectedIndex={setSelectedIndex} + > + {groupPermission && selectedIndex >= 0 && ( + { + setGroupPermission({ + ...groupPermission, + manageCollections: + !groupPermission.manageCollections, + }); + }} + onClickViewCollections={() => { + setGroupPermission({ + ...groupPermission, + viewCollections: !groupPermission.viewCollections, + }); + }} + /> + )} + + ); +}; + +export default ManageGroups; diff --git a/src/components/Forms/CreateCollectionForm/index.tsx b/src/components/Forms/CreateCollectionForm/index.tsx index 1464472..1ad713d 100644 --- a/src/components/Forms/CreateCollectionForm/index.tsx +++ b/src/components/Forms/CreateCollectionForm/index.tsx @@ -16,6 +16,7 @@ import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollecti import ManageProblem from "./ManageProblems"; import ManageProblems from "./ManageProblems"; import FormSaveButton from "../FormSaveButton"; +import ManageGroups from "./ManageGroups"; const TabList = [ { @@ -26,37 +27,41 @@ const TabList = [ value: "problems", label: "Manage Problems", }, -] + { + value: "groups", + label: "Manage Groups & Permissions", + }, +]; export type OnCollectionSavedCallback = { - setLoading?: React.Dispatch> - createRequest?: CreateCollectionRequestForm -} + setLoading?: React.Dispatch>; + createRequest?: CreateCollectionRequestForm; +}; const CreateCollectionForm = ({ createRequestInitialValue, onCollectionSave, }: { - createRequestInitialValue: CreateCollectionRequestForm - onCollectionSave: (callback: OnCollectionSavedCallback) => void + createRequestInitialValue: CreateCollectionRequestForm; + onCollectionSave: (callback: OnCollectionSavedCallback) => void; }) => { - - const navigate = useNavigate(); - const [currentForm, setCurrentForm] = useState("general"); + const navigate = useNavigate(); + const [currentForm, setCurrentForm] = useState("general"); const [loading, setLoading] = useState(false); - const [createRequest, setCreateRequest] = useState(createRequestInitialValue) + const [createRequest, setCreateRequest] = + useState(createRequestInitialValue); const handleSave = () => { onCollectionSave({ setLoading, createRequest, - }) - } + }); + }; - return ( -
+ return ( +
-

+

)} + + {currentForm === "groups" && ( + + )}

- ) -} + ); +}; -export default CreateCollectionForm \ No newline at end of file +export default CreateCollectionForm; diff --git a/src/components/Forms/CreateCourseForm/ManageGroups.tsx b/src/components/Forms/CreateCourseForm/ManageGroups.tsx index c0b9c73..9e686b9 100644 --- a/src/components/Forms/CreateCourseForm/ManageGroups.tsx +++ b/src/components/Forms/CreateCourseForm/ManageGroups.tsx @@ -1,6 +1,13 @@ -import React from "react"; -import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; -import GroupAndPermissionManager from "../GroupAndPermissionManager"; +import React, { useEffect, useState } from "react"; +import { + CourseGroupPermissionRequestForm, + CreateCourseRequestForm, +} from "../../../types/forms/CreateCourseRequestForm"; +import GroupAndPermissionManager, { + GroupAndPermissionManagerOnAddGroupsCallback, + GroupAndPermissionManagerOnRemoveGroupCallback, +} from "../GroupAndPermissionManager"; +import CoursePermissionSwitchGroup from "../PermissionSwitchGroups/CoursePermissionSwitchGroup"; const ManageGroups = ({ createRequest, @@ -11,11 +18,93 @@ const ManageGroups = ({ React.SetStateAction >; }) => { + const [groupPermission, setGroupPermission] = + useState(); + + const [selectedIndex, setselectedIndex] = useState(-1); + + const handleAddGroups = ({ + addingGroups, + }: GroupAndPermissionManagerOnAddGroupsCallback) => { + const newGroupPermissions = addingGroups.map((group) => ({ + group_id: group.group_id, + group, + manageCourses: group.permission_manage_topics, + viewCourseLogs: group.permission_view_topics_log, + viewCourses: group.permission_view_topics, + })); + + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions, + ...newGroupPermissions, + ], + }); + }; + + const handleRemoveGroup = ({index}:GroupAndPermissionManagerOnRemoveGroupCallback) => { + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions.slice(0, index), + ...createRequest.groupPermissions.slice(index + 1), + ], + }); + } + + useEffect(() => { + setGroupPermission(createRequest.groupPermissions[selectedIndex]); + }, [selectedIndex]); + + useEffect(() => { + if (groupPermission) { + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions.slice(0, selectedIndex), + groupPermission, + ...createRequest.groupPermissions.slice(selectedIndex + 1), + ], + }); + } + }, [groupPermission]); + return ( + onAddGroups={(e) => handleAddGroups(e)} + onRemoveGroup={(e) => handleRemoveGroup(e)} + selectedIndex={selectedIndex} + setSelectedIndex={setselectedIndex} + > + {groupPermission && selectedIndex >= 0 && ( + + setGroupPermission({ + ...groupPermission, + manageCourses: !groupPermission.manageCourses, + }) + } + onClickViewCourseLogs={() => + setGroupPermission({ + ...groupPermission, + viewCourseLogs: !groupPermission.viewCourseLogs, + }) + } + onClickViewCourses={() => + setGroupPermission({ + ...groupPermission, + viewCourses: !groupPermission.viewCourses, + }) + } + /> + )} + ); }; diff --git a/src/components/Forms/GroupAndPermissionManager.tsx b/src/components/Forms/GroupAndPermissionManager.tsx index 20c6cc3..1f388bc 100644 --- a/src/components/Forms/GroupAndPermissionManager.tsx +++ b/src/components/Forms/GroupAndPermissionManager.tsx @@ -7,7 +7,7 @@ import { CourseGroupPermissionRequestForm, CreateCourseRequestForm, } from "../../types/forms/CreateCourseRequestForm"; -import { PlusCircle, Users, X } from "lucide-react"; +import { Eye, PlusCircle, Users, X } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "../shadcn/Tooltip"; import { Dialog, DialogContent } from "../shadcn/Dialog"; import { Input } from "../shadcn/Input"; @@ -22,6 +22,17 @@ import { ContextMenuItem, ContextMenuTrigger, } from "../shadcn/ContextMenu"; +import { CreateCollectionRequestForm } from "../../types/forms/CreateCollectionRequestForm"; +import { set } from 'react-hook-form'; +import { useNavigate } from "react-router-dom"; + +export type GroupAndPermissionManagerOnAddGroupsCallback = { + addingGroups: GroupModel[]; +} + +export type GroupAndPermissionManagerOnRemoveGroupCallback = { + index: number; +} const GroupListItem = ({ hexColor = "#000000", @@ -63,14 +74,20 @@ const GroupListItem = ({ const GroupListItemContextMenu = ({ children, onClickRemove = () => {}, + onClickViewGroup = () => {}, }: { children: React.ReactNode; onClickRemove?: () => void; + onClickViewGroup?: () => void; }) => { return ( {children} + onClickViewGroup()}> + + View Group + onClickRemove()}> Remove @@ -83,22 +100,34 @@ const GroupListItemContextMenu = ({ const GroupAndPermissionManager = ({ createRequest, setCreateRequest, + onAddGroups=()=>{}, + onRemoveGroup=()=>{}, + selectedIndex=-1, + setSelectedIndex=()=>{}, + children, }: { - createRequest: CreateCourseRequestForm; + createRequest: CreateCourseRequestForm | CreateCollectionRequestForm; setCreateRequest: React.Dispatch< - React.SetStateAction + React.SetStateAction> | React.Dispatch< + React.SetStateAction >; + onAddGroups?: (e: GroupAndPermissionManagerOnAddGroupsCallback) => void; + onRemoveGroup?: (e: GroupAndPermissionManagerOnRemoveGroupCallback) => void; + selectedIndex?: number; + setSelectedIndex?: React.Dispatch>; + children: React.ReactNode; }) => { const accountId = String(localStorage.getItem("account_id")); + const navigate = useNavigate(); const [allGroups, setAllGroups] = useState([]); const [openAddGroupsDialog, setOpenAddGroupsDialog] = useState(false); const [selectedGroupIds, setSelectedGroupIds] = useState([]); - const [groupPermission, setGroupPermission] = - useState(); - const [selectedIndex, setselectedIndex] = useState(-1); + // const [groupPermission, setGroupPermission] = + // useState(); + // const [selectedIndex, setselectedIndex] = useState(-1); const handleSelectGroupCheckbox = (groupId: string) => { if (selectedGroupIds.includes(groupId)) { @@ -109,13 +138,14 @@ const GroupAndPermissionManager = ({ }; const handleRemoveGroupPermission = (index: number) => { - setCreateRequest({ - ...createRequest, - groupPermissions: [ - ...createRequest.groupPermissions.slice(0, index), - ...createRequest.groupPermissions.slice(index + 1), - ], - }); + // setCreateRequest({ + // ...createRequest, + // groupPermissions: [ + // ...createRequest.groupPermissions.slice(0, index), + // ...createRequest.groupPermissions.slice(index + 1), + // ], + // }); + onRemoveGroup({index}) } const getNotInPermissionGroup = () => { @@ -128,45 +158,47 @@ const GroupAndPermissionManager = ({ }; const handleAddGroups = () => { - const addGroups = allGroups.filter((group) => + const addingGroups = allGroups.filter((group) => selectedGroupIds.includes(group.group_id) ); - const newGroupPermissions = addGroups.map((group) => ({ - group_id: group.group_id, - group, - manageCourses: group.permission_manage_topics, - viewCourseLogs: group.permission_view_topics_log, - viewCourses: group.permission_view_topics, - })); + // const newGroupPermissions = addingGroups.map((group) => ({ + // group_id: group.group_id, + // group, + // manageCourses: group.permission_manage_topics, + // viewCourseLogs: group.permission_view_topics_log, + // viewCourses: group.permission_view_topics, + // })); - setCreateRequest({ - ...createRequest, - groupPermissions: [ - ...createRequest.groupPermissions, - ...newGroupPermissions, - ], - }); + // setCreateRequest({ + // ...createRequest, + // groupPermissions: [ + // ...createRequest.groupPermissions, + // ...newGroupPermissions, + // ], + // }); + + onAddGroups({addingGroups}) setSelectedGroupIds([]); setOpenAddGroupsDialog(false); }; - useEffect(() => { - setGroupPermission(createRequest?.groupPermissions[selectedIndex]); - }, [selectedIndex]); + // useEffect(() => { + // setGroupPermission(createRequest.groupPermissions[selectedIndex]); + // }, [selectedIndex]); - useEffect(() => { - if (groupPermission) { - setCreateRequest({ - ...createRequest, - groupPermissions: [ - ...createRequest.groupPermissions.slice(0, selectedIndex), - groupPermission, - ...createRequest.groupPermissions.slice(selectedIndex + 1), - ], - }); - } - }, [groupPermission]); + // useEffect(() => { + // if (groupPermission) { + // setCreateRequest({ + // ...createRequest, + // groupPermissions: [ + // ...createRequest.groupPermissions.slice(0, selectedIndex), + // groupPermission, + // ...createRequest.groupPermissions.slice(selectedIndex + 1), + // ], + // }); + // } + // }, [groupPermission]); useEffect(() => { GroupService.getAllAsCreator(accountId).then((response) => { @@ -196,8 +228,9 @@ const GroupAndPermissionManager = ({
{createRequest.groupPermissions.map( (groupPermission, index) => ( -
setselectedIndex(index)}> +
setSelectedIndex(index)}> navigate(`/my/groups/${groupPermission.group.group_id}`)} onClickRemove={() => handleRemoveGroupPermission(index)} >
- {groupPermission && selectedIndex >= 0 && ( - - setGroupPermission({ - ...groupPermission, - manageCourses: - !groupPermission.manageCourses, - }) - } - onClickViewCourseLogs={() => - setGroupPermission({ - ...groupPermission, - viewCourseLogs: - !groupPermission.viewCourseLogs, - }) - } - onClickViewCourses={() => - setGroupPermission({ - ...groupPermission, - viewCourses: !groupPermission.viewCourses, - }) - } - /> - )} + {children}
diff --git a/src/components/Forms/PermissionSwitchGroups/CollectionPermissionSwitchGroup.tsx b/src/components/Forms/PermissionSwitchGroups/CollectionPermissionSwitchGroup.tsx index d09e65c..c51b3aa 100644 --- a/src/components/Forms/PermissionSwitchGroups/CollectionPermissionSwitchGroup.tsx +++ b/src/components/Forms/PermissionSwitchGroups/CollectionPermissionSwitchGroup.tsx @@ -4,13 +4,15 @@ import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestF import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; const CollectionPermissionSwitchGroup = ({ - createRequest, - setCreateRequest, + manageCollectionsChecked = false, + viewCollectionsChecked = false, + onClickManageCollections = () => {}, + onClickViewCollections = () => {}, }: { - createRequest: CreateGroupRequestForm; - setCreateRequest: React.Dispatch< - React.SetStateAction - >; + manageCollectionsChecked?: boolean; + viewCollectionsChecked?: boolean; + onClickManageCollections?: () => void | undefined; + onClickViewCollections?: () => void | undefined; }) => { return ( <> @@ -18,25 +20,15 @@ const CollectionPermissionSwitchGroup = ({ title="Manage Collections" description="Can edit collections name and description. Can add or remove problems from collection as well." - checked={createRequest.manageCollections} - onClick={() => - setCreateRequest({ - ...createRequest, - manageCollections: !createRequest.manageCollections, - }) - } + checked={manageCollectionsChecked} + onClick={() => onClickManageCollections()} /> - setCreateRequest({ - ...createRequest, - viewCollections: !createRequest.viewCollections, - }) - } + checked={viewCollectionsChecked} + onClick={() => onClickViewCollections()} /> ); diff --git a/src/services/Collection.service.ts b/src/services/Collection.service.ts index ab34fb3..482f5d3 100644 --- a/src/services/Collection.service.ts +++ b/src/services/Collection.service.ts @@ -1,6 +1,6 @@ import axios from "axios"; import { CollectionServiceAPI, GetCollectionByAccountResponse } from "../types/apis/Collection.api"; -import { CollectionModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../types/models/Collection.model"; +import { CollectionModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../types/models/Collection.model"; import { BASE_URL } from "../constants/BackendBaseURL"; export const CollectionService: CollectionServiceAPI = { @@ -8,8 +8,8 @@ export const CollectionService: CollectionServiceAPI = { return axios.post(`${BASE_URL}/api/accounts/${accountId}/collections`,request); }, - get: (collectionId) => { - return axios.get(`${BASE_URL}/api/collections/${collectionId}`); + get: (collectionId,accountId) => { + return axios.get(`${BASE_URL}/api/accounts/${accountId}/collections/${collectionId}`); }, getAllAsCreator: (accountId) => { @@ -31,5 +31,9 @@ export const CollectionService: CollectionServiceAPI = { updateProblem: (collectionId,problemIds) => { return axios.put(`${BASE_URL}/api/collections/${collectionId}/problems/update`,{problem_ids: problemIds}); }, + + updateGroupPermissions: (collectionId,accountId,groups) => { + return axios.put(`${BASE_URL}/api/accounts/${accountId}/collections/${collectionId}/groups`,{groups}); + } } \ No newline at end of file diff --git a/src/services/Topic.service.ts b/src/services/Topic.service.ts index 8f4f3ae..ba7a482 100644 --- a/src/services/Topic.service.ts +++ b/src/services/Topic.service.ts @@ -41,8 +41,8 @@ export const TopicService: TopicSerivceAPI = { return response; }, - updateGroupPermissions: async (topicId, groups) => { - const response = await axios.put(`${BASE_URL}/api/topics/${topicId}/groups`, { + updateGroupPermissions: async (topicId,accountId, groups) => { + const response = await axios.put(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}/groups`, { groups: groups }); return response; diff --git a/src/types/adapters/Collection.adapter.ts b/src/types/adapters/Collection.adapter.ts index d1f5c1e..69ec849 100644 --- a/src/types/adapters/Collection.adapter.ts +++ b/src/types/adapters/Collection.adapter.ts @@ -1,4 +1,5 @@ -import { CollectionHashedTable, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../models/Collection.model"; +import { CreateCollectionRequestForm } from "../forms/CreateCollectionRequestForm"; +import { CollectionHashedTable, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../models/Collection.model"; export function transformCollectionPopulateProblemSecureModel2CollectionHashedTable(collections: CollectionPopulateCollectionProblemPopulateProblemModel[] ): CollectionHashedTable { let result:CollectionHashedTable = {}; @@ -6,4 +7,23 @@ export function transformCollectionPopulateProblemSecureModel2CollectionHashedTa result[collection.collection_id] = collection; } return result; +} + +export function transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest(collection: CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel): CreateCollectionRequestForm { + + console.log(collection); + return { + title: collection.name, + description: JSON.parse(String(collection.description)), + problemsInterface: collection.problems.map((cp) => ({ + id: cp.problem.problem_id, + name: cp.problem.title, + })), + groupPermissions: collection.group_permissions.map((cgp) => ({ + group_id: cgp.group.group_id, + group: cgp.group, + manageCollections: cgp.permission_manage_collections, + viewCollections: cgp.permission_view_collections, + })), + } } \ No newline at end of file diff --git a/src/types/adapters/CreateCollectionRequestForm.adapter.ts b/src/types/adapters/CreateCollectionRequestForm.adapter.ts index f662efe..499791e 100644 --- a/src/types/adapters/CreateCollectionRequestForm.adapter.ts +++ b/src/types/adapters/CreateCollectionRequestForm.adapter.ts @@ -1,10 +1,25 @@ +import { CollectionGroupPermissionCreateRequest } from "../apis/Collection.api"; import { CreateCollectionRequestForm } from "../forms/CreateCollectionRequestForm"; import { CollectionCreateRequest } from "../models/Collection.model"; -export function transformCreateCollectionRequestForm2CreateCollectionRequestForm(createRequest:CreateCollectionRequestForm): CollectionCreateRequest { - return { +export function transformCreateCollectionRequestForm2CreateCollectionRequestForm(createRequest:CreateCollectionRequestForm): { + request: CollectionCreateRequest + problemIds: string[] + groups: CollectionGroupPermissionCreateRequest[] +} { + const request = { name: createRequest.title, description: JSON.stringify(createRequest.description), }; + + const problemIds = createRequest.problemsInterface.map((problem) => problem.id as string); + + const groups = createRequest.groupPermissions.map((groupPermission) => ({ + group_id: groupPermission.group_id, + permission_manage_collections: groupPermission.manageCollections, + permission_view_collections: groupPermission.viewCollections, + })); + + return { request, problemIds, groups }; } diff --git a/src/types/apis/Collection.api.ts b/src/types/apis/Collection.api.ts index f66ca8e..b575ace 100644 --- a/src/types/apis/Collection.api.ts +++ b/src/types/apis/Collection.api.ts @@ -1,16 +1,23 @@ import { AxiosResponse } from "axios" -import { CollectionCreateRequest, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateProblemSecureModel, CollectionProblemModel, CollectionUpdateRequest } from "../models/Collection.model" +import { CollectionCreateRequest, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel, CollectionUpdateRequest } from "../models/Collection.model" export type GetCollectionByAccountResponse = { collections: CollectionPopulateCollectionProblemPopulateProblemModel[]; } +export type CollectionGroupPermissionCreateRequest = { + group_id: string; + permission_manage_collections?: boolean + permission_view_collections?: boolean +} + export type CollectionServiceAPI = { create: (accountId:string,request:CollectionCreateRequest) => Promise>; - get: (collectionId:string) => Promise>; + get: (collectionId:string,accountId:string) => Promise>; getAllAsCreator: (accountId:string) => Promise>; update: (collectionId:string,request:CollectionUpdateRequest) => Promise>; addProblem: (collectionId:string,problemIds:string[]) => Promise>; removeProblem: (collectionId:string,problemIds:string[]) => Promise>; updateProblem: (collectionId:string,problemIds:string[]) => Promise>; + updateGroupPermissions: (collectionId:string,accountId:string,groups:CollectionGroupPermissionCreateRequest[]) => Promise>; } \ No newline at end of file diff --git a/src/types/apis/Topic.api.ts b/src/types/apis/Topic.api.ts index 8328cf3..9a15e6b 100644 --- a/src/types/apis/Topic.api.ts +++ b/src/types/apis/Topic.api.ts @@ -25,5 +25,5 @@ export type TopicSerivceAPI = { getPublicByAccount: (accountId:string,courseId:string) => Promise>; update: (courseId:string,accountId:string,request: FormData) => Promise>; updateCollections: (topicId:string,collectionIds:string[]) => Promise>; - updateGroupPermissions: (topicId:string,groups:CourseGroupPermissionCreateRequest[]) => Promise>; + updateGroupPermissions: (topicId:string,accountId:string,groups:CourseGroupPermissionCreateRequest[]) => Promise>; } \ No newline at end of file diff --git a/src/types/forms/CreateCollectionRequestForm.ts b/src/types/forms/CreateCollectionRequestForm.ts index 74300ae..996c99a 100644 --- a/src/types/forms/CreateCollectionRequestForm.ts +++ b/src/types/forms/CreateCollectionRequestForm.ts @@ -1,9 +1,17 @@ import { ItemInterface } from "react-sortablejs"; import { CollectionProblemPopulateProblemSecureModel } from "../models/Collection.model"; import { PlateEditorValueType } from "../PlateEditorValueType"; +import { GroupModel } from "../models/Group.model"; +import { CollectionPermissionRequestForm } from "./CreateGroupRequestForm"; + +export type CollectionnGroupPermissionRequestForm = { + group_id: string; + group: GroupModel; +} & CollectionPermissionRequestForm export type CreateCollectionRequestForm = { title: string; description: PlateEditorValueType; problemsInterface: ItemInterface[]; + groupPermissions: CollectionnGroupPermissionRequestForm[]; } \ No newline at end of file diff --git a/src/types/forms/CreateCourseRequestForm.ts b/src/types/forms/CreateCourseRequestForm.ts index e35097b..4c6208d 100644 --- a/src/types/forms/CreateCourseRequestForm.ts +++ b/src/types/forms/CreateCourseRequestForm.ts @@ -16,5 +16,5 @@ export type CreateCourseRequestForm = { isPrivate?: boolean; collectionsInterface: ItemInterface[]; groupPermissions: CourseGroupPermissionRequestForm[]; - course: TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel | null; -} \ No newline at end of file + course: TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel | TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel | null; +} diff --git a/src/types/models/Collection.model.ts b/src/types/models/Collection.model.ts index 91400ed..1e0d856 100644 --- a/src/types/models/Collection.model.ts +++ b/src/types/models/Collection.model.ts @@ -1,3 +1,4 @@ +import { GroupModel } from "./Group.model"; import { ProblemModel, ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, ProblemSecureModel } from "./Problem.model"; export type CollectionModel = { @@ -63,4 +64,22 @@ export type CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAnd export type CollectionPopulateCollectionProblemPopulateProblemModel = CollectionModel & { problems: CollectionProblemPopulateProblemModel[]; +} + +export type CollectionGroupPermissionModel = { + collection_group_permission_id: string + group: string + permission_manage_collections: boolean + permission_view_collections: boolean + collection: string +} + + +export type CollectionGroupPermissionPopulateGroupModel = CollectionGroupPermissionModel & { + group: GroupModel +} + +export type CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel = CollectionModel & { + problems: CollectionProblemPopulateProblemModel[]; + group_permissions: CollectionGroupPermissionPopulateGroupModel[]; } \ No newline at end of file diff --git a/src/views/My/Collections/CreateCollection.tsx b/src/views/My/Collections/CreateCollection.tsx index de167ca..a10c2ba 100644 --- a/src/views/My/Collections/CreateCollection.tsx +++ b/src/views/My/Collections/CreateCollection.tsx @@ -20,6 +20,7 @@ const formInitialValue: CreateCollectionRequestForm = { }, ], problemsInterface: [], + groupPermissions: [], }; const CreateCollection = () => { @@ -33,20 +34,17 @@ const CreateCollection = () => { return } - const createCollectionRequest = + const {request,problemIds} = transformCreateCollectionRequestForm2CreateCollectionRequestForm( createRequest as CreateCollectionRequestForm ); - const problemIds = (createRequest as CreateCollectionRequestForm).problemsInterface.map( - (problem) => problem.id as string - ); + setLoading(true) - CollectionService.create(accountId,createCollectionRequest).then(response => { + CollectionService.create(accountId,request).then(response => { return CollectionService.updateProblem(response.data.collection_id,problemIds) }).then(response => { - // setCollectionId(response.data.collection_id) toast({ title: "Create Completed" }) diff --git a/src/views/My/Collections/EditCollection.tsx b/src/views/My/Collections/EditCollection.tsx index 15c2719..9cf7ccd 100644 --- a/src/views/My/Collections/EditCollection.tsx +++ b/src/views/My/Collections/EditCollection.tsx @@ -1,18 +1,17 @@ import React, { useEffect } from 'react' -import NavbarSidebarLayout from '../../../layout/NavbarSidebarLayout' -import CreateCollectionForm, { OnCollectionSavedCallback } from '../../../components/Forms/CreateCollectionForm' -import { CreateCollectionRequestForm } from '../../../types/forms/CreateCollectionRequestForm' import { useParams } from 'react-router-dom' +import CreateCollectionForm, { OnCollectionSavedCallback } from '../../../components/Forms/CreateCollectionForm' +import { toast } from '../../../components/shadcn/UseToast' +import NavbarSidebarLayout from '../../../layout/NavbarSidebarLayout' import { CollectionService } from '../../../services/Collection.service' -import { ItemInterface } from 'react-sortablejs' -import { transformCreateProblemRequestForm2CreateProblemRequest } from '../../../types/adapters/CreateProblemRequestForm.adapter' +import { transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest } from '../../../types/adapters/Collection.adapter' import { transformCreateCollectionRequestForm2CreateCollectionRequestForm } from '../../../types/adapters/CreateCollectionRequestForm.adapter' -import { set } from 'react-hook-form' -import { toast } from '../../../components/shadcn/UseToast' -import { handleDeprecatedDescription } from '../../../utilities/HandleDeprecatedDescription' +import { CreateCollectionRequestForm } from '../../../types/forms/CreateCollectionRequestForm' const EditCollection = () => { + const accountId = String(localStorage.getItem("account_id")); + const {collectionId} = useParams(); const editCollectionId = String(collectionId); @@ -24,15 +23,15 @@ const EditCollection = () => { return; } - const problemIds = (createRequest as CreateCollectionRequestForm).problemsInterface.map( - (problem) => problem.id as string - ); - const request = transformCreateCollectionRequestForm2CreateCollectionRequestForm(createRequest as CreateCollectionRequestForm) + + const {request,problemIds,groups} = transformCreateCollectionRequestForm2CreateCollectionRequestForm(createRequest as CreateCollectionRequestForm) setLoading(true) CollectionService.update(editCollectionId,request).then(response => { return CollectionService.updateProblem(response.data.collection_id,problemIds) + }).then(response => { + return CollectionService.updateGroupPermissions(response.data.collection_id,accountId,groups) }).then(response => { setLoading(false) console.log("Save") @@ -43,17 +42,10 @@ const EditCollection = () => { } useEffect(()=> { - CollectionService.get(editCollectionId).then(response => { - setCreateRequest({ - title: response.data.name, - description: JSON.parse(handleDeprecatedDescription(String(response.data.description))), - problemsInterface: response.data.problems.map(collectionProblem => ( - { - id: collectionProblem.problem.problem_id, - name: collectionProblem.problem.title - } as ItemInterface - )) - }) + CollectionService.get(editCollectionId,accountId).then(response => { + setCreateRequest( + transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest(response.data) + ) }) },[editCollectionId]) diff --git a/src/views/My/Collections/MyCollections.tsx b/src/views/My/Collections/MyCollections.tsx index 0debbd2..00fd826 100644 --- a/src/views/My/Collections/MyCollections.tsx +++ b/src/views/My/Collections/MyCollections.tsx @@ -7,14 +7,14 @@ import { useNavigate } from "react-router-dom"; import CardContainer from "../../../components/CardContainer"; import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; import { CollectionService } from "../../../services/Collection.service"; -import { CollectionModel, CollectionProblemModel } from "../../../types/models/Collection.model"; +import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionProblemModel } from "../../../types/models/Collection.model"; import { FolderPlus } from "lucide-react"; const MyCollections = () => { const navigate = useNavigate(); const accountId = String(localStorage.getItem("account_id")); - const [collections, setCollections] = useState([]); + const [collections, setCollections] = useState([]); const {setSection} = useContext(NavSidebarContext) useEffect(() => { diff --git a/src/views/My/Courses/EditCourse.tsx b/src/views/My/Courses/EditCourse.tsx index 5d19dbe..0acea31 100644 --- a/src/views/My/Courses/EditCourse.tsx +++ b/src/views/My/Courses/EditCourse.tsx @@ -1,22 +1,14 @@ -import React, { useEffect, useState } from "react"; -import NavbarSidebarLayout from "../../../layout/NavbarSidebarLayout"; -import CreateCollectionForm, { - OnCollectionSavedCallback, -} from "../../../components/Forms/CreateCollectionForm"; -import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; -import { ELEMENT_PARAGRAPH } from "@udecode/plate-paragraph"; -import { transformCreateCollectionRequestForm2CreateCollectionRequestForm } from "../../../types/adapters/CreateCollectionRequestForm.adapter"; -import { CollectionService } from "../../../services/Collection.service"; -import { toast } from "../../../components/shadcn/UseToast"; -import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; import CreateCourseForm, { OnCourseSavedCallback, } from "../../../components/Forms/CreateCourseForm"; -import { transformCreateCourseRequestForm2CreateTopicRequest } from "../../../types/adapters/CreateCourseRequestForm.adapter"; +import { toast } from "../../../components/shadcn/UseToast"; +import NavbarSidebarLayout from "../../../layout/NavbarSidebarLayout"; import { TopicService } from "../../../services/Topic.service"; -import { useParams } from "react-router-dom"; -import { ItemInterface } from "react-sortablejs"; +import { transformCreateCourseRequestForm2CreateTopicRequest } from "../../../types/adapters/CreateCourseRequestForm.adapter"; import { transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest } from "../../../types/adapters/Topic.adapter"; +import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; const EditCourse = () => { const { courseId } = useParams(); @@ -48,7 +40,7 @@ const EditCourse = () => { ); }) .then(() => { - return TopicService.updateGroupPermissions(editCourseId, groups); + return TopicService.updateGroupPermissions(editCourseId,accountId,groups); }) .then(() => { setLoading(false); From 8e8acb6622329c65b327b00210fdbe30b6c3df83 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 7 Jan 2024 16:07:20 +0700 Subject: [PATCH 12/24] Course collections management --- .../CreateCourseForm/ManageCollections.tsx | 225 +++++++++++------- .../CreateGroupForm/ManagePermissions.tsx | 12 +- src/constants/BackendBaseURL.ts | 6 +- src/services/Group.service.ts | 2 +- src/types/apis/Collection.api.ts | 4 +- src/views/My/Collections/MyCollections.tsx | 51 +++- 6 files changed, 197 insertions(+), 103 deletions(-) diff --git a/src/components/Forms/CreateCourseForm/ManageCollections.tsx b/src/components/Forms/CreateCourseForm/ManageCollections.tsx index efb41b2..aaea657 100644 --- a/src/components/Forms/CreateCourseForm/ManageCollections.tsx +++ b/src/components/Forms/CreateCourseForm/ManageCollections.tsx @@ -30,6 +30,8 @@ import { } from "../../../types/models/Collection.model"; import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; import { CourseNavSidebarContext } from "../../../contexts/CourseNavSidebarContexnt"; +import { Tabs, TabsList, TabsTrigger } from "../../shadcn/Tabs"; +import { Switch } from "../../shadcn/Switch"; const ManageCollections = ({ createRequest, @@ -52,6 +54,7 @@ const ManageCollections = ({ ); const [initial, setInitial] = useState(true); + const [tabValue, setTabValue] = useState("add"); const [selectedCollectionsSortableIds, setSelectedCollectionsSortableIds] = useState([]); @@ -100,7 +103,7 @@ const ManageCollections = ({ response.data.collections ) ); - + setAllCollectionsSortable( response.data.collections.map((collection) => ({ id: collection.collection_id, @@ -116,7 +119,8 @@ const ManageCollections = ({ ...allCollections, ...transformCollectionPopulateProblemSecureModel2CollectionHashedTable( createRequest.course.collections.map( - (cc) => cc.collection + (cc) => + cc.collection as CollectionPopulateCollectionProblemPopulateProblemModel ) ), }); @@ -130,12 +134,12 @@ const ManageCollections = ({ // ), // }) - console.log('ccc', - createRequest.course.collections.map((cc) => cc.collection) - ); + // console.log('ccc', + // createRequest.course.collections.map((cc) => cc.collection) + // ); } - console.log(createRequest.course); - }, [createRequest, allCollections]); + // console.log(createRequest.course); + }, [createRequest.course, allCollections]); useEffect(() => { if (initial) { @@ -148,100 +152,145 @@ const ManageCollections = ({ return (
-
-

Manage Collections

- - +
+ setTabValue(e)}> + + Add / Remove + + Permissions + + +
-
-
+ {tabValue === "add" && ( +
+
+
+
+ + + {selectedCollectionsSortable?.map( + (item) => ( + + handleRemoveSelectedCollection( + item.id as string + ) + } + key={item.id} + collection={ + allCollections[ + item.id as string + ] as CollectionPopulateCollectionProblemPopulateProblemModel + } + /> + ) + )} + + +
+
+
+ +
+ +
+ +
+ + + + {allCollectionsSortable?.map((item) => ( +
+ + handleQuickToggleSelectedCollection( + item + ) + } + disabled={selectedCollectionsSortableIds.includes( + item.id as string + )} + key={item.id} + collection={ + allCollections[ + item.id as string + ] as CollectionPopulateCollectionProblemPopulateProblemModel + } + /> +
+ ))} +
+
+
+
+ )} + {tabValue === "permission" && ( +
- +
{selectedCollectionsSortable?.map( (item) => ( - - handleRemoveSelectedCollection( - item.id as string - ) - } - key={item.id} - collection={ - allCollections[ - item.id as string - ] as CollectionPopulateCollectionProblemPopulateProblemModel - } - /> +
+
+ +
+ +
+ View Collection + +
+
+ Manage Collection + +
+
) )} - +
- -
- -
- -
- - - - {allCollectionsSortable?.map((item) => ( -
- - handleQuickToggleSelectedCollection( - item - ) - } - disabled={selectedCollectionsSortableIds.includes( - item.id as string - )} - key={item.id} - collection={ - allCollections[ - item.id as string - ] as CollectionPopulateCollectionProblemPopulateProblemModel - } - /> -
- ))} -
-
-
-
+ )}
); }; diff --git a/src/components/Forms/CreateGroupForm/ManagePermissions.tsx b/src/components/Forms/CreateGroupForm/ManagePermissions.tsx index b10a97a..6d45d55 100644 --- a/src/components/Forms/CreateGroupForm/ManagePermissions.tsx +++ b/src/components/Forms/CreateGroupForm/ManagePermissions.tsx @@ -57,8 +57,16 @@ const ManagePermissions = ({ Collection Permission

setCreateRequest({ + ...createRequest, + manageCollections: !createRequest.manageCollections + })} + onClickViewCollections={() => setCreateRequest({ + ...createRequest, + viewCollections: !createRequest.viewCollections + })} />

Problem Permission diff --git a/src/constants/BackendBaseURL.ts b/src/constants/BackendBaseURL.ts index f620904..cc79b04 100644 --- a/src/constants/BackendBaseURL.ts +++ b/src/constants/BackendBaseURL.ts @@ -1,2 +1,6 @@ +import axios from "axios"; + // export const BASE_URL = 'http://192.168.0.11:8000'; -export const BASE_URL = 'http://localhost:8000'; \ No newline at end of file +export const BASE_URL = 'http://localhost:8000'; +// axios.defaults.headers.common['account_id'] = localStorage.getItem('account_id') || null; +axios.defaults.headers.common['Authorization'] = localStorage.getItem('token') || null; diff --git a/src/services/Group.service.ts b/src/services/Group.service.ts index 8e1e31e..5737e7b 100644 --- a/src/services/Group.service.ts +++ b/src/services/Group.service.ts @@ -13,7 +13,7 @@ export const GroupService: GroupSerivceAPI = { getAllAsCreator: async (accountId:string,query?:any) => { const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/groups`,{ - params: query + params: query, }) return response; diff --git a/src/types/apis/Collection.api.ts b/src/types/apis/Collection.api.ts index b575ace..fb1424c 100644 --- a/src/types/apis/Collection.api.ts +++ b/src/types/apis/Collection.api.ts @@ -3,6 +3,8 @@ import { CollectionCreateRequest, CollectionModel, CollectionPopulateCollectionP export type GetCollectionByAccountResponse = { collections: CollectionPopulateCollectionProblemPopulateProblemModel[]; + manageable_collections: CollectionPopulateCollectionProblemPopulateProblemModel[]; + } export type CollectionGroupPermissionCreateRequest = { @@ -14,8 +16,8 @@ export type CollectionGroupPermissionCreateRequest = { export type CollectionServiceAPI = { create: (accountId:string,request:CollectionCreateRequest) => Promise>; get: (collectionId:string,accountId:string) => Promise>; - getAllAsCreator: (accountId:string) => Promise>; update: (collectionId:string,request:CollectionUpdateRequest) => Promise>; + getAllAsCreator: (accountId:string) => Promise>; addProblem: (collectionId:string,problemIds:string[]) => Promise>; removeProblem: (collectionId:string,problemIds:string[]) => Promise>; updateProblem: (collectionId:string,problemIds:string[]) => Promise>; diff --git a/src/views/My/Collections/MyCollections.tsx b/src/views/My/Collections/MyCollections.tsx index 00fd826..3efeb45 100644 --- a/src/views/My/Collections/MyCollections.tsx +++ b/src/views/My/Collections/MyCollections.tsx @@ -7,21 +7,32 @@ import { useNavigate } from "react-router-dom"; import CardContainer from "../../../components/CardContainer"; import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; import { CollectionService } from "../../../services/Collection.service"; -import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionProblemModel } from "../../../types/models/Collection.model"; +import { + CollectionModel, + CollectionPopulateCollectionProblemPopulateProblemModel, + CollectionProblemModel, +} from "../../../types/models/Collection.model"; import { FolderPlus } from "lucide-react"; +import { Tabs, TabsList, TabsTrigger } from "../../../components/shadcn/Tabs"; const MyCollections = () => { const navigate = useNavigate(); const accountId = String(localStorage.getItem("account_id")); - const [collections, setCollections] = useState([]); - const {setSection} = useContext(NavSidebarContext) + const [collections, setCollections] = useState< + CollectionPopulateCollectionProblemPopulateProblemModel[] + >([]); + const [manageableCollections, setManageableCollections] = useState([]); + const { setSection } = useContext(NavSidebarContext); + + const [tabValue, setTabValue] = useState("personal"); useEffect(() => { - setSection("COLLECTIONS") - CollectionService.getAllAsCreator(accountId).then((response => { - setCollections(response.data.collections) - })) + setSection("COLLECTIONS"); + CollectionService.getAllAsCreator(accountId).then((response) => { + setCollections(response.data.collections); + setManageableCollections(response.data.manageable_collections); + }); }, []); return ( @@ -36,6 +47,21 @@ const MyCollections = () => {

+
+ setTabValue(e)} + > + + + Personal + + + Manageable + + + +
- {collections.map(collection => ( - - ))} + {tabValue === "personal" && + collections.map((collection) => ( + + ))} + {tabValue === "manageable" && + manageableCollections.map((collection) => ( + + ))}
From 95d09352c6d99610d645ae69b16e13fa339f93c7 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Tue, 9 Jan 2024 03:01:31 +0700 Subject: [PATCH 13/24] manage collection in course (Not done) --- .../CollectionCards/MyCollectionMiniCard2.tsx | 1 + .../CreateCollectionForm/ManageGroups.tsx | 4 +- .../CreateCourseForm/ManageCollections.tsx | 255 +++++++----------- .../Forms/CreateCourseForm/ManageGroups.tsx | 224 ++++++++++++--- .../Forms/GroupAndPermissionManager.tsx | 6 +- .../CollectionPermissionSwitchGroup.tsx | 58 ++-- src/components/RolesCombobox.tsx | 91 +++++++ src/services/Topic.service.ts | 4 +- .../CreateCourseRequestForm.adapter.ts | 18 +- src/types/adapters/Topic.adapter.ts | 16 +- src/types/apis/Topic.api.ts | 4 +- .../forms/CreateCollectionRequestForm.ts | 4 +- src/types/forms/CreateCourseRequestForm.ts | 17 +- src/types/models/Topic.model.ts | 14 +- src/views/My/Courses/CreateCourse.tsx | 64 +++-- src/views/My/Courses/EditCourse.tsx | 32 ++- 16 files changed, 560 insertions(+), 252 deletions(-) create mode 100644 src/components/RolesCombobox.tsx diff --git a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx index 2373916..97ebe70 100644 --- a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx @@ -32,6 +32,7 @@ import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateProblemSecureModel, + CollectionProblemPopulateProblemModel, } from "../../../types/models/Collection.model"; const checkRuntimeStatus = (testcases: TestcaseModel[]) => { diff --git a/src/components/Forms/CreateCollectionForm/ManageGroups.tsx b/src/components/Forms/CreateCollectionForm/ManageGroups.tsx index 964677f..7616dd8 100644 --- a/src/components/Forms/CreateCollectionForm/ManageGroups.tsx +++ b/src/components/Forms/CreateCollectionForm/ManageGroups.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { - CollectionnGroupPermissionRequestForm, + CollectionGroupPermissionRequestForm, CreateCollectionRequestForm, } from "../../../types/forms/CreateCollectionRequestForm"; import GroupAndPermissionManager, { GroupAndPermissionManagerOnAddGroupsCallback, GroupAndPermissionManagerOnRemoveGroupCallback } from "../GroupAndPermissionManager"; @@ -16,7 +16,7 @@ const ManageGroups = ({ >; }) => { const [groupPermission, setGroupPermission] = - useState(); + useState(); const [selectedIndex, setSelectedIndex] = useState(-1); diff --git a/src/components/Forms/CreateCourseForm/ManageCollections.tsx b/src/components/Forms/CreateCourseForm/ManageCollections.tsx index aaea657..d9ef0bc 100644 --- a/src/components/Forms/CreateCourseForm/ManageCollections.tsx +++ b/src/components/Forms/CreateCourseForm/ManageCollections.tsx @@ -1,37 +1,18 @@ -import React, { useContext, useEffect, useState } from "react"; -import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; +import React, { useEffect, useState } from "react"; import { ReactSortable } from "react-sortablejs"; -import { Button } from "../../shadcn/Button"; -import { Separator } from "../../shadcn/Seperator"; -import { Input } from "../../shadcn/Input"; -import { ProblemService } from "../../../services/Problem.service"; -import { - ProblemHashedTable, - ProblemModel, - ProblemSecureModel, -} from "../../../types/models/Problem.model"; -import { ItemInterface } from "./../../../../node_modules/react-sortablejs/dist/index.d"; -import MyProblemCard from "../../Cards/ProblemCards/MyProblemCard"; -import CardContainer from "../../CardContainer"; -import SortableCardContainer from "../../SortableCardContainer"; -import MyProblemMiniCard from "../../Cards/ProblemCards/MyProblemMiniCard"; -import { ScrollArea } from "../../shadcn/ScrollArea"; -import { Item } from "@radix-ui/react-context-menu"; -import { transformProblemModel2ProblemHashedTable } from "../../../types/adapters/Problem.adapter"; -import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; -import MyCollectionMiniCard from "../../Cards/CollectionCards/MyCollectionMiniCard"; import { CollectionService } from "../../../services/Collection.service"; import { transformCollectionPopulateProblemSecureModel2CollectionHashedTable } from "../../../types/adapters/Collection.adapter"; +import { CollectionItemInterface, CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; import { CollectionHashedTable, - CollectionModel, - CollectionPopulateCollectionProblemPopulateProblemModel, - CollectionPopulateProblemSecureModel, + CollectionPopulateCollectionProblemPopulateProblemModel } from "../../../types/models/Collection.model"; import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; -import { CourseNavSidebarContext } from "../../../contexts/CourseNavSidebarContexnt"; +import { Input } from "../../shadcn/Input"; +import { ScrollArea } from "../../shadcn/ScrollArea"; +import { Separator } from "../../shadcn/Seperator"; import { Tabs, TabsList, TabsTrigger } from "../../shadcn/Tabs"; -import { Switch } from "../../shadcn/Switch"; +import { ItemInterface } from "./../../../../node_modules/react-sortablejs/dist/index.d"; const ManageCollections = ({ createRequest, @@ -45,10 +26,11 @@ const ManageCollections = ({ const accountId = String(localStorage.getItem("account_id")); const [allCollectionsSortable, setAllCollectionsSortable] = useState< - ItemInterface[] + CollectionItemInterface[] >([]); const [selectedCollectionsSortable, setSelectedCollectionsSortable] = - useState([]); + useState([]); + const [allCollections, setAllCollections] = useState( {} ); @@ -70,7 +52,7 @@ const ManageCollections = ({ ]); }; - const handleQuickToggleSelectedCollection = (item: ItemInterface) => { + const handleQuickToggleSelectedCollection = (item: CollectionItemInterface) => { // if (selectedCollectionsSortable.find((item1) => item1.id === item.id)) { // console.log("Remove"); // handleRemoveSelectedCollection(item.id as string); @@ -108,6 +90,7 @@ const ManageCollections = ({ response.data.collections.map((collection) => ({ id: collection.collection_id, name: collection.name, + collection: collection, })) ); }); @@ -143,7 +126,11 @@ const ManageCollections = ({ useEffect(() => { if (initial) { - setSelectedCollectionsSortable(createRequest.collectionsInterface); + setSelectedCollectionsSortable(createRequest.course?.collections.map((cc) => ({ + id: cc.collection.collection_id, + name: cc.collection.name, + collection: cc.collection + })) ?? ([] as CollectionItemInterface[])); setInitial(false); } @@ -152,145 +139,97 @@ const ManageCollections = ({ return (
-
- setTabValue(e)}> - - Add / Remove - - Permissions - - - -
- - {tabValue === "add" && ( -
-
-
-
- - - {selectedCollectionsSortable?.map( - (item) => ( - - handleRemoveSelectedCollection( - item.id as string - ) - } - key={item.id} - collection={ - allCollections[ - item.id as string - ] as CollectionPopulateCollectionProblemPopulateProblemModel - } - /> - ) - )} - - -
-
-
- -
- -
- -
- - - - {allCollectionsSortable?.map((item) => ( -
- - handleQuickToggleSelectedCollection( - item - ) - } - disabled={selectedCollectionsSortableIds.includes( - item.id as string - )} - key={item.id} - collection={ - allCollections[ - item.id as string - ] as CollectionPopulateCollectionProblemPopulateProblemModel - } - /> -
- ))} -
-
-
-
- )} - {tabValue === "permission" && ( -
+ +
+
-
+ {selectedCollectionsSortable?.map( (item) => ( -
-
- -
- -
- View Collection - -
-
- Manage Collection - -
-
+ + handleRemoveSelectedCollection( + item.id as string + ) + } + key={item.id} + collection={ + item.collection + // allCollections[ + // item.id as string + // ] as CollectionPopulateCollectionProblemPopulateProblemModel + } + /> ) )} -
+
- )} + +
+ +
+ +
+ + + + {allCollectionsSortable?.map((item) => ( +
+ + handleQuickToggleSelectedCollection( + item + ) + } + disabled={selectedCollectionsSortableIds.includes( + item.id as string + )} + key={item.id} + collection={ + item.collection + // allCollections[ + // item.id as string + // ] as CollectionPopulateCollectionProblemPopulateProblemModel + } + /> +
+ ))} +
+
+
+
); }; diff --git a/src/components/Forms/CreateCourseForm/ManageGroups.tsx b/src/components/Forms/CreateCourseForm/ManageGroups.tsx index 9e686b9..4495c1d 100644 --- a/src/components/Forms/CreateCourseForm/ManageGroups.tsx +++ b/src/components/Forms/CreateCourseForm/ManageGroups.tsx @@ -8,6 +8,14 @@ import GroupAndPermissionManager, { GroupAndPermissionManagerOnRemoveGroupCallback, } from "../GroupAndPermissionManager"; import CoursePermissionSwitchGroup from "../PermissionSwitchGroups/CoursePermissionSwitchGroup"; +import { Tabs, TabsList, TabsTrigger } from "../../shadcn/Tabs"; +import PermissionSwitchScrollArea from "../../Permissions/PermissionSwitchScrollArea"; +import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; +import { + CollectionPopulateCollectionProblemPopulateProblemModel, + CollectionProblemPopulateProblemModel, +} from "../../../types/models/Collection.model"; +import { Switch } from "../../shadcn/Switch"; const ManageGroups = ({ createRequest, @@ -22,6 +30,7 @@ const ManageGroups = ({ useState(); const [selectedIndex, setselectedIndex] = useState(-1); + const [currentGroupId, setCurrentGroupId] = useState(""); const handleAddGroups = ({ addingGroups, @@ -43,7 +52,9 @@ const ManageGroups = ({ }); }; - const handleRemoveGroup = ({index}:GroupAndPermissionManagerOnRemoveGroupCallback) => { + const handleRemoveGroup = ({ + index, + }: GroupAndPermissionManagerOnRemoveGroupCallback) => { setCreateRequest({ ...createRequest, groupPermissions: [ @@ -51,10 +62,13 @@ const ManageGroups = ({ ...createRequest.groupPermissions.slice(index + 1), ], }); - } + }; useEffect(() => { setGroupPermission(createRequest.groupPermissions[selectedIndex]); + if (selectedIndex >= 0) { + setCurrentGroupId(createRequest.groupPermissions[selectedIndex].group_id) + } }, [selectedIndex]); useEffect(() => { @@ -70,41 +84,179 @@ const ManageGroups = ({ } }, [groupPermission]); + const [tabValue, setTabValue] = useState("course"); + return ( - handleAddGroups(e)} - onRemoveGroup={(e) => handleRemoveGroup(e)} - selectedIndex={selectedIndex} - setSelectedIndex={setselectedIndex} - > - {groupPermission && selectedIndex >= 0 && ( - - setGroupPermission({ - ...groupPermission, - manageCourses: !groupPermission.manageCourses, - }) - } - onClickViewCourseLogs={() => - setGroupPermission({ - ...groupPermission, - viewCourseLogs: !groupPermission.viewCourseLogs, - }) - } - onClickViewCourses={() => - setGroupPermission({ - ...groupPermission, - viewCourses: !groupPermission.viewCourses, - }) - } - /> - )} - + <> +
+ setTabValue(e)}> + + + Course Permissions + + + Collection Permissions + + + +
+ handleAddGroups(e)} + onRemoveGroup={(e) => handleRemoveGroup(e)} + selectedIndex={selectedIndex} + setSelectedIndex={setselectedIndex} + > + <> + {tabValue === "course" && ( + + {groupPermission && selectedIndex >= 0 && ( + + setGroupPermission({ + ...groupPermission, + manageCourses: + !groupPermission.manageCourses, + }) + } + onClickViewCourseLogs={() => + setGroupPermission({ + ...groupPermission, + viewCourseLogs: + !groupPermission.viewCourseLogs, + }) + } + onClickViewCourses={() => + setGroupPermission({ + ...groupPermission, + viewCourses: + !groupPermission.viewCourses, + }) + } + /> + )} + + )} + + {tabValue === "collections" && + groupPermission && + selectedIndex >= 0 && ( +
+ {createRequest.course?.collections?.map( + (courseCollection) => ( +
+
+ +
+ +
+ View Collection + gp.group.group_id === currentGroupId)?.permission_view_collections} + onClick={() => { + const newGroupPermissions = courseCollection.collection.group_permissions.map((gp) => { + if(gp.group.group_id === currentGroupId) { + return { + ...gp, + permission_view_collections: !gp.permission_view_collections + } + } else { + return gp; + } + }) + + if (!createRequest.course) { + return; + } + + setCreateRequest({ + ...createRequest, + course: { + ...createRequest.course, + collections: createRequest.course.collections.map((cc) => { + if(cc.collection.collection_id === courseCollection.collection.collection_id) { + return { + ...cc, + collection: { + ...cc.collection, + group_permissions: newGroupPermissions + } + } + } else { + return cc; + } + }) + } + }) + }} + + className="ml-2" /> +
+
+ Manage Collection + gp.group.group_id === currentGroupId)?.permission_manage_collections} + onClick={() => { + const newGroupPermissions = courseCollection.collection.group_permissions.map((gp) => { + if(gp.group.group_id === currentGroupId) { + return { + ...gp, + permission_manage_collections: true //!gp.permission_manage_collections + } + } else { + return gp; + } + }) + + if (!createRequest.course) { + return; + } + + setCreateRequest({ + ...createRequest, + course: { + ...createRequest.course, + collections: createRequest.course.collections.map((cc) => { + if(cc.collection.collection_id === courseCollection.collection.collection_id) { + return { + ...cc, + collection: { + ...cc.collection, + group_permissions: newGroupPermissions + } + } + } else { + return cc; + } + }) + } + }) + }} + className="ml-2" /> +
+
+ ) + )} +
+ )} + +
+ ); }; diff --git a/src/components/Forms/GroupAndPermissionManager.tsx b/src/components/Forms/GroupAndPermissionManager.tsx index 1f388bc..f1daae2 100644 --- a/src/components/Forms/GroupAndPermissionManager.tsx +++ b/src/components/Forms/GroupAndPermissionManager.tsx @@ -253,11 +253,9 @@ const GroupAndPermissionManager = ({
-
- + {children} - -
+ {}, onClickViewCollections = () => {}, + variant = "normal", }: { manageCollectionsChecked?: boolean; viewCollectionsChecked?: boolean; onClickManageCollections?: () => void | undefined; onClickViewCollections?: () => void | undefined; + variant?: "normal" | "minimal"; }) => { + const CollectionPermissions = [ + { + title: "Manage Collections", + description: + "Can edit collections name and description. Can add or remove problems from collection as well.", + checked: manageCollectionsChecked, + onClick: onClickManageCollections, + }, + { + title: "View Collections", + description: + "Can view collection and thier problems. Note that those problems must be accessible as well.", + checked: viewCollectionsChecked, + onClick: onClickViewCollections, + }, + ]; + return ( - <> - onClickManageCollections()} - /> - onClickViewCollections()} - /> - + (variant === "normal" && ( + <> + {CollectionPermissions.map((permission) => ( + + ))} + + )) || + (variant === "minimal" && ( + <> + {CollectionPermissions.map((permission) => ( +
+ {permission.title} + +
+ ))} + + )) ); }; diff --git a/src/components/RolesCombobox.tsx b/src/components/RolesCombobox.tsx new file mode 100644 index 0000000..825806e --- /dev/null +++ b/src/components/RolesCombobox.tsx @@ -0,0 +1,91 @@ +"use client" + +import * as React from "react" +import { Check, ChevronsUpDown } from "lucide-react" + +import { cn } from "../lib/utils" +import { Button } from "./shadcn/Button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "./shadcn/Command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "./shadcn/Popover" + +const frameworks = [ + { + value: "next.js", + label: "Next.js", + }, + { + value: "sveltekit", + label: "SvelteKit", + }, + { + value: "nuxt.js", + label: "Nuxt.js", + }, + { + value: "remix", + label: "Remix", + }, + { + value: "astro", + label: "Astro", + }, +] + +export function RolesCombobox() { + const [open, setOpen] = React.useState(false) + const [value, setValue] = React.useState("") + + return ( + + + + + + + + No framework found. + + {frameworks.map((framework) => ( + { + setValue(currentValue === value ? "" : currentValue) + setOpen(false) + }} + > + + {framework.label} + + ))} + + + + + ) +} diff --git a/src/services/Topic.service.ts b/src/services/Topic.service.ts index ba7a482..a51835d 100644 --- a/src/services/Topic.service.ts +++ b/src/services/Topic.service.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { GetAllTopicsByAccountResponse, TopicSerivceAPI } from "../types/apis/Topic.api"; import { BASE_URL } from "../constants/BackendBaseURL"; -import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Topic.model"; +import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupAndTopicGroupPermissionPopulateGroupModel } from "../types/models/Topic.model"; export const TopicService: TopicSerivceAPI = { create: async (accountId, request) => { @@ -10,7 +10,7 @@ export const TopicService: TopicSerivceAPI = { }, get: async (accountId,topicId) => { - const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}`); + const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}`); return response; }, diff --git a/src/types/adapters/CreateCourseRequestForm.adapter.ts b/src/types/adapters/CreateCourseRequestForm.adapter.ts index fa4243b..361777e 100644 --- a/src/types/adapters/CreateCourseRequestForm.adapter.ts +++ b/src/types/adapters/CreateCourseRequestForm.adapter.ts @@ -1,3 +1,4 @@ +import { CollectionGroupPermissionCreateRequest } from "../apis/Collection.api"; import { CourseGroupPermissionCreateRequest } from "../apis/Topic.api"; import { CreateCourseRequestForm } from "../forms/CreateCourseRequestForm"; @@ -7,6 +8,10 @@ export function transformCreateCourseRequestForm2CreateTopicRequest( formData: FormData; collectionIds: string[]; groups: CourseGroupPermissionCreateRequest[]; + collectionGroupsPermissions: { + collection_id: string; + groupPermissions: CollectionGroupPermissionCreateRequest[]; + }[] } { const formData = new FormData(); formData.append("name", createRequest.title); @@ -24,5 +29,16 @@ export function transformCreateCourseRequestForm2CreateTopicRequest( permission_view_topics_log: groupPermission.viewCourseLogs, })); - return { formData, collectionIds, groups }; + const collectionGroupsPermissions = createRequest.course?.collections.map((cc) => ({ + collection_id: cc.collection.collection_id, + groupPermissions: cc.collection.group_permissions.map((gp) => ({ + group_id: gp.group.group_id, + permission_manage_collections: gp.permission_manage_collections, + permission_view_collections: gp.permission_view_collections, + })) + })) ?? [] + + console.log("collectionGroupsPermissions", collectionGroupsPermissions) + + return { formData, collectionIds, groups, collectionGroupsPermissions }; } diff --git a/src/types/adapters/Topic.adapter.ts b/src/types/adapters/Topic.adapter.ts index ee68d06..8df6e9d 100644 --- a/src/types/adapters/Topic.adapter.ts +++ b/src/types/adapters/Topic.adapter.ts @@ -1,7 +1,7 @@ import { CreateCourseRequestForm } from "../forms/CreateCourseRequestForm"; -import { TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; +import { TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; -export function transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest(topic:TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel): CreateCourseRequestForm { +export function transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest(topic:TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupAndTopicGroupPermissionPopulateGroupModel): CreateCourseRequestForm { return { title: topic.name, description: JSON.parse(String(topic.description)), @@ -18,6 +18,16 @@ export function transformTopicPopulateTopicCollectionPopulateCollectionAndTopicG viewCourses: gp.permission_view_topics, viewCourseLogs: gp.permission_view_topics_log, })), - course: topic + course: topic, + collectionGroupPermissions: topic.collections.map((tc) => ({ + collection_id: tc.collection.collection_id, + collection: tc.collection, + groupPermissions: tc.collection.group_permissions.map((gp) => ({ + group_id: gp.group.group_id, + group: gp.group, + manageCollections: gp.permission_manage_collections, + viewCollections: gp.permission_view_collections, + })) + })) } } \ No newline at end of file diff --git a/src/types/apis/Topic.api.ts b/src/types/apis/Topic.api.ts index 9a15e6b..867fbcb 100644 --- a/src/types/apis/Topic.api.ts +++ b/src/types/apis/Topic.api.ts @@ -1,5 +1,5 @@ import { AxiosResponse } from "axios"; -import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "../models/Topic.model"; +import { TopicModel, TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; export type GetAllTopicsByAccountResponse = { topics: TopicPopulateTopicCollectionPopulateCollectionModel[]; @@ -19,7 +19,7 @@ export type CourseGroupPermissionCreateRequest = { export type TopicSerivceAPI = { create: (accountid: string,request: FormData) => Promise>; - get: (accountid: string,courseId:string) => Promise>; + get: (accountid: string,courseId:string) => Promise>; getAllAsCreator: (accountId:string) => Promise>; getAllAccessibleByAccount: (accountId:string) => Promise>; getPublicByAccount: (accountId:string,courseId:string) => Promise>; diff --git a/src/types/forms/CreateCollectionRequestForm.ts b/src/types/forms/CreateCollectionRequestForm.ts index 996c99a..8e572df 100644 --- a/src/types/forms/CreateCollectionRequestForm.ts +++ b/src/types/forms/CreateCollectionRequestForm.ts @@ -4,7 +4,7 @@ import { PlateEditorValueType } from "../PlateEditorValueType"; import { GroupModel } from "../models/Group.model"; import { CollectionPermissionRequestForm } from "./CreateGroupRequestForm"; -export type CollectionnGroupPermissionRequestForm = { +export type CollectionGroupPermissionRequestForm = { group_id: string; group: GroupModel; } & CollectionPermissionRequestForm @@ -13,5 +13,5 @@ export type CreateCollectionRequestForm = { title: string; description: PlateEditorValueType; problemsInterface: ItemInterface[]; - groupPermissions: CollectionnGroupPermissionRequestForm[]; + groupPermissions: CollectionGroupPermissionRequestForm[]; } \ No newline at end of file diff --git a/src/types/forms/CreateCourseRequestForm.ts b/src/types/forms/CreateCourseRequestForm.ts index 4c6208d..af1dcf4 100644 --- a/src/types/forms/CreateCourseRequestForm.ts +++ b/src/types/forms/CreateCourseRequestForm.ts @@ -2,13 +2,25 @@ import { ItemInterface } from "react-sortablejs"; import { PlateEditorValueType } from "../PlateEditorValueType"; import { CoursePermissionRequestForm } from "./CreateGroupRequestForm"; import { GroupModel, TopicGroupPermissionPopulateGroupModel } from "../models/Group.model"; -import { TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; +import { TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel, TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupAndTopicGroupPermissionPopulateGroupModel } from "../models/Topic.model"; +import { CollectionGroupPermissionRequestForm } from "./CreateCollectionRequestForm"; +import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel } from "../models/Collection.model"; export type CourseGroupPermissionRequestForm = { group_id: string; group: GroupModel; } & CoursePermissionRequestForm +export type CourseCollectionsGroupPermissionRequestForm = { + collection_id: string; + collection: CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel; + groupPermissions: CollectionGroupPermissionRequestForm[]; +} + +export type CollectionItemInterface = ItemInterface & { + collection: CollectionPopulateCollectionProblemPopulateProblemModel; +} + export type CreateCourseRequestForm = { title: string; description: PlateEditorValueType; @@ -16,5 +28,6 @@ export type CreateCourseRequestForm = { isPrivate?: boolean; collectionsInterface: ItemInterface[]; groupPermissions: CourseGroupPermissionRequestForm[]; - course: TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel | TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel | null; + course: null | TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupAndTopicGroupPermissionPopulateGroupModel //| TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel | TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel | null; + // collectionGroupPermissions: CourseCollectionsGroupPermissionRequestForm[]; } diff --git a/src/types/models/Topic.model.ts b/src/types/models/Topic.model.ts index 523df98..f85d09b 100644 --- a/src/types/models/Topic.model.ts +++ b/src/types/models/Topic.model.ts @@ -1,4 +1,4 @@ -import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel } from "./Collection.model" +import { CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel } from "./Collection.model" import { TopicGroupPermissionPopulateGroupModel } from "./Group.model" export type TopicModel = { @@ -62,4 +62,16 @@ export type TopicCollectionPopulateCollectionPopulateCollectionProblemPopulatePr export type TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel = TopicModel & { collections: TopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemModel[] group_permissions: TopicGroupPermissionPopulateGroupModel[] +} + +export type TopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel = { + id: string; + collection: CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel; + order: number; + topic: number; +} + +export type TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupAndTopicGroupPermissionPopulateGroupModel = TopicModel & { + collections: TopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel[] + group_permissions: TopicGroupPermissionPopulateGroupModel[] } \ No newline at end of file diff --git a/src/views/My/Courses/CreateCourse.tsx b/src/views/My/Courses/CreateCourse.tsx index 7ba853d..3949df1 100644 --- a/src/views/My/Courses/CreateCourse.tsx +++ b/src/views/My/Courses/CreateCourse.tsx @@ -23,35 +23,65 @@ const formInitialValue: CreateCourseRequestForm = { image: null, isPrivate: false, collectionsInterface: [], - groupPermissions: [] + groupPermissions: [], + course: null, + // collectionGroupPermissions: [], }; const CreateCourse = () => { - - const navigate = useNavigate() + const navigate = useNavigate(); const accountId = String(localStorage.getItem("account_id")); const handleSave = ({ setLoading, createRequest, - }: OnCourseSavedCallback) => { - if ( !setLoading || !createRequest) { + if (!setLoading || !createRequest) { return; } - const {formData,collectionIds} = transformCreateCourseRequestForm2CreateTopicRequest(createRequest) + const { formData, collectionIds, groups, collectionGroupsPermissions } = + transformCreateCourseRequestForm2CreateTopicRequest(createRequest); + + setLoading(true); + TopicService.create(accountId, formData) + .then((response) => { + return TopicService.updateCollections( + response.data.topic_id, + collectionIds + ); + }) + .then((response) => { + return TopicService.updateGroupPermissions( + response.data.topic_id, + accountId, + groups + ); + }) + .then((response) => { + let promise = []; + for (const collection of collectionGroupsPermissions) { + promise.push( + CollectionService.updateGroupPermissions( + collection.collection_id, + accountId, + collection.groupPermissions + ) + ); + } - setLoading(true) - TopicService.create(accountId, formData).then((response) => { - return TopicService.updateCollections(response.data.topic_id,collectionIds) - }).then((response) => { - setLoading(false) - toast({ - title: "Create Completed" + return { + promise: Promise.all(promise), + topic_id: response.data.topic_id, + }; }) - navigate(`/my/courses/${response.data.topic_id}`) - }) + .then(({ topic_id }) => { + setLoading(false); + toast({ + title: "Create Completed", + }); + navigate(`/my/courses/${topic_id}`); + }); }; return ( @@ -62,12 +92,12 @@ const CreateCourse = () => { // } onCourseSave={({ createRequest, - + setLoading, }) => handleSave({ createRequest, - + setLoading, }) } diff --git a/src/views/My/Courses/EditCourse.tsx b/src/views/My/Courses/EditCourse.tsx index 0acea31..e6749e4 100644 --- a/src/views/My/Courses/EditCourse.tsx +++ b/src/views/My/Courses/EditCourse.tsx @@ -9,6 +9,7 @@ import { TopicService } from "../../../services/Topic.service"; import { transformCreateCourseRequestForm2CreateTopicRequest } from "../../../types/adapters/CreateCourseRequestForm.adapter"; import { transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest } from "../../../types/adapters/Topic.adapter"; import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; +import { CollectionService } from "../../../services/Collection.service"; const EditCourse = () => { const { courseId } = useParams(); @@ -26,13 +27,13 @@ const EditCourse = () => { return; } - const { formData, collectionIds, groups } = + const { formData, collectionIds, groups, collectionGroupsPermissions } = transformCreateCourseRequestForm2CreateTopicRequest(createRequest); - console.log(formData.get("name")) + console.log(formData.get("name")); setLoading(true); - TopicService.update(editCourseId,accountId, formData) + TopicService.update(editCourseId, accountId, formData) .then(() => { return TopicService.updateCollections( editCourseId, @@ -40,20 +41,39 @@ const EditCourse = () => { ); }) .then(() => { - return TopicService.updateGroupPermissions(editCourseId,accountId,groups); + return TopicService.updateGroupPermissions( + editCourseId, + accountId, + groups + ); + }) + .then(() => { + + let promise = []; + for (const collection of collectionGroupsPermissions) { + promise.push( + CollectionService.updateGroupPermissions( + collection.collection_id, + accountId, + collection.groupPermissions + ) + ); + } + + return Promise.all(promise) }) .then(() => { setLoading(false); toast({ title: "Update Completed", }); - }) + }); }; useEffect(() => { TopicService.get(accountId, editCourseId).then((response) => { const { data } = response; - console.log(data) + console.log(data); setCreateRequest( transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest( data From 7740bdd5265a6a64919ce9135eb99ee6584cffd4 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Tue, 9 Jan 2024 15:22:34 +0700 Subject: [PATCH 14/24] CourseCollection permission manager --- .../CreateCollectionForm/ManageGroups.tsx | 35 ++- .../Forms/CreateCourseForm/ManageGroups.tsx | 264 +++++++++++++----- .../Forms/GroupAndPermissionManager.tsx | 14 +- .../CreateCourseRequestForm.adapter.ts | 8 +- src/types/adapters/Topic.adapter.ts | 27 +- src/types/forms/CreateCourseRequestForm.ts | 3 +- src/views/My/Courses/CreateCourse.tsx | 2 +- 7 files changed, 257 insertions(+), 96 deletions(-) diff --git a/src/components/Forms/CreateCollectionForm/ManageGroups.tsx b/src/components/Forms/CreateCollectionForm/ManageGroups.tsx index 7616dd8..bc48427 100644 --- a/src/components/Forms/CreateCollectionForm/ManageGroups.tsx +++ b/src/components/Forms/CreateCollectionForm/ManageGroups.tsx @@ -3,8 +3,13 @@ import { CollectionGroupPermissionRequestForm, CreateCollectionRequestForm, } from "../../../types/forms/CreateCollectionRequestForm"; -import GroupAndPermissionManager, { GroupAndPermissionManagerOnAddGroupsCallback, GroupAndPermissionManagerOnRemoveGroupCallback } from "../GroupAndPermissionManager"; +import GroupAndPermissionManager, { + GroupAndPermissionManagerOnAddGroupsCallback, + GroupAndPermissionManagerOnRemoveGroupCallback, +} from "../GroupAndPermissionManager"; import CollectionPermissionSwitchGroup from "../PermissionSwitchGroups/CollectionPermissionSwitchGroup"; +import { GroupModel } from "../../../types/models/Group.model"; +import { GroupService } from "../../../services/Group.service"; const ManageGroups = ({ createRequest, @@ -15,6 +20,9 @@ const ManageGroups = ({ React.SetStateAction >; }) => { + + const accountId = String(localStorage.getItem("account_id")); + const [groupPermission, setGroupPermission] = useState(); @@ -39,7 +47,9 @@ const ManageGroups = ({ }); }; - const handleRemoveGroup = ({index}:GroupAndPermissionManagerOnRemoveGroupCallback) => { + const handleRemoveGroup = ({ + index, + }: GroupAndPermissionManagerOnRemoveGroupCallback) => { setCreateRequest({ ...createRequest, groupPermissions: [ @@ -47,13 +57,13 @@ const ManageGroups = ({ ...createRequest.groupPermissions.slice(index + 1), ], }); - } + }; useEffect(() => { setGroupPermission(createRequest.groupPermissions[selectedIndex]); }, [selectedIndex]); - useEffect(() => { + useEffect(() => { if (groupPermission) { setCreateRequest({ ...createRequest, @@ -66,14 +76,23 @@ const ManageGroups = ({ } }, [groupPermission]); + const [allGroups, setAllGroups] = useState([]); + + useEffect(() => { + GroupService.getAllAsCreator(accountId).then((response) => { + setAllGroups(response.data.groups); + }) + },[accountId]) + return ( handleAddGroups(e)} - onRemoveGroup={(e) => handleRemoveGroup(e)} - selectedIndex={selectedIndex} - setSelectedIndex={setSelectedIndex} + onAddGroups={(e) => handleAddGroups(e)} + onRemoveGroup={(e) => handleRemoveGroup(e)} + selectedIndex={selectedIndex} + setSelectedIndex={setSelectedIndex} > {groupPermission && selectedIndex >= 0 && ( >; }) => { + const accountId = String(localStorage.getItem("account_id")); + const [groupPermission, setGroupPermission] = useState(); @@ -55,19 +63,24 @@ const ManageGroups = ({ const handleRemoveGroup = ({ index, }: GroupAndPermissionManagerOnRemoveGroupCallback) => { + // console.log("Remove group",createRequest.groupPermissions.length-1,index,selectedIndex) + if (index === selectedIndex) { + setselectedIndex(-1); + // console.log("Change",selectedIndex) + } setCreateRequest({ ...createRequest, - groupPermissions: [ - ...createRequest.groupPermissions.slice(0, index), - ...createRequest.groupPermissions.slice(index + 1), - ], + groupPermissions: createRequest.groupPermissions.filter((v,i) => i !== index), }); }; useEffect(() => { - setGroupPermission(createRequest.groupPermissions[selectedIndex]); - if (selectedIndex >= 0) { - setCurrentGroupId(createRequest.groupPermissions[selectedIndex].group_id) + console.log(createRequest.groupPermissions,selectedIndex) + if (selectedIndex >= 0 && selectedIndex < createRequest.groupPermissions.length ) { + setGroupPermission(createRequest.groupPermissions[selectedIndex]); + setCurrentGroupId( + createRequest.groupPermissions[selectedIndex].group_id + ); } }, [selectedIndex]); @@ -85,6 +98,17 @@ const ManageGroups = ({ }, [groupPermission]); const [tabValue, setTabValue] = useState("course"); + const [allGroups, setAllGroups] = useState([]); + + useEffect(() => { + GroupService.getAllAsCreator(accountId).then((response) => { + setAllGroups(response.data.groups); + }); + }, [accountId]); + + useEffect(() => { + console.log(createRequest); + }, [createRequest]); return ( <> @@ -101,6 +125,7 @@ const ManageGroups = ({
handleAddGroups(e)} @@ -152,7 +177,7 @@ const ManageGroups = ({ groupPermission && selectedIndex >= 0 && (
- {createRequest.course?.collections?.map( + {createRequest.collectionsInterface?.map( (courseCollection) => (
@@ -166,88 +191,197 @@ const ManageGroups = ({
View Collection - gp.group.group_id === currentGroupId)?.permission_view_collections} + + gp.group + .group_id === + currentGroupId + )?.viewCollections + } onClick={() => { - const newGroupPermissions = courseCollection.collection.group_permissions.map((gp) => { - if(gp.group.group_id === currentGroupId) { - return { - ...gp, - permission_view_collections: !gp.permission_view_collections + const findGroup = + courseCollection.groupPermissions.find( + (gp) => + gp.group + .group_id === + currentGroupId + ); + + const newGroupPermissions = + courseCollection.groupPermissions.map( + (gp) => { + if ( + gp.group + .group_id === + currentGroupId + ) { + return { + ...gp, + viewCollections: + !gp.viewCollections, + }; + } else { + return gp; + } } - } else { - return gp; - } - }) + ); - if (!createRequest.course) { + if ( + !findGroup || + newGroupPermissions.length === + 0 + ) { + newGroupPermissions.push( + { + group_id: + currentGroupId, + group: allGroups.find( + (g) => + g.group_id === + currentGroupId + )!, + viewCollections: + true, + manageCollections: + false, + } + ); + } + + if ( + !createRequest.course + ) { return; } + console.log( + "newGroupPermissions", + newGroupPermissions + ); + setCreateRequest({ ...createRequest, - course: { - ...createRequest.course, - collections: createRequest.course.collections.map((cc) => { - if(cc.collection.collection_id === courseCollection.collection.collection_id) { - return { - ...cc, - collection: { - ...cc.collection, - group_permissions: newGroupPermissions - } + collectionsInterface: + createRequest.collectionsInterface.map( + (cc) => { + if ( + cc + .collection + .collection_id === + courseCollection + .collection + .collection_id + ) { + return { + ...cc, + groupPermissions: + newGroupPermissions, + }; + } else { + return cc; } - } else { - return cc; } - }) - } - }) + ), + }); }} - - className="ml-2" /> + className="ml-2" + />
Manage Collection - gp.group.group_id === currentGroupId)?.permission_manage_collections} + + gp.group + .group_id === + currentGroupId + )?.manageCollections + } onClick={() => { - const newGroupPermissions = courseCollection.collection.group_permissions.map((gp) => { - if(gp.group.group_id === currentGroupId) { - return { - ...gp, - permission_manage_collections: true //!gp.permission_manage_collections + console.log( + courseCollection.collection + ); + + const findGroup = + courseCollection.groupPermissions.find( + (gp) => + gp.group + .group_id === + currentGroupId + ); + const newGroupPermissions = + courseCollection.groupPermissions.map( + (gp) => { + if ( + gp.group + .group_id === + currentGroupId + ) { + return { + ...gp, + manageCollections: + !gp.manageCollections, + }; + } else if ( + !findGroup + ) { + return { + group_id: + currentGroupId, + group: allGroups.find( + ( + g + ) => + g.group_id === + currentGroupId + )!, + viewCollections: + false, + manageCollections: + true, + }; + } else { + return gp; + } } - } else { - return gp; - } - }) + ); - if (!createRequest.course) { + if ( + !createRequest.course + ) { return; } setCreateRequest({ ...createRequest, - course: { - ...createRequest.course, - collections: createRequest.course.collections.map((cc) => { - if(cc.collection.collection_id === courseCollection.collection.collection_id) { - return { - ...cc, - collection: { - ...cc.collection, - group_permissions: newGroupPermissions - } + collectionsInterface: + createRequest.collectionsInterface.map( + (cc) => { + if ( + cc + .collection + .collection_id === + courseCollection + .collection + .collection_id + ) { + return { + ...cc, + groupPermissions: + newGroupPermissions, + }; + } else { + return cc; } - } else { - return cc; } - }) - } - }) + ), + }); }} - className="ml-2" /> + className="ml-2" + />
) diff --git a/src/components/Forms/GroupAndPermissionManager.tsx b/src/components/Forms/GroupAndPermissionManager.tsx index f1daae2..53291b9 100644 --- a/src/components/Forms/GroupAndPermissionManager.tsx +++ b/src/components/Forms/GroupAndPermissionManager.tsx @@ -98,6 +98,7 @@ const GroupListItemContextMenu = ({ }; const GroupAndPermissionManager = ({ + allGroups=[], createRequest, setCreateRequest, onAddGroups=()=>{}, @@ -106,6 +107,7 @@ const GroupAndPermissionManager = ({ setSelectedIndex=()=>{}, children, }: { + allGroups: GroupModel[]; createRequest: CreateCourseRequestForm | CreateCollectionRequestForm; setCreateRequest: React.Dispatch< React.SetStateAction> | React.Dispatch< @@ -117,10 +119,8 @@ const GroupAndPermissionManager = ({ setSelectedIndex?: React.Dispatch>; children: React.ReactNode; }) => { - const accountId = String(localStorage.getItem("account_id")); const navigate = useNavigate(); - const [allGroups, setAllGroups] = useState([]); const [openAddGroupsDialog, setOpenAddGroupsDialog] = useState(false); const [selectedGroupIds, setSelectedGroupIds] = useState([]); @@ -200,11 +200,11 @@ const GroupAndPermissionManager = ({ // } // }, [groupPermission]); - useEffect(() => { - GroupService.getAllAsCreator(accountId).then((response) => { - setAllGroups(response.data.groups); - }); - }, []); + // useEffect(() => { + // GroupService.getAllAsCreator(accountId).then((response) => { + // setAllGroups(response.data.groups); + // }); + // }, []); return (
diff --git a/src/types/adapters/CreateCourseRequestForm.adapter.ts b/src/types/adapters/CreateCourseRequestForm.adapter.ts index 361777e..aae9023 100644 --- a/src/types/adapters/CreateCourseRequestForm.adapter.ts +++ b/src/types/adapters/CreateCourseRequestForm.adapter.ts @@ -29,12 +29,12 @@ export function transformCreateCourseRequestForm2CreateTopicRequest( permission_view_topics_log: groupPermission.viewCourseLogs, })); - const collectionGroupsPermissions = createRequest.course?.collections.map((cc) => ({ + const collectionGroupsPermissions = createRequest.collectionsInterface.map((cc) => ({ collection_id: cc.collection.collection_id, - groupPermissions: cc.collection.group_permissions.map((gp) => ({ + groupPermissions: cc.groupPermissions.map((gp) => ({ group_id: gp.group.group_id, - permission_manage_collections: gp.permission_manage_collections, - permission_view_collections: gp.permission_view_collections, + permission_manage_collections: gp.manageCollections, + permission_view_collections: gp.viewCollections, })) })) ?? [] diff --git a/src/types/adapters/Topic.adapter.ts b/src/types/adapters/Topic.adapter.ts index 8df6e9d..b840286 100644 --- a/src/types/adapters/Topic.adapter.ts +++ b/src/types/adapters/Topic.adapter.ts @@ -10,6 +10,13 @@ export function transformTopicPopulateTopicCollectionPopulateCollectionAndTopicG collectionsInterface: topic.collections.map((tc) => ({ id: tc.collection.collection_id, name: tc.collection.name, + collection: tc.collection, + groupPermissions: tc.collection.group_permissions.map((gp) => ({ + group_id: gp.group.group_id, + group: gp.group, + manageCollections: gp.permission_manage_collections, + viewCollections: gp.permission_view_collections, + })), })), groupPermissions: topic.group_permissions.map((gp) => ({ group_id: gp.group.group_id, @@ -19,15 +26,15 @@ export function transformTopicPopulateTopicCollectionPopulateCollectionAndTopicG viewCourseLogs: gp.permission_view_topics_log, })), course: topic, - collectionGroupPermissions: topic.collections.map((tc) => ({ - collection_id: tc.collection.collection_id, - collection: tc.collection, - groupPermissions: tc.collection.group_permissions.map((gp) => ({ - group_id: gp.group.group_id, - group: gp.group, - manageCollections: gp.permission_manage_collections, - viewCollections: gp.permission_view_collections, - })) - })) + // collectionGroupPermissions: topic.collections.map((tc) => ({ + // collection_id: tc.collection.collection_id, + // collection: tc.collection, + // groupPermissions: tc.collection.group_permissions.map((gp) => ({ + // group_id: gp.group.group_id, + // group: gp.group, + // manageCollections: gp.permission_manage_collections, + // viewCollections: gp.permission_view_collections, + // })) + // })) } } \ No newline at end of file diff --git a/src/types/forms/CreateCourseRequestForm.ts b/src/types/forms/CreateCourseRequestForm.ts index af1dcf4..523686f 100644 --- a/src/types/forms/CreateCourseRequestForm.ts +++ b/src/types/forms/CreateCourseRequestForm.ts @@ -19,6 +19,7 @@ export type CourseCollectionsGroupPermissionRequestForm = { export type CollectionItemInterface = ItemInterface & { collection: CollectionPopulateCollectionProblemPopulateProblemModel; + groupPermissions: CollectionGroupPermissionRequestForm[]; } export type CreateCourseRequestForm = { @@ -26,7 +27,7 @@ export type CreateCourseRequestForm = { description: PlateEditorValueType; image?: File | string | null; isPrivate?: boolean; - collectionsInterface: ItemInterface[]; + collectionsInterface: CollectionItemInterface[] //ItemInterface[]; groupPermissions: CourseGroupPermissionRequestForm[]; course: null | TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupAndTopicGroupPermissionPopulateGroupModel //| TopicPopulateTopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemAndTopicGroupPermissionPopulateGroupModel | TopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel | null; // collectionGroupPermissions: CourseCollectionsGroupPermissionRequestForm[]; diff --git a/src/views/My/Courses/CreateCourse.tsx b/src/views/My/Courses/CreateCourse.tsx index 3949df1..28b49f4 100644 --- a/src/views/My/Courses/CreateCourse.tsx +++ b/src/views/My/Courses/CreateCourse.tsx @@ -25,7 +25,7 @@ const formInitialValue: CreateCourseRequestForm = { collectionsInterface: [], groupPermissions: [], course: null, - // collectionGroupPermissions: [], + collectionGroupPermissions: [], }; const CreateCourse = () => { From daf6474ed408c620eb49a254f697e87ec97eb947 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Tue, 9 Jan 2024 17:25:09 +0700 Subject: [PATCH 15/24] Mini bug fixed on course collection permission manager --- .../Cards/ProblemCards/MyProblemMiniCard2.tsx | 4 +-- .../CreateCourseForm/ManageCollections.tsx | 11 +++++-- .../Forms/CreateCourseForm/ManageGroups.tsx | 31 ++++++++----------- .../CoursePermissionSwitchGroup.tsx | 5 +++ .../Permissions/PermissionSwitch.tsx | 4 ++- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx b/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx index 5ca031a..34cfadf 100644 --- a/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx +++ b/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx @@ -119,12 +119,12 @@ const MyProblemMiniCard2 = ({ // className={`pt-6 px-5 ${disabled ? "opacity-50" : }`}`} >
-
+
-

{problem.title}

+

{problem.title}

{/*
{collection.problems.length} diff --git a/src/components/Forms/CreateCourseForm/ManageCollections.tsx b/src/components/Forms/CreateCourseForm/ManageCollections.tsx index d9ef0bc..ae81b79 100644 --- a/src/components/Forms/CreateCourseForm/ManageCollections.tsx +++ b/src/components/Forms/CreateCourseForm/ManageCollections.tsx @@ -11,8 +11,6 @@ import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniC import { Input } from "../../shadcn/Input"; import { ScrollArea } from "../../shadcn/ScrollArea"; import { Separator } from "../../shadcn/Seperator"; -import { Tabs, TabsList, TabsTrigger } from "../../shadcn/Tabs"; -import { ItemInterface } from "./../../../../node_modules/react-sortablejs/dist/index.d"; const ManageCollections = ({ createRequest, @@ -91,6 +89,7 @@ const ManageCollections = ({ id: collection.collection_id, name: collection.name, collection: collection, + groupPermissions: [], })) ); }); @@ -129,7 +128,13 @@ const ManageCollections = ({ setSelectedCollectionsSortable(createRequest.course?.collections.map((cc) => ({ id: cc.collection.collection_id, name: cc.collection.name, - collection: cc.collection + collection: cc.collection, + groupPermissions: cc.collection.group_permissions.map((gc) => ({ + group_id: gc.group.group_id, + group: gc.group, + manageCollections: gc.permission_manage_collections, + viewCollections: gc.permission_view_collections, + })), })) ?? ([] as CollectionItemInterface[])); setInitial(false); } diff --git a/src/components/Forms/CreateCourseForm/ManageGroups.tsx b/src/components/Forms/CreateCourseForm/ManageGroups.tsx index 67c928d..285aba0 100644 --- a/src/components/Forms/CreateCourseForm/ManageGroups.tsx +++ b/src/components/Forms/CreateCourseForm/ManageGroups.tsx @@ -1,27 +1,22 @@ import React, { useEffect, useState } from "react"; +import { GroupService } from "../../../services/Group.service"; import { CourseGroupPermissionRequestForm, CreateCourseRequestForm, } from "../../../types/forms/CreateCourseRequestForm"; +import { + CollectionPopulateCollectionProblemPopulateProblemModel +} from "../../../types/models/Collection.model"; +import { GroupModel } from "../../../types/models/Group.model"; +import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; +import PermissionSwitchScrollArea from "../../Permissions/PermissionSwitchScrollArea"; +import { Switch } from "../../shadcn/Switch"; +import { Tabs, TabsList, TabsTrigger } from "../../shadcn/Tabs"; import GroupAndPermissionManager, { GroupAndPermissionManagerOnAddGroupsCallback, GroupAndPermissionManagerOnRemoveGroupCallback, } from "../GroupAndPermissionManager"; import CoursePermissionSwitchGroup from "../PermissionSwitchGroups/CoursePermissionSwitchGroup"; -import { Tabs, TabsList, TabsTrigger } from "../../shadcn/Tabs"; -import PermissionSwitchScrollArea from "../../Permissions/PermissionSwitchScrollArea"; -import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; -import { - CollectionGroupPermissionPopulateGroupModel, - CollectionPopulateCollectionProblemPopulateProblemModel, - CollectionProblemPopulateProblemModel, -} from "../../../types/models/Collection.model"; -import { Switch } from "../../shadcn/Switch"; -import { GroupModel } from "../../../types/models/Group.model"; -import { GroupService } from "../../../services/Group.service"; -import ManageCollections from "./ManageCollections"; -import { CollectionPermissionRequestForm } from "../../../types/forms/CreateGroupRequestForm"; -import { CollectionGroupPermissionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; const ManageGroups = ({ createRequest, @@ -193,7 +188,7 @@ const ManageGroups = ({ View Collection gp.group .group_id === @@ -202,7 +197,7 @@ const ManageGroups = ({ } onClick={() => { const findGroup = - courseCollection.groupPermissions.find( + courseCollection.groupPermissions?.find( (gp) => gp.group .group_id === @@ -293,7 +288,7 @@ const ManageGroups = ({ Manage Collection gp.group .group_id === @@ -306,7 +301,7 @@ const ManageGroups = ({ ); const findGroup = - courseCollection.groupPermissions.find( + courseCollection.groupPermissions?.find( (gp) => gp.group .group_id === diff --git a/src/components/Forms/PermissionSwitchGroups/CoursePermissionSwitchGroup.tsx b/src/components/Forms/PermissionSwitchGroups/CoursePermissionSwitchGroup.tsx index a073d6a..2887038 100644 --- a/src/components/Forms/PermissionSwitchGroups/CoursePermissionSwitchGroup.tsx +++ b/src/components/Forms/PermissionSwitchGroups/CoursePermissionSwitchGroup.tsx @@ -5,6 +5,7 @@ import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseReques import { set } from 'react-hook-form'; const CoursePermissionSwitchGroup = ({ + disabled = false, manageCoursesChecked=false, viewCourseLogsChecked=false, viewCoursesChecked=false, @@ -12,6 +13,7 @@ const CoursePermissionSwitchGroup = ({ onClickViewCourseLogs=()=>{}, onClickViewCourses=()=>{}, }: { + disabled?: boolean; manageCoursesChecked?: boolean; viewCourseLogsChecked?: boolean; viewCoursesChecked?: boolean; @@ -26,6 +28,7 @@ const CoursePermissionSwitchGroup = ({ description="Can edit course name and description. Can add or remove collections from course as well." checked={manageCoursesChecked} onClick={() => onClickManageCourses()} + disabled={disabled} /> onClickViewCourseLogs()} + disabled={disabled} /> onClickViewCourses()} + disabled={disabled} /> ); diff --git a/src/components/Permissions/PermissionSwitch.tsx b/src/components/Permissions/PermissionSwitch.tsx index dcdf2c6..6f1e5bc 100644 --- a/src/components/Permissions/PermissionSwitch.tsx +++ b/src/components/Permissions/PermissionSwitch.tsx @@ -7,18 +7,20 @@ const PermissionSwitch = ({ description = "Description", checked = false, onClick = () => {}, + disabled = false, }: { title?: ReactNode; description?: ReactNode; checked?: boolean; onClick?: () => void; + disabled?: boolean; }) => { return (

{title}

- +

{description}

From 54164fd16b7bfbaf01d9b3184681e73cd6e2ac80 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Thu, 11 Jan 2024 01:47:08 +0700 Subject: [PATCH 16/24] Problem permission (Draft) --- .../Forms/CreateCourseForm/ManageGroups.tsx | 74 +++++----- .../Forms/CreateCourseForm/index.tsx | 7 +- .../Forms/CreateProblemForm/ManageGroups.tsx | 135 ++++++++++++++++++ .../Forms/CreateProblemForm/index.tsx | 11 ++ .../Forms/GroupAndPermissionManager.tsx | 63 +------- .../ProblemPermissionSwitchGroup.tsx | 35 ++--- .../NavbarCollectionsProblemsAccordion.tsx | 10 -- src/components/ProblemViewLayout.tsx | 16 +-- .../TestcaseValidationAccordion.tsx | 6 +- src/services/Problem.service.ts | 6 +- .../CreateProblemRequestForm.adapter.ts | 20 ++- src/types/adapters/Problem.adapter.ts | 22 ++- src/types/apis/Problem.api.ts | 22 ++- src/types/forms/CreateProblemRequestForm.ts | 8 ++ src/types/models/Group.model.ts | 8 ++ src/types/models/Problem.model.ts | 2 +- src/views/My/Problems/CreateProblem.tsx | 8 +- src/views/My/Problems/EditProblem.tsx | 25 ++-- 18 files changed, 307 insertions(+), 171 deletions(-) create mode 100644 src/components/Forms/CreateProblemForm/ManageGroups.tsx diff --git a/src/components/Forms/CreateCourseForm/ManageGroups.tsx b/src/components/Forms/CreateCourseForm/ManageGroups.tsx index 285aba0..6b4c621 100644 --- a/src/components/Forms/CreateCourseForm/ManageGroups.tsx +++ b/src/components/Forms/CreateCourseForm/ManageGroups.tsx @@ -4,9 +4,7 @@ import { CourseGroupPermissionRequestForm, CreateCourseRequestForm, } from "../../../types/forms/CreateCourseRequestForm"; -import { - CollectionPopulateCollectionProblemPopulateProblemModel -} from "../../../types/models/Collection.model"; +import { CollectionPopulateCollectionProblemPopulateProblemModel } from "../../../types/models/Collection.model"; import { GroupModel } from "../../../types/models/Group.model"; import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; import PermissionSwitchScrollArea from "../../Permissions/PermissionSwitchScrollArea"; @@ -58,20 +56,22 @@ const ManageGroups = ({ const handleRemoveGroup = ({ index, }: GroupAndPermissionManagerOnRemoveGroupCallback) => { - // console.log("Remove group",createRequest.groupPermissions.length-1,index,selectedIndex) if (index === selectedIndex) { setselectedIndex(-1); - // console.log("Change",selectedIndex) } setCreateRequest({ ...createRequest, - groupPermissions: createRequest.groupPermissions.filter((v,i) => i !== index), + groupPermissions: createRequest.groupPermissions.filter( + (v, i) => i !== index + ), }); }; useEffect(() => { - console.log(createRequest.groupPermissions,selectedIndex) - if (selectedIndex >= 0 && selectedIndex < createRequest.groupPermissions.length ) { + if ( + selectedIndex >= 0 && + selectedIndex < createRequest.groupPermissions.length + ) { setGroupPermission(createRequest.groupPermissions[selectedIndex]); setCurrentGroupId( createRequest.groupPermissions[selectedIndex].group_id @@ -101,10 +101,6 @@ const ManageGroups = ({ }); }, [accountId]); - useEffect(() => { - console.log(createRequest); - }, [createRequest]); - return ( <>
@@ -193,7 +189,7 @@ const ManageGroups = ({ gp.group .group_id === currentGroupId - )?.viewCollections + )?.viewCollections || false } onClick={() => { const findGroup = @@ -251,11 +247,6 @@ const ManageGroups = ({ return; } - console.log( - "newGroupPermissions", - newGroupPermissions - ); - setCreateRequest({ ...createRequest, collectionsInterface: @@ -293,13 +284,9 @@ const ManageGroups = ({ gp.group .group_id === currentGroupId - )?.manageCollections + )?.manageCollections || false } onClick={() => { - console.log( - courseCollection.collection - ); - const findGroup = courseCollection.groupPermissions?.find( (gp) => @@ -307,6 +294,7 @@ const ManageGroups = ({ .group_id === currentGroupId ); + const newGroupPermissions = courseCollection.groupPermissions.map( (gp) => { @@ -320,30 +308,34 @@ const ManageGroups = ({ manageCollections: !gp.manageCollections, }; - } else if ( - !findGroup - ) { - return { - group_id: - currentGroupId, - group: allGroups.find( - ( - g - ) => - g.group_id === - currentGroupId - )!, - viewCollections: - false, - manageCollections: - true, - }; } else { return gp; } } ); + if ( + !findGroup || + newGroupPermissions.length === + 0 + ) { + newGroupPermissions.push( + { + group_id: + currentGroupId, + group: allGroups.find( + (g) => + g.group_id === + currentGroupId + )!, + viewCollections: + false, + manageCollections: + true, + } + ); + } + if ( !createRequest.course ) { diff --git a/src/components/Forms/CreateCourseForm/index.tsx b/src/components/Forms/CreateCourseForm/index.tsx index 2679038..684974c 100644 --- a/src/components/Forms/CreateCourseForm/index.tsx +++ b/src/components/Forms/CreateCourseForm/index.tsx @@ -49,7 +49,6 @@ const CreateCourseForm = ({ onCourseSave: (callback: OnCourseSavedCallback) => void; // onCollectionSave: (callback: OnCollectionSavedCallback) => void; }) => { - const accountId = String(localStorage.getItem("account_id")); const navigate = useNavigate(); @@ -92,7 +91,11 @@ const CreateCourseForm = ({ setCurrentForm(tab.value) } diff --git a/src/components/Forms/CreateProblemForm/ManageGroups.tsx b/src/components/Forms/CreateProblemForm/ManageGroups.tsx new file mode 100644 index 0000000..02861bf --- /dev/null +++ b/src/components/Forms/CreateProblemForm/ManageGroups.tsx @@ -0,0 +1,135 @@ +import React, { useEffect, useState } from "react"; +import { + CreateProblemRequestForm, + ProblemGroupPermissionRequestForm, +} from "../../../types/forms/CreateProblemRequestForm"; +import GroupAndPermissionManager, { + GroupAndPermissionManagerOnAddGroupsCallback, + GroupAndPermissionManagerOnRemoveGroupCallback, +} from "../GroupAndPermissionManager"; +import { GroupModel } from "../../../types/models/Group.model"; +import { GroupService } from "../../../services/Group.service"; +import PermissionSwitchScrollArea from "../../Permissions/PermissionSwitchScrollArea"; +import ProblemPermissionSwitchGroup from "../PermissionSwitchGroups/ProblemPermissionSwitchGroup"; + +const ManageGroups = ({ + createRequest, + setCreateRequest, +}: { + createRequest: CreateProblemRequestForm; + setCreateRequest: React.Dispatch< + React.SetStateAction + >; +}) => { + createRequest; + setCreateRequest; + + const accountId = String(localStorage.getItem("account_id")); + + const [groupPermission, setGroupPermission] = + useState(); + + const [selectedIndex, setselectedIndex] = useState(-1); + const [currentGroupId, setCurrentGroupId] = useState(""); + + const handleAddGroups = ({ + addingGroups, + }: GroupAndPermissionManagerOnAddGroupsCallback) => { + const newGroupPermissions = addingGroups.map((group) => ({ + groupId: group.group_id, + group, + manageProblems: group.permission_manage_problems, + viewProblems: group.permission_view_problems, + })); + + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions, + ...newGroupPermissions, + ], + }); + }; + + const handleRemoveGroup = ({ + index, + }: GroupAndPermissionManagerOnRemoveGroupCallback) => { + if (index === selectedIndex) { + setselectedIndex(-1); + } + setCreateRequest({ + ...createRequest, + groupPermissions: createRequest.groupPermissions.filter( + (v, i) => i !== index + ), + }); + }; + + useEffect(() => { + if ( + selectedIndex >= 0 && + selectedIndex < createRequest.groupPermissions.length + ) { + setGroupPermission(createRequest.groupPermissions[selectedIndex]); + setCurrentGroupId( + createRequest.groupPermissions[selectedIndex].groupId + ); + } + }, [selectedIndex]); + + useEffect(() => { + if (groupPermission) { + setCreateRequest({ + ...createRequest, + groupPermissions: [ + ...createRequest.groupPermissions.slice(0, selectedIndex), + groupPermission, + ...createRequest.groupPermissions.slice(selectedIndex + 1), + ], + }); + } + }, [groupPermission]); + + const [allGroups, setAllGroups] = useState([]); + + useEffect(() => { + GroupService.getAllAsCreator(accountId).then((response) => { + setAllGroups(response.data.groups); + }); + }, [accountId]); + + return ( + handleAddGroups(e)} + onRemoveGroup={(e) => handleRemoveGroup(e)} + selectedIndex={selectedIndex} + setSelectedIndex={setselectedIndex} + > + + {groupPermission && selectedIndex >= 0 && ( + { + setGroupPermission({ + ...groupPermission, + manageProblems: !groupPermission.manageProblems, + }); + }} + onClickViewProblems={() => { + setGroupPermission({ + ...groupPermission, + viewProblems: !groupPermission.viewProblems, + }); + }} + /> + )} + + + ); +}; + +export default ManageGroups; diff --git a/src/components/Forms/CreateProblemForm/index.tsx b/src/components/Forms/CreateProblemForm/index.tsx index 32e451e..0cbef86 100644 --- a/src/components/Forms/CreateProblemForm/index.tsx +++ b/src/components/Forms/CreateProblemForm/index.tsx @@ -16,6 +16,7 @@ import Requirement from "./Requirement"; import Privacy from "./Privacy"; import { TestcaseModel } from "../../../types/models/Problem.model"; import FormSaveButton from "../FormSaveButton"; +import ManageGroups from "./ManageGroups"; const TabList = [ { @@ -34,6 +35,10 @@ const TabList = [ value: "privacy", label: "Privacy", }, + { + value: "groups", + label: "Manage Groups & Permissions", + }, ]; const testcaseFormat = (testcases: string, delimeter: string) => { @@ -163,6 +168,12 @@ const CreateProblemForm = ({ setCreateRequest={setCreateRequest} /> )} + {currentForm === "groups" && ( + + )}
); diff --git a/src/components/Forms/GroupAndPermissionManager.tsx b/src/components/Forms/GroupAndPermissionManager.tsx index 53291b9..f183c05 100644 --- a/src/components/Forms/GroupAndPermissionManager.tsx +++ b/src/components/Forms/GroupAndPermissionManager.tsx @@ -25,6 +25,7 @@ import { import { CreateCollectionRequestForm } from "../../types/forms/CreateCollectionRequestForm"; import { set } from 'react-hook-form'; import { useNavigate } from "react-router-dom"; +import { CreateProblemRequestForm } from "../../types/forms/CreateProblemRequestForm"; export type GroupAndPermissionManagerOnAddGroupsCallback = { addingGroups: GroupModel[]; @@ -108,11 +109,12 @@ const GroupAndPermissionManager = ({ children, }: { allGroups: GroupModel[]; - createRequest: CreateCourseRequestForm | CreateCollectionRequestForm; - setCreateRequest: React.Dispatch< - React.SetStateAction> | React.Dispatch< - React.SetStateAction - >; + createRequest: CreateCourseRequestForm | CreateCollectionRequestForm | CreateProblemRequestForm; + setCreateRequest: + React.Dispatch> | + React.Dispatch> | + React.Dispatch>; + onAddGroups?: (e: GroupAndPermissionManagerOnAddGroupsCallback) => void; onRemoveGroup?: (e: GroupAndPermissionManagerOnRemoveGroupCallback) => void; selectedIndex?: number; @@ -125,10 +127,6 @@ const GroupAndPermissionManager = ({ useState(false); const [selectedGroupIds, setSelectedGroupIds] = useState([]); - // const [groupPermission, setGroupPermission] = - // useState(); - // const [selectedIndex, setselectedIndex] = useState(-1); - const handleSelectGroupCheckbox = (groupId: string) => { if (selectedGroupIds.includes(groupId)) { setSelectedGroupIds(selectedGroupIds.filter((g) => g !== groupId)); @@ -138,13 +136,6 @@ const GroupAndPermissionManager = ({ }; const handleRemoveGroupPermission = (index: number) => { - // setCreateRequest({ - // ...createRequest, - // groupPermissions: [ - // ...createRequest.groupPermissions.slice(0, index), - // ...createRequest.groupPermissions.slice(index + 1), - // ], - // }); onRemoveGroup({index}) } @@ -161,51 +152,11 @@ const GroupAndPermissionManager = ({ const addingGroups = allGroups.filter((group) => selectedGroupIds.includes(group.group_id) ); - // const newGroupPermissions = addingGroups.map((group) => ({ - // group_id: group.group_id, - // group, - // manageCourses: group.permission_manage_topics, - // viewCourseLogs: group.permission_view_topics_log, - // viewCourses: group.permission_view_topics, - // })); - - // setCreateRequest({ - // ...createRequest, - // groupPermissions: [ - // ...createRequest.groupPermissions, - // ...newGroupPermissions, - // ], - // }); - onAddGroups({addingGroups}) - setSelectedGroupIds([]); setOpenAddGroupsDialog(false); }; - // useEffect(() => { - // setGroupPermission(createRequest.groupPermissions[selectedIndex]); - // }, [selectedIndex]); - - // useEffect(() => { - // if (groupPermission) { - // setCreateRequest({ - // ...createRequest, - // groupPermissions: [ - // ...createRequest.groupPermissions.slice(0, selectedIndex), - // groupPermission, - // ...createRequest.groupPermissions.slice(selectedIndex + 1), - // ], - // }); - // } - // }, [groupPermission]); - - // useEffect(() => { - // GroupService.getAllAsCreator(accountId).then((response) => { - // setAllGroups(response.data.groups); - // }); - // }, []); - return (
diff --git a/src/components/Forms/PermissionSwitchGroups/ProblemPermissionSwitchGroup.tsx b/src/components/Forms/PermissionSwitchGroups/ProblemPermissionSwitchGroup.tsx index 518ddcb..ec4e61d 100644 --- a/src/components/Forms/PermissionSwitchGroups/ProblemPermissionSwitchGroup.tsx +++ b/src/components/Forms/PermissionSwitchGroups/ProblemPermissionSwitchGroup.tsx @@ -1,40 +1,29 @@ -import React from "react"; import PermissionSwitch from "../../Permissions/PermissionSwitch"; -import { CreateGroupRequestForm } from "../../../types/forms/CreateGroupRequestForm"; -import { CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; const ProblemPermissionSwitchGroup = ({ - createRequest, - setCreateRequest, + manageProblemsChecked = false, + viewProblemsChecked = false, + onClickManageProblems = () => {}, + onClickViewProblems = () => {}, }: { - createRequest: CreateGroupRequestForm; - setCreateRequest: React.Dispatch< - React.SetStateAction - >; + manageProblemsChecked?: boolean; + viewProblemsChecked?: boolean; + onClickManageProblems?: () => void | undefined; + onClickViewProblems?: () => void | undefined; }) => { return ( <> - setCreateRequest({ - ...createRequest, - manageProblems: !createRequest.manageProblems, - }) - } + checked={manageProblemsChecked} + onClick={() => onClickManageProblems()} /> - setCreateRequest({ - ...createRequest, - viewProblems: !createRequest.viewProblems, - }) - } + checked={viewProblemsChecked} + onClick={() => onClickViewProblems()} /> ); diff --git a/src/components/NavbarCollectionsProblemsAccordion.tsx b/src/components/NavbarCollectionsProblemsAccordion.tsx index 1c0dc82..388e18a 100644 --- a/src/components/NavbarCollectionsProblemsAccordion.tsx +++ b/src/components/NavbarCollectionsProblemsAccordion.tsx @@ -71,16 +71,6 @@ const NavbarCollectionsProblemsAccordion = ({ } = useContext(CourseNavSidebarContext) const handleAccordionTrigger = (index:number) => { - console.log({ - course, - setCourse, - isOpen, - setIsOpen, - section, - setSection, - recentOpenCollection, - setRecentOpenCollection - }) if (recentOpenCollection.length > 0 && recentOpenCollection.includes(String(index))) { setRecentOpenCollection(recentOpenCollection.filter((value) => value !== String(index))) } diff --git a/src/components/ProblemViewLayout.tsx b/src/components/ProblemViewLayout.tsx index 42be497..b699de0 100644 --- a/src/components/ProblemViewLayout.tsx +++ b/src/components/ProblemViewLayout.tsx @@ -72,14 +72,14 @@ const ProblemViewLayout = ({ }; - useEffect(() => { - - console.log('pb',JSON.parse( - handleDeprecatedDescription( - String(problem?.description) - ) - )) - },[problem]) + // useEffect(() => { + + // console.log('pb',JSON.parse( + // handleDeprecatedDescription( + // String(problem?.description) + // ) + // )) + // },[problem]) return ( diff --git a/src/components/TestcaseValidationAccordion.tsx b/src/components/TestcaseValidationAccordion.tsx index 3dd03f4..a6e2c86 100644 --- a/src/components/TestcaseValidationAccordion.tsx +++ b/src/components/TestcaseValidationAccordion.tsx @@ -29,9 +29,9 @@ const TestcaseValidationInstance = ({ // const [inputValue, setInputValue] = useState("1 2 3"); // const [outputValue, setOutputValue] = useState("Hello World!"); - useEffect(() => { - console.log(inputValue, outputValue, status); - }, [outputValue]); + // useEffect(() => { + // console.log(inputValue, outputValue, status); + // }, [outputValue]); return ( diff --git a/src/services/Problem.service.ts b/src/services/Problem.service.ts index 916160d..6c7b537 100644 --- a/src/services/Problem.service.ts +++ b/src/services/Problem.service.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { GetAllProblemsByAccountResponse, GetAllProblemsResponse, ProblemServiceAPI, ValidateProgramResponse } from "../types/apis/Problem.api"; +import { GetAllProblemsByAccountResponse, GetAllProblemsResponse, ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel, ProblemServiceAPI, ValidateProgramResponse } from "../types/apis/Problem.api"; import { BASE_URL } from "../constants/BackendBaseURL"; import { ProblemModel, ProblemPoplulateCreatorModel } from "../types/models/Problem.model"; import { ErrorResponse } from "../types/apis/ErrorHandling"; @@ -17,8 +17,8 @@ export const ProblemService: ProblemServiceAPI = { return axios.get(`${BASE_URL}/api/accounts/${accountId}/problems`); }, - get: async (problemId) => { - return axios.get(`${BASE_URL}/api/problems/${problemId}`); + get: async (accountId,problemId) => { + return axios.get(`${BASE_URL}/api/accounts/${accountId}/problems/${problemId}`); }, update: async (problemId,request) => { diff --git a/src/types/adapters/CreateProblemRequestForm.adapter.ts b/src/types/adapters/CreateProblemRequestForm.adapter.ts index 0eb2fe4..f871c68 100644 --- a/src/types/adapters/CreateProblemRequestForm.adapter.ts +++ b/src/types/adapters/CreateProblemRequestForm.adapter.ts @@ -1,11 +1,15 @@ import { testcaseParse } from "../../utilities/TestcaseFormat"; -import { CreateProblemRequest } from "../apis/Problem.api"; +import { CreateProblemRequest, ProblemGroupPermissionCreateRequest } from "../apis/Problem.api"; import { CreateProblemRequestForm } from "../forms/CreateProblemRequestForm"; export const transformCreateProblemRequestForm2CreateProblemRequest = ( createRequest: CreateProblemRequestForm -): CreateProblemRequest => { - return { +): { + request: CreateProblemRequest + groups: ProblemGroupPermissionCreateRequest[] +} => { + + const request = { title: createRequest.title, language: createRequest.language, description: JSON.stringify(createRequest.description), @@ -15,5 +19,13 @@ export const transformCreateProblemRequestForm2CreateProblemRequest = ( createRequest.testcase_delimeter ), time_limit: createRequest.time_limit, - }; + } + + const groups = createRequest.groupPermissions.map((groupPermission) => ({ + group_id: groupPermission.groupId, + permission_manage_problems: groupPermission.manageProblems, + permission_view_problems: groupPermission.viewProblems, + })) + + return {request, groups}; }; \ No newline at end of file diff --git a/src/types/adapters/Problem.adapter.ts b/src/types/adapters/Problem.adapter.ts index 3421dfd..e7c0231 100644 --- a/src/types/adapters/Problem.adapter.ts +++ b/src/types/adapters/Problem.adapter.ts @@ -1,7 +1,27 @@ +import { ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel } from "../apis/Problem.api"; +import { CreateProblemRequestForm } from "../forms/CreateProblemRequestForm"; import { ProblemHashedTable, ProblemModel, ProblemPopulateTestcases } from "../models/Problem.model"; +export function transformProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel2CreateProblemRequestForm(problem: ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel): CreateProblemRequestForm { + return { + title: problem.title, + description: JSON.parse(String(problem.description)), + language: problem.language, + solution: problem.solution, + testcases: problem.testcases.map(testcase => testcase.input).join(":::\n"), + testcase_delimeter: ":::", + time_limit: problem.time_limit, + groupPermissions: problem.group_permissions.map((permission) => ({ + groupId: permission.group.group_id, + group: permission.group, + manageProblems: permission.permission_manage_problems, + viewProblems: permission.permission_view_problems + })) + } +} + export function transformProblemModel2ProblemHashedTable(problems: ProblemModel[] | ProblemPopulateTestcases[]): ProblemHashedTable { - let result = [] + let result:ProblemHashedTable = {} for (const problem of problems) { result[problem.problem_id] = problem } diff --git a/src/types/apis/Problem.api.ts b/src/types/apis/Problem.api.ts index 4d07ba3..85ade29 100644 --- a/src/types/apis/Problem.api.ts +++ b/src/types/apis/Problem.api.ts @@ -1,6 +1,8 @@ import { AxiosResponse } from "axios"; import { ErrorResponse } from "./ErrorHandling"; -import { ProblemModel, ProblemPoplulateCreatorModel, ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, ProblemPopulateTestcases } from "../models/Problem.model"; +import { ProblemModel, ProblemPoplulateCreatorModel, ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, ProblemPopulateTestcases, TestcaseModel } from "../models/Problem.model"; +import { AccountModel } from "../models/Account.model"; +import { GroupModel, ProblemGroupPermissionModel } from "../models/Group.model"; export type CreateProblemRequest = { title: string; @@ -52,10 +54,26 @@ export type ProblemServiceAPI = { create: (accountId:string,request: CreateProblemRequest) => Promise>; getAll: () => Promise>; getAllAsCreator: (accountId:string) => Promise>; - get: (problemId:string) => Promise>; + get: (accountId:string,problemId:string) => Promise>; update: (problemId:string, request: UpdateProblemRequest | CreateProblemRequest) => Promise>; // deleteMultiple: (problemIds:string[]) => Promise>; delete: (problemId:string) => Promise>; validateProgram: (request: ValidateProgramRequest) => Promise>; +} + +export type ProblemGroupPermissionCreateRequest = { + group_id: string; + permission_manage_problems?: boolean + permission_view_problems?: boolean +} + +export type ProblemGroupPermissionPopulateGroupModel = ProblemGroupPermissionModel & { + group: GroupModel +} + +export type ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel = ProblemModel & { + creator: AccountModel; + testcases: TestcaseModel[]; + group_permissions: ProblemGroupPermissionPopulateGroupModel[] } \ No newline at end of file diff --git a/src/types/forms/CreateProblemRequestForm.ts b/src/types/forms/CreateProblemRequestForm.ts index 955cbad..36d4b7c 100644 --- a/src/types/forms/CreateProblemRequestForm.ts +++ b/src/types/forms/CreateProblemRequestForm.ts @@ -1,5 +1,12 @@ import { PlateEditorValueType } from "../PlateEditorValueType"; +import { GroupModel } from "../models/Group.model"; import { TestcaseModel } from "../models/Problem.model"; +import { ProblemPermissionRequestForm } from "./CreateGroupRequestForm"; + +export type ProblemGroupPermissionRequestForm = { + groupId: string; + group: GroupModel +} & ProblemPermissionRequestForm export type CreateProblemRequestForm = { title: string; @@ -10,4 +17,5 @@ export type CreateProblemRequestForm = { testcase_delimeter: string; time_limit: number; validated_testcases?: TestcaseModel[]; + groupPermissions: ProblemGroupPermissionRequestForm[]; }; \ No newline at end of file diff --git a/src/types/models/Group.model.ts b/src/types/models/Group.model.ts index 18b3b5e..8085037 100644 --- a/src/types/models/Group.model.ts +++ b/src/types/models/Group.model.ts @@ -45,6 +45,14 @@ export type TopicGroupPermissionModel = { topic: string } +export type ProblemGroupPermissionModel = { + problem_group_permission_id: string + group: string + permission_manage_problems: boolean + permission_view_problems: boolean + problem: string +} + export type TopicGroupPermissionPopulateGroupModel = TopicGroupPermissionModel & { group: GroupModel } \ No newline at end of file diff --git a/src/types/models/Problem.model.ts b/src/types/models/Problem.model.ts index 430039a..4e2af60 100644 --- a/src/types/models/Problem.model.ts +++ b/src/types/models/Problem.model.ts @@ -62,4 +62,4 @@ export type ProblemHealth = { has_source_code: boolean testcase_count: number no_runtime_error: boolean -} \ No newline at end of file +} diff --git a/src/views/My/Problems/CreateProblem.tsx b/src/views/My/Problems/CreateProblem.tsx index d96219c..bb6f5c3 100644 --- a/src/views/My/Problems/CreateProblem.tsx +++ b/src/views/My/Problems/CreateProblem.tsx @@ -8,7 +8,7 @@ import { ProblemService } from "../../../services/Problem.service"; import { transformCreateProblemRequestForm2CreateProblemRequest } from "../../../types/adapters/CreateProblemRequestForm.adapter"; import { toast } from "../../../components/shadcn/UseToast"; -const formInitialValue = { +const formInitialValue: CreateProblemRequestForm = { title: "", description: [ { @@ -22,6 +22,7 @@ const formInitialValue = { testcases: "", testcase_delimeter: ":::", time_limit: 1.5, + groupPermissions: [], }; const CreateProblem = () => { @@ -32,9 +33,12 @@ const CreateProblem = () => { const handleSave:OnProblemSaveCallback = (setLoading, createRequest) => { setLoading(true); + + const {request} = transformCreateProblemRequestForm2CreateProblemRequest(createRequest) + ProblemService.create( accountId, - transformCreateProblemRequestForm2CreateProblemRequest(createRequest) + request ).then((response) => { setLoading(false); toast({ diff --git a/src/views/My/Problems/EditProblem.tsx b/src/views/My/Problems/EditProblem.tsx index 9438ad4..d60227c 100644 --- a/src/views/My/Problems/EditProblem.tsx +++ b/src/views/My/Problems/EditProblem.tsx @@ -10,8 +10,12 @@ import { transformCreateProblemRequestForm2CreateProblemRequest } from "../../.. import { CreateProblemRequestForm } from "../../../types/forms/CreateProblemRequestForm"; import { useParams } from "react-router-dom"; import { ProblemPoplulateCreatorModel } from "../../../types/models/Problem.model"; +import { transformProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel2CreateProblemRequestForm } from "../../../types/adapters/Problem.adapter"; const EditProblem = () => { + + const accountId = String(localStorage.getItem("account_id")); + const { problemId } = useParams(); const editProblemId = String(problemId); @@ -25,9 +29,12 @@ const EditProblem = () => { createRequest ) => { setLoading(true); + + const { request } = transformCreateProblemRequestForm2CreateProblemRequest(createRequest) + ProblemService.update( String(editProblemId), - transformCreateProblemRequestForm2CreateProblemRequest(createRequest) + request ).then((response) => { console.log("Update Completed", response.data); setLoading(false); @@ -52,21 +59,9 @@ const EditProblem = () => { }; useEffect(() => { - ProblemService.get(editProblemId).then((response) => { + ProblemService.get(accountId,editProblemId).then((response) => { setProblem(response.data) - setCreateRequest({ - title: response.data.title, - description: JSON.parse( - handleDeprecatedDescription(String(response.data.description)) - ), - language: response.data.language, - solution: response.data.solution, - testcases: response.data.testcases - .map((testcase) => testcase.input) - .join(":::\n"), - testcase_delimeter: ":::", - time_limit: response.data.time_limit, - }); + setCreateRequest(transformProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel2CreateProblemRequestForm(response.data)); }); }, []); return ( From ff0e27aa190eaaa1dde164b303260134befdd2d7 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Thu, 11 Jan 2024 10:20:02 +0700 Subject: [PATCH 17/24] Manage problem permissions --- src/services/Problem.service.ts | 4 + src/types/apis/Problem.api.ts | 144 ++++++++++++++---------- src/views/My/Problems/CreateProblem.tsx | 4 +- src/views/My/Problems/EditProblem.tsx | 4 +- src/views/My/Problems/MyProblems.tsx | 26 ++++- 5 files changed, 120 insertions(+), 62 deletions(-) diff --git a/src/services/Problem.service.ts b/src/services/Problem.service.ts index 6c7b537..d03b29a 100644 --- a/src/services/Problem.service.ts +++ b/src/services/Problem.service.ts @@ -33,6 +33,10 @@ export const ProblemService: ProblemServiceAPI = { // return axios.delete(`${BASE_URL}/api/problems/`, {problem: problemIds}); // }, + updateGroupPermissions: async (problemId, accountId,groups) => { + return axios.put(`${BASE_URL}/api/accounts/${accountId}/problems/${problemId}/groups`, {groups}); + }, + validateProgram: async (request) => { return axios.post(`${BASE_URL}/api/problems/validate`, request); } diff --git a/src/types/apis/Problem.api.ts b/src/types/apis/Problem.api.ts index 85ade29..009c751 100644 --- a/src/types/apis/Problem.api.ts +++ b/src/types/apis/Problem.api.ts @@ -1,79 +1,109 @@ import { AxiosResponse } from "axios"; import { ErrorResponse } from "./ErrorHandling"; -import { ProblemModel, ProblemPoplulateCreatorModel, ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, ProblemPopulateTestcases, TestcaseModel } from "../models/Problem.model"; +import { + ProblemModel, + ProblemPoplulateCreatorModel, + ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, + ProblemPopulateTestcases, + TestcaseModel, +} from "../models/Problem.model"; import { AccountModel } from "../models/Account.model"; import { GroupModel, ProblemGroupPermissionModel } from "../models/Group.model"; export type CreateProblemRequest = { - title: string; - language: string; - description: string | null; - solution: string; - testcases: string[]; - time_limit: number; -} + title: string; + language: string; + description: string | null; + solution: string; + testcases: string[]; + time_limit: number; +}; export type UpdateProblemRequest = { - title?: string; - language?: string; - description?: string; - solution?: string; - testcases?: string[]; - time_limit?: number; -} + title?: string; + language?: string; + description?: string; + solution?: string; + testcases?: string[]; + time_limit?: number; +}; export type GetAllProblemsByAccountResponse = { - problems: ProblemPopulateTestcases[]; -} + problems: ProblemPopulateTestcases[]; + manageable_problems: ProblemPopulateTestcases[]; +}; export type ValidateProgramRequest = { - language: string; - source_code: string; - testcases: string[]; - time_limited: number; -} + language: string; + source_code: string; + testcases: string[]; + time_limited: number; +}; export type RuntimeResult = { - input: string; - output: string | null; - runtime_status: string; -} + input: string; + output: string | null; + runtime_status: string; +}; export type ValidateProgramResponse = { - runnable: boolean; - has_error: boolean; - has_timeout: boolean; - runtime_results: RuntimeResult[]; -} + runnable: boolean; + has_error: boolean; + has_timeout: boolean; + runtime_results: RuntimeResult[]; +}; export type GetAllProblemsResponse = { - problems: ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel[] -} - -export type ProblemServiceAPI = { - create: (accountId:string,request: CreateProblemRequest) => Promise>; - getAll: () => Promise>; - getAllAsCreator: (accountId:string) => Promise>; - get: (accountId:string,problemId:string) => Promise>; - update: (problemId:string, request: UpdateProblemRequest | CreateProblemRequest) => Promise>; - // deleteMultiple: (problemIds:string[]) => Promise>; - delete: (problemId:string) => Promise>; - - validateProgram: (request: ValidateProgramRequest) => Promise>; -} + problems: ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel[]; +}; export type ProblemGroupPermissionCreateRequest = { - group_id: string; - permission_manage_problems?: boolean - permission_view_problems?: boolean -} + group_id: string; + permission_manage_problems?: boolean; + permission_view_problems?: boolean; +}; -export type ProblemGroupPermissionPopulateGroupModel = ProblemGroupPermissionModel & { - group: GroupModel -} +export type ProblemGroupPermissionPopulateGroupModel = + ProblemGroupPermissionModel & { + group: GroupModel; + }; -export type ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel = ProblemModel & { - creator: AccountModel; - testcases: TestcaseModel[]; - group_permissions: ProblemGroupPermissionPopulateGroupModel[] -} \ No newline at end of file +export type ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel = + ProblemModel & { + creator: AccountModel; + testcases: TestcaseModel[]; + group_permissions: ProblemGroupPermissionPopulateGroupModel[]; + }; + +export type ProblemServiceAPI = { + create: ( + accountId: string, + request: CreateProblemRequest + ) => Promise>; + getAll: () => Promise>; + getAllAsCreator: ( + accountId: string + ) => Promise>; + get: ( + accountId: string, + problemId: string + ) => Promise< + AxiosResponse + >; + update: ( + problemId: string, + request: UpdateProblemRequest | CreateProblemRequest + ) => Promise>; + // deleteMultiple: (problemIds:string[]) => Promise>; + delete: (problemId: string) => Promise>; + updateGroupPermissions: ( + problemId: string, + accountId: string, + groups: ProblemGroupPermissionCreateRequest[] + ) => Promise< + AxiosResponse + >; + validateProgram: ( + request: ValidateProgramRequest + ) => Promise>; +}; diff --git a/src/views/My/Problems/CreateProblem.tsx b/src/views/My/Problems/CreateProblem.tsx index bb6f5c3..f49dc18 100644 --- a/src/views/My/Problems/CreateProblem.tsx +++ b/src/views/My/Problems/CreateProblem.tsx @@ -34,12 +34,14 @@ const CreateProblem = () => { const handleSave:OnProblemSaveCallback = (setLoading, createRequest) => { setLoading(true); - const {request} = transformCreateProblemRequestForm2CreateProblemRequest(createRequest) + const {request,groups} = transformCreateProblemRequestForm2CreateProblemRequest(createRequest) ProblemService.create( accountId, request ).then((response) => { + return ProblemService.updateGroupPermissions(response.data.problem_id,accountId,groups) + }).then((response) => { setLoading(false); toast({ title: "Create Completed", diff --git a/src/views/My/Problems/EditProblem.tsx b/src/views/My/Problems/EditProblem.tsx index d60227c..7dc9394 100644 --- a/src/views/My/Problems/EditProblem.tsx +++ b/src/views/My/Problems/EditProblem.tsx @@ -30,12 +30,14 @@ const EditProblem = () => { ) => { setLoading(true); - const { request } = transformCreateProblemRequestForm2CreateProblemRequest(createRequest) + const { request,groups } = transformCreateProblemRequestForm2CreateProblemRequest(createRequest) ProblemService.update( String(editProblemId), request ).then((response) => { + return ProblemService.updateGroupPermissions(response.data.problem_id,accountId,groups) + }).then((response) => { console.log("Update Completed", response.data); setLoading(false); toast({ diff --git a/src/views/My/Problems/MyProblems.tsx b/src/views/My/Problems/MyProblems.tsx index 76c5bbf..794b9e6 100644 --- a/src/views/My/Problems/MyProblems.tsx +++ b/src/views/My/Problems/MyProblems.tsx @@ -11,22 +11,27 @@ import CardContainer from "../../../components/CardContainer"; import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; import DeleteProblemConfirmationDialog from "../../../components/DeleteProblemConfirmationDialog"; import { FilePlus } from "lucide-react"; +import { Tabs,TabsList, TabsTrigger } from "../../../components/shadcn/Tabs"; const MyProblems = () => { const accountId = String(localStorage.getItem("account_id")); const navigate = useNavigate(); const [problems, setProblems] = useState([]); + const [manageableProblems, setManageableProblems] = useState([]); + const {section,setSection} = useContext(NavSidebarContext) + const [tabValue, setTabValue] = useState("personal") + useEffect(() => { ProblemService.getAllAsCreator(accountId).then((response) => { setProblems(response.data.problems); - console.log(response.data.problems); + setManageableProblems(response.data.manageable_problems) }); setSection("PROBLEMS") - }, []); + }, [accountId]); return ( @@ -40,6 +45,18 @@ const MyProblems = () => {
+
+ setTabValue(e)}> + + + Personal + + + Manageable + + + +
- {problems.map((problem, index) => ( + {tabValue === "personal" && problems.map((problem, index) => ( + + ))} + {tabValue === "manageable" && manageableProblems.map((problem, index) => ( ))} From 9c12557caf1a6879140043afd9c2386e83c93dbb Mon Sep 17 00:00:00 2001 From: KanonKC Date: Thu, 11 Jan 2024 12:40:52 +0700 Subject: [PATCH 18/24] Update collection to handle problem collection permissions --- .../CreateCollectionForm/ManageProblems.tsx | 138 +++++++++------- .../CreateCourseForm/ManageCollections.tsx | 66 +++----- src/services/Collection.service.ts | 4 +- src/services/Problem.service.ts | 4 +- src/types/adapters/Collection.adapter.ts | 13 +- src/types/adapters/Problem.adapter.ts | 3 +- src/types/apis/Collection.api.ts | 4 +- src/types/apis/Problem.api.ts | 8 +- .../forms/CreateCollectionRequestForm.ts | 12 +- src/types/models/Collection.model.ts | 151 ++++++++++-------- src/types/models/Problem.model.ts | 12 +- src/types/models/Submission.model.ts | 2 +- 12 files changed, 232 insertions(+), 185 deletions(-) diff --git a/src/components/Forms/CreateCollectionForm/ManageProblems.tsx b/src/components/Forms/CreateCollectionForm/ManageProblems.tsx index 5c32cce..7db43bd 100644 --- a/src/components/Forms/CreateCollectionForm/ManageProblems.tsx +++ b/src/components/Forms/CreateCollectionForm/ManageProblems.tsx @@ -1,24 +1,18 @@ import React, { useEffect, useState } from "react"; -import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; import { ReactSortable } from "react-sortablejs"; -import { Button } from "../../shadcn/Button"; -import { Separator } from "../../shadcn/Seperator"; -import { Input } from "../../shadcn/Input"; import { ProblemService } from "../../../services/Problem.service"; -import { - ProblemHashedTable, - ProblemModel, - ProblemSecureModel, -} from "../../../types/models/Problem.model"; -import { ItemInterface } from "./../../../../node_modules/react-sortablejs/dist/index.d"; -import MyProblemCard from "../../Cards/ProblemCards/MyProblemCard"; -import CardContainer from "../../CardContainer"; -import SortableCardContainer from "../../SortableCardContainer"; -import MyProblemMiniCard from "../../Cards/ProblemCards/MyProblemMiniCard"; -import { ScrollArea } from "../../shadcn/ScrollArea"; -import { Item } from "@radix-ui/react-context-menu"; import { transformProblemModel2ProblemHashedTable } from "../../../types/adapters/Problem.adapter"; +import { + CreateCollectionRequestForm, + ProblemItemInterface, +} from "../../../types/forms/CreateCollectionRequestForm"; +import { ProblemHashedTable } from "../../../types/models/Problem.model"; import MyProblemMiniCard2 from "../../Cards/ProblemCards/MyProblemMiniCard2"; +import { Button } from "../../shadcn/Button"; +import { Input } from "../../shadcn/Input"; +import { ScrollArea } from "../../shadcn/ScrollArea"; +import { Separator } from "../../shadcn/Seperator"; +import { ItemInterface } from "./../../../../node_modules/react-sortablejs/dist/index.d"; const ManageProblems = ({ createRequest, @@ -29,33 +23,33 @@ const ManageProblems = ({ React.SetStateAction >; }) => { - const accountId = String(localStorage.getItem("account_id")); const [allProblemsSortable, setAllProblemsSortable] = useState< - ItemInterface[] + ProblemItemInterface[] >([]); const [selectedProblemsSortable, setSelectedProblemsSortable] = useState< - ItemInterface[] + ProblemItemInterface[] >([]); - const [allProblems, setAllProblems] = useState< - ProblemHashedTable - >({}); + const [allProblems, setAllProblems] = useState({}); const [initial, setInitial] = useState(true); - const [selectedProblemSortableIds, setSelectedProblemSortableIds] = useState([]); + const [selectedProblemSortableIds, setSelectedProblemSortableIds] = + useState([]); useEffect(() => { - setSelectedProblemSortableIds(selectedProblemsSortable.map((item) => item.id as string)); - },[selectedProblemsSortable]) + setSelectedProblemSortableIds( + selectedProblemsSortable.map((item) => item.id as string) + ); + }, [selectedProblemsSortable]); const handleRemoveSelectedProblem = (id: string) => { - setSelectedProblemsSortable( - [...selectedProblemsSortable.filter((item) => item.id !== id)] - ); - } + setSelectedProblemsSortable([ + ...selectedProblemsSortable.filter((item) => item.id !== id), + ]); + }; - const handleQuickToggleSelectedProblem = (item: ItemInterface) => { + const handleQuickToggleSelectedProblem = (item: ProblemItemInterface) => { // if (selectedProblemsSortable.find((item1) => item1.id === item.id)) { // console.log("Remove"); // handleRemoveSelectedProblem(item.id as string); @@ -66,11 +60,10 @@ const ManageProblems = ({ if (selectedProblemSortableIds.includes(item.id as string)) { handleRemoveSelectedProblem(item.id as string); - } - else { + } else { setSelectedProblemsSortable([...selectedProblemsSortable, item]); } - } + }; useEffect(() => { setCreateRequest({ @@ -81,33 +74,56 @@ const ManageProblems = ({ useEffect(() => { ProblemService.getAllAsCreator(accountId).then((response) => { - setAllProblems(transformProblemModel2ProblemHashedTable(response.data.problems)); + setAllProblems( + transformProblemModel2ProblemHashedTable(response.data.problems) + ); setAllProblemsSortable( response.data.problems.map((problem) => ({ - id: problem?.problem_id, - name: problem?.title + id: problem.problem_id, + name: problem.title, + problem: problem, + groupPermissions: [], })) ); }); }, [accountId]); + useEffect(() => { + if (createRequest.collection) { + setAllProblems({ + ...allProblems, + ...transformProblemModel2ProblemHashedTable( + createRequest.collection.problems.map((cp) => cp.problem) + ), + }); + } + }, [createRequest.collection, allProblems]); + useEffect(() => { if (initial) { - setSelectedProblemsSortable(createRequest.problemsInterface) + console.log("AAA",createRequest) + // setSelectedProblemsSortable(createRequest.problemsInterface); + setSelectedProblemsSortable( + createRequest.collection?.problems.map((cp) => ({ + id: cp.problem.problem_id, + name: cp.problem.title, + problem: cp.problem, + groupPermissions: cp.problem.group_permissions.map((gc) => ({ + groupId: gc.group.group_id, + group: gc.group, + manageProblems: gc.permission_manage_problems, + viewProblems: gc.permission_view_problems, + })), + })) ?? ([] as ProblemItemInterface[]) + ); setInitial(false); } - console.log("Create Request", createRequest); - },[createRequest]) + // console.log("Create Request", createRequest); + }, [createRequest]); return (
-
-

Manage Problems

- - -
-
@@ -123,11 +139,13 @@ const ManageProblems = ({ {selectedProblemsSortable?.map((item) => ( handleRemoveSelectedProblem(item.id as string)} - key={item.id} - problem={ - allProblems[item.id as string] + onClick={() => + handleRemoveSelectedProblem( + item.id as string + ) } + key={item.id} + problem={item.problem} /> ))} @@ -157,14 +175,24 @@ const ManageProblems = ({ className="grid grid-cols-3 gap-2 p-2 rounded-md" > {allProblemsSortable?.map((item) => ( -
+
handleQuickToggleSelectedProblem(item)} - disabled={selectedProblemSortableIds.includes(item.id as string)} - key={item.id} - problem={ - allProblems[item.id as string] + onClick={() => + handleQuickToggleSelectedProblem( + item + ) } + disabled={selectedProblemSortableIds.includes( + item.id as string + )} + key={item.id} + problem={item.problem} />
))} diff --git a/src/components/Forms/CreateCourseForm/ManageCollections.tsx b/src/components/Forms/CreateCourseForm/ManageCollections.tsx index ae81b79..8a58294 100644 --- a/src/components/Forms/CreateCourseForm/ManageCollections.tsx +++ b/src/components/Forms/CreateCourseForm/ManageCollections.tsx @@ -2,10 +2,13 @@ import React, { useEffect, useState } from "react"; import { ReactSortable } from "react-sortablejs"; import { CollectionService } from "../../../services/Collection.service"; import { transformCollectionPopulateProblemSecureModel2CollectionHashedTable } from "../../../types/adapters/Collection.adapter"; -import { CollectionItemInterface, CreateCourseRequestForm } from "../../../types/forms/CreateCourseRequestForm"; +import { + CollectionItemInterface, + CreateCourseRequestForm, +} from "../../../types/forms/CreateCourseRequestForm"; import { CollectionHashedTable, - CollectionPopulateCollectionProblemPopulateProblemModel + CollectionPopulateCollectionProblemPopulateProblemModel, } from "../../../types/models/Collection.model"; import MyCollectionMiniCard2 from "../../Cards/CollectionCards/MyCollectionMiniCard2"; import { Input } from "../../shadcn/Input"; @@ -50,7 +53,9 @@ const ManageCollections = ({ ]); }; - const handleQuickToggleSelectedCollection = (item: CollectionItemInterface) => { + const handleQuickToggleSelectedCollection = ( + item: CollectionItemInterface + ) => { // if (selectedCollectionsSortable.find((item1) => item1.id === item.id)) { // console.log("Remove"); // handleRemoveSelectedCollection(item.id as string); @@ -106,36 +111,26 @@ const ManageCollections = ({ ) ), }); - - // console.log({ - // ...allCollections, - // ...transformCollectionPopulateProblemSecureModel2CollectionHashedTable( - // createRequest.course.collections.map( - // (cc) => cc.collection - // ) as CollectionModel[] - // ), - // }) - - // console.log('ccc', - // createRequest.course.collections.map((cc) => cc.collection) - // ); } - // console.log(createRequest.course); }, [createRequest.course, allCollections]); useEffect(() => { if (initial) { - setSelectedCollectionsSortable(createRequest.course?.collections.map((cc) => ({ - id: cc.collection.collection_id, - name: cc.collection.name, - collection: cc.collection, - groupPermissions: cc.collection.group_permissions.map((gc) => ({ - group_id: gc.group.group_id, - group: gc.group, - manageCollections: gc.permission_manage_collections, - viewCollections: gc.permission_view_collections, - })), - })) ?? ([] as CollectionItemInterface[])); + setSelectedCollectionsSortable( + createRequest.course?.collections.map((cc) => ({ + id: cc.collection.collection_id, + name: cc.collection.name, + collection: cc.collection, + groupPermissions: cc.collection.group_permissions.map( + (gc) => ({ + group_id: gc.group.group_id, + group: gc.group, + manageCollections: gc.permission_manage_collections, + viewCollections: gc.permission_view_collections, + }) + ), + })) ?? ([] as CollectionItemInterface[]) + ); setInitial(false); } @@ -144,7 +139,6 @@ const ManageCollections = ({ return (
-
@@ -167,12 +161,7 @@ const ManageCollections = ({ ) } key={item.id} - collection={ - item.collection - // allCollections[ - // item.id as string - // ] as CollectionPopulateCollectionProblemPopulateProblemModel - } + collection={item.collection} /> ) )} @@ -222,12 +211,7 @@ const ManageCollections = ({ item.id as string )} key={item.id} - collection={ - item.collection - // allCollections[ - // item.id as string - // ] as CollectionPopulateCollectionProblemPopulateProblemModel - } + collection={item.collection} />
))} diff --git a/src/services/Collection.service.ts b/src/services/Collection.service.ts index 482f5d3..bedd324 100644 --- a/src/services/Collection.service.ts +++ b/src/services/Collection.service.ts @@ -1,6 +1,6 @@ import axios from "axios"; import { CollectionServiceAPI, GetCollectionByAccountResponse } from "../types/apis/Collection.api"; -import { CollectionModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../types/models/Collection.model"; +import { CollectionModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateCollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../types/models/Collection.model"; import { BASE_URL } from "../constants/BackendBaseURL"; export const CollectionService: CollectionServiceAPI = { @@ -9,7 +9,7 @@ export const CollectionService: CollectionServiceAPI = { }, get: (collectionId,accountId) => { - return axios.get(`${BASE_URL}/api/accounts/${accountId}/collections/${collectionId}`); + return axios.get(`${BASE_URL}/api/accounts/${accountId}/collections/${collectionId}`); }, getAllAsCreator: (accountId) => { diff --git a/src/services/Problem.service.ts b/src/services/Problem.service.ts index d03b29a..119b248 100644 --- a/src/services/Problem.service.ts +++ b/src/services/Problem.service.ts @@ -1,7 +1,7 @@ import axios from "axios"; -import { GetAllProblemsByAccountResponse, GetAllProblemsResponse, ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel, ProblemServiceAPI, ValidateProgramResponse } from "../types/apis/Problem.api"; +import { GetAllProblemsByAccountResponse, GetAllProblemsResponse, ProblemServiceAPI, ValidateProgramResponse } from "../types/apis/Problem.api"; import { BASE_URL } from "../constants/BackendBaseURL"; -import { ProblemModel, ProblemPoplulateCreatorModel } from "../types/models/Problem.model"; +import { ProblemModel, ProblemPoplulateCreatorModel, ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel } from "../types/models/Problem.model"; import { ErrorResponse } from "../types/apis/ErrorHandling"; export const ProblemService: ProblemServiceAPI = { diff --git a/src/types/adapters/Collection.adapter.ts b/src/types/adapters/Collection.adapter.ts index 69ec849..6fde69e 100644 --- a/src/types/adapters/Collection.adapter.ts +++ b/src/types/adapters/Collection.adapter.ts @@ -1,5 +1,5 @@ import { CreateCollectionRequestForm } from "../forms/CreateCollectionRequestForm"; -import { CollectionHashedTable, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../models/Collection.model"; +import { CollectionHashedTable, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateCollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel } from "../models/Collection.model"; export function transformCollectionPopulateProblemSecureModel2CollectionHashedTable(collections: CollectionPopulateCollectionProblemPopulateProblemModel[] ): CollectionHashedTable { let result:CollectionHashedTable = {}; @@ -9,15 +9,21 @@ export function transformCollectionPopulateProblemSecureModel2CollectionHashedTa return result; } -export function transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest(collection: CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel): CreateCollectionRequestForm { +export function transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest(collection: CollectionPopulateCollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupAndCollectionGroupPermissionsPopulateGroupModel): CreateCollectionRequestForm { - console.log(collection); return { title: collection.name, description: JSON.parse(String(collection.description)), problemsInterface: collection.problems.map((cp) => ({ id: cp.problem.problem_id, name: cp.problem.title, + problem: cp.problem, + groupPermissions: cp.problem.group_permissions.map((pgp) => ({ + groupId: pgp.group.group_id, + group: pgp.group, + manageProblems: pgp.permission_manage_problems, + viewProblems: pgp.permission_view_problems, + })), })), groupPermissions: collection.group_permissions.map((cgp) => ({ group_id: cgp.group.group_id, @@ -25,5 +31,6 @@ export function transformCollectionPopulateCollectionProblemsPopulateProblemAndC manageCollections: cgp.permission_manage_collections, viewCollections: cgp.permission_view_collections, })), + collection: collection, } } \ No newline at end of file diff --git a/src/types/adapters/Problem.adapter.ts b/src/types/adapters/Problem.adapter.ts index e7c0231..fdc5823 100644 --- a/src/types/adapters/Problem.adapter.ts +++ b/src/types/adapters/Problem.adapter.ts @@ -1,6 +1,5 @@ -import { ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel } from "../apis/Problem.api"; import { CreateProblemRequestForm } from "../forms/CreateProblemRequestForm"; -import { ProblemHashedTable, ProblemModel, ProblemPopulateTestcases } from "../models/Problem.model"; +import { ProblemHashedTable, ProblemModel, ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel, ProblemPopulateTestcases } from "../models/Problem.model"; export function transformProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel2CreateProblemRequestForm(problem: ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel): CreateProblemRequestForm { return { diff --git a/src/types/apis/Collection.api.ts b/src/types/apis/Collection.api.ts index fb1424c..a84f559 100644 --- a/src/types/apis/Collection.api.ts +++ b/src/types/apis/Collection.api.ts @@ -1,5 +1,5 @@ import { AxiosResponse } from "axios" -import { CollectionCreateRequest, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel, CollectionUpdateRequest } from "../models/Collection.model" +import { CollectionCreateRequest, CollectionModel, CollectionPopulateCollectionProblemPopulateProblemModel, CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateCollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateProblemSecureModel, CollectionProblemModel, CollectionUpdateRequest } from "../models/Collection.model" export type GetCollectionByAccountResponse = { collections: CollectionPopulateCollectionProblemPopulateProblemModel[]; @@ -15,7 +15,7 @@ export type CollectionGroupPermissionCreateRequest = { export type CollectionServiceAPI = { create: (accountId:string,request:CollectionCreateRequest) => Promise>; - get: (collectionId:string,accountId:string) => Promise>; + get: (collectionId:string,accountId:string) => Promise>; update: (collectionId:string,request:CollectionUpdateRequest) => Promise>; getAllAsCreator: (accountId:string) => Promise>; addProblem: (collectionId:string,problemIds:string[]) => Promise>; diff --git a/src/types/apis/Problem.api.ts b/src/types/apis/Problem.api.ts index 009c751..e837713 100644 --- a/src/types/apis/Problem.api.ts +++ b/src/types/apis/Problem.api.ts @@ -4,6 +4,7 @@ import { ProblemModel, ProblemPoplulateCreatorModel, ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, + ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel, ProblemPopulateTestcases, TestcaseModel, } from "../models/Problem.model"; @@ -68,13 +69,6 @@ export type ProblemGroupPermissionPopulateGroupModel = group: GroupModel; }; -export type ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel = - ProblemModel & { - creator: AccountModel; - testcases: TestcaseModel[]; - group_permissions: ProblemGroupPermissionPopulateGroupModel[]; - }; - export type ProblemServiceAPI = { create: ( accountId: string, diff --git a/src/types/forms/CreateCollectionRequestForm.ts b/src/types/forms/CreateCollectionRequestForm.ts index 8e572df..69aa1fd 100644 --- a/src/types/forms/CreateCollectionRequestForm.ts +++ b/src/types/forms/CreateCollectionRequestForm.ts @@ -1,17 +1,25 @@ import { ItemInterface } from "react-sortablejs"; -import { CollectionProblemPopulateProblemSecureModel } from "../models/Collection.model"; +import { CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel, CollectionPopulateCollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupAndCollectionGroupPermissionsPopulateGroupModel, CollectionProblemPopulateProblemSecureModel } from "../models/Collection.model"; import { PlateEditorValueType } from "../PlateEditorValueType"; import { GroupModel } from "../models/Group.model"; import { CollectionPermissionRequestForm } from "./CreateGroupRequestForm"; +import { ProblemGroupPermissionRequestForm } from "./CreateProblemRequestForm"; +import { ProblemPopulateTestcases } from "../models/Problem.model"; export type CollectionGroupPermissionRequestForm = { group_id: string; group: GroupModel; } & CollectionPermissionRequestForm +export type ProblemItemInterface = ItemInterface & { + problem: ProblemPopulateTestcases; + groupPermissions: ProblemGroupPermissionRequestForm[]; +} + export type CreateCollectionRequestForm = { title: string; description: PlateEditorValueType; - problemsInterface: ItemInterface[]; + problemsInterface: ProblemItemInterface[]; groupPermissions: CollectionGroupPermissionRequestForm[]; + collection: null | CollectionPopulateCollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupAndCollectionGroupPermissionsPopulateGroupModel; } \ No newline at end of file diff --git a/src/types/models/Collection.model.ts b/src/types/models/Collection.model.ts index 1e0d856..f8a2ccf 100644 --- a/src/types/models/Collection.model.ts +++ b/src/types/models/Collection.model.ts @@ -1,85 +1,104 @@ import { GroupModel } from "./Group.model"; -import { ProblemModel, ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, ProblemSecureModel } from "./Problem.model"; +import { + ProblemModel, + ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, + ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel, + ProblemSecureModel, +} from "./Problem.model"; export type CollectionModel = { - collection_id: string; - creator: string; - name: string; - description: string | null; - is_active: boolean; - is_private: boolean; - created_date: string; - updated_date: string; -} + collection_id: string; + creator: string; + name: string; + description: string | null; + is_active: boolean; + is_private: boolean; + created_date: string; + updated_date: string; +}; export type CollectionPopulateProblemSecureModel = CollectionModel & { - problems: ProblemSecureModel[]; -} + problems: ProblemSecureModel[]; +}; export type CollectionProblemModel = CollectionModel & { - problems: CollectionProblemPopulateProblemSecureModel[]; -} + problems: CollectionProblemPopulateProblemSecureModel[]; +}; export type CollectionCreateRequest = { - name: string; - description?: string; -} + name: string; + description?: string; +}; export type CollectionUpdateRequest = { - name?: string; - description?: string; -} + name?: string; + description?: string; +}; export type CollectionProblemPopulateProblemModel = { - id: string; - problem: ProblemModel; - order: number; - collection: number; -} + id: string; + problem: ProblemModel; + order: number; + collection: number; +}; export type CollectionProblemPopulateProblemSecureModel = { - id: string; - problem: ProblemSecureModel; - order: number; - collection: number; -} - - + id: string; + problem: ProblemSecureModel; + order: number; + collection: number; +}; export type CollectionHashedTable = { - [collection_id:string]: CollectionPopulateCollectionProblemPopulateProblemModel -} - -export type CollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel = { - id: string; - problem: ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel; - order: number; - collection: number; -} - -export type CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel = CollectionModel & { - problems: CollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel[] -} - - -export type CollectionPopulateCollectionProblemPopulateProblemModel = CollectionModel & { - problems: CollectionProblemPopulateProblemModel[]; -} + [ + collection_id: string + ]: CollectionPopulateCollectionProblemPopulateProblemModel; +}; + +export type CollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel = + { + id: string; + problem: ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel; + order: number; + collection: number; + }; + +export type CollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel = + CollectionModel & { + problems: CollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel[]; + }; + +export type CollectionPopulateCollectionProblemPopulateProblemModel = + CollectionModel & { + problems: CollectionProblemPopulateProblemModel[]; + }; export type CollectionGroupPermissionModel = { - collection_group_permission_id: string - group: string - permission_manage_collections: boolean - permission_view_collections: boolean - collection: string -} - - -export type CollectionGroupPermissionPopulateGroupModel = CollectionGroupPermissionModel & { - group: GroupModel -} - -export type CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel = CollectionModel & { - problems: CollectionProblemPopulateProblemModel[]; - group_permissions: CollectionGroupPermissionPopulateGroupModel[]; -} \ No newline at end of file + collection_group_permission_id: string; + group: string; + permission_manage_collections: boolean; + permission_view_collections: boolean; + collection: string; +}; + +export type CollectionGroupPermissionPopulateGroupModel = + CollectionGroupPermissionModel & { + group: GroupModel; + }; + +export type CollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel = + CollectionModel & { + problems: CollectionProblemPopulateProblemModel[]; + group_permissions: CollectionGroupPermissionPopulateGroupModel[]; + }; + +export type CollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel = + CollectionProblemModel & { + problem: ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel; + }; + +export type CollectionPopulateCollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupAndCollectionGroupPermissionsPopulateGroupModel = + CollectionModel & { + group_permissions: CollectionGroupPermissionPopulateGroupModel[]; + problems: CollectionProblemsPopulateProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel[]; + }; diff --git a/src/types/models/Problem.model.ts b/src/types/models/Problem.model.ts index 4e2af60..3118885 100644 --- a/src/types/models/Problem.model.ts +++ b/src/types/models/Problem.model.ts @@ -1,4 +1,5 @@ -import { AccountSecureModel } from "./Account.model" +import { ProblemGroupPermissionPopulateGroupModel } from "../apis/Problem.api" +import { AccountModel, AccountSecureModel } from "./Account.model" import { SubmissionPopulateSubmissionTestcasesSecureModel } from "./Submission.model" export type TestcaseModel = { @@ -55,7 +56,7 @@ export type ProblemPopulateTestcases = ProblemModel & { } export type ProblemHashedTable = { - [id:string]: ProblemModel | ProblemPopulateTestcases + [id:string]: ProblemModel | ProblemPopulateTestcases | ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel } export type ProblemHealth = { @@ -63,3 +64,10 @@ export type ProblemHealth = { testcase_count: number no_runtime_error: boolean } + +export type ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel = + ProblemModel & { + creator: AccountModel; + testcases: TestcaseModel[]; + group_permissions: ProblemGroupPermissionPopulateGroupModel[]; + }; \ No newline at end of file diff --git a/src/types/models/Submission.model.ts b/src/types/models/Submission.model.ts index f3f639b..0693779 100644 --- a/src/types/models/Submission.model.ts +++ b/src/types/models/Submission.model.ts @@ -70,7 +70,7 @@ export type SubmissionPopulateSubmissionTestcaseSecureModel = { export type GetSubmissionByAccountProblemSubmissionModel = { submission_id: string - problem: number + problem: string language: string submission_code: string is_passed: boolean From 90a76b51b87c6c6ac208c119841bb4183cd143c2 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Thu, 11 Jan 2024 14:02:19 +0700 Subject: [PATCH 19/24] Manage collection problems permissions --- .../CreateCollectionForm/ManageGroups.tsx | 325 ++++++++++++++++-- .../CreateCollectionRequestForm.adapter.ts | 52 ++- src/views/My/Collections/CreateCollection.tsx | 67 +++- src/views/My/Collections/EditCollection.tsx | 152 +++++--- src/views/My/Courses/CreateCourse.tsx | 2 +- src/views/My/Courses/EditCourse.tsx | 3 +- 6 files changed, 470 insertions(+), 131 deletions(-) diff --git a/src/components/Forms/CreateCollectionForm/ManageGroups.tsx b/src/components/Forms/CreateCollectionForm/ManageGroups.tsx index bc48427..702c96e 100644 --- a/src/components/Forms/CreateCollectionForm/ManageGroups.tsx +++ b/src/components/Forms/CreateCollectionForm/ManageGroups.tsx @@ -10,6 +10,10 @@ import GroupAndPermissionManager, { import CollectionPermissionSwitchGroup from "../PermissionSwitchGroups/CollectionPermissionSwitchGroup"; import { GroupModel } from "../../../types/models/Group.model"; import { GroupService } from "../../../services/Group.service"; +import { Tabs, TabsList, TabsTrigger } from "../../shadcn/Tabs"; +import PermissionSwitchScrollArea from "../../Permissions/PermissionSwitchScrollArea"; +import MyProblemMiniCard2 from "../../Cards/ProblemCards/MyProblemMiniCard2"; +import { Switch } from "../../shadcn/Switch"; const ManageGroups = ({ createRequest, @@ -20,13 +24,13 @@ const ManageGroups = ({ React.SetStateAction >; }) => { - const accountId = String(localStorage.getItem("account_id")); const [groupPermission, setGroupPermission] = useState(); const [selectedIndex, setSelectedIndex] = useState(-1); + const [currentGroupId, setCurrentGroupId] = useState(""); const handleAddGroups = ({ addingGroups, @@ -52,15 +56,22 @@ const ManageGroups = ({ }: GroupAndPermissionManagerOnRemoveGroupCallback) => { setCreateRequest({ ...createRequest, - groupPermissions: [ - ...createRequest.groupPermissions.slice(0, index), - ...createRequest.groupPermissions.slice(index + 1), - ], + groupPermissions: createRequest.groupPermissions.filter( + (v, i) => i !== index + ), }); }; useEffect(() => { - setGroupPermission(createRequest.groupPermissions[selectedIndex]); + if ( + selectedIndex >= 0 && + selectedIndex < createRequest.groupPermissions.length + ) { + setGroupPermission(createRequest.groupPermissions[selectedIndex]); + setCurrentGroupId( + createRequest.groupPermissions[selectedIndex].group_id + ); + } }, [selectedIndex]); useEffect(() => { @@ -76,44 +87,284 @@ const ManageGroups = ({ } }, [groupPermission]); + const [tabValue, setTabValue] = useState("collection"); const [allGroups, setAllGroups] = useState([]); useEffect(() => { GroupService.getAllAsCreator(accountId).then((response) => { setAllGroups(response.data.groups); - }) - },[accountId]) + }); + }, [accountId]); return ( - handleAddGroups(e)} - onRemoveGroup={(e) => handleRemoveGroup(e)} - selectedIndex={selectedIndex} - setSelectedIndex={setSelectedIndex} - > - {groupPermission && selectedIndex >= 0 && ( - { - setGroupPermission({ - ...groupPermission, - manageCollections: - !groupPermission.manageCollections, - }); - }} - onClickViewCollections={() => { - setGroupPermission({ - ...groupPermission, - viewCollections: !groupPermission.viewCollections, - }); - }} - /> - )} - + <> +
+ setTabValue(e)}> + + + Collection Permissions + + + Problem Permissions + + + +
+ handleAddGroups(e)} + onRemoveGroup={(e) => handleRemoveGroup(e)} + selectedIndex={selectedIndex} + setSelectedIndex={setSelectedIndex} + > + <> + {tabValue === "collection" && ( + + {groupPermission && selectedIndex >= 0 && ( + { + setGroupPermission({ + ...groupPermission, + manageCollections: + !groupPermission.manageCollections, + }); + }} + onClickViewCollections={() => { + setGroupPermission({ + ...groupPermission, + viewCollections: + !groupPermission.viewCollections, + }); + }} + /> + )} + + )} + + {tabValue === "problems" && + groupPermission && + selectedIndex >= 0 && ( +
+ {createRequest.problemsInterface.map( + (collectionProblem) => ( +
+
+ +
+ +
+ View Problem + + gp.group + .group_id === + currentGroupId + )?.viewProblems || + false + } + onClick={() => { + const findGroup = + collectionProblem.groupPermissions?.find( + (gp) => + gp.group + .group_id === + currentGroupId + ); + + const newGroupPermissions = + collectionProblem.groupPermissions.map( + (gp) => { + if ( + gp.group + .group_id === + currentGroupId + ) { + return { + ...gp, + viewProblems: + !gp.viewProblems, + }; + } else { + return gp; + } + } + ); + + if ( + !findGroup || + newGroupPermissions.length === + 0 + ) { + newGroupPermissions.push( + { + groupId: + currentGroupId, + group: allGroups.find( + (g) => + g.group_id === + currentGroupId + )!, + viewProblems: + true, + manageProblems: + false, + } + ); + } + + if ( + !createRequest.collection + ) { + return; + } + + setCreateRequest({ + ...createRequest, + problemsInterface: + createRequest.problemsInterface.map( + (cp) => { + if ( + cp + .problem + .problem_id === + collectionProblem + .problem + .problem_id + ) { + return { + ...cp, + groupPermissions: + newGroupPermissions, + }; + } else { + return cp; + } + } + ), + }); + }} + className="ml-2" + /> +
+
+ Manage Problem + + gp.group + .group_id === + currentGroupId + )?.manageProblems || + false + } + onClick={() => { + const findGroup = + collectionProblem.groupPermissions?.find( + (gp) => + gp.group + .group_id === + currentGroupId + ); + + const newGroupPermissions = + collectionProblem.groupPermissions.map( + (gp) => { + if ( + gp.group + .group_id === + currentGroupId + ) { + return { + ...gp, + manageProblems: + !gp.manageProblems, + }; + } else { + return gp; + } + } + ); + + if ( + !findGroup || + newGroupPermissions.length === + 0 + ) { + newGroupPermissions.push( + { + groupId: + currentGroupId, + group: allGroups.find( + (g) => + g.group_id === + currentGroupId + )!, + viewProblems: + false, + manageProblems: + true, + } + ); + } + + if ( + !createRequest.collection + ) { + return; + } + + setCreateRequest({ + ...createRequest, + problemsInterface: + createRequest.problemsInterface.map( + (cp) => { + if ( + cp + .problem + .problem_id === + collectionProblem + .problem + .problem_id + ) { + return { + ...cp, + groupPermissions: + newGroupPermissions, + }; + } else { + return cp; + } + } + ), + }); + }} + className="ml-2" + /> +
+
+ ) + )} +
+ )} + +
+ ); }; diff --git a/src/types/adapters/CreateCollectionRequestForm.adapter.ts b/src/types/adapters/CreateCollectionRequestForm.adapter.ts index 499791e..9ecea93 100644 --- a/src/types/adapters/CreateCollectionRequestForm.adapter.ts +++ b/src/types/adapters/CreateCollectionRequestForm.adapter.ts @@ -1,25 +1,43 @@ import { CollectionGroupPermissionCreateRequest } from "../apis/Collection.api"; +import { ProblemGroupPermissionCreateRequest } from "../apis/Problem.api"; import { CreateCollectionRequestForm } from "../forms/CreateCollectionRequestForm"; import { CollectionCreateRequest } from "../models/Collection.model"; -export function transformCreateCollectionRequestForm2CreateCollectionRequestForm(createRequest:CreateCollectionRequestForm): { - request: CollectionCreateRequest - problemIds: string[] - groups: CollectionGroupPermissionCreateRequest[] -} { - const request = { - name: createRequest.title, - description: JSON.stringify(createRequest.description), - }; +export function transformCreateCollectionRequestForm2CreateCollectionRequestForm( + createRequest: CreateCollectionRequestForm +): { + request: CollectionCreateRequest; + problemIds: string[]; + groups: CollectionGroupPermissionCreateRequest[]; + problemGroupPermissions: { + problem_id: string; + groupPermissions: ProblemGroupPermissionCreateRequest[]; + }[]; +} { + const request = { + name: createRequest.title, + description: JSON.stringify(createRequest.description), + }; - const problemIds = createRequest.problemsInterface.map((problem) => problem.id as string); + const problemIds = createRequest.problemsInterface.map( + (problem) => problem.id as string + ); - const groups = createRequest.groupPermissions.map((groupPermission) => ({ - group_id: groupPermission.group_id, - permission_manage_collections: groupPermission.manageCollections, - permission_view_collections: groupPermission.viewCollections, - })); + const groups = createRequest.groupPermissions.map((groupPermission) => ({ + group_id: groupPermission.group_id, + permission_manage_collections: groupPermission.manageCollections, + permission_view_collections: groupPermission.viewCollections, + })); - return { request, problemIds, groups }; -} + const problemGroupPermissions = + createRequest.problemsInterface.map((cp) => ({ + problem_id: cp.problem.problem_id, + groupPermissions: cp.groupPermissions.map((gp) => ({ + group_id: gp.group.group_id, + permission_manage_problems: gp.manageProblems, + permission_view_problems: gp.viewProblems, + })), + })) ?? []; + return { request, problemIds, groups, problemGroupPermissions }; +} diff --git a/src/views/My/Collections/CreateCollection.tsx b/src/views/My/Collections/CreateCollection.tsx index a10c2ba..8759dbe 100644 --- a/src/views/My/Collections/CreateCollection.tsx +++ b/src/views/My/Collections/CreateCollection.tsx @@ -9,6 +9,7 @@ import { transformCreateCollectionRequestForm2CreateCollectionRequestForm } from import { CollectionService } from "../../../services/Collection.service"; import { toast } from "../../../components/shadcn/UseToast"; import { useNavigate } from "react-router-dom"; +import { ProblemService } from "../../../services/Problem.service"; const formInitialValue: CreateCollectionRequestForm = { title: "", @@ -21,43 +22,73 @@ const formInitialValue: CreateCollectionRequestForm = { ], problemsInterface: [], groupPermissions: [], + collection: null, }; const CreateCollection = () => { - const accountId = String(localStorage.getItem("account_id")); const navigate = useNavigate(); - const handleSave = ({ createRequest,setLoading }: OnCollectionSavedCallback) => { - - if ( !setLoading || !createRequest) { - return + const handleSave = ({ + createRequest, + setLoading, + }: OnCollectionSavedCallback) => { + if (!setLoading || !createRequest) { + return; } - const {request,problemIds} = + const { request, problemIds, problemGroupPermissions,groups } = transformCreateCollectionRequestForm2CreateCollectionRequestForm( createRequest as CreateCollectionRequestForm ); - - setLoading(true) + setLoading(true); + + CollectionService.create(accountId, request) + .then((response) => { + return CollectionService.updateProblem( + response.data.collection_id, + problemIds + ); + }) + .then((response) => { + return CollectionService.updateGroupPermissions( + response.data.collection_id, + accountId, + groups + ); + }) + .then((response) => { + let promise = []; + for (const problem of problemGroupPermissions) { + promise.push( + ProblemService.updateGroupPermissions( + problem.problem_id, + accountId, + problem.groupPermissions + ) + ); + } - CollectionService.create(accountId,request).then(response => { - return CollectionService.updateProblem(response.data.collection_id,problemIds) - }).then(response => { - toast({ - title: "Create Completed" + return { + promise: Promise.all(promise), + collection_id: response.data.collection_id, + }; }) - navigate(`/my/collections/${response.data.collection_id}`) - setLoading(false) - }) + .then((response) => { + toast({ + title: "Create Completed", + }); + navigate(`/my/collections/${response.collection_id}`); + setLoading(false); + }); }; return ( - handleSave({ createRequest,setLoading }) + onCollectionSave={({ createRequest, setLoading }) => + handleSave({ createRequest, setLoading }) } createRequestInitialValue={formInitialValue} /> diff --git a/src/views/My/Collections/EditCollection.tsx b/src/views/My/Collections/EditCollection.tsx index 9cf7ccd..30bb6f7 100644 --- a/src/views/My/Collections/EditCollection.tsx +++ b/src/views/My/Collections/EditCollection.tsx @@ -1,59 +1,99 @@ -import React, { useEffect } from 'react' -import { useParams } from 'react-router-dom' -import CreateCollectionForm, { OnCollectionSavedCallback } from '../../../components/Forms/CreateCollectionForm' -import { toast } from '../../../components/shadcn/UseToast' -import NavbarSidebarLayout from '../../../layout/NavbarSidebarLayout' -import { CollectionService } from '../../../services/Collection.service' -import { transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest } from '../../../types/adapters/Collection.adapter' -import { transformCreateCollectionRequestForm2CreateCollectionRequestForm } from '../../../types/adapters/CreateCollectionRequestForm.adapter' -import { CreateCollectionRequestForm } from '../../../types/forms/CreateCollectionRequestForm' +import React, { useEffect } from "react"; +import { useParams } from "react-router-dom"; +import CreateCollectionForm, { + OnCollectionSavedCallback, +} from "../../../components/Forms/CreateCollectionForm"; +import { toast } from "../../../components/shadcn/UseToast"; +import NavbarSidebarLayout from "../../../layout/NavbarSidebarLayout"; +import { CollectionService } from "../../../services/Collection.service"; +import { transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest } from "../../../types/adapters/Collection.adapter"; +import { transformCreateCollectionRequestForm2CreateCollectionRequestForm } from "../../../types/adapters/CreateCollectionRequestForm.adapter"; +import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm"; +import { ProblemService } from "../../../services/Problem.service"; const EditCollection = () => { + const accountId = String(localStorage.getItem("account_id")); - const accountId = String(localStorage.getItem("account_id")); - - const {collectionId} = useParams(); - const editCollectionId = String(collectionId); - - const [createRequest, setCreateRequest] = React.useState() - - const handleSave = ({setLoading,createRequest}: OnCollectionSavedCallback) => { - - if (!setLoading || !createRequest) { - return; - } - - - const {request,problemIds,groups} = transformCreateCollectionRequestForm2CreateCollectionRequestForm(createRequest as CreateCollectionRequestForm) - - setLoading(true) - - CollectionService.update(editCollectionId,request).then(response => { - return CollectionService.updateProblem(response.data.collection_id,problemIds) - }).then(response => { - return CollectionService.updateGroupPermissions(response.data.collection_id,accountId,groups) - }).then(response => { - setLoading(false) - console.log("Save") - toast({ - title: "Update Completed" - }) - }) - } - - useEffect(()=> { - CollectionService.get(editCollectionId,accountId).then(response => { - setCreateRequest( - transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest(response.data) - ) - }) - },[editCollectionId]) - - return ( - - {createRequest && handleSave({setLoading,createRequest})} createRequestInitialValue={createRequest}/>} - - ) -} - -export default EditCollection \ No newline at end of file + const { collectionId } = useParams(); + const editCollectionId = String(collectionId); + + const [createRequest, setCreateRequest] = + React.useState(); + + const handleSave = ({ + setLoading, + createRequest, + }: OnCollectionSavedCallback) => { + if (!setLoading || !createRequest) { + return; + } + + const { request, problemIds, groups, problemGroupPermissions } = + transformCreateCollectionRequestForm2CreateCollectionRequestForm( + createRequest as CreateCollectionRequestForm + ); + + setLoading(true); + + CollectionService.update(editCollectionId, request) + .then((response) => { + return CollectionService.updateProblem( + response.data.collection_id, + problemIds + ); + }) + .then((response) => { + return CollectionService.updateGroupPermissions( + response.data.collection_id, + accountId, + groups + ); + }) + .then(() => { + let promise = []; + for (const problem of problemGroupPermissions) { + promise.push( + ProblemService.updateGroupPermissions( + problem.problem_id, + accountId, + problem.groupPermissions + ) + ); + } + + return Promise.all(promise); + }) + .then((response) => { + setLoading(false); + console.log("Save"); + toast({ + title: "Update Completed", + }); + }); + }; + + useEffect(() => { + CollectionService.get(editCollectionId, accountId).then((response) => { + setCreateRequest( + transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest( + response.data + ) + ); + }); + }, [editCollectionId]); + + return ( + + {createRequest && ( + + handleSave({ setLoading, createRequest }) + } + createRequestInitialValue={createRequest} + /> + )} + + ); +}; + +export default EditCollection; diff --git a/src/views/My/Courses/CreateCourse.tsx b/src/views/My/Courses/CreateCourse.tsx index 28b49f4..3949df1 100644 --- a/src/views/My/Courses/CreateCourse.tsx +++ b/src/views/My/Courses/CreateCourse.tsx @@ -25,7 +25,7 @@ const formInitialValue: CreateCourseRequestForm = { collectionsInterface: [], groupPermissions: [], course: null, - collectionGroupPermissions: [], + // collectionGroupPermissions: [], }; const CreateCourse = () => { diff --git a/src/views/My/Courses/EditCourse.tsx b/src/views/My/Courses/EditCourse.tsx index e6749e4..ea8e0de 100644 --- a/src/views/My/Courses/EditCourse.tsx +++ b/src/views/My/Courses/EditCourse.tsx @@ -48,7 +48,6 @@ const EditCourse = () => { ); }) .then(() => { - let promise = []; for (const collection of collectionGroupsPermissions) { promise.push( @@ -60,7 +59,7 @@ const EditCourse = () => { ); } - return Promise.all(promise) + return Promise.all(promise); }) .then(() => { setLoading(false); From 0c5e3508c6e771c7039d824417afdcd212f2a08c Mon Sep 17 00:00:00 2001 From: KanonKC Date: Thu, 11 Jan 2024 14:25:58 +0700 Subject: [PATCH 20/24] Basic search engine --- .../ProblemCards/PublicProblemMiniCard.tsx | 2 +- src/views/My/Collections/MyCollections.tsx | 31 ++++++++++++++++--- src/views/My/Courses/MyCourses.tsx | 22 +++++++++++-- src/views/My/Groups/MyGroups.tsx | 15 +++++++-- src/views/My/Problems/MyProblems.tsx | 22 ++++++++++--- src/views/ViewCourse.tsx | 18 ++++++----- src/views/ViewCourseProblem.tsx | 2 +- src/views/ViewProblem.tsx | 2 +- 8 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src/components/Cards/ProblemCards/PublicProblemMiniCard.tsx b/src/components/Cards/ProblemCards/PublicProblemMiniCard.tsx index 5dc2d8c..764caa1 100644 --- a/src/components/Cards/ProblemCards/PublicProblemMiniCard.tsx +++ b/src/components/Cards/ProblemCards/PublicProblemMiniCard.tsx @@ -18,7 +18,7 @@ const PublicProblemMiniCard = ({ return (
-

+

{problem?.title}

diff --git a/src/views/My/Collections/MyCollections.tsx b/src/views/My/Collections/MyCollections.tsx index 3efeb45..6d3fc6c 100644 --- a/src/views/My/Collections/MyCollections.tsx +++ b/src/views/My/Collections/MyCollections.tsx @@ -22,10 +22,31 @@ const MyCollections = () => { const [collections, setCollections] = useState< CollectionPopulateCollectionProblemPopulateProblemModel[] >([]); - const [manageableCollections, setManageableCollections] = useState([]); + const [manageableCollections, setManageableCollections] = useState< + CollectionPopulateCollectionProblemPopulateProblemModel[] + >([]); + const [filteredCollections, setFilteredCollections] = useState< + CollectionPopulateCollectionProblemPopulateProblemModel[] + >([]); + const [filteredManageableCollections, setFilteredManageableCollections] = useState< + CollectionPopulateCollectionProblemPopulateProblemModel[] + >([]); + const { setSection } = useContext(NavSidebarContext); const [tabValue, setTabValue] = useState("personal"); + const [searchValue, setSearchValue] = useState("") + + useEffect(() => { + if (!searchValue || searchValue === "") { + setFilteredCollections(collections) + setFilteredManageableCollections(manageableCollections) + } + else { + setFilteredCollections(collections.filter((collection) => collection.name.toLowerCase().includes(searchValue.toLowerCase()))) + setFilteredManageableCollections(manageableCollections.filter((collection) => collection.name.toLowerCase().includes(searchValue.toLowerCase()))) + } + },[searchValue,collections,manageableCollections]) useEffect(() => { setSection("COLLECTIONS"); @@ -44,8 +65,8 @@ const MyCollections = () => { My Collections
-
- +
+ setSearchValue(e.target.value)} placeholder="Search ..." />
{ {tabValue === "personal" && - collections.map((collection) => ( + filteredCollections.map((collection) => ( ))} {tabValue === "manageable" && - manageableCollections.map((collection) => ( + filteredManageableCollections.map((collection) => ( ))} diff --git a/src/views/My/Courses/MyCourses.tsx b/src/views/My/Courses/MyCourses.tsx index ba567d7..239da5a 100644 --- a/src/views/My/Courses/MyCourses.tsx +++ b/src/views/My/Courses/MyCourses.tsx @@ -19,8 +19,24 @@ const MyCourses = () => { const [topics, setTopics] = useState([]) const [manageableTopics, setManageableTopics] = useState([]) + + const [filteredTopics, setFilteredTopics] = useState([]) + const [filteredManageableTopics, setFilteredManageableTopics] = useState([]) const [tabValue, setTabValue] = useState("personal") + const [searchValue, setSearchValue] = useState("") + + useEffect(() => { + if (!searchValue || searchValue === "") { + setFilteredTopics(topics) + setFilteredManageableTopics(manageableTopics) + } + else { + setFilteredTopics(topics.filter((topic) => topic.name.toLowerCase().includes(searchValue.toLowerCase()))) + setFilteredManageableTopics(manageableTopics.filter((topic) => topic.name.toLowerCase().includes(searchValue.toLowerCase()))) + } + },[searchValue,topics,manageableTopics]) + useEffect(( )=> { setSection("COURSES") @@ -40,7 +56,7 @@ const MyCourses = () => {
- + setSearchValue(e.target.value)} placeholder="Search ..." />
setTabValue(e)}> @@ -66,12 +82,12 @@ const MyCourses = () => { { - tabValue === "personal" && topics.map(topic => ( + tabValue === "personal" && filteredTopics.map(topic => ( )) } { - tabValue === "manageable" && manageableTopics.map(topic => ( + tabValue === "manageable" && filteredManageableTopics.map(topic => ( )) } diff --git a/src/views/My/Groups/MyGroups.tsx b/src/views/My/Groups/MyGroups.tsx index f3b9c60..b681969 100644 --- a/src/views/My/Groups/MyGroups.tsx +++ b/src/views/My/Groups/MyGroups.tsx @@ -20,6 +20,17 @@ const MyGroups = () => { const {setSection} = useContext(NavSidebarContext) const [groups, setGroups] = useState([]) + const [filteredGroups, setFilteredGroups] = useState([]) + const [searchValue, setSearchValue] = useState("") + + useEffect(() => { + if (!searchValue || searchValue === "") { + setFilteredGroups(groups) + } + else { + setFilteredGroups(groups.filter((group) => group.name.toLowerCase().includes(searchValue.toLowerCase()))) + } + },[searchValue,groups]) useEffect(( )=> { setSection("GROUPS") @@ -40,7 +51,7 @@ const MyGroups = () => {
- + setSearchValue(e.target.value)}/>
-
- +
+ setSearchValue(e.target.value)} placeholder="Search ..." />
setTabValue(e)}> @@ -66,10 +80,10 @@ const MyProblems = () => {
- {tabValue === "personal" && problems.map((problem, index) => ( + {tabValue === "personal" && filteredProblems.map((problem, index) => ( ))} - {tabValue === "manageable" && manageableProblems.map((problem, index) => ( + {tabValue === "manageable" && filteredManageableProblems.map((problem, index) => ( ))} diff --git a/src/views/ViewCourse.tsx b/src/views/ViewCourse.tsx index 66ca152..9f54a30 100644 --- a/src/views/ViewCourse.tsx +++ b/src/views/ViewCourse.tsx @@ -36,7 +36,7 @@ const ViewCourse = () => { // const [course,setCourse] - const {course,setCourse} = useContext(CourseNavSidebarContext); + const { course, setCourse } = useContext(CourseNavSidebarContext); // useEffect(() => { // TopicService.getPublicByAccount(accountId, String(courseId)).then( @@ -65,13 +65,15 @@ const ViewCourse = () => { collections={course?.collections as TopicCollectionPopulateCollectionPopulateCollectionProblemPopulateProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel[]} /> */} -
- {course?.collections.map((tc) => ( - - ))} -
+ +
+ {course?.collections.map((tc) => ( + + ))} +
+
{/* */} {/* */} diff --git a/src/views/ViewCourseProblem.tsx b/src/views/ViewCourseProblem.tsx index 70d57cb..81ca3e2 100644 --- a/src/views/ViewCourseProblem.tsx +++ b/src/views/ViewCourseProblem.tsx @@ -22,7 +22,7 @@ const ViewCourseProblem = () => { useState(); useEffect(() => { - ProblemService.get(String(problemId)).then((response) => { + ProblemService.get(accountId,String(problemId)).then((response) => { setProblem(response.data); }); diff --git a/src/views/ViewProblem.tsx b/src/views/ViewProblem.tsx index 4f02acb..885b87e 100644 --- a/src/views/ViewProblem.tsx +++ b/src/views/ViewProblem.tsx @@ -87,7 +87,7 @@ const ViewProblem = () => { }; useEffect(() => { - ProblemService.get(String(problemId)).then((response) => { + ProblemService.get(accountId,String(problemId)).then((response) => { setProblem(response.data); }); From bf43e4033b8f34158fc941ab13d98d8837987f13 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Fri, 12 Jan 2024 01:38:40 +0700 Subject: [PATCH 21/24] Basic search --- .../CollectionCards/MyCollectionMiniCard2.tsx | 20 ++++++----- .../Cards/ProblemCards/MyProblemMiniCard2.tsx | 3 +- .../Forms/CreateCollectionForm/index.tsx | 2 +- .../Forms/CreateCourseForm/index.tsx | 2 +- .../Forms/CreateGroupForm/index.tsx | 2 +- .../Forms/CreateProblemForm/index.tsx | 2 +- src/views/My/Groups/MyGroups.tsx | 17 ++++----- src/views/ViewProblem.tsx | 36 +++++-------------- 8 files changed, 32 insertions(+), 52 deletions(-) diff --git a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx index 97ebe70..bfe682a 100644 --- a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx @@ -46,31 +46,33 @@ const checkRuntimeStatus = (testcases: TestcaseModel[]) => { const MyCollectionContextMenu = ({ children, - problem, + collection, }: { children: React.ReactNode; - problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; + collection: CollectionModel; }) => { + + const navigate = useNavigate(); const [openDeleteDialog, setOpenDeleteDialog] = useState(false); return ( - window.location.reload()} - /> + /> */} {children} - + navigate(`/my/collections/${collection.collection_id}`)}> Edit - setOpenDeleteDialog(true)}> + {/* setOpenDeleteDialog(true)}> Delete - + */} ); @@ -118,7 +120,7 @@ const MyCollectionMiniCard2 = ({ return ( collection && ( - // + onClick()} onMouseOver={handleMouseOver} @@ -137,7 +139,7 @@ const MyCollectionMiniCard2 = ({
- // + ) ); }; diff --git a/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx b/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx index 34cfadf..480bdc2 100644 --- a/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx +++ b/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx @@ -45,6 +45,7 @@ const MyProblemContextMenu = ({ problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; }) => { const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + const navigate = useNavigate(); return ( @@ -56,7 +57,7 @@ const MyProblemContextMenu = ({ /> {children} - + navigate(`/my/problems/${problem.problem_id}`)}> Edit diff --git a/src/components/Forms/CreateCollectionForm/index.tsx b/src/components/Forms/CreateCollectionForm/index.tsx index 1ad713d..d057ddb 100644 --- a/src/components/Forms/CreateCollectionForm/index.tsx +++ b/src/components/Forms/CreateCollectionForm/index.tsx @@ -65,7 +65,7 @@ const CreateCollectionForm = ({ navigate("/my/collections")} + onClick={() => navigate(-1)} /> {createRequest.title === "" ? "Create Problem" diff --git a/src/components/Forms/CreateCourseForm/index.tsx b/src/components/Forms/CreateCourseForm/index.tsx index 684974c..16656dd 100644 --- a/src/components/Forms/CreateCourseForm/index.tsx +++ b/src/components/Forms/CreateCourseForm/index.tsx @@ -77,7 +77,7 @@ const CreateCourseForm = ({ navigate("/my/courses")} + onClick={() => navigate(-1)} /> {createRequest.title === "" ? "Create Course" diff --git a/src/components/Forms/CreateGroupForm/index.tsx b/src/components/Forms/CreateGroupForm/index.tsx index 640aaf5..9e96766 100644 --- a/src/components/Forms/CreateGroupForm/index.tsx +++ b/src/components/Forms/CreateGroupForm/index.tsx @@ -78,7 +78,7 @@ const CreateGroupForm = ({ navigate("/my/groups")} + onClick={() => navigate(-1)} /> {createRequest.name === "" ? "Create Group" diff --git a/src/components/Forms/CreateProblemForm/index.tsx b/src/components/Forms/CreateProblemForm/index.tsx index 0cbef86..ba04b06 100644 --- a/src/components/Forms/CreateProblemForm/index.tsx +++ b/src/components/Forms/CreateProblemForm/index.tsx @@ -112,7 +112,7 @@ const CreateProblemForm = ({ navigate("/my/problems")} + onClick={() => navigate(-1)} /> {createRequest.title === "" ? "Create Problem" diff --git a/src/views/My/Groups/MyGroups.tsx b/src/views/My/Groups/MyGroups.tsx index b681969..cbd2840 100644 --- a/src/views/My/Groups/MyGroups.tsx +++ b/src/views/My/Groups/MyGroups.tsx @@ -1,17 +1,14 @@ -import React, { useContext, useEffect, useState } from "react"; -import NavbarSidebarLayout from "../../../layout/NavbarSidebarLayout"; -import { Input } from "../../../components/shadcn/Input"; -import { Button } from "../../../components/shadcn/Button"; +import { LibraryBig } from "lucide-react"; +import { useContext, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import CardContainer from "../../../components/CardContainer"; -import MyCourseCard from "../../../components/Cards/CourseCards/MyCourseCard"; -import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; -import { TopicService } from "../../../services/Topic.service"; -import { TopicPopulateTopicCollectionPopulateCollectionModel } from "../../../types/models/Topic.model"; -import { LibraryBig } from "lucide-react"; import MyGroupCard from "../../../components/Cards/MyGroupCard"; -import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../../../types/models/Group.model"; +import { Button } from "../../../components/shadcn/Button"; +import { Input } from "../../../components/shadcn/Input"; +import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; +import NavbarSidebarLayout from "../../../layout/NavbarSidebarLayout"; import { GroupService } from "../../../services/Group.service"; +import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../../../types/models/Group.model"; const MyGroups = () => { diff --git a/src/views/ViewProblem.tsx b/src/views/ViewProblem.tsx index 885b87e..a080246 100644 --- a/src/views/ViewProblem.tsx +++ b/src/views/ViewProblem.tsx @@ -1,38 +1,18 @@ -import React, { useEffect, useState } from "react"; -import NavbarMenuLayout from "../layout/NavbarMenuLayout"; +import { ELEMENT_PARAGRAPH } from "@udecode/plate-paragraph"; +import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { Separator } from "../components/shadcn/Seperator"; -import PlateEditor from "../components/PlateEditor"; -import ReadOnlyPlate from "../components/ReadOnlyPlate"; -import { Editor as MonacoEditor } from "@monaco-editor/react"; -import { Label } from "../components/shadcn/Label"; -import { Combobox } from "../components/shadcn/Combobox"; -import { ProgrammingLanguageOptions } from "../constants/ProgrammingLanguage"; -import { Button } from "../components/shadcn/Button"; -import TestcasesGradingIndicator from "../components/TestcasesGradingIndicator"; import { styled } from "styled-components"; +import ProblemViewLayout, { + OnSubmitProblemViewLayoutCallback, +} from "../components/ProblemViewLayout"; +import NavbarMenuLayout from "../layout/NavbarMenuLayout"; import { ProblemService } from "../services/Problem.service"; -import { ProblemPoplulateCreatorModel } from "../types/models/Problem.model"; import { SubmissionService } from "../services/Submission.service"; +import { ProblemPoplulateCreatorModel } from "../types/models/Problem.model"; import { GetSubmissionByAccountProblemResponse, - SubmissionModel, - SubmissionPopulateSubmissionTestcasesSecureModel, + SubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Submission.model"; -import { SubmitProblemResponse } from "../types/apis/Submission.api"; -import PreviousSubmissionsCombobox from "../components/PreviousSubmissionsCombobox"; -import { SubmitProblemResponse2GetSubmissionByAccountProblemResponse } from "../types/adapters/Submission.adapter"; -import { ELEMENT_PARAGRAPH } from "@udecode/plate-paragraph"; -import { - ArrowLeft, - ChevronLeftIcon, - ChevronLeftSquareIcon, - Loader2, -} from "lucide-react"; -import { readableDateFormat } from "../utilities/ReadableDateFormat"; -import ProblemViewLayout, { - OnSubmitProblemViewLayoutCallback, -} from "../components/ProblemViewLayout"; const handleDeprecatedDescription = (description: string): string => { if (description[0] === "[") { From fe0164dab736749f6168c651cdd72bafba8df6e2 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Fri, 12 Jan 2024 20:17:44 +0700 Subject: [PATCH 22/24] Carousel + ContextMenu for Problem/Collection/Course --- @/components/ui/carousel.tsx | 257 ++++++++++++++++++ package-lock.json | 26 ++ package.json | 1 + .../Cards/AccountCards/AccountMiniCard.tsx | 2 +- .../Cards/AccountCards/AccountMiniCard2.tsx | 2 +- .../CollectionCards/MyCollectionCard.tsx | 7 +- .../CollectionCards/MyCollectionMiniCard.tsx | 2 +- .../CollectionCards/MyCollectionMiniCard2.tsx | 67 ++--- .../Cards/CourseCards/MyCourseCard.tsx | 103 +++---- src/components/Cards/MyGroupCard.tsx | 2 + .../Cards/ProblemCards/MyProblemCard.tsx | 78 +++--- .../Cards/ProblemCards/MyProblemMiniCard.tsx | 2 +- .../Cards/ProblemCards/MyProblemMiniCard2.tsx | 67 ++--- .../Cards/ProblemCards/PublicProblemCard.tsx | 2 + .../ProblemCards/PublicProblemMiniCard.tsx | 2 + .../ContextMenus/MyCollectionContextMenu.tsx | 125 +++++++++ .../ContextMenus/MyCourseContextMenu.tsx | 125 +++++++++ .../ContextMenus/MyProblemContextMenu.tsx | 106 ++++++++ .../DeleteCollectionConfirmationDialog.tsx | 49 ++++ .../DeleteCourseConfirmationDialog.tsx | 61 +++++ .../DeleteProblemConfirmationDialog.tsx | 8 +- .../Forms/CreateProblemForm/Requirement.tsx | 99 ++++++- src/components/ProblemViewLayout.tsx | 8 +- src/components/shadcn/Carousel.tsx | 257 ++++++++++++++++++ src/services/Collection.service.ts | 4 + src/services/Problem.service.ts | 8 +- src/services/Topic.service.ts | 5 + .../CreateProblemRequestForm.adapter.ts | 1 + src/types/adapters/Problem.adapter.ts | 3 +- src/types/apis/Collection.api.ts | 3 +- src/types/apis/Problem.api.ts | 4 + src/types/apis/Topic.api.ts | 3 +- src/types/forms/CreateProblemRequestForm.ts | 1 + src/types/models/Problem.model.ts | 12 +- src/utilities/OnMiddleClickOpenInNewTab.ts | 9 + src/views/Dashboard.tsx | 55 +++- src/views/Login.tsx | 16 +- src/views/My/Problems/CreateProblem.tsx | 1 + src/views/My/Problems/MyProblems.tsx | 2 +- src/views/ViewCourseProblem.tsx | 19 +- src/views/ViewProblem.tsx | 8 +- 41 files changed, 1402 insertions(+), 210 deletions(-) create mode 100644 @/components/ui/carousel.tsx create mode 100644 src/components/ContextMenus/MyCollectionContextMenu.tsx create mode 100644 src/components/ContextMenus/MyCourseContextMenu.tsx create mode 100644 src/components/ContextMenus/MyProblemContextMenu.tsx create mode 100644 src/components/Dialogs/DeleteCollectionConfirmationDialog.tsx create mode 100644 src/components/Dialogs/DeleteCourseConfirmationDialog.tsx rename src/components/{ => Dialogs}/DeleteProblemConfirmationDialog.tsx (83%) create mode 100644 src/components/shadcn/Carousel.tsx create mode 100644 src/utilities/OnMiddleClickOpenInNewTab.ts diff --git a/@/components/ui/carousel.tsx b/@/components/ui/carousel.tsx new file mode 100644 index 0000000..9782265 --- /dev/null +++ b/@/components/ui/carousel.tsx @@ -0,0 +1,257 @@ +import * as React from "react" +import useEmblaCarousel, { + type EmblaCarouselType as CarouselApi, + type EmblaOptionsType as CarouselOptions, + type EmblaPluginType as CarouselPlugin, +} from "embla-carousel-react" +import { ArrowLeft, ArrowRight } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +type CarouselProps = { + opts?: CarouselOptions + plugins?: CarouselPlugin[] + orientation?: "horizontal" | "vertical" + setApi?: (api: CarouselApi) => void +} + +type CarouselContextProps = { + carouselRef: ReturnType[0] + api: ReturnType[1] + scrollPrev: () => void + scrollNext: () => void + canScrollPrev: boolean + canScrollNext: boolean +} & CarouselProps + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error("useCarousel must be used within a ") + } + + return context +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props + }, + ref + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return + } + + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault() + scrollPrev() + } else if (event.key === "ArrowRight") { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext] + ) + + React.useEffect(() => { + if (!api || !setApi) { + return + } + + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) { + return + } + + onSelect(api) + api.on("reInit", onSelect) + api.on("select", onSelect) + + return () => { + api?.off("select", onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) + } +) +Carousel.displayName = "Carousel" + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) +}) +CarouselContent.displayName = "CarouselContent" + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel() + + return ( +
+ ) +}) +CarouselItem.displayName = "CarouselItem" + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) +}) +CarouselPrevious.displayName = "CarouselPrevious" + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +}) +CarouselNext.displayName = "CarouselNext" + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +} diff --git a/package-lock.json b/package-lock.json index d946822..fa35325 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,6 +77,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", + "embla-carousel-react": "^8.0.0-rc19", "lucide-react": "^0.288.0", "prop-types": "^15.8.1", "react": "^18.2.0", @@ -4438,6 +4439,31 @@ "integrity": "sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==", "dev": true }, + "node_modules/embla-carousel": { + "version": "8.0.0-rc19", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.0.0-rc19.tgz", + "integrity": "sha512-PAChVyYoVZo8subkBN8LjZ7+0vk4CmVvMnxH0Y2ux76VUEUBl1wk5xDo8+MUhH5MXU6ZrgkBpMe++bKob1Z+2g==" + }, + "node_modules/embla-carousel-react": { + "version": "8.0.0-rc19", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.0.0-rc19.tgz", + "integrity": "sha512-4BBj1HvlUqhWXFyDJOL/JbQ74jtekfdH646B1wQzM9QmWn6CEcbD/SmovKqc6B5jYTKaaGEGEEw7bpUJRajA8w==", + "dependencies": { + "embla-carousel": "8.0.0-rc19", + "embla-carousel-reactive-utils": "8.0.0-rc19" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.0.0-rc19", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.0.0-rc19.tgz", + "integrity": "sha512-ed9NppY0OxTtrSIwTCYNcMLlQfSNcNy8Zsw8uIG0te3qrhvQ2ePPsbcElK2SRAV8VMU6G7JQweQIb6amzYMDXA==", + "peerDependencies": { + "embla-carousel": "8.0.0-rc19" + } + }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", diff --git a/package.json b/package.json index b69de2c..8e3026e 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", + "embla-carousel-react": "^8.0.0-rc19", "lucide-react": "^0.288.0", "prop-types": "^15.8.1", "react": "^18.2.0", diff --git a/src/components/Cards/AccountCards/AccountMiniCard.tsx b/src/components/Cards/AccountCards/AccountMiniCard.tsx index 2e41167..5e7b727 100644 --- a/src/components/Cards/AccountCards/AccountMiniCard.tsx +++ b/src/components/Cards/AccountCards/AccountMiniCard.tsx @@ -27,7 +27,7 @@ import { ContextMenuContent, ContextMenuItem, } from "../../shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import DeleteProblemConfirmationDialog from "../../Dialogs/DeleteProblemConfirmationDialog"; import Checkmark from "../../Checkmark"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; import { CollectionModel, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; diff --git a/src/components/Cards/AccountCards/AccountMiniCard2.tsx b/src/components/Cards/AccountCards/AccountMiniCard2.tsx index 6b9c8d2..9ee38e8 100644 --- a/src/components/Cards/AccountCards/AccountMiniCard2.tsx +++ b/src/components/Cards/AccountCards/AccountMiniCard2.tsx @@ -27,7 +27,7 @@ import { ContextMenuContent, ContextMenuItem, } from "../../shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import DeleteProblemConfirmationDialog from "../../Dialogs/DeleteProblemConfirmationDialog"; import Checkmark from "../../Checkmark"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; import { CollectionModel, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; diff --git a/src/components/Cards/CollectionCards/MyCollectionCard.tsx b/src/components/Cards/CollectionCards/MyCollectionCard.tsx index b9dde4d..baae849 100644 --- a/src/components/Cards/CollectionCards/MyCollectionCard.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionCard.tsx @@ -5,6 +5,8 @@ import { CollectionPopulateCollectionProblemPopulateProblemModel, CollectionProb import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; import Checkmark from "../../Checkmark"; import { Card, CardContent } from "../../shadcn/Card"; +import { onMiddleClickOpenInNewTab } from "../../../utilities/OnMiddleClickOpenInNewTab"; +import MyCollectionContextMenu from "../../ContextMenus/MyCollectionContextMenu"; const MyCollectionCard = ({ collection @@ -27,7 +29,9 @@ const MyCollectionCard = ({ }; return ( - + onMiddleClickOpenInNewTab(e,`/my/collections/${collection.collection_id}`)} onClick={() => navigate(`/my/collections/${collection.collection_id}`)} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} @@ -86,6 +90,7 @@ const MyCollectionCard = ({
+ ); }; diff --git a/src/components/Cards/CollectionCards/MyCollectionMiniCard.tsx b/src/components/Cards/CollectionCards/MyCollectionMiniCard.tsx index f064771..928a21b 100644 --- a/src/components/Cards/CollectionCards/MyCollectionMiniCard.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionMiniCard.tsx @@ -25,7 +25,7 @@ import { ContextMenuContent, ContextMenuItem, } from "../../shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import DeleteProblemConfirmationDialog from "../../Dialogs/DeleteProblemConfirmationDialog"; import Checkmark from "../../Checkmark"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; import { CollectionModel, CollectionPopulateProblemSecureModel } from "../../../types/models/Collection.model"; diff --git a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx index bfe682a..f732a3c 100644 --- a/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionMiniCard2.tsx @@ -25,7 +25,7 @@ import { ContextMenuContent, ContextMenuItem, } from "../../shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import DeleteProblemConfirmationDialog from "../../Dialogs/DeleteProblemConfirmationDialog"; import Checkmark from "../../Checkmark"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; import { @@ -34,6 +34,8 @@ import { CollectionPopulateProblemSecureModel, CollectionProblemPopulateProblemModel, } from "../../../types/models/Collection.model"; +import { onMiddleClickOpenInNewTab } from "../../../utilities/OnMiddleClickOpenInNewTab"; +import MyCollectionContextMenu from "../../ContextMenus/MyCollectionContextMenu"; const checkRuntimeStatus = (testcases: TestcaseModel[]) => { for (const testcase of testcases) { @@ -44,39 +46,39 @@ const checkRuntimeStatus = (testcases: TestcaseModel[]) => { return true; }; -const MyCollectionContextMenu = ({ - children, - collection, -}: { - children: React.ReactNode; - collection: CollectionModel; -}) => { +// const MyCollectionContextMenu = ({ +// children, +// collection, +// }: { +// children: React.ReactNode; +// collection: CollectionModel; +// }) => { - const navigate = useNavigate(); - const [openDeleteDialog, setOpenDeleteDialog] = useState(false); +// const navigate = useNavigate(); +// const [openDeleteDialog, setOpenDeleteDialog] = useState(false); - return ( - - {/* window.location.reload()} - /> */} - {children} - - navigate(`/my/collections/${collection.collection_id}`)}> - - Edit - - {/* setOpenDeleteDialog(true)}> - - Delete - */} - - - ); -}; +// return ( +// +// {/* window.location.reload()} +// /> */} +// {children} +// +// navigate(`/my/collections/${collection.collection_id}`)}> +// +// Edit +// +// {/* setOpenDeleteDialog(true)}> +// +// Delete +// */} +// +// +// ); +// }; const MyCollectionMiniCard2 = ({ // problem, @@ -122,6 +124,7 @@ const MyCollectionMiniCard2 = ({ collection && ( onMiddleClickOpenInNewTab(e,`/my/collections/${collection.collection_id}`)} onClick={() => onClick()} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} diff --git a/src/components/Cards/CourseCards/MyCourseCard.tsx b/src/components/Cards/CourseCards/MyCourseCard.tsx index b3c5c90..3afae1e 100644 --- a/src/components/Cards/CourseCards/MyCourseCard.tsx +++ b/src/components/Cards/CourseCards/MyCourseCard.tsx @@ -5,70 +5,79 @@ import { TopicPopulateTopicCollectionPopulateCollectionModel } from "../../../ty import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; import { BASE_URL } from "../../../constants/BackendBaseURL"; import { useNavigate } from "react-router-dom"; +import { onMiddleClickOpenInNewTab } from "../../../utilities/OnMiddleClickOpenInNewTab"; +import MyCourseContextMenu from "../../ContextMenus/MyCourseContextMenu"; const MyCourseCard = ({ course, }: { course: TopicPopulateTopicCollectionPopulateCollectionModel; }) => { - const navigate = useNavigate(); const [mouseOver, setMouseOver] = useState(false); return ( - navigate(`/my/courses/${course.topic_id}`)} - onMouseOver={() => setMouseOver(true)} - onMouseOut={() => setMouseOver(false)} - className={`pt-6 px-5 cursor-pointer ${ - mouseOver ? "border-green-500 bg-green-100" : "" - }`} - > - {/*
+ + + onMiddleClickOpenInNewTab( + e, + `/my/courses/${course.topic_id}` + ) + } + onClick={() => navigate(`/my/courses/${course.topic_id}`)} + onMouseOver={() => setMouseOver(true)} + onMouseOut={() => setMouseOver(false)} + className={`pt-6 px-5 cursor-pointer ${ + mouseOver ? "border-green-500 bg-green-100" : "" + }`} + > + {/*
*/} - -
- - {mouseOver ? ( -

{course.name}

- ) : ( - course.name - )} -
-
-
-
-

Lasted Updated

-

- {readableDateFormat(course.updated_date)} -

-
-
-

Created Date

-

- {readableDateFormat(course.created_date)} -

-
+ +
+ + {mouseOver ? ( +

{course.name}

+ ) : ( + course.name + )}
+
+
+
+

Lasted Updated

+

+ {readableDateFormat(course.updated_date)} +

+
+
+

Created Date

+

+ {readableDateFormat(course.created_date)} +

+
+
-
-
-

Visibility

-

Public

+
+
+

Visibility

+

Public

+
-
-
-

- - Collections ({course.collections.length}) -

+
+

+ + Collections ({course.collections.length}) +

+
-
- - {/*
*/} - +
+ {/*
*/} + + ); }; diff --git a/src/components/Cards/MyGroupCard.tsx b/src/components/Cards/MyGroupCard.tsx index 8cee522..424bec5 100644 --- a/src/components/Cards/MyGroupCard.tsx +++ b/src/components/Cards/MyGroupCard.tsx @@ -4,6 +4,7 @@ import { useNavigate } from "react-router-dom"; import { User, Users } from "lucide-react"; import { readableDateFormat } from "../../utilities/ReadableDateFormat"; import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../../types/models/Group.model"; +import { onMiddleClickOpenInNewTab } from "../../utilities/onMiddleClickOpenInNewTab"; const MyGroupCard = ({ group @@ -19,6 +20,7 @@ const MyGroupCard = ({ return ( onMiddleClickOpenInNewTab(e,`/my/groups/${group?.group_id}`)} onMouseOver={() => setMouseOver(true)} onMouseOut={() => setMouseOver(false)} className={`pt-6 px-5 cursor-pointer ${ diff --git a/src/components/Cards/ProblemCards/MyProblemCard.tsx b/src/components/Cards/ProblemCards/MyProblemCard.tsx index c600ba4..ba631ab 100644 --- a/src/components/Cards/ProblemCards/MyProblemCard.tsx +++ b/src/components/Cards/ProblemCards/MyProblemCard.tsx @@ -1,31 +1,26 @@ -import React, { useState } from "react"; -import { Card, CardContent, CardTitle } from "../../shadcn/Card"; -import { Button } from "../../shadcn/Button"; import { - Check, - CheckCircle2, FileSpreadsheet, - Pencil, PencilIcon, - Trash, - X, + Trash } from "lucide-react"; +import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import { - ProblemModel, ProblemPopulateTestcases, - ProblemSecureModel, - TestcaseModel, + TestcaseModel } from "../../../types/models/Problem.model"; import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; +import { onMiddleClickOpenInNewTab } from "../../../utilities/OnMiddleClickOpenInNewTab"; +import Checkmark from "../../Checkmark"; +import DeleteProblemConfirmationDialog from "../../Dialogs/DeleteProblemConfirmationDialog"; +import { Card, CardContent } from "../../shadcn/Card"; import { ContextMenu, - ContextMenuTrigger, ContextMenuContent, ContextMenuItem, + ContextMenuTrigger, } from "../../shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; -import Checkmark from "../../Checkmark"; +import MyProblemContextMenu from "../../ContextMenus/MyProblemContextMenu"; const checkRuntimeStatus = (testcases: TestcaseModel[]) => { for (const testcase of testcases) { @@ -36,35 +31,35 @@ const checkRuntimeStatus = (testcases: TestcaseModel[]) => { return true; }; -const MyProblemContextMenu = ({ children,problem }: { - children: React.ReactNode - problem: ProblemPopulateTestcases -}) => { +// const MyProblemContextMenu = ({ children,problem }: { +// children: React.ReactNode +// problem: ProblemPopulateTestcases +// }) => { - const [openDeleteDialog, setOpenDeleteDialog] = useState(false); +// const [openDeleteDialog, setOpenDeleteDialog] = useState(false); - return ( - - window.location.reload()} - /> - {children} - - - - Edit - - setOpenDeleteDialog(true)}> - - Delete - - - - ); -}; +// return ( +// +// window.location.reload()} +// /> +// {children} +// +// +// +// Edit +// +// setOpenDeleteDialog(true)}> +// +// Delete +// +// +// +// ); +// }; const MyProblemCard = ({ problem }: { problem: ProblemPopulateTestcases }) => { const navigate = useNavigate(); @@ -86,6 +81,7 @@ const MyProblemCard = ({ problem }: { problem: ProblemPopulateTestcases }) => { onMiddleClickOpenInNewTab(e,`/my/problems/${problem.problem_id}`)} onClick={() => navigate(`/my/problems/${problem.problem_id}`)} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} diff --git a/src/components/Cards/ProblemCards/MyProblemMiniCard.tsx b/src/components/Cards/ProblemCards/MyProblemMiniCard.tsx index 3bd8579..496c41e 100644 --- a/src/components/Cards/ProblemCards/MyProblemMiniCard.tsx +++ b/src/components/Cards/ProblemCards/MyProblemMiniCard.tsx @@ -24,7 +24,7 @@ import { ContextMenuContent, ContextMenuItem, } from "../../shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import DeleteProblemConfirmationDialog from "../../Dialogs/DeleteProblemConfirmationDialog"; import Checkmark from "../../Checkmark"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; diff --git a/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx b/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx index 480bdc2..d90a39e 100644 --- a/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx +++ b/src/components/Cards/ProblemCards/MyProblemMiniCard2.tsx @@ -24,9 +24,11 @@ import { ContextMenuContent, ContextMenuItem, } from "../../shadcn/ContextMenu"; -import DeleteProblemConfirmationDialog from "../../DeleteProblemConfirmationDialog"; +import DeleteProblemConfirmationDialog from "../../Dialogs/DeleteProblemConfirmationDialog"; import Checkmark from "../../Checkmark"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../shadcn/Tooltip"; +import { onMiddleClickOpenInNewTab } from "../../../utilities/OnMiddleClickOpenInNewTab"; +import MyProblemContextMenu from "../../ContextMenus/MyProblemContextMenu"; const checkRuntimeStatus = (testcases: TestcaseModel[]) => { for (const testcase of testcases) { @@ -37,38 +39,38 @@ const checkRuntimeStatus = (testcases: TestcaseModel[]) => { return true; }; -const MyProblemContextMenu = ({ - children, - problem, -}: { - children: React.ReactNode; - problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; -}) => { - const [openDeleteDialog, setOpenDeleteDialog] = useState(false); - const navigate = useNavigate(); +// const MyProblemContextMenu = ({ +// children, +// problem, +// }: { +// children: React.ReactNode; +// problem: ProblemPopulateTestcases | ProblemSecureModel | ProblemModel; +// }) => { +// const [openDeleteDialog, setOpenDeleteDialog] = useState(false); +// const navigate = useNavigate(); - return ( - - window.location.reload()} - /> - {children} - - navigate(`/my/problems/${problem.problem_id}`)}> - - Edit - - setOpenDeleteDialog(true)}> - - Delete - - - - ); -}; +// return ( +// +// window.location.reload()} +// /> +// {children} +// +// navigate(`/my/problems/${problem.problem_id}`)}> +// +// Edit +// +// setOpenDeleteDialog(true)}> +// +// Delete +// +// +// +// ); +// }; const MyProblemMiniCard2 = ({ problem, @@ -112,6 +114,7 @@ const MyProblemMiniCard2 = ({ problem && ( onMiddleClickOpenInNewTab(e,`/my/problems/${problem.problem_id}`)} onClick={() => onClick()} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} diff --git a/src/components/Cards/ProblemCards/PublicProblemCard.tsx b/src/components/Cards/ProblemCards/PublicProblemCard.tsx index b5e54d0..099e2b3 100644 --- a/src/components/Cards/ProblemCards/PublicProblemCard.tsx +++ b/src/components/Cards/ProblemCards/PublicProblemCard.tsx @@ -7,6 +7,7 @@ import { Button } from "../../shadcn/Button"; import { Label } from "../../shadcn/Label"; import { useNavigate } from "react-router-dom"; import { FileSpreadsheet, Puzzle } from "lucide-react"; +import { onMiddleClickOpenInNewTab } from "../../../utilities/OnMiddleClickOpenInNewTab"; const PublicProblemCard = ({ problem, @@ -72,6 +73,7 @@ const PublicProblemCard = ({
+ +
+ +
+ ); +}; + +export default DeleteCollectionConfirmationDialog; diff --git a/src/components/Dialogs/DeleteCourseConfirmationDialog.tsx b/src/components/Dialogs/DeleteCourseConfirmationDialog.tsx new file mode 100644 index 0000000..e6eba75 --- /dev/null +++ b/src/components/Dialogs/DeleteCourseConfirmationDialog.tsx @@ -0,0 +1,61 @@ +import React, { useState } from "react"; +import { Dialog, DialogContent, DialogTitle } from "../shadcn/Dialog"; +import { Button } from "../shadcn/Button"; +import { ProblemService } from "../../services/Problem.service"; +import { + ProblemModel, + ProblemPopulateTestcases, + ProblemSecureModel, +} from "../../types/models/Problem.model"; +import { CollectionModel } from "../../types/models/Collection.model"; +import { CollectionService } from "../../services/Collection.service"; +import { TopicModel } from "../../types/models/Topic.model"; +import { TopicService } from "../../services/Topic.service"; + +const DeleteCourseConfirmationDialog = ({ + open, + setOpen, + course, + afterDelete = () => {}, +}: { + course: TopicModel; + open: boolean; + setOpen: React.Dispatch>; + afterDelete?: () => void; +}) => { + const accountId = String(localStorage.getItem("account_id")); + + const handleDelete = () => { + setOpen(false); + TopicService.delete(course.topic_id,accountId).then(() => + afterDelete() + ); + }; + + return ( + setOpen(false)}> + +
+
Are you sure you want to delete this course?
+
{course.name}
+
+
+ + +
+
+
+ ); +}; + +export default DeleteCourseConfirmationDialog; diff --git a/src/components/DeleteProblemConfirmationDialog.tsx b/src/components/Dialogs/DeleteProblemConfirmationDialog.tsx similarity index 83% rename from src/components/DeleteProblemConfirmationDialog.tsx rename to src/components/Dialogs/DeleteProblemConfirmationDialog.tsx index 6761b6b..deae215 100644 --- a/src/components/DeleteProblemConfirmationDialog.tsx +++ b/src/components/Dialogs/DeleteProblemConfirmationDialog.tsx @@ -1,8 +1,8 @@ import React, { useState } from "react"; -import { Dialog, DialogContent, DialogTitle } from "./shadcn/Dialog"; -import { Button } from "./shadcn/Button"; -import { ProblemService } from "../services/Problem.service"; -import { ProblemModel, ProblemPopulateTestcases, ProblemSecureModel } from "../types/models/Problem.model"; +import { Dialog, DialogContent, DialogTitle } from "../shadcn/Dialog"; +import { Button } from "../shadcn/Button"; +import { ProblemService } from "../../services/Problem.service"; +import { ProblemModel, ProblemPopulateTestcases, ProblemSecureModel } from "../../types/models/Problem.model"; const DeleteProblemConfirmationDialog = ({ open, diff --git a/src/components/Forms/CreateProblemForm/Requirement.tsx b/src/components/Forms/CreateProblemForm/Requirement.tsx index 4a3e6dd..ac22fc4 100644 --- a/src/components/Forms/CreateProblemForm/Requirement.tsx +++ b/src/components/Forms/CreateProblemForm/Requirement.tsx @@ -1,15 +1,79 @@ -import React from 'react' -import { CreateProblemRequestForm } from '../../../types/forms/CreateProblemRequestForm'; -import { Label } from '../../shadcn/Label'; -import { Input } from '../../shadcn/Input'; +import React, { ReactNode, useEffect, useState } from "react"; +import { CreateProblemRequestForm } from "../../../types/forms/CreateProblemRequestForm"; +import { Label } from "../../shadcn/Label"; +import { Input } from "../../shadcn/Input"; +import { Checkbox } from "../../shadcn/Checkbox"; +import { ProgrammingLanguageOptions } from "../../../constants/ProgrammingLanguage"; + +const AllowedLanguageCheckbox = ({ + children, + checked = false, + onClick = () => {}, +}: { + children: ReactNode; + checked?: boolean; + onClick?: () => void; +}) => { + return ( +
+ onClick()} /> +
{children}
+
+ ); +}; const Requirement = ({ createRequest, setCreateRequest, }: { createRequest: CreateProblemRequestForm; - setCreateRequest: React.Dispatch>; + setCreateRequest: React.Dispatch< + React.SetStateAction + >; }) => { + const hasSelectedAllLanguage = () => { + return ( + createRequest.allowedLanguage.filter((lang) => lang !== "") + .length === ProgrammingLanguageOptions.length + ); + }; + + const handleAllOption = () => { + if (hasSelectedAllLanguage()) { + setCreateRequest({ + ...createRequest, + allowedLanguage: [], + }); + } else { + setCreateRequest({ + ...createRequest, + allowedLanguage: ProgrammingLanguageOptions.map( + (proLang) => proLang.value + ), + }); + } + }; + + const handleOnClick = (language: string) => { + if (createRequest.allowedLanguage.includes(language)) { + setCreateRequest({ + ...createRequest, + allowedLanguage: createRequest.allowedLanguage.filter( + (lang) => lang !== language + ), + }); + } else { + setCreateRequest({ + ...createRequest, + allowedLanguage: [...createRequest.allowedLanguage, language], + }); + } + }; + + useEffect(() => { + console.log(createRequest); + }, [createRequest]); + return (
@@ -23,8 +87,31 @@ const Requirement = ({ }) } /> + + +
+ + All + +
+ +
+ {ProgrammingLanguageOptions.map((proLang) => ( + handleOnClick(proLang.value)} + > + {proLang.label} + + ))} +
); }; -export default Requirement \ No newline at end of file +export default Requirement; diff --git a/src/components/ProblemViewLayout.tsx b/src/components/ProblemViewLayout.tsx index b699de0..e72c719 100644 --- a/src/components/ProblemViewLayout.tsx +++ b/src/components/ProblemViewLayout.tsx @@ -2,7 +2,7 @@ import { ArrowLeft, Loader2 } from "lucide-react"; import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { readableDateFormat } from "../utilities/ReadableDateFormat"; -import { ProblemPoplulateCreatorModel } from "../types/models/Problem.model"; +import { ProblemPoplulateCreatorModel, ProblemPopulateCreatorSecureModel } from "../types/models/Problem.model"; import ReadOnlyPlate from "./ReadOnlyPlate"; import { handleDeprecatedDescription } from "../utilities/HandleDeprecatedDescription"; import { Separator } from "./shadcn/Seperator"; @@ -29,7 +29,7 @@ const ProblemViewLayout = ({ previousSubmissions }:{ onSubmit: (callback: OnSubmitProblemViewLayoutCallback) => void - problem: ProblemPoplulateCreatorModel + problem: ProblemPoplulateCreatorModel | ProblemPopulateCreatorSecureModel previousSubmissions: GetSubmissionByAccountProblemResponse }) => { @@ -131,7 +131,7 @@ const ProblemViewLayout = ({
problem?.allowed_languages.includes(lang.value))} onSelect={(value) => setSelectedLanguage(value)} initialValue={selectedLanguage} value={selectedLanguage} @@ -179,7 +179,7 @@ const ProblemViewLayout = ({ } /> + ) +}) +CarouselPrevious.displayName = "CarouselPrevious" + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +}) +CarouselNext.displayName = "CarouselNext" + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +} diff --git a/src/services/Collection.service.ts b/src/services/Collection.service.ts index bedd324..eaafb1b 100644 --- a/src/services/Collection.service.ts +++ b/src/services/Collection.service.ts @@ -20,6 +20,10 @@ export const CollectionService: CollectionServiceAPI = { return axios.put(`${BASE_URL}/api/collections/${collectionId}`,request); }, + delete: (collectionId,accountId) => { + return axios.delete(`${BASE_URL}/api/accounts/${accountId}/collections/${collectionId}`); + }, + addProblem: (collectionId,problemIds) => { return axios.put(`${BASE_URL}/api/collections/${collectionId}/problems/add`,{problemIds}); }, diff --git a/src/services/Problem.service.ts b/src/services/Problem.service.ts index 119b248..9b66a0f 100644 --- a/src/services/Problem.service.ts +++ b/src/services/Problem.service.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { GetAllProblemsByAccountResponse, GetAllProblemsResponse, ProblemServiceAPI, ValidateProgramResponse } from "../types/apis/Problem.api"; import { BASE_URL } from "../constants/BackendBaseURL"; -import { ProblemModel, ProblemPoplulateCreatorModel, ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel } from "../types/models/Problem.model"; +import { ProblemModel, ProblemPoplulateCreatorModel, ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel, ProblemPopulateCreatorSecureModel, ProblemSecureModel } from "../types/models/Problem.model"; import { ErrorResponse } from "../types/apis/ErrorHandling"; export const ProblemService: ProblemServiceAPI = { @@ -39,5 +39,9 @@ export const ProblemService: ProblemServiceAPI = { validateProgram: async (request) => { return axios.post(`${BASE_URL}/api/problems/validate`, request); - } + }, + + getPublic: async (problemId) => { + return axios.get(`${BASE_URL}/api/problems/${problemId}`); + }, } \ No newline at end of file diff --git a/src/services/Topic.service.ts b/src/services/Topic.service.ts index a51835d..2afaa5f 100644 --- a/src/services/Topic.service.ts +++ b/src/services/Topic.service.ts @@ -24,6 +24,11 @@ export const TopicService: TopicSerivceAPI = { return response; }, + delete: async (topicId,accountId) => { + const response = await axios.delete(`${BASE_URL}/api/accounts/${accountId}/topics/${topicId}`); + return response; + }, + getAllAsCreator: async (accountId) => { const response = await axios.get(`${BASE_URL}/api/accounts/${accountId}/topics`); return response; diff --git a/src/types/adapters/CreateProblemRequestForm.adapter.ts b/src/types/adapters/CreateProblemRequestForm.adapter.ts index f871c68..9b12702 100644 --- a/src/types/adapters/CreateProblemRequestForm.adapter.ts +++ b/src/types/adapters/CreateProblemRequestForm.adapter.ts @@ -19,6 +19,7 @@ export const transformCreateProblemRequestForm2CreateProblemRequest = ( createRequest.testcase_delimeter ), time_limit: createRequest.time_limit, + allowed_languages: createRequest.allowedLanguage.filter((language) => language !== "").join(","), } const groups = createRequest.groupPermissions.map((groupPermission) => ({ diff --git a/src/types/adapters/Problem.adapter.ts b/src/types/adapters/Problem.adapter.ts index fdc5823..e823493 100644 --- a/src/types/adapters/Problem.adapter.ts +++ b/src/types/adapters/Problem.adapter.ts @@ -15,7 +15,8 @@ export function transformProblemPopulateAccountAndTestcasesAndProblemGroupPermis group: permission.group, manageProblems: permission.permission_manage_problems, viewProblems: permission.permission_view_problems - })) + })), + allowedLanguage: problem.allowed_languages.split(",") } } diff --git a/src/types/apis/Collection.api.ts b/src/types/apis/Collection.api.ts index a84f559..1b7c088 100644 --- a/src/types/apis/Collection.api.ts +++ b/src/types/apis/Collection.api.ts @@ -16,7 +16,8 @@ export type CollectionGroupPermissionCreateRequest = { export type CollectionServiceAPI = { create: (accountId:string,request:CollectionCreateRequest) => Promise>; get: (collectionId:string,accountId:string) => Promise>; - update: (collectionId:string,request:CollectionUpdateRequest) => Promise>; + update: (collectionId:string,accountId:string,request:CollectionUpdateRequest) => Promise>; + delete: (collectionId:string,accountId:string) => Promise>; getAllAsCreator: (accountId:string) => Promise>; addProblem: (collectionId:string,problemIds:string[]) => Promise>; removeProblem: (collectionId:string,problemIds:string[]) => Promise>; diff --git a/src/types/apis/Problem.api.ts b/src/types/apis/Problem.api.ts index e837713..6076ce8 100644 --- a/src/types/apis/Problem.api.ts +++ b/src/types/apis/Problem.api.ts @@ -5,7 +5,9 @@ import { ProblemPoplulateCreatorModel, ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel, + ProblemPopulateCreatorSecureModel, ProblemPopulateTestcases, + ProblemSecureModel, TestcaseModel, } from "../models/Problem.model"; import { AccountModel } from "../models/Account.model"; @@ -18,6 +20,7 @@ export type CreateProblemRequest = { solution: string; testcases: string[]; time_limit: number; + allowed_languages: string; }; export type UpdateProblemRequest = { @@ -100,4 +103,5 @@ export type ProblemServiceAPI = { validateProgram: ( request: ValidateProgramRequest ) => Promise>; + getPublic: (problemId: string) => Promise>; }; diff --git a/src/types/apis/Topic.api.ts b/src/types/apis/Topic.api.ts index 867fbcb..795c831 100644 --- a/src/types/apis/Topic.api.ts +++ b/src/types/apis/Topic.api.ts @@ -19,11 +19,12 @@ export type CourseGroupPermissionCreateRequest = { export type TopicSerivceAPI = { create: (accountid: string,request: FormData) => Promise>; - get: (accountid: string,courseId:string) => Promise>; + get: (accountId: string,courseId:string) => Promise>; getAllAsCreator: (accountId:string) => Promise>; getAllAccessibleByAccount: (accountId:string) => Promise>; getPublicByAccount: (accountId:string,courseId:string) => Promise>; update: (courseId:string,accountId:string,request: FormData) => Promise>; + delete: (courseId:string,accountId:string) => Promise>; updateCollections: (topicId:string,collectionIds:string[]) => Promise>; updateGroupPermissions: (topicId:string,accountId:string,groups:CourseGroupPermissionCreateRequest[]) => Promise>; } \ No newline at end of file diff --git a/src/types/forms/CreateProblemRequestForm.ts b/src/types/forms/CreateProblemRequestForm.ts index 36d4b7c..273d197 100644 --- a/src/types/forms/CreateProblemRequestForm.ts +++ b/src/types/forms/CreateProblemRequestForm.ts @@ -18,4 +18,5 @@ export type CreateProblemRequestForm = { time_limit: number; validated_testcases?: TestcaseModel[]; groupPermissions: ProblemGroupPermissionRequestForm[]; + allowedLanguage: string[]; }; \ No newline at end of file diff --git a/src/types/models/Problem.model.ts b/src/types/models/Problem.model.ts index 3118885..db63125 100644 --- a/src/types/models/Problem.model.ts +++ b/src/types/models/Problem.model.ts @@ -24,15 +24,23 @@ export type ProblemModel = { testcases: TestcaseModel[] created_date: string; updated_date: string; + allowed_languages: string } export type ProblemSecureModel = { problem_id: string + language: string title: string description: string + time_limit: string + created_date: string + updated_date: string + allowed_languages: string + creator: string +} + +export type ProblemPopulateCreatorSecureModel = ProblemSecureModel & { creator: AccountSecureModel - created_date: string; - updated_date: string; } export type ProblemPoplulateCreatorModel = ProblemModel & { diff --git a/src/utilities/OnMiddleClickOpenInNewTab.ts b/src/utilities/OnMiddleClickOpenInNewTab.ts new file mode 100644 index 0000000..ae0d5b6 --- /dev/null +++ b/src/utilities/OnMiddleClickOpenInNewTab.ts @@ -0,0 +1,9 @@ +export function onMiddleClickOpenInNewTab( + e: React.MouseEvent | React.MouseEvent, + url: string +) { + if (e.button === 1) { + const win = window.open(url, "_blank"); + if (win) win.focus(); + } +} diff --git a/src/views/Dashboard.tsx b/src/views/Dashboard.tsx index fa95306..c639867 100644 --- a/src/views/Dashboard.tsx +++ b/src/views/Dashboard.tsx @@ -7,6 +7,13 @@ import SubmissionCard from "../components/Cards/SubmissionCard"; import { TopicModel } from "../types/models/Topic.model"; import { TopicService } from "../services/Topic.service"; import PublicCourseCard from "../components/Cards/CourseCards/PublicCourseCard"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "../components/shadcn/Carousel"; const Dashboard = () => { const accountId = String(localStorage.getItem("account_id")); @@ -25,7 +32,7 @@ const Dashboard = () => { account_id: accountId, sort_date: 1, start: 0, - end: 4, + end: 10, }).then((response) => { setPreviousAttemptedProblems(response.data.submissions); // console.log(response.data.submissions); @@ -45,24 +52,44 @@ const Dashboard = () => { {username}

-

Previous Attempted

+

+ Previous Attempted +

+ + + + {previousAttemptedProblems.map((submission, index) => ( + + + + ))} + + + + -
+ {/*
{previousAttemptedProblems.map((submission, index) => ( ))} -
+
*/} -

Courses

-
- - { - accessibleCourses.map((course) => ( - - )) - } - -
+

Courses

+ + + {accessibleCourses.map((course,index) => ( + + + + ))} + + + + +
); diff --git a/src/views/Login.tsx b/src/views/Login.tsx index fad7f04..fabb238 100644 --- a/src/views/Login.tsx +++ b/src/views/Login.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { Form, @@ -23,14 +23,18 @@ import CenterContainer from "../layout/CenterLayout"; import { AuthService } from "../services/Auth.service"; import { AccountModel } from "../types/models/Account.model"; import { useNavigate } from "react-router-dom"; +import { Loader2 } from "lucide-react"; // import { getAuthorization, login } from "../services/auth.service"; const Login = () => { const form = useForm(); const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + setLoading(true); const { username, password } = form.getValues(); const { data, status } = await AuthService.login({ username, @@ -53,6 +57,7 @@ const Login = () => { window.location.reload() navigate(-1) } + setLoading(false); }; return ( @@ -115,7 +120,14 @@ const Login = () => { /> diff --git a/src/views/My/Problems/CreateProblem.tsx b/src/views/My/Problems/CreateProblem.tsx index f49dc18..9b9c19e 100644 --- a/src/views/My/Problems/CreateProblem.tsx +++ b/src/views/My/Problems/CreateProblem.tsx @@ -23,6 +23,7 @@ const formInitialValue: CreateProblemRequestForm = { testcase_delimeter: ":::", time_limit: 1.5, groupPermissions: [], + allowedLanguage: [], }; const CreateProblem = () => { diff --git a/src/views/My/Problems/MyProblems.tsx b/src/views/My/Problems/MyProblems.tsx index 253567d..5f76ab4 100644 --- a/src/views/My/Problems/MyProblems.tsx +++ b/src/views/My/Problems/MyProblems.tsx @@ -9,7 +9,7 @@ import { ProblemService } from "../../../services/Problem.service"; import { ProblemModel, ProblemPopulateTestcases } from "../../../types/models/Problem.model"; import CardContainer from "../../../components/CardContainer"; import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; -import DeleteProblemConfirmationDialog from "../../../components/DeleteProblemConfirmationDialog"; +import DeleteProblemConfirmationDialog from "../../../components/Dialogs/DeleteProblemConfirmationDialog"; import { FilePlus } from "lucide-react"; import { Tabs,TabsList, TabsTrigger } from "../../../components/shadcn/Tabs"; diff --git a/src/views/ViewCourseProblem.tsx b/src/views/ViewCourseProblem.tsx index 81ca3e2..d8ef133 100644 --- a/src/views/ViewCourseProblem.tsx +++ b/src/views/ViewCourseProblem.tsx @@ -1,28 +1,25 @@ -import React, { useContext, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; -import { ProblemPoplulateCreatorModel } from "../types/models/Problem.model"; -import { GetSubmissionByAccountProblemResponse } from "../types/models/Submission.model"; -import { ProblemService } from "../services/Problem.service"; -import { SubmissionService } from "../services/Submission.service"; -import NavbarMenuLayout from "../layout/NavbarMenuLayout"; import ProblemViewLayout, { OnSubmitProblemViewLayoutCallback, } from "../components/ProblemViewLayout"; import CourseNavbarSidebarLayout from "../layout/CourseNavbarSidebarLayout"; -import { CourseNavSidebarContext } from "../contexts/CourseNavSidebarContexnt"; -import { TopicService } from "../services/Topic.service"; +import { ProblemService } from "../services/Problem.service"; +import { SubmissionService } from "../services/Submission.service"; +import { ProblemPopulateCreatorSecureModel } from "../types/models/Problem.model"; +import { GetSubmissionByAccountProblemResponse } from "../types/models/Submission.model"; const ViewCourseProblem = () => { const accountId = String(localStorage.getItem("account_id")); const { courseId, problemId } = useParams(); - const [problem, setProblem] = useState(); + const [problem, setProblem] = useState(); const [previousSubmissions, setPreviousSubmissions] = useState(); useEffect(() => { - ProblemService.get(accountId,String(problemId)).then((response) => { + ProblemService.getPublic(String(problemId)).then((response) => { setProblem(response.data); }); @@ -68,7 +65,7 @@ const ViewCourseProblem = () => {
handleSubmit(e)} - problem={problem as ProblemPoplulateCreatorModel} + problem={problem as ProblemPopulateCreatorSecureModel} previousSubmissions={ previousSubmissions as GetSubmissionByAccountProblemResponse } diff --git a/src/views/ViewProblem.tsx b/src/views/ViewProblem.tsx index a080246..c620eee 100644 --- a/src/views/ViewProblem.tsx +++ b/src/views/ViewProblem.tsx @@ -8,7 +8,7 @@ import ProblemViewLayout, { import NavbarMenuLayout from "../layout/NavbarMenuLayout"; import { ProblemService } from "../services/Problem.service"; import { SubmissionService } from "../services/Submission.service"; -import { ProblemPoplulateCreatorModel } from "../types/models/Problem.model"; +import { ProblemPoplulateCreatorModel, ProblemPopulateCreatorSecureModel } from "../types/models/Problem.model"; import { GetSubmissionByAccountProblemResponse, SubmissionPopulateSubmissionTestcasesSecureModel @@ -34,7 +34,7 @@ const ViewProblem = () => { const accountId = String(localStorage.getItem("account_id")); const [selectedLanguage, setSelectedLanguage] = useState("python"); - const [problem, setProblem] = useState(); + const [problem, setProblem] = useState(); const [grading, setGrading] = useState(false); const [submitCodeValue, setSubmitCodeValue] = useState(""); @@ -67,7 +67,7 @@ const ViewProblem = () => { }; useEffect(() => { - ProblemService.get(accountId,String(problemId)).then((response) => { + ProblemService.getPublic(String(problemId)).then((response) => { setProblem(response.data); }); @@ -107,7 +107,7 @@ const ViewProblem = () => {
handleSubmit(e)} - problem={problem as ProblemPoplulateCreatorModel} + problem={problem as ProblemPopulateCreatorSecureModel} previousSubmissions={ previousSubmissions as GetSubmissionByAccountProblemResponse } From ce0986febf64d4674f57e1507d9c6dea73de318e Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 13 Jan 2024 12:30:11 +0700 Subject: [PATCH 23/24] Improve login qol --- src/views/Login.tsx | 60 ++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/views/Login.tsx b/src/views/Login.tsx index fabb238..fd96863 100644 --- a/src/views/Login.tsx +++ b/src/views/Login.tsx @@ -31,33 +31,39 @@ const Login = () => { const navigate = useNavigate(); const [loading, setLoading] = useState(false); + const [userNotFound, setUserNotFound] = useState(false); + const [wrongPassword, setWrongPassword] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); + setWrongPassword(false); + setUserNotFound(false); const { username, password } = form.getValues(); - const { data, status } = await AuthService.login({ + AuthService.login({ username, password, - }); - - const WRONG_PASSWORD = status === 406 - const SUCCESS = status === 202 - - if (WRONG_PASSWORD) { - return - } - else if (SUCCESS) { - const account = data - localStorage.setItem('account_id',String(account.account_id)) - localStorage.setItem('username',account.username) - if (account.token) { - localStorage.setItem('token',account.token) + }).then((response) => { + if (response.status === 202) { + const account = response.data; + localStorage.setItem("account_id", String(account.account_id)); + localStorage.setItem("username", account.username); + if (account.token) { + localStorage.setItem("token", account.token); + } + window.location.reload(); + navigate(-1); + } + setLoading(false); + }).catch((error) => { + if (error.response.status === 404) { + setUserNotFound(true); + } + else if (error.response.status === 406) { + setWrongPassword(true); } - window.location.reload() - navigate(-1) - } - setLoading(false); + setLoading(false); + }) }; return ( @@ -82,7 +88,9 @@ const Login = () => { - + + {userNotFound && "User doesn't exist."} + )} /> @@ -96,7 +104,9 @@ const Login = () => { - + + {wrongPassword && "Wrong password."} + )} /> @@ -119,13 +129,17 @@ const Login = () => { )} /> - From 581882c56ed502e6c1d61f1ec36db19a3db1cbd6 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 13 Jan 2024 12:54:10 +0700 Subject: [PATCH 24/24] New edit path and query section --- .../CollectionCards/MyCollectionCard.tsx | 4 +- .../Cards/CourseCards/MyCourseCard.tsx | 2 +- src/components/Cards/MyGroupCard.tsx | 2 +- .../Cards/ProblemCards/MyProblemCard.tsx | 4 +- .../ContextMenus/MyCollectionContextMenu.tsx | 2 +- .../ContextMenus/MyCourseContextMenu.tsx | 2 +- .../ContextMenus/MyProblemContextMenu.tsx | 2 +- .../Forms/CreateCollectionForm/index.tsx | 21 ++++++---- .../Forms/CreateCourseForm/index.tsx | 38 +++++++++--------- .../Forms/CreateGroupForm/index.tsx | 22 +++++++---- .../Forms/CreateProblemForm/index.tsx | 39 +++++++------------ .../Forms/GroupAndPermissionManager.tsx | 2 +- src/router.tsx | 8 ++-- src/views/My/Collections/CreateCollection.tsx | 2 +- src/views/My/Courses/CreateCourse.tsx | 2 +- src/views/My/Groups/CreateGroup.tsx | 2 +- src/views/My/Problems/CreateProblem.tsx | 2 +- 17 files changed, 79 insertions(+), 77 deletions(-) diff --git a/src/components/Cards/CollectionCards/MyCollectionCard.tsx b/src/components/Cards/CollectionCards/MyCollectionCard.tsx index baae849..97d59b9 100644 --- a/src/components/Cards/CollectionCards/MyCollectionCard.tsx +++ b/src/components/Cards/CollectionCards/MyCollectionCard.tsx @@ -31,8 +31,8 @@ const MyCollectionCard = ({ return ( onMiddleClickOpenInNewTab(e,`/my/collections/${collection.collection_id}`)} - onClick={() => navigate(`/my/collections/${collection.collection_id}`)} + onMouseDown={(e) => onMiddleClickOpenInNewTab(e,`/my/collections/${collection.collection_id}/edit`)} + onClick={() => navigate(`/my/collections/${collection.collection_id}/edit`)} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} className={`pt-6 px-5 cursor-pointer ${ diff --git a/src/components/Cards/CourseCards/MyCourseCard.tsx b/src/components/Cards/CourseCards/MyCourseCard.tsx index 3afae1e..bc1e52d 100644 --- a/src/components/Cards/CourseCards/MyCourseCard.tsx +++ b/src/components/Cards/CourseCards/MyCourseCard.tsx @@ -25,7 +25,7 @@ const MyCourseCard = ({ `/my/courses/${course.topic_id}` ) } - onClick={() => navigate(`/my/courses/${course.topic_id}`)} + onClick={() => navigate(`/my/courses/${course.topic_id}/edit`)} onMouseOver={() => setMouseOver(true)} onMouseOut={() => setMouseOver(false)} className={`pt-6 px-5 cursor-pointer ${ diff --git a/src/components/Cards/MyGroupCard.tsx b/src/components/Cards/MyGroupCard.tsx index 424bec5..44fcb54 100644 --- a/src/components/Cards/MyGroupCard.tsx +++ b/src/components/Cards/MyGroupCard.tsx @@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom"; import { User, Users } from "lucide-react"; import { readableDateFormat } from "../../utilities/ReadableDateFormat"; import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../../types/models/Group.model"; -import { onMiddleClickOpenInNewTab } from "../../utilities/onMiddleClickOpenInNewTab"; +import { onMiddleClickOpenInNewTab } from "../../utilities/OnMiddleClickOpenInNewTab"; const MyGroupCard = ({ group diff --git a/src/components/Cards/ProblemCards/MyProblemCard.tsx b/src/components/Cards/ProblemCards/MyProblemCard.tsx index ba631ab..ae73822 100644 --- a/src/components/Cards/ProblemCards/MyProblemCard.tsx +++ b/src/components/Cards/ProblemCards/MyProblemCard.tsx @@ -81,8 +81,8 @@ const MyProblemCard = ({ problem }: { problem: ProblemPopulateTestcases }) => { onMiddleClickOpenInNewTab(e,`/my/problems/${problem.problem_id}`)} - onClick={() => navigate(`/my/problems/${problem.problem_id}`)} + onMouseDown={(e) => onMiddleClickOpenInNewTab(e,`/my/problems/${problem.problem_id}/edit`)} + onClick={() => navigate(`/my/problems/${problem.problem_id}/edit`)} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} className={`pt-6 px-5 cursor-pointer ${ diff --git a/src/components/ContextMenus/MyCollectionContextMenu.tsx b/src/components/ContextMenus/MyCollectionContextMenu.tsx index 474ed92..8bb322f 100644 --- a/src/components/ContextMenus/MyCollectionContextMenu.tsx +++ b/src/components/ContextMenus/MyCollectionContextMenu.tsx @@ -100,7 +100,7 @@ const MyCollectionContextMenu = ({ - navigate(`/my/collections/${collection.collection_id}`) + navigate(`/my/collections/${collection.collection_id}/edit`) } > diff --git a/src/components/ContextMenus/MyCourseContextMenu.tsx b/src/components/ContextMenus/MyCourseContextMenu.tsx index 86e3a4e..214965e 100644 --- a/src/components/ContextMenus/MyCourseContextMenu.tsx +++ b/src/components/ContextMenus/MyCourseContextMenu.tsx @@ -100,7 +100,7 @@ const MyCourseContextMenu = ({ - navigate(`/my/courses/${course.topic_id}`) + navigate(`/my/courses/${course.topic_id}/edit`) } > diff --git a/src/components/ContextMenus/MyProblemContextMenu.tsx b/src/components/ContextMenus/MyProblemContextMenu.tsx index c65c77b..383f31c 100644 --- a/src/components/ContextMenus/MyProblemContextMenu.tsx +++ b/src/components/ContextMenus/MyProblemContextMenu.tsx @@ -81,7 +81,7 @@ const MyProblemContextMenu = ({ - navigate(`/my/problems/${problem.problem_id}`) + navigate(`/my/problems/${problem.problem_id}/edit`) } > diff --git a/src/components/Forms/CreateCollectionForm/index.tsx b/src/components/Forms/CreateCollectionForm/index.tsx index d057ddb..c598d7c 100644 --- a/src/components/Forms/CreateCollectionForm/index.tsx +++ b/src/components/Forms/CreateCollectionForm/index.tsx @@ -1,6 +1,6 @@ import { ELEMENT_PARAGRAPH } from "@udecode/plate-paragraph"; import React, { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import { PlateEditorValueType } from "../../../types/PlateEditorValueType"; import { ProblemService } from "../../../services/Problem.service"; import { toast } from "../../shadcn/UseToast"; @@ -46,7 +46,14 @@ const CreateCollectionForm = ({ onCollectionSave: (callback: OnCollectionSavedCallback) => void; }) => { const navigate = useNavigate(); - const [currentForm, setCurrentForm] = useState("general"); + const [currentForm, setCurrentForm] = useSearchParams(); + + useEffect(() => { + if (!currentForm.get("section")) { + setCurrentForm({ section: "general" }); + } + }, [currentForm]) + const [loading, setLoading] = useState(false); const [createRequest, setCreateRequest] = useState(createRequestInitialValue); @@ -73,14 +80,14 @@ const CreateCollectionForm = ({
- + {TabList.map((tab, index) => ( - setCurrentForm(tab.value) + setCurrentForm({section: tab.value}) } > {tab.label} @@ -97,20 +104,20 @@ const CreateCollectionForm = ({
- {currentForm === "general" && ( + {currentForm.get("section") === "general" && ( )} - {currentForm === "problems" && ( + {currentForm.get("section") === "problems" && ( )} - {currentForm === "groups" && ( + {currentForm.get("section") === "groups" && ( { const accountId = String(localStorage.getItem("account_id")); + const [currentForm, setCurrentForm] = useSearchParams(); + + useEffect(() => { + if (!currentForm.get("section")) { + setCurrentForm({ section: "general" }); + } + }, [currentForm]) + const navigate = useNavigate(); - const [currentForm, setCurrentForm] = useState("general"); + // const [currentForm, setCurrentForm] = useState(searchParams.get("section")); const [loading, setLoading] = useState(false); const [courseId, setCourseId] = useState(-1); @@ -85,7 +83,7 @@ const CreateCourseForm = ({
- + {TabList.map((tab, index) => ( - setCurrentForm(tab.value) + setCurrentForm({section: tab.value}) } > {tab.label} @@ -114,20 +112,20 @@ const CreateCourseForm = ({
- {currentForm === "general" && ( + {currentForm.get("section") === "general" && ( )} - {currentForm === "collections" && ( + {currentForm.get("section") === "collections" && ( )} - {currentForm === "groups" && ( + {currentForm.get("section") === "groups" && ( void; }) => { const navigate = useNavigate(); - const [currentForm, setCurrentForm] = useState("general"); + const [currentForm, setCurrentForm] = useSearchParams(); const [loading, setLoading] = useState(false); const [groupId, setGroupId] = useState(-1); @@ -61,6 +61,12 @@ const CreateGroupForm = ({ createRequestInitialValue ); + useEffect(() => { + if (!currentForm.get("section")) { + setCurrentForm({ section: "general" }); + } + }, [currentForm]) + const handleSave = () => { console.log(createRequest) onCourseSave({ @@ -86,14 +92,14 @@ const CreateGroupForm = ({
- + {TabList.map((tab, index) => ( - setCurrentForm(tab.value) + setCurrentForm({section: tab.value}) } > {tab.label} @@ -110,19 +116,19 @@ const CreateGroupForm = ({
- {currentForm === "general" && ( + {currentForm.get("section") === "general" && ( )} - {currentForm === "members" && ( + {currentForm.get("section") === "members" && ( )} - {currentForm === "permissions" && ( + {currentForm.get("section") === "permissions" && ( { return testcases.replace(/\r\n/g, "\n").split(delimeter + "\n"); }; -const transformCreateProblemRequestForm2CreateProblemRequest = ( - createRequest: CreateProblemRequestForm -): CreateProblemRequest => { - return { - title: createRequest.title, - language: createRequest.language, - description: JSON.stringify(createRequest.description), - solution: createRequest.solution, - testcases: testcaseFormat( - createRequest.testcases, - createRequest.testcase_delimeter - ), - time_limit: createRequest.time_limit, - }; -}; export type OnProblemSaveCallback = ( setLoading: React.Dispatch>, @@ -82,7 +67,7 @@ const CreateProblemForm = ({ const [loading, setLoading] = useState(false); - const [currentForm, setCurrentForm] = React.useState("general"); + const [currentForm, setCurrentForm] = useSearchParams(); const [createRequest, setCreateRequest] = useState(createRequestInitialValue); @@ -92,6 +77,12 @@ const CreateProblemForm = ({ onProblemSave(setLoading, createRequest); }; + useEffect(() => { + if (!currentForm.get("section")) { + setCurrentForm({ section: "general" }); + } + }, [currentForm]) + useEffect(() => { console.log(problemId) },[problemId]) @@ -120,14 +111,14 @@ const CreateProblemForm = ({
- + {TabList.map((tab, index) => ( - setCurrentForm(tab.value) + setCurrentForm({section: tab.value}) } > {tab.label} @@ -144,31 +135,31 @@ const CreateProblemForm = ({
- {currentForm === "general" && ( + {currentForm.get("section") === "general" && ( )} - {currentForm === "scoring" && ( + {currentForm.get("section") === "scoring" && ( )} - {currentForm === "requirement" && ( + {currentForm.get("section") === "requirement" && ( )} - {currentForm === "privacy" && ( + {currentForm.get("section") === "privacy" && ( )} - {currentForm === "groups" && ( + {currentForm.get("section") === "groups" && ( (
setSelectedIndex(index)}> navigate(`/my/groups/${groupPermission.group.group_id}`)} + onClickViewGroup={() => navigate(`/my/groups/${groupPermission.group.group_id}/edit`)} onClickRemove={() => handleRemoveGroupPermission(index)} > { } /> } /> - } /> + } /> } /> - } /> } /> + } /> } /> } /> - } /> + } /> } /> } /> - } /> + } /> } /> } /> diff --git a/src/views/My/Collections/CreateCollection.tsx b/src/views/My/Collections/CreateCollection.tsx index 8759dbe..1f789d2 100644 --- a/src/views/My/Collections/CreateCollection.tsx +++ b/src/views/My/Collections/CreateCollection.tsx @@ -79,7 +79,7 @@ const CreateCollection = () => { toast({ title: "Create Completed", }); - navigate(`/my/collections/${response.collection_id}`); + navigate(`/my/collections/${response.collection_id}/edit`); setLoading(false); }); }; diff --git a/src/views/My/Courses/CreateCourse.tsx b/src/views/My/Courses/CreateCourse.tsx index 3949df1..59fe8dd 100644 --- a/src/views/My/Courses/CreateCourse.tsx +++ b/src/views/My/Courses/CreateCourse.tsx @@ -80,7 +80,7 @@ const CreateCourse = () => { toast({ title: "Create Completed", }); - navigate(`/my/courses/${topic_id}`); + navigate(`/my/courses/${topic_id}/edit`); }); }; diff --git a/src/views/My/Groups/CreateGroup.tsx b/src/views/My/Groups/CreateGroup.tsx index 310c3d5..0cbf5f1 100644 --- a/src/views/My/Groups/CreateGroup.tsx +++ b/src/views/My/Groups/CreateGroup.tsx @@ -47,7 +47,7 @@ const CreateGroup = () => { }) .then((response) => { setLoading(false); - navigate(`/my/groups/${response.data.group_id}`); + navigate(`/my/groups/${response.data.group_id}/edit`); }); }; diff --git a/src/views/My/Problems/CreateProblem.tsx b/src/views/My/Problems/CreateProblem.tsx index 9b9c19e..33f3ff1 100644 --- a/src/views/My/Problems/CreateProblem.tsx +++ b/src/views/My/Problems/CreateProblem.tsx @@ -47,7 +47,7 @@ const CreateProblem = () => { toast({ title: "Create Completed", }); - navigate(`/my/problems/${response.data.problem_id}`); + navigate(`/my/problems/${response.data.problem_id}/edit`); }); };