diff --git a/apps/client/src/features/app-settings/panel/manage-panel/CustomViewsList.tsx b/apps/client/src/features/app-settings/panel/manage-panel/CustomViewsList.tsx index 3336f2209..1343d8103 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/CustomViewsList.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/CustomViewsList.tsx @@ -1,5 +1,10 @@ import { CustomViewSummary } from 'ontime-types'; +import { useState } from 'react'; +import { deleteCustomView } from '../../../../common/api/customViews'; +import { maybeAxiosError } from '../../../../common/api/utils'; +import Button from '../../../../common/components/buttons/Button'; +import Dialog from '../../../../common/components/dialog/Dialog'; import * as Panel from '../../panel-utils/PanelUtils'; import CustomViewsListItem from './CustomViewsListItem'; @@ -13,21 +18,71 @@ interface CustomViewsListProps { } export default function CustomViewsList({ views, onOpenUpload, onMutate, onError }: CustomViewsListProps) { + const [targetSlug, setTargetSlug] = useState(null); + const [isDeleting, setIsDeleting] = useState(false); + + const openDelete = (slug: string) => { + setTargetSlug(slug); + }; + + const submitDelete = async () => { + if (!targetSlug) { + return; + } + + try { + setIsDeleting(true); + await deleteCustomView(targetSlug); + onMutate(); + } catch (error) { + onError(maybeAxiosError(error)); + } finally { + setIsDeleting(false); + setTargetSlug(null); + } + }; + return ( - - - - Name - URL - - - - - {views.length === 0 && } - {views.map((view, index) => ( - - ))} - - + <> + + + + Name + URL + + + + + {views.length === 0 && } + {views.map((view, index) => ( + + ))} + + + setTargetSlug(null)} + title='Delete custom view' + showBackdrop + showCloseButton + bodyElements='You will permanently delete this file. Are you sure?' + footerElements={ + <> + + + + } + /> + ); } diff --git a/apps/client/src/features/app-settings/panel/manage-panel/CustomViewsListItem.tsx b/apps/client/src/features/app-settings/panel/manage-panel/CustomViewsListItem.tsx index 0cd8671a9..7ae153246 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/CustomViewsListItem.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/CustomViewsListItem.tsx @@ -1,6 +1,6 @@ import { IoDownloadOutline, IoOpenOutline, IoTrash } from 'react-icons/io5'; -import { deleteCustomView, downloadCustomView } from '../../../../common/api/customViews'; +import { downloadCustomView } from '../../../../common/api/customViews'; import { maybeAxiosError } from '../../../../common/api/utils'; import IconButton from '../../../../common/components/buttons/IconButton'; import { handleLinks } from '../../../../common/utils/linkUtils'; @@ -12,11 +12,11 @@ import style from './CustomViews.module.scss'; interface CustomViewsListItemProps { slug: string; index: number; - onMutate: () => void; + onDelete: (slug: string) => void; onError: (message: string) => void; } -export default function CustomViewsListItem({ slug, index, onMutate, onError }: CustomViewsListItemProps) { +export default function CustomViewsListItem({ slug, index, onDelete, onError }: CustomViewsListItemProps) { const handlePreview = () => { handleLinks(`external/${encodeURIComponent(slug)}/`); }; @@ -29,15 +29,6 @@ export default function CustomViewsListItem({ slug, index, onMutate, onError }: } }; - const handleDelete = async () => { - try { - await deleteCustomView(slug); - onMutate(); - } catch (error) { - onError(maybeAxiosError(error)); - } - }; - return ( {slug} @@ -62,7 +53,7 @@ export default function CustomViewsListItem({ slug, index, onMutate, onError }: onDelete(slug)} aria-label='Delete custom view' data-testid={`custom-view__delete_${index}`} > diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx index d93122e0e..c2dfa1cde 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx @@ -16,7 +16,9 @@ import { renameProject, } from '../../../../common/api/db'; import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/utils'; +import Button from '../../../../common/components/buttons/Button'; import IconButton from '../../../../common/components/buttons/IconButton'; +import Dialog from '../../../../common/components/dialog/Dialog'; import { DropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu'; import { cx } from '../../../../common/utils/styleUtils'; import * as Panel from '../../panel-utils/PanelUtils'; @@ -50,6 +52,7 @@ export default function ProjectListItem({ }: ProjectListItemProps) { const [submitError, setSubmitError] = useState(null); const [loading, setLoading] = useState(false); + const [isDeleteOpen, setDeleteOpen] = useState(false); const handleSubmitAction = (actionType: 'rename' | 'duplicate') => { return async (values: ProjectFormValues) => { @@ -99,6 +102,11 @@ export default function ProjectListItem({ } }; + const submitDelete = async () => { + await handleDelete(filename); + setDeleteOpen(false); + }; + const handleToggleEditMode = (editMode: EditMode, filename: string | null) => { setSubmitError(null); onToggleEditMode(editMode, filename); @@ -141,7 +149,7 @@ export default function ProjectListItem({ current={current} filename={filename} onChangeEditMode={handleToggleEditMode} - onDelete={handleDelete} + onDelete={() => setDeleteOpen(true)} onLoad={handleLoad} isDisabled={loading || showMergeForm} onMerge={(filename) => handleToggleEditMode('merge', filename)} @@ -157,6 +165,24 @@ export default function ProjectListItem({ )} + setDeleteOpen(false)} + title='Delete project' + showBackdrop + showCloseButton + bodyElements='You will permanently remove this project file. Are you sure?' + footerElements={ + <> + + + + } + /> ); } @@ -166,7 +192,7 @@ interface ActionMenuProps { filename: string; isDisabled: boolean; onChangeEditMode: (editMode: EditMode, filename: string) => void; - onDelete: (filename: string) => Promise; + onDelete: () => void; onLoad: (filename: string) => Promise; onMerge: (filename: string) => void; } @@ -208,7 +234,7 @@ function ActionMenu(props: ActionMenuProps) { { type: 'item', icon: IoCopyOutline, label: 'Duplicate', onClick: handleDuplicate }, { type: 'item', icon: IoDocumentOutline, label: 'Download', onClick: handleDownload }, { type: 'divider' }, - { type: 'item', icon: IoTrash, label: 'Delete', onClick: () => onDelete(filename), disabled: current }, + { type: 'item', icon: IoTrash, label: 'Delete', onClick: onDelete, disabled: current }, ]} >