Skip to content

Commit

Permalink
Add delete button to search topic page.
Browse files Browse the repository at this point in the history
Signed-off-by: Aliwoto <[email protected]>
  • Loading branch information
ALiwoto committed Aug 23, 2024
1 parent 6b2349b commit 3308b52
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 96 deletions.
225 changes: 155 additions & 70 deletions src/api/api.ts

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions src/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,21 @@ class ExamSphereAPIClient extends UserApi {
return searchTopicResult;
}

public async deleteTopic(topicId: number): Promise<boolean> {
if (!this.isLoggedIn()) {
throw new Error("Not logged in");
}

let deleteTopicResult = (await this.topicApi.deleteTopicV1(`Bearer ${this.accessToken}`, topicId))?.data.result;
if (!deleteTopicResult) {
// we shouldn't reach here, because if there is an error somewhere,
// it should have already been thrown by the API client
throw new Error("Failed to delete topic");
}

return deleteTopicResult;
}

/**
* Returns true if we are considered as "logged in" by the API client,
* This method only checks if the access token is present, it doesn't
Expand Down
4 changes: 0 additions & 4 deletions src/components/menus/sideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ const RenderManageTopicsMenu = () => {
label={CurrentAppTranslation.SearchTopicsText}
href='/searchTopic'
></MenuItem>
<MenuItem
label={CurrentAppTranslation.EditTopicsText}
href='/editTopic'
></MenuItem>
</MenuItem>
)
};
Expand Down
4 changes: 2 additions & 2 deletions src/pages/createTopicPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const CreateTopicPage: React.FC = () => {
<DashboardContainer>
<CreateUserContainer>
<CreateUserForm onSubmit={handleSubmit}>
<TitleLabel>{CurrentAppTranslation.CreateNewUserText}</TitleLabel>
<TitleLabel>{CurrentAppTranslation.CreateNewTopicText}</TitleLabel>
<TextField
style={{
width: '100%',
Expand All @@ -49,7 +49,7 @@ const CreateTopicPage: React.FC = () => {
value={createTopicData.topic_name ?? ''}
onChange={(e) => { handleInputChange(e as any) }}
required />
<SubmitButton type="submit">{CurrentAppTranslation.CreateUserButtonText}</SubmitButton>
<SubmitButton type="submit">{CurrentAppTranslation.CreateTopicButtonText}</SubmitButton>
</CreateUserForm>
</CreateUserContainer>
</DashboardContainer>
Expand Down
140 changes: 123 additions & 17 deletions src/pages/searchTopicPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useState, Fragment } from 'react';
import {
TextField,
List,
Expand All @@ -7,6 +7,7 @@ import {
Paper,
Grid,
Typography,
Button,
} from '@mui/material';
import { Box } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
Expand All @@ -16,12 +17,78 @@ import apiClient from '../apiClient';
import DashboardContainer from '../components/containers/dashboardContainer';
import { CurrentAppTranslation } from '../translations/appTranslation';

const RenderTopicsList = (topics: SearchedTopicInfo[] | undefined, forEdit: boolean = false) => {
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';
import useAppSnackbar from '../components/snackbars/useAppSnackbars';
import { extractErrorDetails } from '../utils/errorUtils';

interface DeleteDialogueProps {
target_topic_id: number;
handleDelete: (topic_id: number) => Promise<void>;
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
fullScreen: boolean;
}

var currentDeleteProps: DeleteDialogueProps | null = null;
var confirmedDeleteTopicId: number = 0;


const DeleteDialogueComponent = () => {
const props = currentDeleteProps;
if (!props) {
return null;
}

const handleClose = () => {
props.setIsOpen(false);
currentDeleteProps = null;
confirmedDeleteTopicId = 0;
};

return (
<Fragment>
<Dialog
fullScreen={props.fullScreen}
open={props.isOpen}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogTitle id="responsive-dialog-title">
{CurrentAppTranslation.AreYouSureDeleteTopicText}
</DialogTitle>
<DialogContent>
<DialogContentText>
{CurrentAppTranslation.DeleteTopicDescriptionText}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose}>
{CurrentAppTranslation.CancelButtonText}
</Button>
<Button onClick={async () => {
await props.handleDelete(props.target_topic_id);
handleClose();
}} autoFocus>
{CurrentAppTranslation.DeleteTopicButtonText}
</Button>
</DialogActions>
</Dialog>
</Fragment>
);
}

const RenderTopicsList = (
topics: SearchedTopicInfo[] | undefined, handleDelete: (topicId: number) => Promise<void>) => {
if (!topics || topics.length === 0) {
return (
<Typography variant="body2" sx={{ textAlign: 'center', mt: 4 }}>
{forEdit ? CurrentAppTranslation.EnterSearchForEdit :
CurrentAppTranslation.NoResultsFoundText}
{CurrentAppTranslation.SearchSomethingForTopicsText}
</Typography>
);
}
Expand All @@ -32,13 +99,7 @@ const RenderTopicsList = (topics: SearchedTopicInfo[] | undefined, forEdit: bool
<ListItem key={topic.topic_id} sx={{ mb: 2 }}>
<Paper
elevation={3}
sx={{ width: '100%', p: 2, borderRadius: 2 }}
onClick={
() => {
// Redirect to user info page, make sure to query encode it
window.location.href = `/topicInfo?topicId=${encodeURIComponent(topic.topic_id!)}`;
}
}>
sx={{ width: '100%', p: 2, borderRadius: 2 }}>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography variant="body2">
Expand All @@ -48,6 +109,15 @@ const RenderTopicsList = (topics: SearchedTopicInfo[] | undefined, forEdit: bool
{`${CurrentAppTranslation.topic_name}: ${topic.topic_name}`}
</Typography>
</Grid>
<Grid item xs={6} sx={{ textAlign: 'right' }}>
<Button
variant="contained"
style={{ backgroundColor: 'red' }}
onClick={() => handleDelete(topic.topic_id!)}
>
Delete
</Button>
</Grid>
</Grid>
</Paper>
</ListItem>
Expand All @@ -59,11 +129,16 @@ const RenderTopicsList = (topics: SearchedTopicInfo[] | undefined, forEdit: bool
const SearchTopicPage = () => {
const urlSearch = new URLSearchParams(window.location.search);
const providedQuery = urlSearch.get('query');
const forEdit = (urlSearch.get('edit') ?? "false") === "true";

const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
const [query, setQuery] = useState(providedQuery ?? '');
const [topics, setTopics] = useState<SearchedTopicInfo[]>([]);
const [isDeleteDialogueOpen, setIsDeleteDialogueOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [deleteDialogue, setDeleteDialogue] = useState<any>(null);
const snackbar = useAppSnackbar();


const handleSearch = async () => {
window.history.pushState(
Expand All @@ -72,10 +147,6 @@ const SearchTopicPage = () => {
`/searchTopic?query=${encodeURIComponent(query)}`,
);

if (query === '') {
return;
}

setIsLoading(true);
const results = await apiClient.searchTopic({
topic_name: query,
Expand All @@ -90,6 +161,40 @@ const SearchTopicPage = () => {
setIsLoading(false);
};

const handleDelete = async (topicId: number) => {
if (!confirmedDeleteTopicId) {
setIsDeleteDialogueOpen(true);
confirmedDeleteTopicId = topicId;
currentDeleteProps = {
target_topic_id: topicId,
handleDelete: handleDelete,
isOpen: true,
setIsOpen: setIsDeleteDialogueOpen,
fullScreen: fullScreen,
};
setDeleteDialogue(DeleteDialogueComponent);
return;
}
confirmedDeleteTopicId = 0;
setDeleteDialogue(null);
currentDeleteProps = null;
setIsDeleteDialogueOpen(false);

setIsLoading(true);

try {
const deleteResult = await apiClient.deleteTopic(topicId);
if (deleteResult) {
await handleSearch();
snackbar.success(CurrentAppTranslation.TopicDeletedSuccessfullyText);
}
} catch (error: any) {
const [errCode, errMessage] = extractErrorDetails(error);
snackbar.error(`(${errCode}): ${errMessage}`);
}

}

useEffect(() => {
// if at first the query is not null (e.g. the providedQuery exists),
// do the search.
Expand All @@ -100,6 +205,7 @@ const SearchTopicPage = () => {

return (
<DashboardContainer>
{(deleteDialogue && isDeleteDialogueOpen) ? deleteDialogue : null}
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '80vh' }}>
<Box sx={{
flexGrow: 1,
Expand Down Expand Up @@ -134,7 +240,7 @@ const SearchTopicPage = () => {
}}>
<CircularProgress size={60} />
</Box>
) : RenderTopicsList(topics, forEdit)}
) : RenderTopicsList(topics, handleDelete)}
</Box>
<Box sx={{ display: 'flex', justifyContent: 'center', margin: '0 auto', }}>
</Box>
Expand Down
9 changes: 8 additions & 1 deletion src/translations/appTranslation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,23 @@ export class AppTranslationBase {
EditText: string = "Edit";
UserInformationText: string = "User Information";
ConfirmYourAccountText = "Confirm Your Account";
CreateNewUserText: string = "Create New User";
CreateNewTopicText: string = "Create New Topic";
DeleteTopicButtonText: string = "Delete Topic";
CancelButtonText: string = "Cancel";

// System messages
AreYouSureDeleteTopicText: string = "Are you sure you want to delete this topic?";
DeleteTopicDescriptionText: string = "This action cannot be undone! All courses and exams related to this topic will be deleted as well.";
TopicDeletedSuccessfullyText: string = "Topic deleted successfully!";
ConfirmationSuccessText: string = "Confirmation was successful!";
PasswordsDoNotMatchText: string = "Passwords do not match!";
FailedToConfirmAccountCreationText: string = "Failed to confirm account creation";
UserNotFoundText: string = "This user doesn't seem to exist...";
CreateNewUserText: string = "Create New User";
UserCreatedSuccessfullyText: string = "User created successfully";
TopicCreatedSuccessfullyText: string = "Topic created successfully";
NoResultsFoundText: string = "No results found, try changing your search query";
SearchSomethingForTopicsText: string = "Search a query or enter empty to list all topics";
EnterSearchForEdit: string = "Enter search query to edit the user";

//#endregion
Expand Down
10 changes: 8 additions & 2 deletions src/translations/faTranslation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,23 @@ class FaTranslation extends AppTranslationBase {
EditText: string = "ویرایش";
UserInformationText: string = "اطلاعات کاربر";
ConfirmYourAccountText = "حساب کاربری خود را تایید کنید";

CreateNewUserText: string = "ایجاد کاربر جدید";
CreateNewTopicText: string = "ایجاد موضوع جدید";
DeleteTopicButtonText: string = "حذف موضوع";
CancelButtonText: string = "لغو";

// System messages
AreYouSureDeleteTopicText: string = "آیا مطمئن هستید که می خواهید این موضوع را حذف کنید؟";
DeleteTopicDescriptionText: string = "این عملیات قابل بازگشت نیست! تمام دوره ها و آزمون های مرتبط با این موضوع نیز حذف خواهند شد.";
TopicDeletedSuccessfullyText: string = "موضوع با موفقیت حذف شد!";
ConfirmationSuccessText: string = "تایید با موفقیت انجام شد!";
PasswordsDoNotMatchText: string = "رمز عبور ها یکسان نیستند!";
FailedToConfirmAccountCreationText: string = "تایید ایجاد حساب کاربری ناموفق بود";
UserNotFoundText: string = "این کاربر وجود ندارد...";
CreateNewUserText: string = "ایجاد کاربر جدید";
UserCreatedSuccessfullyText: string = "کاربر با موفقیت ایجاد شد";
TopicCreatedSuccessfullyText: string = "موضوع با موفقیت ایجاد شد";
NoResultsFoundText: string = "نتیجه ای یافت نشد، تلاش کنید تا جستجوی خود را تغییر دهید";
SearchSomethingForTopicsText: string = "برای جستجو یک کلمه وارد کنید یا خالی بگذارید تا همه موضوعات لیست شوند";
EnterSearchForEdit: string = "برای ویرایش کاربر جستجو کنید";

//#endregion
Expand Down

0 comments on commit 3308b52

Please sign in to comment.