refactor: split admin.css monolith, standardize CSS architecture

- Split admin.css (3228 lines) into 12 focused files: variables, base,
  forms, buttons, layout, components, tables, skeleton, datepicker,
  filemanager, pagination, responsive
- Extracted shared styles from offers.css and dashboard.css into
  components.css and forms.css (offers-* → admin-* prefix)
- Standardized naming: dash-kpi-* → admin-kpi-*, session-* → dash-session-*,
  rich-editor → admin-rich-editor
- Deleted duplicate offers-tabs (using admin-tabs everywhere)
- Deduplicated DatePicker and FileManager CSS (~360 lines removed)
- Added 16 utility classes to base.css (font sizes, widths, gaps, margins)
- Deleted empty admin.css

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-27 13:00:45 +01:00
parent cde560a2c3
commit e0ea997c24
29 changed files with 3542 additions and 3856 deletions

View File

@@ -515,7 +515,7 @@ export default function CompanySettings({
</motion.div>
)}
<div className="offers-settings-grid">
<div className="admin-settings-grid">
{/* Company Info */}
<motion.div
className="admin-card"
@@ -1080,7 +1080,7 @@ export default function CompanySettings({
</div>
<div className="admin-card-body">
<div className="admin-form-row">
<div className="offers-logo-section">
<div className="admin-logo-section">
<label
className="admin-form-label"
style={{ display: "block", marginBottom: 4 }}
@@ -1088,7 +1088,7 @@ export default function CompanySettings({
Logo (světlý režim)
</label>
{logoUrl && (
<div className="offers-logo-preview">
<div className="admin-logo-preview">
<img src={logoUrl} alt="Logo (světlý režim)" />
</div>
)}
@@ -1130,7 +1130,7 @@ export default function CompanySettings({
PNG, JPEG, GIF nebo WebP, max 5 MB
</small>
</div>
<div className="offers-logo-section">
<div className="admin-logo-section">
<label
className="admin-form-label"
style={{ display: "block", marginBottom: 4 }}
@@ -1138,7 +1138,7 @@ export default function CompanySettings({
Logo (tmavý režim)
</label>
{logoUrlDark && (
<div className="offers-logo-preview">
<div className="admin-logo-preview">
<img src={logoUrlDark} alt="Logo (tmavý režim)" />
</div>
)}

View File

@@ -338,7 +338,7 @@ export default function Dashboard() {
{/* Skeleton loading */}
{dashLoading && (
<div className="admin-skeleton" style={{ padding: 0, gap: "1.25rem" }}>
<div className="dash-kpi-grid dash-kpi-4">
<div className="admin-kpi-grid admin-kpi-4">
{[0, 1, 2, 3].map((i) => (
<div
key={i}

View File

@@ -1220,14 +1220,14 @@ export default function InvoiceDetail() {
<form onSubmit={handleCreateSubmit}>
{/* Basic info */}
<motion.div
className="offers-editor-section"
className="admin-editor-section"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.06 }}
>
<h3 className="admin-card-title">Základní údaje</h3>
<div className="admin-form">
<div className="offers-form-row-3">
<div className="admin-form-row admin-form-row-3">
<FormField label="Číslo faktury">
<input
type="text"
@@ -1242,7 +1242,7 @@ export default function InvoiceDetail() {
required
>
{form.customer_id ? (
<div className="offers-customer-selected">
<div className="admin-customer-selected">
<span>{form.customer_name}</span>
<button
type="button"
@@ -1271,7 +1271,7 @@ export default function InvoiceDetail() {
</div>
) : (
<div
className="offers-customer-select"
className="admin-customer-select"
onClick={(e) => e.stopPropagation()}
>
<input
@@ -1287,16 +1287,16 @@ export default function InvoiceDetail() {
autoComplete="off"
/>
{showCustomerDropdown && (
<div className="offers-customer-dropdown">
<div className="admin-customer-dropdown">
{filteredCustomers.length === 0 ? (
<div className="offers-customer-dropdown-empty">
<div className="admin-customer-dropdown-empty">
Žádní zákazníci
</div>
) : (
filteredCustomers.slice(0, 10).map((c) => (
<div
key={c.id}
className="offers-customer-dropdown-item"
className="admin-customer-dropdown-item"
onMouseDown={() => selectCustomer(c)}
>
<div>{c.name}</div>
@@ -1392,7 +1392,7 @@ export default function InvoiceDetail() {
</FormField>
</div>
<div className="offers-form-row-3">
<div className="admin-form-row admin-form-row-3">
<FormField label="Forma úhrady">
<select
value={form.payment_method}
@@ -1580,8 +1580,8 @@ export default function InvoiceDetail() {
</DndContext>
{/* Totals */}
<div className="offers-totals-summary">
<div className="offers-totals-row">
<div className="admin-totals-summary">
<div className="admin-totals-row">
<span>Mezisoučet:</span>
<span>
{formatCurrency(createTotals.subtotal, form.currency)}
@@ -1590,13 +1590,13 @@ export default function InvoiceDetail() {
{form.apply_vat &&
Object.entries(createTotals.vatByRate).map(
([rate, amount]) => (
<div key={rate} className="offers-totals-row">
<div key={rate} className="admin-totals-row">
<span>DPH {rate}%:</span>
<span>{formatCurrency(amount, form.currency)}</span>
</div>
),
)}
<div className="offers-totals-row offers-totals-total">
<div className="admin-totals-row admin-totals-total">
<span>Celkem k úhradě:</span>
<span>
{formatCurrency(createTotals.total, form.currency)}
@@ -1608,7 +1608,7 @@ export default function InvoiceDetail() {
{/* Notes */}
<motion.div
className="offers-editor-section"
className="admin-editor-section"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.15 }}
@@ -1738,14 +1738,14 @@ export default function InvoiceDetail() {
{/* Info */}
<motion.div
className="offers-editor-section"
className="admin-editor-section"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.06 }}
>
<h3 className="admin-card-title">Informace</h3>
<div className="admin-form">
<div className="offers-form-row-3 mb-2">
<div className="admin-form-row admin-form-row-3 mb-2">
<FormField label="Zákazník">
<div className="fw-500">{invoice.customer_name || "\u2014"}</div>
{invoice.customer && (
@@ -1778,7 +1778,7 @@ export default function InvoiceDetail() {
<div>{invoice.currency}</div>
</FormField>
</div>
<div className="offers-form-row-3 mb-2">
<div className="admin-form-row admin-form-row-3 mb-2">
<FormField label="Datum vystavení">
<div>{formatDate(invoice.issue_date)}</div>
</FormField>
@@ -1795,7 +1795,7 @@ export default function InvoiceDetail() {
<div>{formatDate(invoice.tax_date)}</div>
</FormField>
</div>
<div className="offers-form-row-3">
<div className="admin-form-row admin-form-row-3">
<FormField label="Forma úhrady">
<div>{invoice.payment_method}</div>
</FormField>
@@ -2007,8 +2007,8 @@ export default function InvoiceDetail() {
</>
)}
<div className="offers-totals-summary">
<div className="offers-totals-row">
<div className="admin-totals-summary">
<div className="admin-totals-row">
<span>Mezisoučet:</span>
<span>
{formatCurrency(editTotals.subtotal, invoice.currency)}
@@ -2016,12 +2016,12 @@ export default function InvoiceDetail() {
</div>
{Number(invoice.apply_vat) > 0 &&
Object.entries(editTotals.vatByRate).map(([rate, amount]) => (
<div key={rate} className="offers-totals-row">
<div key={rate} className="admin-totals-row">
<span>DPH {rate}%:</span>
<span>{formatCurrency(amount, invoice.currency)}</span>
</div>
))}
<div className="offers-totals-row offers-totals-total">
<div className="admin-totals-row admin-totals-total">
<span>Celkem k úhradě:</span>
<span>{formatCurrency(editTotals.total, invoice.currency)}</span>
</div>
@@ -2031,7 +2031,7 @@ export default function InvoiceDetail() {
{/* Notes */}
<motion.div
className="offers-editor-section"
className="admin-editor-section"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.15 }}

View File

@@ -334,7 +334,7 @@ export default function Invoices() {
style={{ width: "140px", borderRadius: "8px" }}
/>
</div>
<div className="dash-kpi-grid dash-kpi-4">
<div className="admin-kpi-grid admin-kpi-4">
{[0, 1, 2, 3].map((i) => (
<div key={i} className="admin-stat-card">
<div
@@ -497,15 +497,15 @@ export default function Invoices() {
</button>
</div>
<div className="offers-tabs mb-4" style={{ justifyContent: "center" }}>
<div className="admin-tabs mb-4" style={{ justifyContent: "center" }}>
<button
className={`offers-tab ${activeTab === "issued" ? "active" : ""}`}
className={`admin-tab ${activeTab === "issued" ? "active" : ""}`}
onClick={() => setActiveTab("issued")}
>
Vydané
</button>
<button
className={`offers-tab ${activeTab === "received" ? "active" : ""}`}
className={`admin-tab ${activeTab === "received" ? "active" : ""}`}
onClick={() => setActiveTab("received")}
>
Přijaté
@@ -522,7 +522,7 @@ export default function Invoices() {
<Suspense
fallback={
<div
className="dash-kpi-grid dash-kpi-4"
className="admin-kpi-grid admin-kpi-4"
style={{ marginBottom: "1.5rem" }}
>
{[0, 1, 2, 3].map((i) => (
@@ -569,7 +569,7 @@ export default function Invoices() {
>
{!hasLoadedOnce.current && statsLoading ? (
<div
className="dash-kpi-grid dash-kpi-4"
className="admin-kpi-grid admin-kpi-4"
style={{ marginBottom: "1.5rem" }}
>
{[0, 1, 2, 3].map((i) => (
@@ -607,7 +607,7 @@ export default function Invoices() {
>
<motion.div
key={slideKey}
className="dash-kpi-grid dash-kpi-4"
className="admin-kpi-grid admin-kpi-4"
custom={slideDirection.current}
variants={{
enter: (dir: number) => ({
@@ -740,11 +740,11 @@ export default function Invoices() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.12 }}
>
<div className="offers-tabs mb-6">
<div className="admin-tabs mb-6">
{STATUS_FILTERS.map((f) => (
<button
key={f.value}
className={`offers-tab ${statusFilter === f.value ? "active" : ""}`}
className={`admin-tab ${statusFilter === f.value ? "active" : ""}`}
onClick={() => {
setStatusFilter(f.value);
setPage(1);

View File

@@ -313,9 +313,9 @@ export default function LeaveApproval() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.06 }}
>
<div className="offers-tabs mb-6">
<div className="admin-tabs mb-6">
<button
className={`offers-tab ${activeTab === "pending" ? "active" : ""}`}
className={`admin-tab ${activeTab === "pending" ? "active" : ""}`}
onClick={() => setActiveTab("pending")}
>
Ke schválení
@@ -333,7 +333,7 @@ export default function LeaveApproval() {
)}
</button>
<button
className={`offers-tab ${activeTab === "processed" ? "active" : ""}`}
className={`admin-tab ${activeTab === "processed" ? "active" : ""}`}
onClick={() => setActiveTab("processed")}
>
Vyřízené

View File

@@ -1011,14 +1011,14 @@ export default function OfferDetail() {
{/* Quotation Form */}
<motion.div
className={`offers-editor-section${isInvalidated || isLockedByOther ? " offers-readonly" : ""}`}
className={`admin-editor-section${isInvalidated || isLockedByOther ? " offers-readonly" : ""}`}
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.06 }}
>
<h3 className="admin-card-title">Základní údaje</h3>
<div className="admin-form">
<div className="offers-form-row-3">
<div className="admin-form-row admin-form-row-3">
<FormField label="Číslo nabídky">
<input
type="text"
@@ -1044,7 +1044,7 @@ export default function OfferDetail() {
</FormField>
<FormField label="Zákazník" error={errors.customer_id}>
{form.customer_id ? (
<div className="offers-customer-selected">
<div className="admin-customer-selected">
<span>{form.customer_name}</span>
{!isInvalidated && !isLockedByOther && (
<button
@@ -1069,7 +1069,7 @@ export default function OfferDetail() {
</div>
) : (
<div
className="offers-customer-select"
className="admin-customer-select"
onClick={(e) => e.stopPropagation()}
>
<input
@@ -1085,16 +1085,16 @@ export default function OfferDetail() {
readOnly={isInvalidated || isLockedByOther}
/>
{showCustomerDropdown && !isInvalidated && (
<div className="offers-customer-dropdown">
<div className="admin-customer-dropdown">
{filteredCustomers.length === 0 ? (
<div className="offers-customer-dropdown-empty">
<div className="admin-customer-dropdown-empty">
Žádní zákazníci
</div>
) : (
filteredCustomers.slice(0, 20).map((c) => (
<div
key={c.id}
className="offers-customer-dropdown-item"
className="admin-customer-dropdown-item"
onMouseDown={() => selectCustomer(c)}
>
<div>{c.name}</div>
@@ -1189,7 +1189,7 @@ export default function OfferDetail() {
</FormField>
</div>
<div className="offers-form-row-3">
<div className="admin-form-row admin-form-row-3">
<FormField label="Sazba DPH (%)">
<div className="flex-row-gap">
<select
@@ -1332,18 +1332,18 @@ export default function OfferDetail() {
</div>
{/* Totals */}
<div className="offers-totals-summary">
<div className="offers-totals-row">
<div className="admin-totals-summary">
<div className="admin-totals-row">
<span>Mezisoučet:</span>
<span>{formatCurrency(subtotal, form.currency)}</span>
</div>
{form.apply_vat && (
<div className="offers-totals-row">
<div className="admin-totals-row">
<span>DPH ({form.vat_rate}%):</span>
<span>{formatCurrency(vatAmount, form.currency)}</span>
</div>
)}
<div className="offers-totals-row offers-totals-total">
<div className="admin-totals-row admin-totals-total">
<span>Celkem:</span>
<span>{formatCurrency(total, form.currency)}</span>
</div>

View File

@@ -73,15 +73,15 @@ export default function OffersTemplates() {
</div>
</motion.div>
<div className="offers-tabs">
<div className="admin-tabs">
<button
className={`offers-tab ${activeTab === "items" ? "active" : ""}`}
className={`admin-tab ${activeTab === "items" ? "active" : ""}`}
onClick={() => setActiveTab("items")}
>
Šablony položek
</button>
<button
className={`offers-tab ${activeTab === "scopes" ? "active" : ""}`}
className={`admin-tab ${activeTab === "scopes" ? "active" : ""}`}
onClick={() => setActiveTab("scopes")}
>
Šablony rozsahu
@@ -826,22 +826,19 @@ function ScopeTemplatesTab() {
<div className="admin-form-group">
<label className="admin-form-label mb-2">Sekce</label>
<div className="offers-scope-list">
<div className="admin-scope-list">
{form.sections.map((section, index) => (
<div
key={section._key}
className="offers-scope-section"
>
<div className="offers-scope-section-header">
<span className="offers-scope-number">
<div key={section._key} className="admin-scope-section">
<div className="admin-scope-section-header">
<span className="admin-scope-number">
{index + 1}.
</span>
<span className="offers-scope-title">
<span className="admin-scope-title">
{section.title ||
section.title_cz ||
`Sekce ${index + 1}`}
</span>
<div className="offers-scope-actions">
<div className="admin-scope-actions">
<button
type="button"
onClick={() => moveSection(index, -1)}

View File

@@ -747,18 +747,18 @@ export default function OrderDetail() {
)}
{/* Totals */}
<div className="offers-totals-summary">
<div className="offers-totals-row">
<div className="admin-totals-summary">
<div className="admin-totals-row">
<span>Mezisoučet:</span>
<span>{formatCurrency(totals.subtotal, order.currency)}</span>
</div>
{Number(order.apply_vat) > 0 && (
<div className="offers-totals-row">
<div className="admin-totals-row">
<span>DPH ({order.vat_rate}%):</span>
<span>{formatCurrency(totals.vatAmount, order.currency)}</span>
</div>
)}
<div className="offers-totals-row offers-totals-total">
<div className="admin-totals-row admin-totals-total">
<span>Celkem k úhradě:</span>
<span>{formatCurrency(totals.total, order.currency)}</span>
</div>
@@ -788,16 +788,16 @@ export default function OrderDetail() {
{order.scope_description}
</div>
)}
<div className="offers-scope-list">
<div className="admin-scope-list">
{order.sections.map((section, index) => (
<div
key={section.id || index}
className="offers-scope-section"
className="admin-scope-section"
style={{ cursor: "default" }}
>
<div className="offers-scope-section-header">
<span className="offers-scope-number">{index + 1}.</span>
<span className="offers-scope-title">
<div className="admin-scope-section-header">
<span className="admin-scope-number">{index + 1}.</span>
<span className="admin-scope-title">
{(order.language === "CZ"
? section.title_cz || section.title
: section.title || section.title_cz) ||
@@ -806,7 +806,7 @@ export default function OrderDetail() {
</div>
{section.content && (
<div
className="offers-scope-content rich-text-view"
className="admin-scope-content admin-rich-text-view"
style={{ padding: "1rem" }}
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(section.content),

View File

@@ -284,7 +284,7 @@ export default function ProjectCreate() {
<div className="admin-form-row">
<FormField label="Zákazník" error={errors.customer_id} required>
{form.customer_id ? (
<div className="offers-customer-selected">
<div className="admin-customer-selected">
<span>{form.customer_name}</span>
<button
type="button"
@@ -308,7 +308,7 @@ export default function ProjectCreate() {
</div>
) : (
<div
className="offers-customer-select"
className="admin-customer-select"
onClick={(e) => e.stopPropagation()}
>
<input
@@ -323,16 +323,16 @@ export default function ProjectCreate() {
placeholder="Hledat zákazníka..."
/>
{showCustomerDropdown && (
<div className="offers-customer-dropdown">
<div className="admin-customer-dropdown">
{filteredCustomers.length === 0 ? (
<div className="offers-customer-dropdown-empty">
<div className="admin-customer-dropdown-empty">
Žádní zákazníci
</div>
) : (
filteredCustomers.slice(0, 20).map((c) => (
<div
key={c.id}
className="offers-customer-dropdown-item"
className="admin-customer-dropdown-item"
onMouseDown={() => selectCustomer(c)}
>
<div>{c.name}</div>

View File

@@ -549,7 +549,7 @@ export default function ReceivedInvoices({
const renderKpi = () => {
if (!hasLoadedOnce.current && statsLoading) {
return (
<div className="dash-kpi-grid dash-kpi-4 mb-6">
<div className="admin-kpi-grid admin-kpi-4 mb-6">
{[0, 1, 2, 3].map((i) => (
<div key={i} className="admin-stat-card">
<div
@@ -586,7 +586,7 @@ export default function ReceivedInvoices({
>
<motion.div
key={slideKey}
className="dash-kpi-grid dash-kpi-4"
className="admin-kpi-grid admin-kpi-4"
custom={slideDirection.current}
variants={{
enter: (dir: number) => ({