mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 10:45:27 +00:00
Disable buttons that can't be used.
This commit is contained in:
parent
9cb05a0ae1
commit
adc6604fa2
9 changed files with 120 additions and 37 deletions
|
|
@ -187,6 +187,40 @@ class ChannelGroupViewSet(viewsets.ModelViewSet):
|
|||
except KeyError:
|
||||
return [Authenticated()]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Add annotation for association counts"""
|
||||
from django.db.models import Count
|
||||
return ChannelGroup.objects.annotate(
|
||||
channel_count=Count('channels', distinct=True),
|
||||
m3u_account_count=Count('m3u_account', distinct=True)
|
||||
)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
"""Override update to check M3U associations"""
|
||||
instance = self.get_object()
|
||||
|
||||
# Check if group has M3U account associations
|
||||
if hasattr(instance, 'm3u_account') and instance.m3u_account.exists():
|
||||
return Response(
|
||||
{"error": "Cannot edit group with M3U account associations"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
"""Override partial_update to check M3U associations"""
|
||||
instance = self.get_object()
|
||||
|
||||
# Check if group has M3U account associations
|
||||
if hasattr(instance, 'm3u_account') and instance.m3u_account.exists():
|
||||
return Response(
|
||||
{"error": "Cannot edit group with M3U account associations"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""Override destroy to check for associations before deletion"""
|
||||
instance = self.get_object()
|
||||
|
|
|
|||
|
|
@ -89,9 +89,12 @@ class StreamSerializer(serializers.ModelSerializer):
|
|||
# Channel Group
|
||||
#
|
||||
class ChannelGroupSerializer(serializers.ModelSerializer):
|
||||
channel_count = serializers.IntegerField(read_only=True)
|
||||
m3u_account_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ChannelGroup
|
||||
fields = ["id", "name"]
|
||||
fields = ["id", "name", "channel_count", "m3u_account_count"]
|
||||
|
||||
|
||||
class ChannelProfileSerializer(serializers.ModelSerializer):
|
||||
|
|
|
|||
|
|
@ -250,7 +250,15 @@ export default class API {
|
|||
});
|
||||
|
||||
if (response.id) {
|
||||
useChannelsStore.getState().addChannelGroup(response);
|
||||
// Add association flags for new groups
|
||||
const processedGroup = {
|
||||
...response,
|
||||
hasChannels: false,
|
||||
hasM3UAccounts: false,
|
||||
canEdit: true,
|
||||
canDelete: true
|
||||
};
|
||||
useChannelsStore.getState().addChannelGroup(processedGroup);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => {
|
|||
const groupListRef = useRef(null);
|
||||
|
||||
const channelGroups = useChannelsStore((s) => s.channelGroups);
|
||||
const canEditChannelGroup = useChannelsStore((s) => s.canEditChannelGroup);
|
||||
|
||||
const logos = useChannelsStore((s) => s.logos);
|
||||
const fetchLogos = useChannelsStore((s) => s.fetchLogos);
|
||||
const streams = useStreamsStore((state) => state.streams);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => {
|
|||
const groupListRef = useRef(null);
|
||||
|
||||
const channelGroups = useChannelsStore((s) => s.channelGroups);
|
||||
const canEditChannelGroup = useChannelsStore((s) => s.canEditChannelGroup);
|
||||
|
||||
const streamProfiles = useStreamProfilesStore((s) => s.profiles);
|
||||
|
||||
const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
// Modal.js
|
||||
import React from 'react';
|
||||
import API from '../../api';
|
||||
import { Flex, TextInput, Button, Modal } from '@mantine/core';
|
||||
import { Flex, TextInput, Button, Modal, Alert } from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { isNotEmpty, useForm } from '@mantine/form';
|
||||
import useChannelsStore from '../../store/channels';
|
||||
|
||||
const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => {
|
||||
const canEditChannelGroup = useChannelsStore((s) => s.canEditChannelGroup);
|
||||
|
||||
// Check if editing is allowed
|
||||
const canEdit = !channelGroup || canEditChannelGroup(channelGroup.id);
|
||||
|
||||
const form = useForm({
|
||||
mode: 'uncontrolled',
|
||||
initialValues: {
|
||||
|
|
@ -17,6 +24,16 @@ const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => {
|
|||
});
|
||||
|
||||
const onSubmit = async () => {
|
||||
// Prevent submission if editing is not allowed
|
||||
if (channelGroup && !canEdit) {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: 'Cannot edit group with M3U account associations',
|
||||
color: 'red',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const values = form.getValues();
|
||||
let newGroup;
|
||||
|
||||
|
|
@ -36,11 +53,17 @@ const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => {
|
|||
|
||||
return (
|
||||
<Modal opened={isOpen} onClose={onClose} title="Channel Group">
|
||||
{channelGroup && !canEdit && (
|
||||
<Alert color="yellow" mb="md">
|
||||
This group cannot be edited because it has M3U account associations.
|
||||
</Alert>
|
||||
)}
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<TextInput
|
||||
id="name"
|
||||
name="name"
|
||||
label="Name"
|
||||
disabled={channelGroup && !canEdit}
|
||||
{...form.getInputProps('name')}
|
||||
key={form.key('name')}
|
||||
/>
|
||||
|
|
@ -50,7 +73,7 @@ const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => {
|
|||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={form.submitting}
|
||||
disabled={form.submitting || (channelGroup && !canEdit)}
|
||||
size="small"
|
||||
>
|
||||
Submit
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ const GroupItem = React.memo(({
|
|||
onCancelEdit,
|
||||
onEdit,
|
||||
onDelete,
|
||||
groupUsage
|
||||
groupUsage,
|
||||
canEditGroup,
|
||||
canDeleteGroup
|
||||
}) => {
|
||||
const getGroupBadges = (group) => {
|
||||
const usage = groupUsage[group.id];
|
||||
|
|
@ -62,16 +64,6 @@ const GroupItem = React.memo(({
|
|||
return badges;
|
||||
};
|
||||
|
||||
const canEditGroup = (group) => {
|
||||
const usage = groupUsage[group.id];
|
||||
return usage?.canEdit !== false;
|
||||
};
|
||||
|
||||
const canDeleteGroup = (group) => {
|
||||
const usage = groupUsage[group.id];
|
||||
return usage?.canDelete !== false && !usage?.hasChannels && !usage?.hasM3UAccounts;
|
||||
};
|
||||
|
||||
return (
|
||||
<Group justify="space-between" p="sm" style={{
|
||||
border: '1px solid #e0e0e0',
|
||||
|
|
@ -133,9 +125,9 @@ const GroupItem = React.memo(({
|
|||
});
|
||||
|
||||
const GroupManager = React.memo(({ isOpen, onClose }) => {
|
||||
// Use a more specific selector to avoid unnecessary re-renders
|
||||
const fetchChannelGroups = useChannelsStore((s) => s.fetchChannelGroups);
|
||||
const channelGroups = useChannelsStore((s) => s.channelGroups);
|
||||
const canEditChannelGroup = useChannelsStore((s) => s.canEditChannelGroup);
|
||||
const canDeleteChannelGroup = useChannelsStore((s) => s.canDeleteChannelGroup);
|
||||
const [editingGroup, setEditingGroup] = useState(null);
|
||||
const [editName, setEditName] = useState('');
|
||||
const [newGroupName, setNewGroupName] = useState('');
|
||||
|
|
@ -171,21 +163,18 @@ const GroupManager = React.memo(({ isOpen, onClose }) => {
|
|||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const fetchGroupUsage = async () => {
|
||||
const fetchGroupUsage = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// This would ideally be a dedicated API endpoint, but we'll use the existing data
|
||||
// For now, we'll determine usage based on the group having associated data
|
||||
// Use the actual channel group data that already has the flags
|
||||
const usage = {};
|
||||
|
||||
// Check which groups have channels or M3U associations
|
||||
// This is a simplified check - in a real implementation you'd want a dedicated API
|
||||
Object.values(channelGroups).forEach(group => {
|
||||
usage[group.id] = {
|
||||
hasChannels: false, // Would need API call to check
|
||||
hasM3UAccounts: false, // Would need API call to check
|
||||
canEdit: true, // Assume editable unless proven otherwise
|
||||
canDelete: true // Assume deletable unless proven otherwise
|
||||
hasChannels: group.hasChannels ?? false,
|
||||
hasM3UAccounts: group.hasM3UAccounts ?? false,
|
||||
canEdit: group.canEdit ?? true,
|
||||
canDelete: group.canDelete ?? true
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -195,7 +184,7 @@ const GroupManager = React.memo(({ isOpen, onClose }) => {
|
|||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [channelGroups]);
|
||||
|
||||
const handleEdit = useCallback((group) => {
|
||||
setEditingGroup(group.id);
|
||||
|
|
@ -414,6 +403,8 @@ const GroupManager = React.memo(({ isOpen, onClose }) => {
|
|||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
groupUsage={groupUsage}
|
||||
canEditGroup={canEditChannelGroup}
|
||||
canDeleteGroup={canDeleteChannelGroup}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
|
@ -431,5 +422,4 @@ const GroupManager = React.memo(({ isOpen, onClose }) => {
|
|||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default GroupManager;
|
||||
|
|
|
|||
|
|
@ -216,6 +216,9 @@ const ChannelRowActions = React.memo(
|
|||
|
||||
const ChannelsTable = ({ }) => {
|
||||
const theme = useMantineTheme();
|
||||
const channelGroups = useChannelsStore((s) => s.channelGroups);
|
||||
const canEditChannelGroup = useChannelsStore((s) => s.canEditChannelGroup);
|
||||
const canDeleteChannelGroup = useChannelsStore((s) => s.canDeleteChannelGroup);
|
||||
|
||||
/**
|
||||
* STORES
|
||||
|
|
@ -241,7 +244,6 @@ const ChannelsTable = ({ }) => {
|
|||
const channels = useChannelsStore((s) => s.channels);
|
||||
const profiles = useChannelsStore((s) => s.profiles);
|
||||
const selectedProfileId = useChannelsStore((s) => s.selectedProfileId);
|
||||
const channelGroups = useChannelsStore((s) => s.channelGroups);
|
||||
const logos = useChannelsStore((s) => s.logos);
|
||||
const [tablePrefs, setTablePrefs] = useLocalStorage('channel-table-prefs', {
|
||||
pageSize: 50,
|
||||
|
|
|
|||
|
|
@ -46,16 +46,24 @@ const useChannelsStore = create((set, get) => ({
|
|||
},
|
||||
|
||||
fetchChannelGroups: async () => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const channelGroups = await api.getChannelGroups();
|
||||
set({
|
||||
channelGroups: channelGroups.reduce((acc, group) => {
|
||||
acc[group.id] = group;
|
||||
return acc;
|
||||
}, {}),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
// Process groups to add association flags
|
||||
const processedGroups = channelGroups.reduce((acc, group) => {
|
||||
acc[group.id] = {
|
||||
...group,
|
||||
hasChannels: group.channel_count > 0,
|
||||
hasM3UAccounts: group.m3u_account_count > 0,
|
||||
canEdit: group.m3u_account_count === 0,
|
||||
canDelete: group.channel_count === 0 && group.m3u_account_count === 0
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
set((state) => ({
|
||||
channelGroups: processedGroups,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch channel groups:', error);
|
||||
set({ error: 'Failed to load channel groups.', isLoading: false });
|
||||
|
|
@ -435,6 +443,17 @@ const useChannelsStore = create((set, get) => ({
|
|||
set({ error: 'Failed to load recordings.', isLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
// Add helper methods for validation
|
||||
canEditChannelGroup: (groupIdOrGroup) => {
|
||||
const groupId = typeof groupIdOrGroup === 'object' ? groupIdOrGroup.id : groupIdOrGroup;
|
||||
return get().channelGroups[groupId]?.canEdit ?? true;
|
||||
},
|
||||
|
||||
canDeleteChannelGroup: (groupIdOrGroup) => {
|
||||
const groupId = typeof groupIdOrGroup === 'object' ? groupIdOrGroup.id : groupIdOrGroup;
|
||||
return get().channelGroups[groupId]?.canDelete ?? true;
|
||||
},
|
||||
}));
|
||||
|
||||
export default useChannelsStore;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue