Replace CRA boilerplate with custom dashboard setup, remove unused files, and add new styles and components.
Some checks failed
continuous-integration/drone Build was killed
Some checks failed
continuous-integration/drone Build was killed
This commit is contained in:
57
.drone.yml
Normal file
57
.drone.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: deploy-backoffice
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: deploying-project
|
||||||
|
image: alpine
|
||||||
|
environment:
|
||||||
|
SSH_USERNAME:
|
||||||
|
from_secret: ssh_username
|
||||||
|
SSH_HOSTNAME:
|
||||||
|
from_secret: ssh_hostname
|
||||||
|
SSH_PRIVATE_KEY:
|
||||||
|
from_secret: ssh_id_rsa
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache rsync openssh
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||||
|
- chmod 600 ~/.ssh/id_rsa
|
||||||
|
- |
|
||||||
|
rsync -avz --delete \
|
||||||
|
-e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" \
|
||||||
|
./ $SSH_USERNAME@$SSH_HOSTNAME:/var/www/${DRONE_REPO_OWNER}/${DRONE_REPO_NAME} \
|
||||||
|
--exclude .git \
|
||||||
|
--exclude node_modules \
|
||||||
|
--exclude .drone.yml
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
|
||||||
|
- name: restarting-project
|
||||||
|
image: alpine
|
||||||
|
environment:
|
||||||
|
SSH_USERNAME:
|
||||||
|
from_secret: ssh_username
|
||||||
|
SSH_HOSTNAME:
|
||||||
|
from_secret: ssh_hostname
|
||||||
|
SSH_PRIVATE_KEY:
|
||||||
|
from_secret: ssh_id_rsa
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache openssh
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||||
|
- chmod 600 ~/.ssh/id_rsa
|
||||||
|
- CONTAINER_NAME="${DRONE_REPO_OWNER}_${DRONE_REPO_NAME}"
|
||||||
|
- ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa $SSH_USERNAME@$SSH_HOSTNAME "docker restart $CONTAINER_NAME"
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- develop
|
||||||
|
event:
|
||||||
|
- push
|
||||||
10
.idea/php.xml
generated
10
.idea/php.xml
generated
@@ -1,5 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="MessDetectorOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PHPCSFixerOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||||
|
<option name="highlightLevel" value="WARNING" />
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
<component name="PhpStanOptionsConfiguration">
|
<component name="PhpStanOptionsConfiguration">
|
||||||
<option name="transferred" value="true" />
|
<option name="transferred" value="true" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"react": "^19.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -1,43 +1,20 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="es">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Request Scheduler - Panel de Control"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
<!--
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
<title>Request Scheduler - Panel de Control</title>
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>React App</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"short_name": "React App",
|
|
||||||
"name": "Create React App Sample",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
||||||
Disallow:
|
|
||||||
38
src/App.css
38
src/App.css
@@ -1,38 +0,0 @@
|
|||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
42
src/App.js
42
src/App.js
@@ -1,23 +1,31 @@
|
|||||||
import logo from './logo.svg';
|
import React, { useState } from 'react';
|
||||||
import './App.css';
|
import './styles/globals.css';
|
||||||
|
import { Sidebar, Header } from './components/Layout';
|
||||||
|
import { DashboardCards } from './components/Dashboard';
|
||||||
|
import RequestTable from './components/Requests/RequestTable';
|
||||||
|
import RequestForm from './components/Requests/RequestForm';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleToggleMobileMenu = () => setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="container">
|
||||||
<header className="App-header">
|
<Sidebar
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
isMobileMenuOpen={isMobileMenuOpen}
|
||||||
<p>
|
onToggleMobileMenu={handleToggleMobileMenu}
|
||||||
Edit <code>src/App.js</code> and save to reload.
|
/>
|
||||||
</p>
|
<main className="main-content">
|
||||||
<a
|
<Header />
|
||||||
className="App-link"
|
<section className="content-wrapper">
|
||||||
href="https://reactjs.org"
|
<DashboardCards />
|
||||||
target="_blank"
|
<div className="content-grid">
|
||||||
rel="noopener noreferrer"
|
<RequestForm />
|
||||||
>
|
<RequestTable />
|
||||||
Learn React
|
</div>
|
||||||
</a>
|
</section>
|
||||||
</header>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
|
||||||
render(<App />);
|
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
58
src/components/Dashboard/DashboardCards.js
Normal file
58
src/components/Dashboard/DashboardCards.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import StatsCard from './StatsCard';
|
||||||
|
import '../../styles/components.css';
|
||||||
|
|
||||||
|
const DashboardCards = () => {
|
||||||
|
const statsData = [
|
||||||
|
{
|
||||||
|
type: 'primary',
|
||||||
|
value: '42',
|
||||||
|
title: 'Solicitudes Programadas',
|
||||||
|
trend: '12% desde la semana pasada',
|
||||||
|
trendDirection: 'up',
|
||||||
|
icon: 'fas fa-clock'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
value: '28',
|
||||||
|
title: 'Solicitudes Pendientes',
|
||||||
|
trend: '5% desde ayer',
|
||||||
|
trendDirection: 'down',
|
||||||
|
icon: 'fas fa-hourglass-half'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'success',
|
||||||
|
value: '156',
|
||||||
|
title: 'Solicitudes Ejecutadas',
|
||||||
|
trend: '8% desde ayer',
|
||||||
|
trendDirection: 'up',
|
||||||
|
icon: 'fas fa-check-circle'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'danger',
|
||||||
|
value: '12',
|
||||||
|
title: 'Solicitudes Fallidas',
|
||||||
|
trend: '3% desde ayer',
|
||||||
|
trendDirection: 'down',
|
||||||
|
icon: 'fas fa-exclamation-circle'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dashboard-cards">
|
||||||
|
{statsData.map((stat, index) => (
|
||||||
|
<StatsCard
|
||||||
|
key={index}
|
||||||
|
type={stat.type}
|
||||||
|
value={stat.value}
|
||||||
|
title={stat.title}
|
||||||
|
trend={stat.trend}
|
||||||
|
trendDirection={stat.trendDirection}
|
||||||
|
icon={stat.icon}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardCards;
|
||||||
23
src/components/Dashboard/StatsCard.js
Normal file
23
src/components/Dashboard/StatsCard.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import '../../styles/components.css';
|
||||||
|
|
||||||
|
const StatsCard = ({ type, value, title, trend, trendDirection, icon }) => {
|
||||||
|
return (
|
||||||
|
<div className={`card ${type}`}>
|
||||||
|
<div className="card-header">
|
||||||
|
<div>
|
||||||
|
<div className="card-value">{value}</div>
|
||||||
|
<div className="card-title">{title}</div>
|
||||||
|
<div className={`card-trend ${trendDirection}`}>
|
||||||
|
<i className={`fas fa-arrow-${trendDirection}`}></i> {trend}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`card-icon ${type}`}>
|
||||||
|
<i className={icon}></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatsCard;
|
||||||
2
src/components/Dashboard/index.js
Normal file
2
src/components/Dashboard/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as DashboardCards } from './DashboardCards';
|
||||||
|
export { default as StatsCard } from './StatsCard';
|
||||||
24
src/components/Layout/Header.js
Normal file
24
src/components/Layout/Header.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import '../../styles/components.css';
|
||||||
|
|
||||||
|
const Header = () => {
|
||||||
|
return (
|
||||||
|
<div className="header">
|
||||||
|
<div className="page-title">
|
||||||
|
<i className="fas fa-tachometer-alt"></i>
|
||||||
|
<span>Dashboard</span>
|
||||||
|
</div>
|
||||||
|
<div className="header-actions">
|
||||||
|
<div className="search-box">
|
||||||
|
<i className="fas fa-search"></i>
|
||||||
|
<input type="text" placeholder="Buscar solicitudes..." />
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-primary">
|
||||||
|
<i className="fas fa-plus"></i> Nueva Solicitud
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
69
src/components/Layout/Sidebar.js
Normal file
69
src/components/Layout/Sidebar.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import '../../styles/components.css';
|
||||||
|
|
||||||
|
const Sidebar = ({ isMobileMenuOpen, onToggleMobileMenu }) => {
|
||||||
|
const navItems = [
|
||||||
|
{ icon: 'fas fa-tachometer-alt', label: 'Dashboard', active: true, badge: null },
|
||||||
|
{ icon: 'fas fa-list', label: 'Solicitudes Programadas', active: false, badge: '12' },
|
||||||
|
{ icon: 'fas fa-plus-circle', label: 'Crear Solicitud', active: false, badge: null },
|
||||||
|
{ icon: 'fas fa-history', label: 'Historial', active: false, badge: null },
|
||||||
|
{ icon: 'fas fa-cog', label: 'Configuración', active: false, badge: null },
|
||||||
|
{ icon: 'fas fa-question-circle', label: 'Ayuda', active: false, badge: null }
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="menu-toggle"
|
||||||
|
id="menuToggle"
|
||||||
|
onClick={onToggleMobileMenu}
|
||||||
|
>
|
||||||
|
<i className="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className={`sidebar ${isMobileMenuOpen ? 'active' : ''}`} id="sidebar">
|
||||||
|
<div className="logo-container">
|
||||||
|
<div className="logo">
|
||||||
|
<div className="logo-icon">
|
||||||
|
<i className="fas fa-clock"></i>
|
||||||
|
</div>
|
||||||
|
<div className="logo-text">Request <span>Scheduler</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="nav-container">
|
||||||
|
<ul className="nav-links">
|
||||||
|
{navItems.map((item, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
className={item.active ? 'active' : ''}
|
||||||
|
data-tooltip={item.label}
|
||||||
|
>
|
||||||
|
<a href="#">
|
||||||
|
<i className={item.icon}></i>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
{item.badge && <span className="badge">{item.badge}</span>}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sidebar-footer">
|
||||||
|
<div className="user-info">
|
||||||
|
<div className="user-avatar">JP</div>
|
||||||
|
<div className="user-details">
|
||||||
|
<div className="user-name">Juan Pérez</div>
|
||||||
|
<div className="user-role">Administrador</div>
|
||||||
|
</div>
|
||||||
|
<a href="#" style={{color: 'white'}}>
|
||||||
|
<i className="fas fa-sign-out-alt"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
||||||
2
src/components/Layout/index.js
Normal file
2
src/components/Layout/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as Sidebar } from './Sidebar';
|
||||||
|
export { default as Header } from './Header';
|
||||||
127
src/components/Requests/RequestForm.js
Normal file
127
src/components/Requests/RequestForm.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import '../../styles/components.css';
|
||||||
|
|
||||||
|
const RequestForm = () => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
url: '',
|
||||||
|
method: 'POST',
|
||||||
|
headers: '',
|
||||||
|
body: '',
|
||||||
|
executeDate: '',
|
||||||
|
executeTime: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Aquí podrías enviar los datos al backend
|
||||||
|
// console.log('Form data', formData);
|
||||||
|
|
||||||
|
// Mostrar notificación de éxito
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.textContent = 'Solicitud guardada correctamente';
|
||||||
|
notification.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: var(--success);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
z-index: 2000;
|
||||||
|
`;
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
setTimeout(() => notification.remove(), 2500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-container">
|
||||||
|
<div className="form-header">
|
||||||
|
<h3><i className="fas fa-paper-plane"></i> Nueva Solicitud</h3>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>URL del Endpoint</label>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
name="url"
|
||||||
|
value={formData.url}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="https://api.ejemplo.com/endpoint"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-row">
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Método</label>
|
||||||
|
<select name="method" value={formData.method} onChange={handleChange}>
|
||||||
|
<option>GET</option>
|
||||||
|
<option>POST</option>
|
||||||
|
<option>PUT</option>
|
||||||
|
<option>DELETE</option>
|
||||||
|
<option>PATCH</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Fecha de Ejecución</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
name="executeDate"
|
||||||
|
value={formData.executeDate}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Hora de Ejecución</label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
name="executeTime"
|
||||||
|
value={formData.executeTime}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Headers (JSON)</label>
|
||||||
|
<textarea
|
||||||
|
name="headers"
|
||||||
|
rows="3"
|
||||||
|
value={formData.headers}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder='{"Authorization": "Bearer ..."}'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>Cuerpo (JSON)</label>
|
||||||
|
<textarea
|
||||||
|
name="body"
|
||||||
|
rows="4"
|
||||||
|
value={formData.body}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder='{"campo": "valor"}'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-actions">
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
<i className="fas fa-save"></i> Guardar
|
||||||
|
</button>
|
||||||
|
<button type="reset" className="btn btn-outline" onClick={() => setFormData({ url: '', method: 'POST', headers: '', body: '', executeDate: '', executeTime: '' })}>
|
||||||
|
<i className="fas fa-eraser"></i> Limpiar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestForm;
|
||||||
100
src/components/Requests/RequestTable.js
Normal file
100
src/components/Requests/RequestTable.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import '../../styles/components.css';
|
||||||
|
|
||||||
|
const RequestTable = () => {
|
||||||
|
const requests = [
|
||||||
|
{
|
||||||
|
id: '#00125',
|
||||||
|
url: 'https://api.ejemplo.com/users',
|
||||||
|
method: 'POST',
|
||||||
|
date: '15/10/2023 14:30',
|
||||||
|
status: 'pending',
|
||||||
|
statusText: 'Pendiente'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '#00124',
|
||||||
|
url: 'https://api.ejemplo.com/products/update',
|
||||||
|
method: 'PUT',
|
||||||
|
date: '15/10/2023 12:15',
|
||||||
|
status: 'completed',
|
||||||
|
statusText: 'Completada'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '#00123',
|
||||||
|
url: 'https://api.ejemplo.com/orders',
|
||||||
|
method: 'GET',
|
||||||
|
date: '15/10/2023 10:45',
|
||||||
|
status: 'failed',
|
||||||
|
statusText: 'Fallida'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const getStatusIcon = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'pending': return 'fas fa-clock';
|
||||||
|
case 'completed': return 'fas fa-check';
|
||||||
|
case 'failed': return 'fas fa-exclamation-triangle';
|
||||||
|
default: return 'fas fa-circle';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="table-container">
|
||||||
|
<div className="table-header">
|
||||||
|
<h3>Solicitudes Recientes</h3>
|
||||||
|
<div className="table-actions">
|
||||||
|
<button className="btn btn-outline btn-sm">
|
||||||
|
<i className="fas fa-filter"></i> Filtrar
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-outline btn-sm">
|
||||||
|
<i className="fas fa-download"></i> Exportar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>Método</th>
|
||||||
|
<th>Fecha de Ejecución</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{requests.map((request, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{request.id}</td>
|
||||||
|
<td>{request.url}</td>
|
||||||
|
<td>{request.method}</td>
|
||||||
|
<td>{request.date}</td>
|
||||||
|
<td>
|
||||||
|
<span className={`status ${request.status}`}>
|
||||||
|
<i className={getStatusIcon(request.status)}></i> {request.statusText}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="actions">
|
||||||
|
<button className="view" title="Ver detalles">
|
||||||
|
<i className="fas fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="execute"
|
||||||
|
title={request.status === 'completed' ? 'Ejecutar ahora' : 'Reintentar'}
|
||||||
|
disabled={request.status === 'completed'}
|
||||||
|
>
|
||||||
|
<i className={request.status === 'failed' ? 'fas fa-redo' : 'fas fa-play'}></i>
|
||||||
|
</button>
|
||||||
|
<button className="cancel" title="Cancelar">
|
||||||
|
<i className="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestTable;
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
||||||
13
src/index.js
13
src/index.js
@@ -1,17 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import './index.css';
|
import './styles/globals.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const container = document.getElementById('root');
|
||||||
|
const root = createRoot(container);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
||||||
reportWebVitals();
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,13 +0,0 @@
|
|||||||
const reportWebVitals = onPerfEntry => {
|
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
|
||||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
||||||
getCLS(onPerfEntry);
|
|
||||||
getFID(onPerfEntry);
|
|
||||||
getFCP(onPerfEntry);
|
|
||||||
getLCP(onPerfEntry);
|
|
||||||
getTTFB(onPerfEntry);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default reportWebVitals;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import '@testing-library/jest-dom';
|
|
||||||
674
src/styles/components.css
Normal file
674
src/styles/components.css
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
/* Sidebar Styles */
|
||||||
|
.sidebar {
|
||||||
|
width: var(--sidebar-width);
|
||||||
|
background: linear-gradient(180deg, var(--secondary) 0%, #1e293b 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 0;
|
||||||
|
transition: var(--transition);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
z-index: 100;
|
||||||
|
position: fixed;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
padding: 24px 20px;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(99, 102, 241, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text span {
|
||||||
|
color: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-container {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li {
|
||||||
|
margin: 4px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li.active {
|
||||||
|
background: rgba(99, 102, 241, 0.15);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li:hover:not(.active) {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links i {
|
||||||
|
margin-right: 12px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links .badge {
|
||||||
|
margin-left: auto;
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-footer {
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.1);
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-details {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-role {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 30px;
|
||||||
|
margin-left: var(--sidebar-width);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
color: var(--secondary);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title i {
|
||||||
|
margin-right: 12px;
|
||||||
|
color: var(--primary);
|
||||||
|
background: rgba(99, 102, 241, 0.1);
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
position: relative;
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box i {
|
||||||
|
color: var(--gray);
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Styles */
|
||||||
|
.btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: var(--transition);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn i {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 12px rgba(99, 102, 241, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: linear-gradient(135deg, var(--success) 0%, #34d399 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background: linear-gradient(135deg, var(--warning) 0%, #fbbf24 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: linear-gradient(135deg, var(--danger) 0%, #f87171 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid var(--primary);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Styles */
|
||||||
|
.dashboard-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
padding: 24px;
|
||||||
|
transition: var(--transition);
|
||||||
|
border: 1px solid rgba(0,0,0,0.03);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.primary::before {
|
||||||
|
background: linear-gradient(90deg, var(--primary) 0%, var(--primary-light) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.success::before {
|
||||||
|
background: linear-gradient(90deg, var(--success) 0%, #34d399 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.warning::before {
|
||||||
|
background: linear-gradient(90deg, var(--warning) 0%, #fbbf24 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.danger::before {
|
||||||
|
background: linear-gradient(90deg, var(--danger) 0%, #f87171 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon.primary {
|
||||||
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon.success {
|
||||||
|
background: linear-gradient(135deg, var(--success) 0%, #34d399 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon.warning {
|
||||||
|
background: linear-gradient(135deg, var(--warning) 0%, #fbbf24 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon.danger {
|
||||||
|
background: linear-gradient(135deg, var(--danger) 0%, #f87171 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-value {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: var(--gray);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-trend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-trend.up {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-trend.down {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Title */
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: var(--secondary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title i {
|
||||||
|
margin-right: 10px;
|
||||||
|
color: var(--primary);
|
||||||
|
background: rgba(99, 102, 241, 0.1);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styles */
|
||||||
|
.table-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 16px 24px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--secondary);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status i {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.pending {
|
||||||
|
background-color: rgba(245, 158, 11, 0.1);
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.completed {
|
||||||
|
background-color: rgba(16, 185, 129, 0.1);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.failed {
|
||||||
|
background-color: rgba(239, 68, 68, 0.1);
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions button {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions .view {
|
||||||
|
background: rgba(99, 102, 241, 0.1);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions .execute {
|
||||||
|
background: rgba(16, 185, 129, 0.1);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions .cancel {
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions button:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Styles */
|
||||||
|
.form-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
padding: 30px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header h3 {
|
||||||
|
color: var(--secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header h3 i {
|
||||||
|
margin-right: 10px;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: var(--transition);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus, select:focus, textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row .form-group {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Request Detail */
|
||||||
|
.detail-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
padding: 30px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header h3 {
|
||||||
|
color: var(--secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header h3 i {
|
||||||
|
margin-right: 10px;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section h4 {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
color: var(--secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section h4 i {
|
||||||
|
margin-right: 8px;
|
||||||
|
color: var(--primary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
width: 180px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
padding: 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
274
src/styles/globals.css
Normal file
274
src/styles/globals.css
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
:root {
|
||||||
|
--primary: #6366f1;
|
||||||
|
--primary-light: #818cf8;
|
||||||
|
--primary-dark: #4f46e5;
|
||||||
|
--secondary: #1e293b;
|
||||||
|
--success: #10b981;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--danger: #ef4444;
|
||||||
|
--info: #3b82f6;
|
||||||
|
--light: #f8fafc;
|
||||||
|
--dark: #0f172a;
|
||||||
|
--gray: #64748b;
|
||||||
|
--gray-light: #cbd5e1;
|
||||||
|
--sidebar-width: 260px;
|
||||||
|
--header-height: 70px;
|
||||||
|
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
--radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #f0f4f8 0%, #e2e8f0 100%);
|
||||||
|
color: var(--dark);
|
||||||
|
line-height: 1.6;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.sidebar {
|
||||||
|
width: 80px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text, .nav-links span:not(.badge), .user-details {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links i {
|
||||||
|
margin-right: 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links .badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
margin-left: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li:hover::after {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
position: absolute;
|
||||||
|
left: 100%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: var(--dark);
|
||||||
|
color: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 1000;
|
||||||
|
margin-left: 10px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-footer {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.dashboard-cards {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dashboard-cards {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-actions {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.main-content {
|
||||||
|
padding: 20px 15px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.active {
|
||||||
|
width: 280px;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle menu para móviles */
|
||||||
|
.menu-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animaciones */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.card, .table-container, .form-container, .detail-container {
|
||||||
|
animation: fadeIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar personalizado */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--primary-light);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user