mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-30 13:30:05 +00:00
Add confirmations for deleting channels.
This commit is contained in:
parent
a387130d23
commit
e65fd59a49
3 changed files with 395 additions and 213 deletions
77
frontend/src/components/ConfirmationDialog.jsx
Normal file
77
frontend/src/components/ConfirmationDialog.jsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { Modal, Group, Text, Button, Checkbox, Box } from '@mantine/core';
|
||||
import React, { useState } from 'react';
|
||||
import useWarningsStore from '../store/warnings';
|
||||
|
||||
/**
|
||||
* A reusable confirmation dialog with option to suppress future warnings
|
||||
*
|
||||
* @param {Object} props - Component props
|
||||
* @param {boolean} props.opened - Whether the dialog is visible
|
||||
* @param {Function} props.onClose - Function to call when closing without confirming
|
||||
* @param {Function} props.onConfirm - Function to call when confirming the action
|
||||
* @param {string} props.title - Dialog title
|
||||
* @param {string} props.message - Dialog message
|
||||
* @param {string} props.confirmLabel - Text for the confirm button
|
||||
* @param {string} props.cancelLabel - Text for the cancel button
|
||||
* @param {string} props.actionKey - Unique key for this type of action (used for suppression)
|
||||
* @param {Function} props.onSuppressChange - Called when "don't show again" option changes
|
||||
* @param {string} [props.size='md'] - Size of the modal
|
||||
*/
|
||||
const ConfirmationDialog = ({
|
||||
opened,
|
||||
onClose,
|
||||
onConfirm,
|
||||
title = 'Confirm Action',
|
||||
message = 'Are you sure you want to proceed?',
|
||||
confirmLabel = 'Confirm',
|
||||
cancelLabel = 'Cancel',
|
||||
actionKey,
|
||||
onSuppressChange,
|
||||
size = 'md', // Add default size parameter - md is a medium width
|
||||
}) => {
|
||||
const suppressWarning = useWarningsStore((s) => s.suppressWarning);
|
||||
const isWarningSuppressed = useWarningsStore((s) => s.isWarningSuppressed);
|
||||
const [suppressChecked, setSuppressChecked] = useState(
|
||||
isWarningSuppressed(actionKey)
|
||||
);
|
||||
|
||||
const handleToggleSuppress = (e) => {
|
||||
setSuppressChecked(e.currentTarget.checked);
|
||||
if (onSuppressChange) {
|
||||
onSuppressChange(e.currentTarget.checked);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (suppressChecked) {
|
||||
suppressWarning(actionKey);
|
||||
}
|
||||
onConfirm();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal opened={opened} onClose={onClose} title={title} size={size} centered>
|
||||
<Box mb={20}>{message}</Box>
|
||||
|
||||
{actionKey && (
|
||||
<Checkbox
|
||||
label="Don't ask me again"
|
||||
checked={suppressChecked}
|
||||
onChange={handleToggleSuppress}
|
||||
mb={20}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button color="red" onClick={handleConfirm}>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</Group>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmationDialog;
|
||||
|
|
@ -49,6 +49,8 @@ import useLocalStorage from '../../hooks/useLocalStorage';
|
|||
import { CustomTable, useTable } from './CustomTable';
|
||||
import ChannelsTableOnboarding from './ChannelsTable/ChannelsTableOnboarding';
|
||||
import ChannelTableHeader from './ChannelsTable/ChannelTableHeader';
|
||||
import useWarningsStore from '../../store/warnings';
|
||||
import ConfirmationDialog from '../ConfirmationDialog';
|
||||
|
||||
const m3uUrlBase = `${window.location.protocol}//${window.location.host}/output/m3u`;
|
||||
const epgUrlBase = `${window.location.protocol}//${window.location.host}/output/epg`;
|
||||
|
|
@ -234,6 +236,10 @@ const ChannelsTable = ({ }) => {
|
|||
const showVideo = useVideoStore((s) => s.showVideo);
|
||||
const [tableSize, _] = useLocalStorage('table-size', 'default');
|
||||
|
||||
// store/warnings
|
||||
const isWarningSuppressed = useWarningsStore((s) => s.isWarningSuppressed);
|
||||
const suppressWarning = useWarningsStore((s) => s.suppressWarning);
|
||||
|
||||
/**
|
||||
* useMemo
|
||||
*/
|
||||
|
|
@ -263,6 +269,11 @@ const ChannelsTable = ({ }) => {
|
|||
const [epgUrl, setEPGUrl] = useState(epgUrlBase);
|
||||
const [m3uUrl, setM3UUrl] = useState(m3uUrlBase);
|
||||
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
const [deleteTarget, setDeleteTarget] = useState(null);
|
||||
const [isBulkDelete, setIsBulkDelete] = useState(false);
|
||||
const [channelToDelete, setChannelToDelete] = useState(null);
|
||||
|
||||
/**
|
||||
* Dereived variables
|
||||
*/
|
||||
|
|
@ -338,11 +349,58 @@ const ChannelsTable = ({ }) => {
|
|||
const deleteChannel = async (id) => {
|
||||
console.log(`Deleting channel with ID: ${id}`);
|
||||
table.setSelectedTableIds([]);
|
||||
|
||||
if (selectedChannelIds.length > 0) {
|
||||
return deleteChannels();
|
||||
// Use bulk delete for multiple selections
|
||||
setIsBulkDelete(true);
|
||||
setChannelToDelete(null);
|
||||
|
||||
if (isWarningSuppressed('delete-channels')) {
|
||||
// Skip warning if suppressed
|
||||
return executeDeleteChannels();
|
||||
}
|
||||
|
||||
setConfirmDeleteOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Single channel delete
|
||||
setIsBulkDelete(false);
|
||||
setDeleteTarget(id);
|
||||
setChannelToDelete(channels[id]); // Store the channel object for displaying details
|
||||
|
||||
if (isWarningSuppressed('delete-channel')) {
|
||||
// Skip warning if suppressed
|
||||
return executeDeleteChannel(id);
|
||||
}
|
||||
|
||||
setConfirmDeleteOpen(true);
|
||||
};
|
||||
|
||||
const executeDeleteChannel = async (id) => {
|
||||
await API.deleteChannel(id);
|
||||
API.requeryChannels();
|
||||
setConfirmDeleteOpen(false);
|
||||
};
|
||||
|
||||
const deleteChannels = async () => {
|
||||
if (isWarningSuppressed('delete-channels')) {
|
||||
// Skip warning if suppressed
|
||||
return executeDeleteChannels();
|
||||
}
|
||||
|
||||
setIsBulkDelete(true);
|
||||
setConfirmDeleteOpen(true);
|
||||
};
|
||||
|
||||
const executeDeleteChannels = async () => {
|
||||
setIsLoading(true);
|
||||
await API.deleteChannels(table.selectedTableIds);
|
||||
await API.requeryChannels();
|
||||
setSelectedChannelIds([]);
|
||||
table.setSelectedTableIds([]);
|
||||
setIsLoading(false);
|
||||
setConfirmDeleteOpen(false);
|
||||
};
|
||||
|
||||
const createRecording = (channel) => {
|
||||
|
|
@ -412,16 +470,6 @@ const ChannelsTable = ({ }) => {
|
|||
[selectedProfileId]
|
||||
);
|
||||
|
||||
// (Optional) bulk delete, but your endpoint is @TODO
|
||||
const deleteChannels = async () => {
|
||||
setIsLoading(true);
|
||||
await API.deleteChannels(table.selectedTableIds);
|
||||
await API.requeryChannels();
|
||||
setSelectedChannelIds([]);
|
||||
table.setSelectedTableIds([]);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const closeChannelForm = () => {
|
||||
setChannel(null);
|
||||
setChannelModalOpen(false);
|
||||
|
|
@ -755,236 +803,264 @@ const ChannelsTable = ({ }) => {
|
|||
const rows = table.getRowModel().rows;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* Header Row: outside the Paper */}
|
||||
<Flex style={{ alignItems: 'center', paddingBottom: 10 }} gap={15}>
|
||||
<Text
|
||||
w={88}
|
||||
h={24}
|
||||
style={{
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fontWeight: 500,
|
||||
fontSize: '20px',
|
||||
lineHeight: 1,
|
||||
letterSpacing: '-0.3px',
|
||||
color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
Channels
|
||||
</Text>
|
||||
<Flex
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10,
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Box>
|
||||
{/* Header Row: outside the Paper */}
|
||||
<Flex style={{ alignItems: 'center', paddingBottom: 10 }} gap={15}>
|
||||
<Text
|
||||
w={37}
|
||||
h={17}
|
||||
w={88}
|
||||
h={24}
|
||||
style={{
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fontWeight: 400,
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
fontSize: '20px',
|
||||
lineHeight: 1,
|
||||
letterSpacing: '-0.3px',
|
||||
color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
Links:
|
||||
Channels
|
||||
</Text>
|
||||
|
||||
<Group gap={5} style={{ paddingLeft: 10 }}>
|
||||
<Popover withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button
|
||||
leftSection={<Tv2 size={18} />}
|
||||
size="compact-sm"
|
||||
p={5}
|
||||
color="green"
|
||||
variant="subtle"
|
||||
style={{
|
||||
borderColor: theme.palette.custom.greenMain,
|
||||
color: theme.palette.custom.greenMain,
|
||||
}}
|
||||
>
|
||||
HDHR
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Group>
|
||||
<TextInput value={hdhrUrl} size="small" readOnly />
|
||||
<ActionIcon
|
||||
onClick={copyHDHRUrl}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
|
||||
<Popover withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button
|
||||
leftSection={<ScreenShare size={18} />}
|
||||
size="compact-sm"
|
||||
p={5}
|
||||
variant="subtle"
|
||||
style={{
|
||||
borderColor: theme.palette.custom.indigoMain,
|
||||
color: theme.palette.custom.indigoMain,
|
||||
}}
|
||||
>
|
||||
M3U
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Group>
|
||||
<TextInput value={m3uUrl} size="small" readOnly />
|
||||
<ActionIcon
|
||||
onClick={copyM3UUrl}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
|
||||
<Popover withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button
|
||||
leftSection={<Scroll size={18} />}
|
||||
size="compact-sm"
|
||||
p={5}
|
||||
variant="subtle"
|
||||
color="gray.5"
|
||||
style={{
|
||||
borderColor: theme.palette.custom.greyBorder,
|
||||
color: theme.palette.custom.greyBorder,
|
||||
}}
|
||||
>
|
||||
EPG
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Group>
|
||||
<TextInput value={epgUrl} size="small" readOnly />
|
||||
<ActionIcon
|
||||
onClick={copyEPGUrl}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Group>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* Paper container: contains top toolbar and table (or ghost state) */}
|
||||
<Paper
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 'calc(100vh - 58px)',
|
||||
backgroundColor: '#27272A',
|
||||
}}
|
||||
>
|
||||
<ChannelTableHeader
|
||||
rows={rows}
|
||||
editChannel={editChannel}
|
||||
deleteChannels={deleteChannels}
|
||||
selectedTableIds={table.selectedTableIds}
|
||||
/>
|
||||
|
||||
{/* Table or ghost empty state inside Paper */}
|
||||
<Box>
|
||||
{Object.keys(channels).length === 0 && (
|
||||
<ChannelsTableOnboarding editChannel={editChannel} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{Object.keys(channels).length > 0 && (
|
||||
<Box
|
||||
<Flex
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 'calc(100vh - 110px)',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<Text
|
||||
w={37}
|
||||
h={17}
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
border: 'solid 1px rgb(68,68,68)',
|
||||
borderRadius: 'var(--mantine-radius-default)',
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fontWeight: 400,
|
||||
fontSize: '14px',
|
||||
lineHeight: 1,
|
||||
letterSpacing: '-0.3px',
|
||||
color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary
|
||||
}}
|
||||
>
|
||||
<CustomTable table={table} />
|
||||
</Box>
|
||||
Links:
|
||||
</Text>
|
||||
|
||||
<Group gap={5} style={{ paddingLeft: 10 }}>
|
||||
<Popover withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button
|
||||
leftSection={<Tv2 size={18} />}
|
||||
size="compact-sm"
|
||||
p={5}
|
||||
color="green"
|
||||
variant="subtle"
|
||||
style={{
|
||||
borderColor: theme.palette.custom.greenMain,
|
||||
color: theme.palette.custom.greenMain,
|
||||
}}
|
||||
>
|
||||
HDHR
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Group>
|
||||
<TextInput value={hdhrUrl} size="small" readOnly />
|
||||
<ActionIcon
|
||||
onClick={copyHDHRUrl}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
|
||||
<Popover withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button
|
||||
leftSection={<ScreenShare size={18} />}
|
||||
size="compact-sm"
|
||||
p={5}
|
||||
variant="subtle"
|
||||
style={{
|
||||
borderColor: theme.palette.custom.indigoMain,
|
||||
color: theme.palette.custom.indigoMain,
|
||||
}}
|
||||
>
|
||||
M3U
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Group>
|
||||
<TextInput value={m3uUrl} size="small" readOnly />
|
||||
<ActionIcon
|
||||
onClick={copyM3UUrl}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
|
||||
<Popover withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button
|
||||
leftSection={<Scroll size={18} />}
|
||||
size="compact-sm"
|
||||
p={5}
|
||||
variant="subtle"
|
||||
color="gray.5"
|
||||
style={{
|
||||
borderColor: theme.palette.custom.greyBorder,
|
||||
color: theme.palette.custom.greyBorder,
|
||||
}}
|
||||
>
|
||||
EPG
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Group>
|
||||
<TextInput value={epgUrl} size="small" readOnly />
|
||||
<ActionIcon
|
||||
onClick={copyEPGUrl}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Group>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* Paper container: contains top toolbar and table (or ghost state) */}
|
||||
<Paper
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 'calc(100vh - 58px)',
|
||||
backgroundColor: '#27272A',
|
||||
}}
|
||||
>
|
||||
<ChannelTableHeader
|
||||
rows={rows}
|
||||
editChannel={editChannel}
|
||||
deleteChannels={deleteChannels}
|
||||
selectedTableIds={table.selectedTableIds}
|
||||
/>
|
||||
|
||||
{/* Table or ghost empty state inside Paper */}
|
||||
<Box>
|
||||
{Object.keys(channels).length === 0 && (
|
||||
<ChannelsTableOnboarding editChannel={editChannel} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{Object.keys(channels).length > 0 && (
|
||||
<Box
|
||||
style={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
zIndex: 3,
|
||||
backgroundColor: '#27272A',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 'calc(100vh - 110px)',
|
||||
}}
|
||||
>
|
||||
<Group
|
||||
gap={5}
|
||||
justify="center"
|
||||
<Box
|
||||
style={{
|
||||
padding: 8,
|
||||
borderTop: '1px solid #666',
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
border: 'solid 1px rgb(68,68,68)',
|
||||
borderRadius: 'var(--mantine-radius-default)',
|
||||
}}
|
||||
>
|
||||
<Text size="xs">Page Size</Text>
|
||||
<NativeSelect
|
||||
size="xxs"
|
||||
value={pagination.pageSize}
|
||||
data={['25', '50', '100', '250']}
|
||||
onChange={onPageSizeChange}
|
||||
style={{ paddingRight: 20 }}
|
||||
/>
|
||||
<Pagination
|
||||
total={pageCount}
|
||||
value={pagination.pageIndex + 1}
|
||||
onChange={onPageIndexChange}
|
||||
size="xs"
|
||||
withEdges
|
||||
style={{ paddingRight: 20 }}
|
||||
/>
|
||||
<Text size="xs">{paginationString}</Text>
|
||||
</Group>
|
||||
<CustomTable table={table} />
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
style={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
zIndex: 3,
|
||||
backgroundColor: '#27272A',
|
||||
}}
|
||||
>
|
||||
<Group
|
||||
gap={5}
|
||||
justify="center"
|
||||
style={{
|
||||
padding: 8,
|
||||
borderTop: '1px solid #666',
|
||||
}}
|
||||
>
|
||||
<Text size="xs">Page Size</Text>
|
||||
<NativeSelect
|
||||
size="xxs"
|
||||
value={pagination.pageSize}
|
||||
data={['25', '50', '100', '250']}
|
||||
onChange={onPageSizeChange}
|
||||
style={{ paddingRight: 20 }}
|
||||
/>
|
||||
<Pagination
|
||||
total={pageCount}
|
||||
value={pagination.pageIndex + 1}
|
||||
onChange={onPageIndexChange}
|
||||
size="xs"
|
||||
withEdges
|
||||
style={{ paddingRight: 20 }}
|
||||
/>
|
||||
<Text size="xs">{paginationString}</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
<ChannelForm
|
||||
channel={channel}
|
||||
isOpen={channelModalOpen}
|
||||
onClose={closeChannelForm}
|
||||
/>
|
||||
<ChannelForm
|
||||
channel={channel}
|
||||
isOpen={channelModalOpen}
|
||||
onClose={closeChannelForm}
|
||||
/>
|
||||
|
||||
<RecordingForm
|
||||
channel={channel}
|
||||
isOpen={recordingModalOpen}
|
||||
onClose={closeRecordingForm}
|
||||
<RecordingForm
|
||||
channel={channel}
|
||||
isOpen={recordingModalOpen}
|
||||
onClose={closeRecordingForm}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<ConfirmationDialog
|
||||
opened={confirmDeleteOpen}
|
||||
onClose={() => setConfirmDeleteOpen(false)}
|
||||
onConfirm={() => isBulkDelete ? executeDeleteChannels() : executeDeleteChannel(deleteTarget)}
|
||||
title={`Confirm ${isBulkDelete ? 'Bulk ' : ''}Channel Deletion`}
|
||||
message={
|
||||
isBulkDelete
|
||||
? `Are you sure you want to delete ${table.selectedTableIds.length} channels? This action cannot be undone.`
|
||||
: channelToDelete
|
||||
? <div style={{ whiteSpace: 'pre-line' }}>
|
||||
{`Are you sure you want to delete the following channel?
|
||||
|
||||
Name: ${channelToDelete.name}
|
||||
Channel Number: ${channelToDelete.channel_number}
|
||||
|
||||
This action cannot be undone.`}
|
||||
</div>
|
||||
: "Are you sure you want to delete this channel? This action cannot be undone."
|
||||
}
|
||||
confirmLabel="Delete"
|
||||
cancelLabel="Cancel"
|
||||
actionKey={isBulkDelete ? 'delete-channels' : 'delete-channel'}
|
||||
onSuppressChange={suppressWarning}
|
||||
size="md"
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
29
frontend/src/store/warnings.jsx
Normal file
29
frontend/src/store/warnings.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
const useWarningsStore = create((set) => ({
|
||||
// Map of action keys to whether they're suppressed
|
||||
suppressedWarnings: {},
|
||||
|
||||
// Function to check if a warning is suppressed
|
||||
isWarningSuppressed: (actionKey) => {
|
||||
const state = useWarningsStore.getState();
|
||||
return state.suppressedWarnings[actionKey] === true;
|
||||
},
|
||||
|
||||
// Function to suppress a warning
|
||||
suppressWarning: (actionKey, suppressed = true) => {
|
||||
set((state) => ({
|
||||
suppressedWarnings: {
|
||||
...state.suppressedWarnings,
|
||||
[actionKey]: suppressed
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
// Function to reset all suppressions
|
||||
resetSuppressions: () => {
|
||||
set({ suppressedWarnings: {} });
|
||||
}
|
||||
}));
|
||||
|
||||
export default useWarningsStore;
|
||||
Loading…
Add table
Add a link
Reference in a new issue