diff --git a/public/logo.png b/public/logo.png deleted file mode 100644 index 95e03f5..0000000 Binary files a/public/logo.png and /dev/null differ diff --git a/src/App.jsx b/src/App.jsx index 9ca1440..28b05cc 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ import Footer from './components/Footer'; import Dashboard from './private/dashboard/Dashboard'; import UserManagement from './private/users/UserManagement'; import FurnitureVariantManagement from './private/fornitures/FurnitureVariantManagement'; +import Categories from './private/categories/Categories'; import LoginPage from './private/LoginPage'; import { Routes, Route, Navigate } from 'react-router-dom'; import { useAuth } from './context/AuthContext'; @@ -24,10 +25,8 @@ export default function App() { const mainLeft = isMobile ? 0 : (drawerExpanded ? DRAWER_EXPANDED : DRAWER_COLLAPSED); - // Evita flicker mientras se restaura sesión if (initializing) return null; - // === RUTAS PÚBLICAS (sin shell) === if (!user) { return ( @@ -37,14 +36,9 @@ export default function App() { ); } - // === RUTAS PRIVADAS (con shell) === return ( <> - + setCurrentView(value)} @@ -64,9 +58,7 @@ export default function App() { }} > - {/* Si ya está autenticado, /login redirige al dashboard */} } /> - )} + {currentView === '/ProductsManagement/CatalogManagement/Categories' && ( + + )} } /> diff --git a/src/api/CategoriesApi.js b/src/api/CategoriesApi.js new file mode 100644 index 0000000..a61128e --- /dev/null +++ b/src/api/CategoriesApi.js @@ -0,0 +1,54 @@ +// src/api/CategoriesApi.js +export default class CategoriesApi { + constructor(token) { + this.baseUrl = 'https://inventory-bff.dream-views.com/api/v1/Tags'; + this.token = token; + } + + headers(json = true) { + return { + accept: 'application/json', + ...(json ? { 'Content-Type': 'application/json' } : {}), + ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), + }; + } + + async getAll() { + const res = await fetch(`${this.baseUrl}/GetAll`, { + method: 'GET', + headers: this.headers(false), + }); + if (!res.ok) throw new Error(`GetAll error ${res.status}: ${await res.text()}`); + return res.json(); + } + + async create(payload) { + const res = await fetch(`${this.baseUrl}/Create`, { + method: 'POST', + headers: this.headers(), + body: JSON.stringify(payload), + }); + if (!res.ok) throw new Error(`Create error ${res.status}: ${await res.text()}`); + return res.json(); + } + + async update(payload) { + const res = await fetch(`${this.baseUrl}/Update`, { + method: 'PUT', + headers: this.headers(), + body: JSON.stringify(payload), + }); + if (!res.ok) throw new Error(`Update error ${res.status}: ${await res.text()}`); + return res.json(); + } + + async delete(payload) { + const res = await fetch(`${this.baseUrl}/Delete`, { + method: 'DELETE', + headers: this.headers(), + body: JSON.stringify(payload), + }); + if (!res.ok) throw new Error(`Delete error ${res.status}: ${await res.text()}`); + return res.json(); + } +} diff --git a/src/components/MenuDrawerPrivate.jsx b/src/components/MenuDrawerPrivate.jsx index 8237123..ce6e1ee 100644 --- a/src/components/MenuDrawerPrivate.jsx +++ b/src/components/MenuDrawerPrivate.jsx @@ -40,11 +40,13 @@ const menuData = [ { title: 'Categories' } ] }, - { title: 'Products', - children: [ - { title: 'AR Assets Library Management' }, - { title: 'Media Management' }, - ] }, + { + title: 'Products', + children: [ + { title: 'AR Assets Library Management' }, + { title: 'Media Management' }, + ] + }, { title: 'Product Collections' }, ] } @@ -54,14 +56,15 @@ const menuData = [ title: 'Customers', icon: , children: [ - { title: 'CRM', + { + title: 'CRM', children: [ - { title: 'Customer List' }, - { title: 'Projects' }, + { title: 'Customer List' }, + { title: 'Projects' }, { title: 'Customer Collections' }, ] - }, - + }, + { title: 'Sales', children: [ @@ -86,12 +89,13 @@ const menuData = [ icon: , children: [ { title: 'Users Management' }, - { title: 'Access Control', + { + title: 'Access Control', children: [ { title: 'Roles' }, { title: 'Permissions' }, ] - }, + }, ] }, { @@ -157,6 +161,8 @@ export default function MenuDrawerPrivate({ onSelect?.('/Users/UserManagement'); } else if (node.title === 'Product Collections') { onSelect?.('/ProductsManagement/CatalogManagement/ProductCollections'); + } else if (node.title === 'Categories') { + onSelect?.('/ProductsManagement/CatalogManagement/Categories'); } else { onSelect?.(node.title); } @@ -199,7 +205,7 @@ export default function MenuDrawerPrivate({ {hasChildren && !collapsed && ( - {node.children.map((child, idx) => renderNode(child, `${key}-`))} + {node.children.map((child) => renderNode(child, `${key}-`))} )} @@ -303,4 +309,4 @@ export default function MenuDrawerPrivate({ ); -} \ No newline at end of file +} diff --git a/src/private/categories/AddOrEditCategoryForm.jsx b/src/private/categories/AddOrEditCategoryForm.jsx index e86a2d6..e6469f2 100644 --- a/src/private/categories/AddOrEditCategoryForm.jsx +++ b/src/private/categories/AddOrEditCategoryForm.jsx @@ -1,64 +1,94 @@ -import { useState, useEffect } from 'react'; -import { Box, Button, TextField, Typography, Paper } from '@mui/material'; +import { useEffect, useMemo, useState } from 'react'; +import { Box, Button, Paper, TextField, Typography } from '@mui/material'; +import { useAuth } from '../../context/AuthContext'; +import CategoriesApi from '../../api/CategoriesApi'; export default function AddOrEditCategoryForm({ onAdd, initialData, onCancel }) { - const [category, setCategory] = useState({ - name: '', - description: '' - }); + const { user } = useAuth(); + const token = user?.thalosToken || localStorage.getItem('thalosToken'); + const api = useMemo(() => new CategoriesApi(token), [token]); - useEffect(() => { - if (initialData) { - setCategory(initialData); - } else { - setCategory({ name: '', description: '' }); - } - }, [initialData]); + const [category, setCategory] = useState({ + _Id: '', + id: '', + name: '', + description: '', + status: 'Active', + }); - const handleChange = (e) => { - const { name, value } = e.target; - setCategory((prev) => ({ ...prev, [name]: value })); - }; + useEffect(() => { + if (initialData) { + setCategory({ + _Id: initialData._id || initialData._Id || '', + id: initialData.id || initialData.Id || initialData._id || initialData._Id || '', + name: initialData.name ?? '', + description: initialData.description ?? '', + status: initialData.status ?? 'Active', + }); + } else { + setCategory({ _Id: '', id: '', name: '', description: '', status: 'Active' }); + } + }, [initialData]); - const handleSubmit = () => { - if (onAdd) { - onAdd(category); - } - }; + const handleChange = (e) => { + const { name, value } = e.target; + setCategory((prev) => ({ ...prev, [name]: value })); + }; - return ( - - - - Category Details - - - - - - - - - - ); + const handleSubmit = async () => { + try { + if (category._Id) { + const payload = { + _Id: category._Id, + Id: category.id || category._Id, + name: category.name, + description: category.description, + status: category.status, + }; + await api.update(payload); + } else { + const payload = { + name: category.name, + description: category.description, + status: category.status, + }; + await api.create(payload); + } + onAdd?.(); + } catch (e) { + console.error('Submit category failed:', e); + } + }; + + return ( + + + {category._Id ? 'Edit Category' : 'Add Category'} + + + + + + + + + + + + ); } diff --git a/src/private/categories/Categories.jsx b/src/private/categories/Categories.jsx index 3b2fba3..41feccb 100644 --- a/src/private/categories/Categories.jsx +++ b/src/private/categories/Categories.jsx @@ -1,142 +1,146 @@ -import SectionContainer from '../../components/SectionContainer.jsx'; -import { useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { Box, Button, Dialog, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material'; import { DataGrid } from '@mui/x-data-grid'; -import { Typography, Button, Dialog, DialogTitle, DialogContent, IconButton, Box } from '@mui/material'; -import AddOrEditCategoryForm from './AddOrEditCategoryForm.jsx'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddOrEditCategoryForm from './AddOrEditCategoryForm'; +import CategoriesApi from '../../api/CategoriesApi'; +import { useAuth } from '../../context/AuthContext'; -import EditRoundedIcon from '@mui/icons-material/EditRounded'; -import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; -import '../../App.css'; +export default function Categories() { + const { user } = useAuth(); + const token = user?.thalosToken || localStorage.getItem('thalosToken'); + const api = useMemo(() => new CategoriesApi(token), [token]); -const columnsBase = [ - { field: 'name', headerName: 'Name', flex: 1 }, - { field: 'description', headerName: 'Description', flex: 2 } -]; + const [rows, setRows] = useState([]); + const [open, setOpen] = useState(false); + const [editingCategory, setEditingCategory] = useState(null); + const [confirmOpen, setConfirmOpen] = useState(false); + const [rowToDelete, setRowToDelete] = useState(null); + const hasLoaded = useRef(false); -export default function Categories({ children, maxWidth = 'lg', sx = {} }) { - const [rows, setRows] = useState([ - { id: 1, name: 'Fabrics', description: 'Textile materials including silk, cotton, and synthetics.' }, - { id: 2, name: 'Leather Goods', description: 'Leather-based components for luxury goods.' }, - { id: 3, name: 'Metal Accessories', description: 'Buttons, zippers, and hardware in metal.' }, - { id: 4, name: 'Embellishments', description: 'Decorative materials such as beads and sequins.' } - ]); + useEffect(() => { + if (!hasLoaded.current) { + loadData(); + hasLoaded.current = true; + } + }, []); - const [open, setOpen] = useState(false); - const [editingCategory, setEditingCategory] = useState(null); - const [confirmOpen, setConfirmOpen] = useState(false); - const [rowToDelete, setRowToDelete] = useState(null); + const loadData = async () => { + try { + const data = await api.getAll(); + setRows(Array.isArray(data) ? data : []); + } catch (e) { + console.error('Failed to load categories:', e); + setRows([]); + } + }; - const handleAddOrEditCategory = (category) => { - if (editingCategory) { - setRows(rows.map((row) => (row.id === editingCategory.id ? { ...editingCategory, ...category } : row))); - } else { - const id = rows.length + 1; - setRows([...rows, { id, ...category }]); - } - setOpen(false); - setEditingCategory(null); - }; + const handleAddClick = () => { + setEditingCategory(null); + setOpen(true); + }; - const handleEditClick = (params) => { - setEditingCategory(params.row); - setOpen(true); - }; + const handleEditClick = (params) => { + const r = params?.row; + if (!r) return; + setEditingCategory({ + _Id: r._id || r._Id || '', + id: r.id || r.Id || '', + name: r.name ?? '', + description: r.description ?? '', + status: r.status ?? 'Active', + }); + setOpen(true); + }; - const handleDeleteClick = (row) => { - setRowToDelete(row); - setConfirmOpen(true); - }; + const handleDeleteClick = (row) => { + setRowToDelete(row); + setConfirmOpen(true); + }; - const confirmDelete = () => { - setRows(rows.filter((row) => row.id !== rowToDelete.id)); - setRowToDelete(null); - setConfirmOpen(false); - }; + const confirmDelete = async () => { + try { + if (!rowToDelete) return; + const payload = { + _Id: rowToDelete._id || rowToDelete._Id, + id: rowToDelete.id || rowToDelete.Id || '', + name: rowToDelete.name, + description: rowToDelete.description, + status: 'Inactive', // soft-delete + }; + await api.update(payload); + await loadData(); + } catch (e) { + console.error('Delete failed:', e); + } finally { + setConfirmOpen(false); + setRowToDelete(null); + } + }; - const columns = [ - ...columnsBase, - { - field: 'actions', - headerName: '', - width: 130, - renderCell: (params) => ( - - handleEditClick(params)} - > - - - handleDeleteClick(params.row)} - > - - - - ) - } - ]; + const handleFormDone = async () => { + await loadData(); + setOpen(false); + setEditingCategory(null); + }; - return ( - - - Categories - + const columns = [ + { field: 'name', headerName: 'Name', flex: 1, minWidth: 200 }, + { field: 'description', headerName: 'Description', flex: 1, minWidth: 250 }, + { field: 'status', headerName: 'Status', width: 140, valueGetter: (p) => p.row?.status ?? 'Active' }, + { + field: 'actions', + headerName: '', + width: 120, + sortable: false, + filterable: false, + renderCell: (params) => ( + + handleEditClick(params)}> + handleDeleteClick(params.row)}> + + ), + }, + ]; - { setOpen(false); setEditingCategory(null); }} fullWidth> - {editingCategory ? 'Edit Category' : 'Add Category'} - - { setOpen(false); setEditingCategory(null); }} /> - - + return ( + + + Categories + + - setConfirmOpen(false)}> - Confirm Delete - - - Are you sure you want to delete {rowToDelete?.name}? - - - - - - - + r._id || r._Id || r.id || r.Id} + /> - - ({ top: 8, bottom: 8 })} - /> + { setOpen(false); setEditingCategory(null); }} fullWidth> + {editingCategory ? 'Edit Category' : 'Add Category'} + + { setOpen(false); setEditingCategory(null); }} + /> + + - - - - - - ); + setConfirmOpen(false)}> + Delete Category + + + + + + + + + ); }