|
6 | 6 | BatteryCharging, |
7 | 7 | Bell, |
8 | 8 | Check, |
| 9 | + ChevronDown, |
9 | 10 | FileText, |
10 | 11 | Github, |
11 | 12 | Heart, |
@@ -97,13 +98,47 @@ function Section({ |
97 | 98 | ); |
98 | 99 | } |
99 | 100 |
|
100 | | -/** Cabecera de zona (no es tarjeta): delimita el territorio de administración. */ |
101 | | -function ZoneHeader({ title }: { title: string }) { |
| 101 | +/** Zona de administración (patrón AdminBar del skill): tarjeta horizontal con |
| 102 | + * borde de acento, título "Administración" y secciones desplegables a la derecha. */ |
| 103 | +function AdminZone() { |
| 104 | + const { t } = useTranslation(); |
| 105 | + const [open, setOpen] = useState(false); |
102 | 106 | return ( |
103 | | - <div className="flex items-center gap-2 px-1 pt-1"> |
104 | | - <ShieldCheck size={15} strokeWidth={1.75} className="text-faint" /> |
105 | | - <h2 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-faint">{title}</h2> |
106 | | - </div> |
| 107 | + <motion.section |
| 108 | + initial={{ opacity: 0, y: 24 }} |
| 109 | + whileInView={{ opacity: 1, y: 0 }} |
| 110 | + viewport={{ once: true, amount: 0.15 }} |
| 111 | + transition={{ duration: 0.5, ease: easeOutQuart }} |
| 112 | + className="helios-card scroll-mt-20 rounded-l-lg border-l-4 border-l-amber-500 bg-amber-500/[0.03] p-5 shadow-card dark:shadow-card-dark" |
| 113 | + > |
| 114 | + <div className="flex flex-wrap items-center gap-3"> |
| 115 | + <div className="flex h-9 items-center gap-2"> |
| 116 | + <ShieldCheck size={18} strokeWidth={1.75} className="text-amber-500" /> |
| 117 | + <h2 className="font-display text-[15px] font-semibold text-app">{t('ajustes.sections.administracion')}</h2> |
| 118 | + </div> |
| 119 | + <div className="hidden h-6 w-px bg-app sm:block" /> |
| 120 | + <button |
| 121 | + type="button" |
| 122 | + aria-expanded={open} |
| 123 | + onClick={() => setOpen((v) => !v)} |
| 124 | + className={cn( |
| 125 | + 'ml-auto inline-flex h-9 items-center gap-2 rounded-lg border px-3 text-[13px] font-medium transition-colors', |
| 126 | + open |
| 127 | + ? 'border-amber-500 bg-amber-500/10 text-amber-500' |
| 128 | + : 'border-app bg-surface text-muted hover:bg-surface-2 hover:text-app', |
| 129 | + )} |
| 130 | + > |
| 131 | + <UserPlus size={16} strokeWidth={1.75} /> |
| 132 | + <span className="hidden sm:inline">{t('ajustes.sections.usuarios')}</span> |
| 133 | + <ChevronDown size={14} className={cn('transition-transform', open && 'rotate-180')} /> |
| 134 | + </button> |
| 135 | + </div> |
| 136 | + {open && ( |
| 137 | + <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-4 border-t border-app pt-4"> |
| 138 | + <UsersSection /> |
| 139 | + </motion.div> |
| 140 | + )} |
| 141 | + </motion.section> |
107 | 142 | ); |
108 | 143 | } |
109 | 144 |
|
@@ -623,7 +658,7 @@ function PricesSection() { |
623 | 658 |
|
624 | 659 | return ( |
625 | 660 | <div className="flex flex-col gap-4"> |
626 | | - <div className="grid max-w-md gap-4"> |
| 661 | + <div className="grid gap-4 sm:grid-cols-3"> |
627 | 662 | {fields.map((f, i) => ( |
628 | 663 | <motion.div |
629 | 664 | key={f.id} |
@@ -1543,49 +1578,37 @@ export default function Ajustes() { |
1543 | 1578 |
|
1544 | 1579 | <div className="mx-auto flex w-full max-w-[1100px] flex-col gap-5"> |
1545 | 1580 |
|
1546 | | - {/* 1. Tu instalación (span-7) + Apariencia (span-5) en horizontal */} |
| 1581 | + {/* 1. Tu instalación (izquierda) + columna derecha: Apariencia y debajo Conexión |
| 1582 | + (aprovecha la diferencia de altura entre instalación y apariencia) */} |
1547 | 1583 | <div className="grid items-start gap-5 lg:grid-cols-12"> |
1548 | 1584 | <div className="lg:col-span-7"> |
1549 | 1585 | <Section id="instalacion" title={t('ajustes.sections.instalacion')}> |
1550 | 1586 | <InstallationSection /> |
1551 | 1587 | </Section> |
1552 | 1588 | </div> |
1553 | | - <div className="lg:col-span-5"> |
| 1589 | + <div className="flex flex-col gap-5 lg:col-span-5"> |
1554 | 1590 | <Section id="apariencia" title={t('ajustes.sections.apariencia')}> |
1555 | 1591 | <ThemeSection /> |
1556 | 1592 | </Section> |
| 1593 | + <Section |
| 1594 | + id="conexion" |
| 1595 | + title={t('ajustes.sections.conexionDatos')} |
| 1596 | + badge={<HealthBadge ok={connectionStatus === 'connected' && dataOk} />} |
| 1597 | + > |
| 1598 | + <ConnectionSection /> |
| 1599 | + </Section> |
1557 | 1600 | </div> |
1558 | 1601 | </div> |
1559 | 1602 |
|
1560 | | - {/* 2. Conexión y datos (span-12, horizontal) */} |
1561 | | - <Section |
1562 | | - id="conexion" |
1563 | | - title={t('ajustes.sections.conexionDatos')} |
1564 | | - badge={<HealthBadge ok={connectionStatus === 'connected' && dataOk} />} |
1565 | | - > |
1566 | | - <ConnectionSection /> |
1567 | | - </Section> |
1568 | | - |
1569 | | - {/* 3. Mi perfil (span-12) */} |
| 1603 | + {/* 2. Mi perfil (span-12) */} |
1570 | 1604 | <Section id="perfil" title={t('ajustes.sections.perfil')}> |
1571 | 1605 | <ProfileSection /> |
1572 | 1606 | </Section> |
1573 | 1607 |
|
1574 | | - {/* 4. Zona administración: Usuarios (admin, span-12) */} |
1575 | | - {userRole === 'admin' && ( |
1576 | | - <> |
1577 | | - <ZoneHeader title={t('ajustes.sections.administracion')} /> |
1578 | | - <Section |
1579 | | - id="usuarios" |
1580 | | - title={t('ajustes.sections.usuarios')} |
1581 | | - className="border-amber-500/40 bg-amber-500/[0.03]" |
1582 | | - > |
1583 | | - <UsersSection /> |
1584 | | - </Section> |
1585 | | - </> |
1586 | | - )} |
| 1608 | + {/* 3. Zona administración (solo admin): barra AdminBar con Usuarios desplegable */} |
| 1609 | + {userRole === 'admin' && <AdminZone />} |
1587 | 1610 |
|
1588 | | - {/* 5. Precios (span-12, abajo, fila vertical) */} |
| 1611 | + {/* 4. Precios (abajo, en una única fila) */} |
1589 | 1612 | <Section id="precios" title={t('ajustes.sections.precios')}> |
1590 | 1613 | <PricesSection /> |
1591 | 1614 | </Section> |
|
0 commit comments