Solucionado submodulos y subiendo codigo real
This commit is contained in:
34
frontend/src/components/Modal.jsx
Normal file
34
frontend/src/components/Modal.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// src/components/Modal.jsx
|
||||
export default function Modal({ title, onClose, children, width = 900 }) {
|
||||
return (
|
||||
<div
|
||||
onClick={onClose}
|
||||
style={{
|
||||
position: "fixed", inset: 0, background: "rgba(15,23,42,0.55)",
|
||||
display: "flex", alignItems: "center", justifyContent: "center",
|
||||
zIndex: 1000, padding: 20,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
background: "#fff", borderRadius: 16, width: "100%", maxWidth: width,
|
||||
maxHeight: "90vh", overflow: "auto", boxShadow: "0 20px 60px rgba(0,0,0,0.3)",
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: "flex", justifyContent: "space-between", alignItems: "center",
|
||||
padding: "16px 22px", borderBottom: "1px solid #e2e8f0", position: "sticky",
|
||||
top: 0, background: "#fff", zIndex: 2,
|
||||
}}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 700, color: "#1e40af" }}>{title}</h3>
|
||||
<button onClick={onClose} style={{
|
||||
border: "none", background: "#f1f5f9", borderRadius: 8, width: 32, height: 32,
|
||||
cursor: "pointer", fontSize: 18, color: "#475569",
|
||||
}}>×</button>
|
||||
</div>
|
||||
<div style={{ padding: 22 }}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
54
frontend/src/components/Sidebar.jsx
Normal file
54
frontend/src/components/Sidebar.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
// src/components/Sidebar.jsx
|
||||
import { useAuth } from "../lib/auth";
|
||||
|
||||
const MENU = [
|
||||
{ id: "ocupabilidad", ico: "📊", label: "Ocupabilidad" },
|
||||
{ id: "ventas", ico: "💰", label: "Ventas" },
|
||||
{ id: "comisiones", ico: "🧮", label: "Comisiones" },
|
||||
{ id: "cobranza", ico: "📋", label: "Cobranza" },
|
||||
{ id: "rentabilidad", ico: "📈", label: "Rentabilidad" },
|
||||
{ id: "asesores", ico: "👥", label: "Asesores" },
|
||||
{ id: "usuarios", ico: "🔐", label: "Usuarios" },
|
||||
];
|
||||
|
||||
export default function Sidebar({ active, onChange }) {
|
||||
const { perfil, permisos, logout } = useAuth();
|
||||
const visibles = MENU.filter((m) => permisos.includes(m.id));
|
||||
|
||||
return (
|
||||
<aside className="sidebar">
|
||||
<div className="sidebar-logo">Escuela <span>Refrigeración</span></div>
|
||||
|
||||
<nav className="nav" style={{ flex: 1 }}>
|
||||
{visibles.map((m) => (
|
||||
<button
|
||||
key={m.id}
|
||||
className={`nav-item ${active === m.id ? "active" : ""}`}
|
||||
onClick={() => onChange(m.id)}
|
||||
>
|
||||
<span className="ico">{m.ico}</span> {m.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div style={{ padding: 14, borderTop: "1px solid rgba(255,255,255,0.08)" }}>
|
||||
<div style={{ fontSize: 12, color: "#94a3b8", marginBottom: 2 }}>
|
||||
{perfil?.nombre || "Usuario"}
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: "#60a5fa", fontWeight: 600, marginBottom: 10 }}>
|
||||
{perfil?.rol || ""}
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
style={{
|
||||
width: "100%", padding: "8px", border: "1px solid rgba(255,255,255,0.15)",
|
||||
borderRadius: 8, background: "transparent", color: "#cbd5e1", fontSize: 12,
|
||||
cursor: "pointer", fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
🚪 Cerrar sesión
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
51
frontend/src/components/UI.jsx
Normal file
51
frontend/src/components/UI.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
// src/components/UI.jsx
|
||||
export function Loader({ text = "Cargando..." }) {
|
||||
return (
|
||||
<div className="loader-wrap">
|
||||
<div className="spinner" />
|
||||
<div className="loader-text">{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorBox({ msg }) {
|
||||
return <div className="error-box">⚠️ {msg}</div>;
|
||||
}
|
||||
|
||||
export function colorSemaforo(pct) {
|
||||
if (pct >= 80) return { bg: "#d1fae5", fg: "#065f46", fill: "#10b981" };
|
||||
if (pct >= 60) return { bg: "#fef9c3", fg: "#854d0e", fill: "#f59e0b" };
|
||||
return { bg: "#fee2e2", fg: "#991b1b", fill: "#ef4444" };
|
||||
}
|
||||
|
||||
export function ProgressBar({ pct }) {
|
||||
const c = colorSemaforo(pct);
|
||||
const w = Math.min(Math.max(pct, 0), 100);
|
||||
return (
|
||||
<div className="pbar">
|
||||
<span className="pct" style={{ color: c.fg }}>{pct.toFixed(1)}%</span>
|
||||
<div className="track">
|
||||
<div className="fill" style={{ width: `${w}%`, background: c.fill }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Filters({ children }) {
|
||||
return <div className="filters">{children}</div>;
|
||||
}
|
||||
|
||||
export function Select({ label, value, options, onChange }) {
|
||||
return (
|
||||
<div className="filter-group">
|
||||
{label && <label>{label}</label>}
|
||||
<select value={value} onChange={(e) => onChange(e.target.value)}>
|
||||
{options.map((o) => {
|
||||
const val = typeof o === "object" ? o.value : o;
|
||||
const txt = typeof o === "object" ? o.label : o;
|
||||
return <option key={val} value={val}>{txt}</option>;
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user