mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-19 09:34:38 +00:00
latest frontend changes
This commit is contained in:
parent
f32adc7abc
commit
105df86af9
6 changed files with 94 additions and 16 deletions
|
|
@ -28,6 +28,7 @@ import Settings from './pages/Settings';
|
|||
import StreamProfiles from './pages/StreamProfiles';
|
||||
import useAuthStore from './store/auth';
|
||||
import logo from './images/logo.png';
|
||||
import Alert from './components/Alert';
|
||||
|
||||
// NEW: import the floating PiP component
|
||||
import FloatingVideo from './components/FloatingVideo';
|
||||
|
|
@ -110,8 +111,8 @@ const App = () => {
|
|||
flexDirection: 'column',
|
||||
ml: `${open ? drawerWidth : miniDrawerWidth}px`,
|
||||
transition: 'width 0.3s, margin-left 0.3s',
|
||||
// height: '100vh',
|
||||
backgroundColor: '#495057',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
|
|
@ -149,6 +150,9 @@ const App = () => {
|
|||
</Box>
|
||||
</Router>
|
||||
|
||||
{/* Global Snackbar for system-wide messages */}
|
||||
<Alert />
|
||||
|
||||
{/* Always-available floating video; remains visible across page changes */}
|
||||
<FloatingVideo />
|
||||
</ThemeProvider>
|
||||
|
|
|
|||
26
frontend/src/components/Alert.js
Normal file
26
frontend/src/components/Alert.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Snackbar, Alert, Button } from '@mui/material';
|
||||
import useAlertStore from '../store/alerts';
|
||||
|
||||
const AlertPopup = () => {
|
||||
const { open, message, severity, hideAlert } = useAlertStore();
|
||||
|
||||
const handleClose = () => {
|
||||
hideAlert();
|
||||
};
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
open={open}
|
||||
autoHideDuration={3000}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
>
|
||||
<Alert onClose={handleClose} severity={severity} sx={{ width: '100%' }}>
|
||||
{message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlertPopup;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Modal.js
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
|
|
@ -9,22 +9,23 @@ import {
|
|||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from "@mui/material";
|
||||
import { useFormik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import API from "../../api";
|
||||
} from '@mui/material';
|
||||
import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import API from '../../api';
|
||||
import useSettingsStore from '../../store/settings';
|
||||
|
||||
const UserAgent = ({ userAgent = null, isOpen, onClose }) => {
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
user_agent_name: "",
|
||||
user_agent: "",
|
||||
description: "",
|
||||
user_agent_name: '',
|
||||
user_agent: '',
|
||||
description: '',
|
||||
is_active: true,
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
user_agent_name: Yup.string().required("Name is required"),
|
||||
user_agent: Yup.string().required("User-Agent is required"),
|
||||
user_agent_name: Yup.string().required('Name is required'),
|
||||
user_agent: Yup.string().required('User-Agent is required'),
|
||||
}),
|
||||
onSubmit: async (values, { setSubmitting, resetForm }) => {
|
||||
if (userAgent?.id) {
|
||||
|
|
@ -60,8 +61,8 @@ const UserAgent = ({ userAgent = null, isOpen, onClose }) => {
|
|||
<Dialog open={isOpen} onClose={onClose}>
|
||||
<DialogTitle
|
||||
sx={{
|
||||
backgroundColor: "primary.main",
|
||||
color: "primary.contrastText",
|
||||
backgroundColor: 'primary.main',
|
||||
color: 'primary.contrastText',
|
||||
}}
|
||||
>
|
||||
User-Agent
|
||||
|
|
@ -132,7 +133,7 @@ const UserAgent = ({ userAgent = null, isOpen, onClose }) => {
|
|||
color="primary"
|
||||
disabled={formik.isSubmitting}
|
||||
>
|
||||
{formik.isSubmitting ? <CircularProgress size={24} /> : "Submit"}
|
||||
{formik.isSubmitting ? <CircularProgress size={24} /> : 'Submit'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import useEPGsStore from '../../store/epgs';
|
|||
import StreamProfileForm from '../forms/StreamProfile';
|
||||
import useStreamProfilesStore from '../../store/streamProfiles';
|
||||
import { TableHelper } from '../../helpers';
|
||||
import useSettingsStore from '../../store/settings';
|
||||
import useAlertStore from '../../store/alerts';
|
||||
|
||||
const StreamProfiles = () => {
|
||||
const [profile, setProfile] = useState(null);
|
||||
|
|
@ -40,6 +42,8 @@ const StreamProfiles = () => {
|
|||
const [activeFilterValue, setActiveFilterValue] = useState('all');
|
||||
|
||||
const streamProfiles = useStreamProfilesStore((state) => state.profiles);
|
||||
const { settings } = useSettingsStore();
|
||||
const { showAlert } = useAlertStore();
|
||||
|
||||
const columns = useMemo(
|
||||
//column definitions...
|
||||
|
|
@ -112,8 +116,13 @@ const StreamProfiles = () => {
|
|||
setProfileModalOpen(true);
|
||||
};
|
||||
|
||||
const deleteStreamProfile = async (ids) => {
|
||||
await API.deleteStreamProfile(ids);
|
||||
const deleteStreamProfile = async (id) => {
|
||||
if (id == settings['default-stream-profile'].value) {
|
||||
showAlert('Cannot delete default stream-profile', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
await API.deleteStreamProfile(id);
|
||||
};
|
||||
|
||||
const closeStreamProfileForm = () => {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import {
|
|||
import useUserAgentsStore from '../../store/userAgents';
|
||||
import UserAgentForm from '../forms/UserAgent';
|
||||
import { TableHelper } from '../../helpers';
|
||||
import useSettingsStore from '../../store/settings';
|
||||
import useAlertStore from '../../store/alerts';
|
||||
|
||||
const UserAgentsTable = () => {
|
||||
const [userAgent, setUserAgent] = useState(null);
|
||||
|
|
@ -34,6 +36,8 @@ const UserAgentsTable = () => {
|
|||
const [activeFilterValue, setActiveFilterValue] = useState('all');
|
||||
|
||||
const userAgents = useUserAgentsStore((state) => state.userAgents);
|
||||
const { settings } = useSettingsStore();
|
||||
const { showAlert } = useAlertStore();
|
||||
|
||||
const columns = useMemo(
|
||||
//column definitions...
|
||||
|
|
@ -108,8 +112,18 @@ const UserAgentsTable = () => {
|
|||
|
||||
const deleteUserAgent = async (ids) => {
|
||||
if (Array.isArray(ids)) {
|
||||
if (ids.includes(settings['default-user-agent'].value)) {
|
||||
showAlert('Cannot delete default user-agent', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
await API.deleteUserAgents(ids);
|
||||
} else {
|
||||
if (ids == settings['default-user-agent'].value) {
|
||||
showAlert('Cannot delete default user-agent', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
await API.deleteUserAgent(ids);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
24
frontend/src/store/alerts.js
Normal file
24
frontend/src/store/alerts.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// frontend/src/store/useAlertStore.js
|
||||
import { create } from 'zustand';
|
||||
|
||||
/**
|
||||
* Global store to track whether a floating video is visible and which URL is playing.
|
||||
*/
|
||||
const useAlertStore = create((set) => ({
|
||||
open: false,
|
||||
message: '',
|
||||
severity: 'info',
|
||||
|
||||
showAlert: (message, severity = 'info') =>
|
||||
set({
|
||||
open: true,
|
||||
message,
|
||||
severity,
|
||||
}),
|
||||
|
||||
hideAlert: () => {
|
||||
set({ open: false });
|
||||
},
|
||||
}));
|
||||
|
||||
export default useAlertStore;
|
||||
Loading…
Add table
Add a link
Reference in a new issue