delete resource

master
Tudor Stanciu 2022-12-18 03:19:48 +02:00
parent 79f9ce0aca
commit 453dfa96ae
7 changed files with 256 additions and 68 deletions

View File

@ -1,6 +1,6 @@
import { useCallback } from "react"; import { useCallback } from "react";
import useHttpRequest from "../../../hooks/useHttpRequest"; import useHttpRequest from "../../../hooks/useHttpRequest";
import { get } from "../../../utils/axios"; import { get, del } from "../../../utils/axios";
import { defaultResourcesFilters } from "../../../constants/resourcesConstants"; import { defaultResourcesFilters } from "../../../constants/resourcesConstants";
const cdn = process.env.REACT_APP_CDN_URL; const cdn = process.env.REACT_APP_CDN_URL;
@ -50,9 +50,19 @@ const useResourcesApi = () => {
[exec] [exec]
); );
const deleteResource = useCallback(
(resourceId, options) => {
const endpoint = `${cdn}/admin/resource?ResourceId=${resourceId}`;
const promise = exec(() => del(endpoint), options);
return promise;
},
[exec]
);
return { return {
getResources, getResources,
getResource getResource,
deleteResource
}; };
}; };

View File

@ -0,0 +1,43 @@
import React from "react";
import PropTypes from "prop-types";
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
const DeleteDialog = ({ open, onClose, onConfirm, title }) => {
return (
<Dialog
open={open}
onClose={onClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{`Are you sure you want to delete the resource '${title}'?`}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
No
</Button>
<Button onClick={onConfirm} color="secondary" autoFocus>
Yes
</Button>
</DialogActions>
</Dialog>
);
};
DeleteDialog.propTypes = {
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
title: PropTypes.string.isRequired
};
export default DeleteDialog;

View File

@ -19,8 +19,8 @@ const MimeTypeComponent = ({ mimeType, mimeTypes, onPropertyChange }) => {
<Grid item xs={12} sm={9}> <Grid item xs={12} sm={9}>
{isAutomaticMimeType ? ( {isAutomaticMimeType ? (
<TextField <TextField
id="resource-path" id="automatic-resource-mime-type"
label="Path on disk" label="Automatic MIME type"
fullWidth fullWidth
disabled disabled
value={mimeType.mimeTypeName} value={mimeType.mimeTypeName}
@ -58,7 +58,7 @@ const MimeTypeComponent = ({ mimeType, mimeTypes, onPropertyChange }) => {
color="primary" color="primary"
/> />
} }
label="Auto mime type" label="Auto MIME type"
/> />
</Grid> </Grid>
</> </>

View File

@ -1,18 +1,37 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Grid, Paper, ButtonBase, TextField } from "@material-ui/core"; import {
Grid,
Paper,
ButtonBase,
TextField,
FormControlLabel,
Checkbox,
Button
} from "@material-ui/core";
import Autocomplete from "@material-ui/lab/Autocomplete";
import { Save as SaveIcon, Delete as DeleteIcon } from "@material-ui/icons";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import style from "../styles"; import style from "../styles";
import ResourcePreviewComponent from "./ResourcePreviewComponent"; import ResourcePreviewComponent from "./ResourcePreviewComponent";
import MimeTypeComponent from "./MimeTypeComponent"; import MimeTypeComponent from "./MimeTypeComponent";
import DeleteDialog from "./DeleteDialog";
import { onTextFieldChange } from "../../../../utils/adapters"; import { onTextFieldChange } from "../../../../utils/adapters";
const useStyles = makeStyles(style); const useStyles = makeStyles(style);
const ResourceComponent = ({ resource, mimeTypes, onPropertyChange }) => { const ResourceComponent = ({
resource,
mimeTypes,
resourceCategories,
onPropertyChange,
deleteProps,
processing
}) => {
const classes = useStyles(); const classes = useStyles();
return ( return (
<>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12} sm={5}> <Grid item xs={12} sm={5}>
@ -59,6 +78,47 @@ const ResourceComponent = ({ resource, mimeTypes, onPropertyChange }) => {
mimeTypes={mimeTypes} mimeTypes={mimeTypes}
onPropertyChange={onPropertyChange} onPropertyChange={onPropertyChange}
/> />
<Grid item xs={12} sm={9}>
<Autocomplete
id="resource-category"
options={resourceCategories}
value={resource.categoryId}
onChange={(_event, value, _reason, _details) =>
onPropertyChange("categoryId")(value.categoryId)
}
getOptionLabel={(option) => {
const optionIsObject =
typeof option === "object" && option !== null;
if (optionIsObject) return option.categoryName;
const category = resourceCategories.find(
(z) => z.categoryId === option
);
return category.categoryName;
}}
getOptionSelected={(option, value) =>
option.categoryId === value
}
renderInput={(params) => (
<TextField {...params} label="Category" />
)}
/>
</Grid>
<Grid item xs={12} sm={3}>
<FormControlLabel
control={
<Checkbox
checked={resource.secured}
onChange={(event) =>
onPropertyChange("secured")(event.target.checked)
}
name="resources-is-secured"
color="primary"
/>
}
label="Secured"
/>
</Grid>
</Grid> </Grid>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
@ -71,15 +131,43 @@ const ResourceComponent = ({ resource, mimeTypes, onPropertyChange }) => {
variant="outlined" variant="outlined"
/> />
</Grid> </Grid>
<Grid container justify="flex-end">
<Grid item>
<Button
variant="outlined"
color="secondary"
className={classes.button}
startIcon={<DeleteIcon />}
onClick={deleteProps.onOpen}
disabled={!!processing}
>
{processing === "delete" ? "Deleting" : "Delete"}
</Button>
<Button
variant="outlined"
color="primary"
className={classes.button}
startIcon={<SaveIcon />}
disabled={!!processing}
>
{processing === "delete" ? "Saving" : "Save"}
</Button>
</Grid>
</Grid>
</Grid> </Grid>
</Paper> </Paper>
<DeleteDialog {...deleteProps} title={resource.resourceName} />
</>
); );
}; };
ResourceComponent.propTypes = { ResourceComponent.propTypes = {
resource: PropTypes.object.isRequired, resource: PropTypes.object.isRequired,
mimeTypes: PropTypes.array.isRequired, mimeTypes: PropTypes.array.isRequired,
onPropertyChange: PropTypes.func.isRequired resourceCategories: PropTypes.array.isRequired,
onPropertyChange: PropTypes.func.isRequired,
deleteProps: PropTypes.object.isRequired,
processing: PropTypes.bool.isRequired
}; };
export default ResourceComponent; export default ResourceComponent;

View File

@ -6,18 +6,23 @@ import { LoadingText } from "../../../../components";
import ResourceComponent from "./ResourceComponent"; import ResourceComponent from "./ResourceComponent";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import style from "../styles"; import style from "../styles";
import { useToast } from "../../../../hooks";
const useStyles = makeStyles(style); const useStyles = makeStyles(style);
const ResourceContainer = () => { const ResourceContainer = () => {
const [state, setState] = useState(null); const [state, setState] = useState(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [mimeTypes, setMimeTypes] = useState([]); const [mimeTypes, setMimeTypes] = useState(null);
const [resourceCategories, setResourceCategories] = useState(null);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [processing, setProcessing] = useState(null);
const classes = useStyles(); const classes = useStyles();
const params = useParams(); const params = useParams();
const { getResource } = useResourcesApi(); const { getResource, deleteResource } = useResourcesApi();
const { getMimeTypes } = useDictionariesApi(); const { getMimeTypes, getResourceCategories } = useDictionariesApi();
const { success } = useToast();
const isNew = useMemo(() => params.id === "new", [params.id]); const isNew = useMemo(() => params.id === "new", [params.id]);
@ -25,6 +30,10 @@ const ResourceContainer = () => {
getMimeTypes().then((r) => setMimeTypes(r)); getMimeTypes().then((r) => setMimeTypes(r));
}, [getMimeTypes]); }, [getMimeTypes]);
useEffect(() => {
getResourceCategories().then((r) => setResourceCategories(r));
}, [getResourceCategories]);
useEffect(() => { useEffect(() => {
if (isNew) return; if (isNew) return;
const resourceId = parseInt(params.id); const resourceId = parseInt(params.id);
@ -39,7 +48,17 @@ const ResourceContainer = () => {
setState((prev) => ({ ...prev, [prop]: value })); setState((prev) => ({ ...prev, [prop]: value }));
}; };
if (loading || state === null) return <LoadingText lines={15} onPaper />; const handleDelete = async (resourceId) => {
setProcessing("delete");
const response = await deleteResource(resourceId);
if (response && response.resourceId === state.resourceId)
success(`Resource '${state.resourceName}' was successfully deleted.`);
setProcessing(null);
setDeleteDialogOpen(false);
};
if (loading || !state || !mimeTypes || !resourceCategories)
return <LoadingText lines={15} onPaper />;
return ( return (
<> <>
@ -48,7 +67,15 @@ const ResourceContainer = () => {
<ResourceComponent <ResourceComponent
resource={state} resource={state}
mimeTypes={mimeTypes} mimeTypes={mimeTypes}
resourceCategories={resourceCategories}
onPropertyChange={handlePropertyChange} onPropertyChange={handlePropertyChange}
deleteProps={{
open: deleteDialogOpen,
onOpen: () => setDeleteDialogOpen(true),
onClose: () => setDeleteDialogOpen(false),
onConfirm: () => handleDelete(state.resourceId)
}}
processing={processing}
/> />
</div> </div>
</> </>

View File

@ -12,6 +12,9 @@ const style = (theme) => {
display: "block", display: "block",
maxWidth: "100%", maxWidth: "100%",
maxHeight: "100%" maxHeight: "100%"
},
button: {
marginRight: theme.spacing(1)
} }
}; };
}; };

View File

@ -6,7 +6,24 @@ const useHttpRequest = () => {
const handleError = useCallback( const handleError = useCallback(
(err) => { (err) => {
const message = `${err.title} ${err.correlationId}`; let message;
switch (err?.statusCode) {
case 500:
message = "Internal server error.";
break;
case 404:
message = err.serverMessage;
break;
case 401:
message = `Unauthorized: ${err.serverMessage}`;
break;
default:
message = "An unexpected error has occurred.";
}
error(message); error(message);
}, },
[error] [error]