diff --git a/frontend/src/components/tables/LogosTable.jsx b/frontend/src/components/tables/LogosTable.jsx
new file mode 100644
index 00000000..df6605d1
--- /dev/null
+++ b/frontend/src/components/tables/LogosTable.jsx
@@ -0,0 +1,356 @@
+import React, { useMemo, useCallback, useState } from 'react';
+import API from '../../api';
+import LogoForm from '../forms/Logo';
+import useChannelsStore from '../../store/channels';
+import useLocalStorage from '../../hooks/useLocalStorage';
+import {
+ SquarePlus,
+ SquareMinus,
+ SquarePen,
+ ExternalLink,
+} from 'lucide-react';
+import {
+ ActionIcon,
+ Box,
+ Text,
+ Paper,
+ Button,
+ Flex,
+ Group,
+ useMantineTheme,
+ LoadingOverlay,
+ Stack,
+ Image,
+ Center,
+} from '@mantine/core';
+import { CustomTable, useTable } from './CustomTable';
+import ConfirmationDialog from '../ConfirmationDialog';
+import { notifications } from '@mantine/notifications';
+
+const LogoRowActions = ({ theme, row, editLogo, deleteLogo }) => {
+ const [tableSize, _] = useLocalStorage('table-size', 'default');
+
+ const onEdit = useCallback(() => {
+ editLogo(row.original);
+ }, [row.original, editLogo]);
+
+ const onDelete = useCallback(() => {
+ deleteLogo(row.original.id);
+ }, [row.original.id, deleteLogo]);
+
+ const iconSize =
+ tableSize == 'default' ? 'sm' : tableSize == 'compact' ? 'xs' : 'md';
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const LogosTable = () => {
+ const theme = useMantineTheme();
+
+ /**
+ * STORES
+ */
+ const { logos, fetchLogos } = useChannelsStore();
+
+ /**
+ * useState
+ */
+ const [selectedLogo, setSelectedLogo] = useState(null);
+ const [logoModalOpen, setLogoModalOpen] = useState(false);
+ const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
+ const [deleteTarget, setDeleteTarget] = useState(null);
+ const [logoToDelete, setLogoToDelete] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+
+ /**
+ * Functions
+ */
+ const executeDeleteLogo = useCallback(async (id) => {
+ setIsLoading(true);
+ try {
+ await API.deleteLogo(id);
+ await fetchLogos();
+ notifications.show({
+ title: 'Success',
+ message: 'Logo deleted successfully',
+ color: 'green',
+ });
+ } catch (error) {
+ notifications.show({
+ title: 'Error',
+ message: 'Failed to delete logo',
+ color: 'red',
+ });
+ } finally {
+ setIsLoading(false);
+ setConfirmDeleteOpen(false);
+ }
+ }, [fetchLogos]);
+
+ const editLogo = useCallback(async (logo = null) => {
+ setSelectedLogo(logo);
+ setLogoModalOpen(true);
+ }, []);
+
+ const deleteLogo = useCallback(async (id) => {
+ const logosArray = Object.values(logos || {});
+ const logo = logosArray.find((l) => l.id === id);
+ setLogoToDelete(logo);
+ setDeleteTarget(id);
+ setConfirmDeleteOpen(true);
+ }, [logos]);
+
+ /**
+ * useMemo
+ */
+ const columns = useMemo(
+ () => [
+ {
+ header: 'Preview',
+ accessorKey: 'cache_url',
+ size: 80,
+ enableSorting: false,
+ cell: ({ getValue, row }) => (
+
+
+
+ ),
+ },
+ {
+ header: 'Name',
+ accessorKey: 'name',
+ size: 200,
+ cell: ({ getValue }) => (
+
+ {getValue()}
+
+ ),
+ },
+ {
+ header: 'URL',
+ accessorKey: 'url',
+ cell: ({ getValue }) => (
+
+
+
+ {getValue()}
+
+
+ {getValue()?.startsWith('http') && (
+ window.open(getValue(), '_blank')}
+ >
+
+
+ )}
+
+ ),
+ },
+ {
+ id: 'actions',
+ size: 80,
+ header: 'Actions',
+ enableSorting: false,
+ cell: ({ row }) => (
+
+ ),
+ },
+ ],
+ [theme, editLogo, deleteLogo]
+ );
+
+ const closeLogoForm = () => {
+ setSelectedLogo(null);
+ setLogoModalOpen(false);
+ fetchLogos(); // Refresh the logos list
+ };
+
+ const data = useMemo(() => {
+ const logosArray = Object.values(logos || {});
+ return logosArray.sort((a, b) => a.id - b.id);
+ }, [logos]);
+
+ const renderHeaderCell = (header) => {
+ return (
+
+ {header.column.columnDef.header}
+
+ );
+ };
+
+ const table = useTable({
+ columns,
+ data,
+ allRowIds: data.map((logo) => logo.id),
+ enablePagination: false,
+ enableRowSelection: false,
+ enableRowVirtualization: false,
+ renderTopToolbar: false,
+ manualSorting: false,
+ manualFiltering: false,
+ manualPagination: false,
+ headerCellRenderFns: {
+ actions: renderHeaderCell,
+ cache_url: renderHeaderCell,
+ name: renderHeaderCell,
+ url: renderHeaderCell,
+ },
+ });
+
+ return (
+ <>
+
+
+
+
+ Logos
+
+
+ ({data.length} logo{data.length !== 1 ? 's' : ''})
+
+
+
+
+ {/* Top toolbar */}
+
+ }
+ variant="light"
+ size="xs"
+ onClick={() => editLogo()}
+ p={5}
+ color={theme.tailwind.green[5]}
+ style={{
+ borderWidth: '1px',
+ borderColor: theme.tailwind.green[5],
+ color: 'white',
+ }}
+ >
+ Add Logo
+
+
+
+ {/* Table container */}
+
+
+
+
+
+
+
+
+
+
+
+
+ setConfirmDeleteOpen(false)}
+ onConfirm={() => executeDeleteLogo(deleteTarget)}
+ title="Delete Logo"
+ message={
+ logoToDelete ? (
+
+ Are you sure you want to delete the logo "{logoToDelete.name}"?
+
+
+ This action cannot be undone.
+
+
+ ) : (
+ 'Are you sure you want to delete this logo?'
+ )
+ }
+ confirmLabel="Delete"
+ cancelLabel="Cancel"
+ size="md"
+ />
+ >
+ );
+};
+
+export default LogosTable;
diff --git a/frontend/src/pages/Logos.jsx b/frontend/src/pages/Logos.jsx
index 7ca879f6..ee26c51e 100644
--- a/frontend/src/pages/Logos.jsx
+++ b/frontend/src/pages/Logos.jsx
@@ -1,39 +1,17 @@
-import React, { useState, useEffect } from 'react';
-import {
- Container,
- Title,
- Button,
- Table,
- Group,
- ActionIcon,
- Text,
- Image,
- Box,
- Center,
- Stack,
- Badge,
-} from '@mantine/core';
-import { SquarePen, Trash2, Plus, ExternalLink } from 'lucide-react';
+import React, { useEffect } from 'react';
+import { Box } from '@mantine/core';
import { notifications } from '@mantine/notifications';
import useChannelsStore from '../store/channels';
-import API from '../api';
-import LogoForm from '../components/forms/Logo';
-import ConfirmationDialog from '../components/ConfirmationDialog';
+import LogosTable from '../components/tables/LogosTable';
const LogosPage = () => {
- const { logos, fetchLogos } = useChannelsStore();
- const [logoFormOpen, setLogoFormOpen] = useState(false);
- const [editingLogo, setEditingLogo] = useState(null);
- const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
- const [logoToDelete, setLogoToDelete] = useState(null);
- const [loading, setLoading] = useState(true);
+ const { fetchLogos } = useChannelsStore();
useEffect(() => {
loadLogos();
}, []);
const loadLogos = async () => {
- setLoading(true);
try {
await fetchLogos();
} catch (error) {
@@ -42,181 +20,13 @@ const LogosPage = () => {
message: 'Failed to load logos',
color: 'red',
});
- } finally {
- setLoading(false);
}
};
- const handleCreateLogo = () => {
- setEditingLogo(null);
- setLogoFormOpen(true);
- };
-
- const handleEditLogo = (logo) => {
- setEditingLogo(logo);
- setLogoFormOpen(true);
- };
-
- const handleDeleteLogo = (logo) => {
- setLogoToDelete(logo);
- setDeleteConfirmOpen(true);
- };
-
- const confirmDeleteLogo = async () => {
- if (!logoToDelete) return;
-
- try {
- await API.deleteLogo(logoToDelete.id);
- await fetchLogos();
- notifications.show({
- title: 'Success',
- message: 'Logo deleted successfully',
- color: 'green',
- });
- } catch (error) {
- notifications.show({
- title: 'Error',
- message: 'Failed to delete logo',
- color: 'red',
- });
- } finally {
- setDeleteConfirmOpen(false);
- setLogoToDelete(null);
- }
- };
-
- const handleFormClose = () => {
- setLogoFormOpen(false);
- setEditingLogo(null);
- loadLogos(); // Refresh the logos list
- };
-
- const logosArray = Object.values(logos || {});
-
- const rows = logosArray.map((logo) => (
-
-
-
-
-
-
-
- {logo.name}
-
-
-
-
- {logo.url}
-
- {logo.url.startsWith('http') && (
- window.open(logo.url, '_blank')}
- >
-
-
- )}
-
-
-
-
- handleEditLogo(logo)}
- color="blue"
- >
-
-
- handleDeleteLogo(logo)}
- color="red"
- >
-
-
-
-
-
- ));
-
return (
- <>
-
-
- Logos
- } onClick={handleCreateLogo}>
- Add Logo
-
-
-
- {loading ? (
-
- Loading logos...
-
- ) : logosArray.length === 0 ? (
-
-
- No logos found
- Click "Add Logo" to create your first logo
-
-
- ) : (
-
-
- Total: {logosArray.length} logo{logosArray.length !== 1 ? 's' : ''}
-
-
-
-
-
- Preview
- Name
- URL
- Actions
-
-
- {rows}
-
-
- )}
-
-
-
-
- setDeleteConfirmOpen(false)}
- onConfirm={confirmDeleteLogo}
- title="Delete Logo"
- message={
- logoToDelete ? (
-
- Are you sure you want to delete the logo "{logoToDelete.name}"?
-
-
- This action cannot be undone.
-
-
- ) : (
- 'Are you sure you want to delete this logo?'
- )
- }
- confirmLabel="Delete"
- cancelLabel="Cancel"
- />
- >
+
+
+
);
};