Add header credits display with reload functionality
Introduced a dynamic credits system displaying available credits in the header and allowing users to reload them via prompt-based input. Updated `Header.js` to include the credits badge and reload button. Adjusted `App.js` to manage credits state with localStorage synchronization. Enhanced styles for new header elements.
This commit is contained in:
39
src/App.js
39
src/App.js
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import './styles/globals.css';
|
import './styles/globals.css';
|
||||||
import { Sidebar, Header } from './components/Layout';
|
import { Sidebar, Header } from './components/Layout';
|
||||||
import { DashboardCards } from './components/Dashboard';
|
import { DashboardCards } from './components/Dashboard';
|
||||||
@@ -11,7 +11,27 @@ import SettingsPage from './components/Settings/SettingsPage';
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
const [activeView, setActiveView] = useState('dashboard'); // 'dashboard' | 'scheduled' | 'create'
|
const [activeView, setActiveView] = useState('dashboard'); // 'dashboard' | 'scheduled' | 'create' | 'history' | 'settings'
|
||||||
|
const [credits, setCredits] = useState(() => {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem('credits');
|
||||||
|
const n = raw != null ? parseInt(raw, 10) : 0;
|
||||||
|
return Number.isNaN(n) ? 0 : n;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onStorage = (e) => {
|
||||||
|
if (e.key === 'credits') {
|
||||||
|
const n = parseInt(e.newValue, 10);
|
||||||
|
if (!Number.isNaN(n)) setCredits(n);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('storage', onStorage);
|
||||||
|
return () => window.removeEventListener('storage', onStorage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleToggleMobileMenu = () => setIsMobileMenuOpen(!isMobileMenuOpen);
|
const handleToggleMobileMenu = () => setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||||
const handleNavigate = (key) => {
|
const handleNavigate = (key) => {
|
||||||
@@ -19,6 +39,19 @@ function App() {
|
|||||||
setIsMobileMenuOpen(false);
|
setIsMobileMenuOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleReloadCredits = () => {
|
||||||
|
const input = window.prompt('¿Cuántos créditos desea recargar?');
|
||||||
|
if (input == null) return;
|
||||||
|
const add = parseInt(input, 10);
|
||||||
|
if (Number.isNaN(add) || add <= 0) {
|
||||||
|
alert('Ingrese un número válido mayor a 0.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const next = credits + add;
|
||||||
|
setCredits(next);
|
||||||
|
try { localStorage.setItem('credits', String(next)); } catch {}
|
||||||
|
};
|
||||||
|
|
||||||
const headerTitle = activeView === 'scheduled'
|
const headerTitle = activeView === 'scheduled'
|
||||||
? 'Solicitudes Programadas'
|
? 'Solicitudes Programadas'
|
||||||
: activeView === 'create'
|
: activeView === 'create'
|
||||||
@@ -38,7 +71,7 @@ function App() {
|
|||||||
onNavigate={handleNavigate}
|
onNavigate={handleNavigate}
|
||||||
/>
|
/>
|
||||||
<main className="main-content">
|
<main className="main-content">
|
||||||
<Header title={headerTitle} />
|
<Header title={headerTitle} credits={credits} onReloadCredits={handleReloadCredits} />
|
||||||
{activeView === 'scheduled' ? (
|
{activeView === 'scheduled' ? (
|
||||||
<ScheduledPage />
|
<ScheduledPage />
|
||||||
) : activeView === 'create' ? (
|
) : activeView === 'create' ? (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import '../../styles/components.css';
|
import '../../styles/components.css';
|
||||||
|
|
||||||
const Header = ({ title = 'Dashboard' }) => {
|
const Header = ({ title = 'Dashboard', credits = 0, onReloadCredits }) => {
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('auth', 'false');
|
localStorage.setItem('auth', 'false');
|
||||||
@@ -23,6 +23,19 @@ const Header = ({ title = 'Dashboard' }) => {
|
|||||||
<i className="fas fa-search"></i>
|
<i className="fas fa-search"></i>
|
||||||
<input type="text" placeholder="Buscar solicitudes..." />
|
<input type="text" placeholder="Buscar solicitudes..." />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="header-credits">
|
||||||
|
<span className="credits-badge" title="Créditos disponibles">
|
||||||
|
<i className="fas fa-coins"></i>
|
||||||
|
Créditos: {credits}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-outline"
|
||||||
|
onClick={() => onReloadCredits && onReloadCredits()}
|
||||||
|
title="Recargar créditos"
|
||||||
|
>
|
||||||
|
<i className="fas fa-sync-alt"></i> Recargar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button className="btn btn-primary">
|
<button className="btn btn-primary">
|
||||||
<i className="fas fa-plus"></i> Nueva Solicitud
|
<i className="fas fa-plus"></i> Nueva Solicitud
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -148,11 +148,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
@@ -181,6 +186,29 @@
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Header credits */
|
||||||
|
.header-credits {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credits-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: rgba(16, 185, 129, 0.1);
|
||||||
|
color: var(--secondary);
|
||||||
|
border: 1px solid rgba(16, 185, 129, 0.25);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credits-badge i {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
.search-box {
|
.search-box {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: white;
|
background: white;
|
||||||
|
|||||||
Reference in New Issue
Block a user