refactor: CSS utility tridy + slouceni badge souboru

- pridano 20 utility trid (flex-1, mb-2, text-right, fw-500, admin-spinner-sm, atd.)
- nahrazeno ~100 opakovanych inline stylu ve 39 JSX souborech
- slouceno leave.css, orders.css, projects.css do admin.css (status badges)
- bundle size: 228.91 -> 228.43 kB (-0.48 kB)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 11:27:15 +01:00
parent f7466f0667
commit 10fbb9ebc7
106 changed files with 431 additions and 475 deletions

View File

@@ -1 +1 @@
{"window_start":1773395802,"count":2}
{"window_start":1773397556,"count":8}

View File

@@ -1 +1 @@
{"window_start":1773395237,"count":1}
{"window_start":1773396919,"count":1}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/AuditLog-BQhFrceK.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/assets/InvoiceCreate-CTHJkHkW.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/assets/InvoiceDetail-PAg_BB2y.js vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/assets/Invoices-Cn75FAfB.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/LeaveApproval-kG7MLpMl.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/LeaveRequests-Bd0og4aD.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/OfferDetail-Bh2jG-Bc.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/Offers-DSsqWWe8.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/OrderDetail-B5bb2z01.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/Orders-BmuD7hMO.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/ProjectCreate-DBrXwzbT.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/ProjectDetail-DCOqYzFf.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/Projects-DimOmyc4.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/Settings-2mPE0jr-.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/Trips-BImocn08.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

81
dist/assets/TripsAdmin-BsKU9QUl.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/TripsHistory-BaHvBurw.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/Users-Vkyk_PMH.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/Vehicles-Dbkg5bxP.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/index-D_wrslmx.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{j as x}from"./vendor-animation-0s3FMHwK.js";import{r as t}from"./vendor-react-BVs3cwbi.js";import{a as L,c as O}from"./index-DR4BORa4.js";function J({column:e,sort:r,order:n}){return r!==e?null:x.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",style:{marginLeft:4,verticalAlign:"middle"},children:x.jsx("path",{d:n==="ASC"?"M18 15l-6-6-6 6":"M6 9l6 6 6-6"})})}function V(e,r="DESC"){const[n,a]=t.useState(e),[o,c]=t.useState(r),i=t.useRef(!1),S=t.useCallback(u=>{i.current=!0,a(m=>m===u?(c(h=>h==="ASC"?"DESC":"ASC"),m):(c("DESC"),u))},[]),d=i.current?n:null;return{sort:n,order:o,handleSort:S,activeSort:d}}function I(e,r=300){const[n,a]=t.useState(e);return t.useEffect(()=>{const o=setTimeout(()=>a(e),r);return()=>clearTimeout(o)},[e,r]),n}const N="/api/admin";function _(e,{dataKey:r,search:n,sort:a,order:o,page:c,perPage:i,extraParams:S,errorMsg:d="Nepodařilo se načíst data"}={}){const u=L(),[m,h]=t.useState([]),[j,D]=t.useState(!0),[w,k]=t.useState(null),l=t.useRef(null),p=S?JSON.stringify(S):"",b=I(n,300),C=t.useCallback(async()=>{l.current&&l.current.abort();const g=new AbortController;l.current=g;try{const s=new URLSearchParams;if(b&&s.set("search",b),a&&s.set("sort",a),o&&s.set("order",o),c&&s.set("page",c),i&&s.set("per_page",i),p){const R=JSON.parse(p);Object.entries(R).forEach(([y,A])=>{A&&s.set(y,A)})}const E=await O(`${N}/${e}?${s}`,{signal:g.signal});if(E.status===401)return;const f=await E.json();f.success?(h(f.data[r]||[]),f.data.pagination&&k(f.data.pagination)):u.error(f.error||d)}catch(s){if(s.name==="AbortError")return;u.error("Chyba připojení")}finally{D(!1)}},[u,e,r,b,a,o,c,i,p,d]);return t.useEffect(()=>(C(),()=>{l.current&&l.current.abort()}),[C]),{items:m,setItems:h,loading:j,pagination:w,refetch:C}}export{J as S,_ as a,V as u};
import{j as x}from"./vendor-animation-0s3FMHwK.js";import{r as t}from"./vendor-react-BVs3cwbi.js";import{a as L,c as O}from"./index-CIpK9ruO.js";function J({column:e,sort:r,order:n}){return r!==e?null:x.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",style:{marginLeft:4,verticalAlign:"middle"},children:x.jsx("path",{d:n==="ASC"?"M18 15l-6-6-6 6":"M6 9l6 6 6-6"})})}function V(e,r="DESC"){const[n,a]=t.useState(e),[o,c]=t.useState(r),i=t.useRef(!1),S=t.useCallback(u=>{i.current=!0,a(m=>m===u?(c(h=>h==="ASC"?"DESC":"ASC"),m):(c("DESC"),u))},[]),d=i.current?n:null;return{sort:n,order:o,handleSort:S,activeSort:d}}function I(e,r=300){const[n,a]=t.useState(e);return t.useEffect(()=>{const o=setTimeout(()=>a(e),r);return()=>clearTimeout(o)},[e,r]),n}const N="/api/admin";function _(e,{dataKey:r,search:n,sort:a,order:o,page:c,perPage:i,extraParams:S,errorMsg:d="Nepodařilo se načíst data"}={}){const u=L(),[m,h]=t.useState([]),[j,D]=t.useState(!0),[w,k]=t.useState(null),l=t.useRef(null),p=S?JSON.stringify(S):"",b=I(n,300),C=t.useCallback(async()=>{l.current&&l.current.abort();const g=new AbortController;l.current=g;try{const s=new URLSearchParams;if(b&&s.set("search",b),a&&s.set("sort",a),o&&s.set("order",o),c&&s.set("page",c),i&&s.set("per_page",i),p){const R=JSON.parse(p);Object.entries(R).forEach(([y,A])=>{A&&s.set(y,A)})}const E=await O(`${N}/${e}?${s}`,{signal:g.signal});if(E.status===401)return;const f=await E.json();f.success?(h(f.data[r]||[]),f.data.pagination&&k(f.data.pagination)):u.error(f.error||d)}catch(s){if(s.name==="AbortError")return;u.error("Chyba připojení")}finally{D(!1)}},[u,e,r,b,a,o,c,i,p,d]);return t.useEffect(()=>(C(),()=>{l.current&&l.current.abort()}),[C]),{items:m,setItems:h,loading:j,pagination:w,refetch:C}}export{J as S,_ as a,V as u};

4
dist/index.html vendored
View File

@@ -29,11 +29,11 @@
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Urbanist:wght@400;500;600;700;800&display=swap"
rel="stylesheet" />
<script type="module" crossorigin src="/assets/index-DR4BORa4.js"></script>
<script type="module" crossorigin src="/assets/index-CIpK9ruO.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BVs3cwbi.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-animation-0s3FMHwK.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-utils-Dyr8OjFr.js">
<link rel="stylesheet" crossorigin href="/assets/index-Fs-Ow1Zz.css">
<link rel="stylesheet" crossorigin href="/assets/index-D_wrslmx.css">
</head>
<body style="background-color: var(--bg-primary, #12121a);">

View File

@@ -3,7 +3,7 @@
'name' => 'boha/website',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'd70620eb05aae34107bc1e3ae0cc59609bb7497d',
'reference' => 'f7466f06678d8ce75795387cce9ff72755ac9241',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -13,7 +13,7 @@
'boha/website' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'd70620eb05aae34107bc1e3ae0cc59609bb7497d',
'reference' => 'f7466f06678d8ce75795387cce9ff72755ac9241',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View File

@@ -11,9 +11,6 @@ import './admin.css'
import './login.css'
import './dashboard.css'
import './attendance.css'
import './leave.css'
import './orders.css'
import './projects.css'
import './settings.css'
import './offers.css'
import './invoices.css'

View File

@@ -291,7 +291,29 @@ img {
text-decoration: underline;
}
/* Login styles moved to login.css */
/* Layout utilities */
.flex-1 { flex: 1; }
.flex-row { display: flex; align-items: center; }
.flex-row-gap { display: flex; align-items: center; gap: var(--space-3); }
.flex-between { display: flex; align-items: center; justify-content: space-between; }
/* Spacing utilities */
.mb-2 { margin-bottom: var(--space-2); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-6 { margin-bottom: var(--space-6); }
.mt-2 { margin-top: var(--space-2); }
.mt-6 { margin-top: var(--space-6); }
.gap-2 { gap: var(--space-2); }
.gap-4 { gap: var(--space-4); }
.gap-5 { gap: var(--space-5); }
/* Typography utilities */
.fw-500 { font-weight: 500; }
.text-right { text-align: right; }
.text-center { text-align: center; }
/* Spinner variant */
.admin-spinner-sm { width: 16px; height: 16px; border-width: 2px; }
/* ============================================================================
Forms
@@ -434,11 +456,10 @@ img {
/* Checkbox */
.admin-form-checkbox {
display: inline-flex;
align-items: center;
align-items: flex-start;
gap: 0;
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.admin-form-checkbox input {
@@ -508,7 +529,7 @@ img {
align-items: center;
font-size: 13px;
color: var(--text-secondary);
line-height: 1;
line-height: 1.4;
}
/* Reorderable List */
@@ -1500,6 +1521,23 @@ img {
color: var(--danger);
}
/* Status Badges - Leave Requests */
.badge-pending { background: color-mix(in srgb, var(--warning) 15%, transparent); color: var(--warning); }
.badge-approved { background: color-mix(in srgb, var(--success) 15%, transparent); color: var(--success); }
.badge-rejected { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
.badge-cancelled { background: var(--muted-light); color: var(--muted); }
/* Status Badges - Orders */
.admin-badge-order-prijata { background: color-mix(in srgb, var(--info) 15%, transparent); color: var(--info); }
.admin-badge-order-realizace { background: color-mix(in srgb, var(--warning) 15%, transparent); color: var(--warning); }
.admin-badge-order-dokoncena { background: color-mix(in srgb, var(--success) 15%, transparent); color: var(--success); }
.admin-badge-order-stornovana { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
/* Status Badges - Projects */
.admin-badge-project-aktivni { background: color-mix(in srgb, var(--success) 15%, transparent); color: var(--success); }
.admin-badge-project-dokonceny { background: color-mix(in srgb, var(--info) 15%, transparent); color: var(--info); }
.admin-badge-project-zruseny { background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger); }
/* ============================================================================
Modals
============================================================================ */

View File

@@ -75,7 +75,7 @@ export default function AdminLayout() {
</svg>
</button>
<div style={{ flex: 1 }} />
<div className="flex-1" />
<button
onClick={toggleTheme}

View File

@@ -107,7 +107,7 @@ export default function ConfirmModal({
>
{loading ? (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
Zpracování...
</>
) : (

View File

@@ -24,7 +24,7 @@ export default class ErrorBoundary extends Component {
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
</div>
<p style={{ marginBottom: '0.5rem' }}>Něco se pokazilo při načítání stránky.</p>
<p className="mb-2">Něco se pokazilo při načítání stránky.</p>
{import.meta.env.DEV && this.state.error && (
<pre className="admin-error-stack">
{this.state.error.message}

View File

@@ -25,7 +25,7 @@ export default function OfferItemsSection({
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.2 }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<div className="flex-between mb-4">
<div>
<h3 className="admin-card-title" style={{ margin: 0 }}>Položky</h3>
{itemsError && <span className="admin-form-error">{itemsError}</span>}
@@ -49,7 +49,7 @@ export default function OfferItemsSection({
className="offers-template-menu-item"
onClick={() => addItemFromTemplate(t)}
>
<div style={{ fontWeight: 500 }}>{t.name}</div>
<div className="fw-500">{t.name}</div>
{t.default_price > 0 && (
<div style={{ fontSize: '0.75rem', color: 'var(--text-tertiary)' }}>
{Number(t.default_price).toFixed(2)}

View File

@@ -14,7 +14,7 @@ export default function OfferScopeSection({
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.3 }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<div className="flex-between mb-4">
<h3 className="admin-card-title" style={{ margin: 0 }}>Rozsah projektu</h3>
{!readOnly && (
<div style={{ display: 'flex', gap: '0.5rem', position: 'relative' }}>

View File

@@ -31,7 +31,7 @@ function ProjectTimeStatus({ form, projectLogs }) {
function ProjectLogRow({ log, index, projectList, onUpdate, onRemove }) {
return (
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', marginBottom: '0.5rem' }}>
<div className="flex-row gap-2 mb-2">
<select
value={log.project_id}
onChange={(e) => onUpdate(index, 'project_id', e.target.value)}

View File

@@ -44,7 +44,7 @@ export default function DashActivityFeed({ activities }) {
return (
<div className="admin-card dash-activity-card">
<div className="admin-card-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div className="admin-card-header flex-between">
<h2 className="admin-card-title">Audit log</h2>
<Link to="/audit-log" className="admin-btn admin-btn-primary admin-btn-sm">Detail &rarr;</Link>
</div>

View File

@@ -8,7 +8,7 @@ export default function DashAttendanceToday({ attendance }) {
return (
<div className="admin-card dash-attendance-card">
<div className="admin-card-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div className="admin-card-header flex-between">
<h2 className="admin-card-title">Docházka dnes</h2>
<Link to="/attendance/admin" className="admin-btn admin-btn-primary admin-btn-sm">Detail &rarr;</Link>
</div>

View File

@@ -81,7 +81,7 @@ export default function DashProfile({
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.25 }}
>
<div className="admin-card-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div className="admin-card-header flex-between">
<h2 className="admin-card-title">Váš účet</h2>
<button onClick={openEditModal} className="admin-btn admin-btn-secondary admin-btn-sm">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
@@ -113,8 +113,8 @@ export default function DashProfile({
{/* 2FA Section */}
<div style={{ borderTop: '1px solid var(--border-color)', marginTop: '1rem', paddingTop: '1rem' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-between">
<div className="flex-row-gap">
<div style={{
width: 36, height: 36, borderRadius: '50%',
display: 'flex', alignItems: 'center', justifyContent: 'center',
@@ -202,7 +202,7 @@ export default function DashProfile({
<div className="admin-modal-body">
{backupCodes ? (
<div>
<div className="admin-role-locked-notice" style={{ marginBottom: '1rem' }}>
<div className="admin-role-locked-notice mb-4">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
@@ -241,7 +241,7 @@ export default function DashProfile({
</div>
)}
{totpSecret && (
<div style={{ marginBottom: '1rem' }}>
<div className="mb-4">
<label className="admin-form-label" style={{ fontSize: '0.75rem' }}>Nebo zadejte klíč ručně:</label>
<div style={{ padding: '0.5rem 0.75rem', background: 'var(--bg-secondary)', borderRadius: '0.375rem', fontFamily: 'monospace', fontSize: '0.875rem', wordBreak: 'break-all', color: 'var(--text-primary)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '0.5rem' }}>
<span>{totpSecret}</span>

View File

@@ -125,7 +125,7 @@ export default function DashSessions() {
{[0, 1, 2].map(i => (
<div key={i} className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/2" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/3" style={{ height: '10px' }} />
</div>

View File

@@ -1,23 +0,0 @@
/* ============================================================================
Leave Request Status Badges
============================================================================ */
.badge-pending {
background: color-mix(in srgb, var(--warning) 15%, transparent);
color: var(--warning);
}
.badge-approved {
background: color-mix(in srgb, var(--success) 15%, transparent);
color: var(--success);
}
.badge-rejected {
background: color-mix(in srgb, var(--danger) 15%, transparent);
color: var(--danger);
}
.badge-cancelled {
background: var(--muted-light);
color: var(--muted);
}

View File

@@ -1,23 +0,0 @@
/* ============================================================================
Order Status Badges
============================================================================ */
.admin-badge-order-prijata {
background: color-mix(in srgb, var(--info) 15%, transparent);
color: var(--info);
}
.admin-badge-order-realizace {
background: color-mix(in srgb, var(--warning) 15%, transparent);
color: var(--warning);
}
.admin-badge-order-dokoncena {
background: color-mix(in srgb, var(--success) 15%, transparent);
color: var(--success);
}
.admin-badge-order-stornovana {
background: color-mix(in srgb, var(--danger) 15%, transparent);
color: var(--danger);
}

View File

@@ -509,7 +509,7 @@ export default function Attendance() {
className="admin-form-textarea"
rows={3}
/>
<div style={{ marginTop: '0.5rem' }}>
<div className="mt-2">
<button
onClick={handleSaveNotes}
className="admin-btn admin-btn-secondary admin-btn-sm"
@@ -543,7 +543,7 @@ export default function Attendance() {
{/* Completed Today */}
{completedToday.length > 0 && (
<div className="admin-card" style={{ marginTop: '1.5rem' }}>
<div className="admin-card mt-6">
<div className="admin-card-header">
<h2 className="admin-card-title">Dnešní dokončené směny</h2>
</div>

View File

@@ -147,8 +147,7 @@ export default function AttendanceAdmin() {
{/* Filters */}
<motion.div
className="admin-card"
style={{ marginBottom: '1.5rem' }}
className="admin-card mb-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
@@ -181,8 +180,7 @@ export default function AttendanceAdmin() {
{/* User Totals */}
{Object.keys(data.user_totals).length > 0 && (
<motion.div
className="admin-grid admin-grid-3"
style={{ marginBottom: '1.5rem' }}
className="admin-grid admin-grid-3 mb-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.15 }}
@@ -190,7 +188,7 @@ export default function AttendanceAdmin() {
{Object.entries(data.user_totals).map(([uid, userData]) => (
<div key={uid} className="admin-card">
<div className="admin-card-body">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}>
<div className="flex-row gap-2 mb-2">
<span style={{ fontWeight: 600 }}>{userData.name}</span>
<span className={`attendance-working-badge ${userData.working ? 'working' : 'finished'}`}>
{userData.working ? '✓' : '✗'}
@@ -213,7 +211,7 @@ export default function AttendanceAdmin() {
)}
</div>
{userData.fund !== null && (
<div style={{ marginTop: '0.5rem' }}>
<div className="mt-2">
<div className="text-secondary" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: '0.8rem' }}>
<span>Fond: {userData.worked_hours}h / {userData.fund}h</span>
{userData.overtime > 0 && (

View File

@@ -297,7 +297,7 @@ export default function AttendanceBalances() {
const yf = getYearFundTotals(userId)
return (
<tr key={userId}>
<td style={{ fontWeight: 500 }}>{balance.name}</td>
<td className="fw-500">{balance.name}</td>
<td className="admin-mono">{balance.vacation_total}</td>
<td className="admin-mono">{balance.vacation_used.toFixed(1)}</td>
<td className="admin-mono">
@@ -355,9 +355,9 @@ export default function AttendanceBalances() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.2 }}
style={{ marginTop: '1.5rem' }}
className="mt-6"
>
<h2 className="admin-page-title" style={{ fontSize: '1.25rem', marginBottom: '1rem' }}>
<h2 className="admin-page-title mb-4" style={{ fontSize: '1.25rem' }}>
Měsíční přehled fondu {year}
</h2>
<div className="admin-grid admin-grid-3">
@@ -437,7 +437,7 @@ export default function AttendanceBalances() {
)}
{fundLoading && (
<div style={{ marginTop: '1.5rem' }}>
<div className="mt-6">
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
{[0, 1, 2].map(i => (
<div key={i} className="admin-skeleton-row">
@@ -456,9 +456,9 @@ export default function AttendanceBalances() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.3 }}
style={{ marginTop: '1.5rem' }}
className="mt-6"
>
<h2 className="admin-page-title" style={{ fontSize: '1.25rem', marginBottom: '1rem' }}>
<h2 className="admin-page-title mb-4" style={{ fontSize: '1.25rem' }}>
Měsíční přehled projektů {year}
</h2>
<div className="admin-grid admin-grid-3">
@@ -555,7 +555,7 @@ export default function AttendanceBalances() {
)}
{projectLoading && (
<div style={{ marginTop: '1.5rem' }}>
<div className="mt-6">
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
{[0, 1, 2].map(i => (
<div key={i} className="admin-skeleton-row">

View File

@@ -235,8 +235,7 @@ export default function AttendanceHistory() {
{/* Filters */}
<motion.div
className="admin-card"
style={{ marginBottom: '1.5rem' }}
className="admin-card mb-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
@@ -256,8 +255,7 @@ export default function AttendanceHistory() {
{/* Monthly Fund Card */}
<motion.div
className="admin-card"
style={{ marginBottom: '1.5rem' }}
className="admin-card mb-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.15 }}
@@ -267,7 +265,7 @@ export default function AttendanceHistory() {
<div className="admin-skeleton" style={{ gap: '0.5rem' }}>
<div className="admin-skeleton-row" style={{ gap: '1rem' }}>
<div className="admin-skeleton-line" style={{ width: '48px', height: '48px', borderRadius: '12px', flexShrink: 0 }} />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/2" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-full" style={{ height: '6px', borderRadius: '3px' }} />
<div className="admin-skeleton-line w-1/3" style={{ height: '10px', marginTop: '0.5rem' }} />

View File

@@ -254,8 +254,7 @@ export default function AttendanceLocation() {
href={`https://www.google.com/maps?q=${record.arrival_lat},${record.arrival_lng}`}
target="_blank"
rel="noopener noreferrer"
className="admin-btn admin-btn-secondary admin-btn-sm"
style={{ marginTop: '0.5rem' }}
className="admin-btn admin-btn-secondary admin-btn-sm mt-2"
>
Otevřít v Google Maps
</a>
@@ -287,8 +286,7 @@ export default function AttendanceLocation() {
href={`https://www.google.com/maps?q=${record.departure_lat},${record.departure_lng}`}
target="_blank"
rel="noopener noreferrer"
className="admin-btn admin-btn-secondary admin-btn-sm"
style={{ marginTop: '0.5rem' }}
className="admin-btn admin-btn-secondary admin-btn-sm mt-2"
>
Otevřít v Google Maps
</a>

View File

@@ -195,7 +195,7 @@ export default function AuditLog() {
<div className="admin-skeleton-line" style={{ width: '80px' }} />
<div className="admin-skeleton-line" style={{ width: '70px', borderRadius: '10px' }} />
<div className="admin-skeleton-line" style={{ width: '80px' }} />
<div className="admin-skeleton-line" style={{ flex: 1 }} />
<div className="admin-skeleton-line flex-1" />
<div className="admin-skeleton-line" style={{ width: '90px' }} />
</div>
))}
@@ -290,11 +290,10 @@ export default function AuditLog() {
)}
<motion.div
className="admin-card"
className="admin-card mb-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
style={{ marginBottom: '1rem' }}
>
<div className="admin-card-body">
<div className="admin-form-row admin-form-row-5">
@@ -399,7 +398,7 @@ export default function AuditLog() {
{!loading && logs.map((log) => (
<tr key={log.id}>
<td className="admin-mono">{formatDatetime(log.created_at)}</td>
<td style={{ fontWeight: 500 }}>{log.username || '-'}</td>
<td className="fw-500">{log.username || '-'}</td>
<td>
<span className={`admin-badge ${ACTION_BADGE_CLASS[log.action] || 'admin-badge-secondary'}`}>
{ACTION_LABELS[log.action] || log.action}

View File

@@ -323,7 +323,7 @@ export default function CompanySettings() {
function renderBankButtonContent() {
if (bankSaving) {
return <><div className="admin-spinner" style={{ width: 14, height: 14, borderWidth: 2 }} />Ukládání...</>
return <><div className="admin-spinner admin-spinner-sm" />Ukládání...</>
}
if (editingBank !== null) return 'Uložit změny'
return (
@@ -351,7 +351,7 @@ export default function CompanySettings() {
<button onClick={handleSave} className="admin-btn admin-btn-primary" disabled={saving}>
{saving ? (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
Ukládání...
</>
) : 'Uložit nastavení'}
@@ -569,7 +569,7 @@ export default function CompanySettings() {
<td className="admin-mono">{acc.iban}</td>
<td className="admin-mono">{acc.bic}</td>
<td>{acc.currency}</td>
<td style={{ textAlign: 'center' }}>
<td className="text-center">
{acc.is_default ? (
<span className="text-accent fw-600"></span>
) : ''}
@@ -780,7 +780,7 @@ export default function CompanySettings() {
<label className="admin-btn admin-btn-secondary" style={{ cursor: 'pointer' }}>
{uploadingLogo ? (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
Nahrávání...
</>
) : (

View File

@@ -226,7 +226,7 @@ export default function Dashboard() {
style={{ border: '2px solid var(--danger)', background: 'var(--danger-light)' }}
>
<div className="admin-card-body" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-row-gap">
<div style={{
width: 40, height: 40, borderRadius: '50%',
display: 'flex', alignItems: 'center', justifyContent: 'center',
@@ -306,7 +306,7 @@ export default function Dashboard() {
<div className="dash-right-col">
{dashData?.projects && (
<div className="admin-card">
<div className="admin-card-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div className="admin-card-header flex-between">
<h2 className="admin-card-title">Aktivní projekty</h2>
<Link to="/projects" className="admin-btn admin-btn-primary admin-btn-sm">Vše &rarr;</Link>
</div>
@@ -326,7 +326,7 @@ export default function Dashboard() {
{dashData?.offers && (
<div className="admin-card">
<div className="admin-card-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div className="admin-card-header flex-between">
<h2 className="admin-card-title">Nabídky</h2>
<Link to="/offers" className="admin-btn admin-btn-primary admin-btn-sm">Zobrazit &rarr;</Link>
</div>

View File

@@ -420,7 +420,7 @@ export default function InvoiceCreate() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div className="flex-row gap-4">
<Link to="/invoices" className="admin-btn-icon" title="Zpět" aria-label="Zpět">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 12H5M12 19l-7-7 7-7" />
@@ -446,7 +446,7 @@ export default function InvoiceCreate() {
<button onClick={handleSubmit} className="admin-btn admin-btn-primary" disabled={saving}>
{saving ? (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
Ukládání...
</>
) : 'Uložit'}
@@ -602,7 +602,7 @@ export default function InvoiceCreate() {
</select>
</FormField>
<FormField label="DPH">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-row-gap">
<label className="admin-form-checkbox" style={{ whiteSpace: 'nowrap' }}>
<input
type="checkbox"
@@ -642,7 +642,7 @@ export default function InvoiceCreate() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.2 }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<div className="flex-between mb-4">
<div>
<h3 className="admin-card-title" style={{ margin: 0 }}>Položky</h3>
{errors.items && <span className="admin-form-error">{errors.items}</span>}
@@ -684,15 +684,14 @@ export default function InvoiceCreate() {
<td style={{ width: '2rem' }}>
<DragHandle listeners={listeners} attributes={attributes} />
</td>
<td className="text-tertiary" style={{ textAlign: 'center', fontWeight: 500 }}>{index + 1}</td>
<td className="text-tertiary text-center fw-500">{index + 1}</td>
<td>
<input
type="text"
value={item.description}
onChange={(e) => updateItem(index, 'description', e.target.value)}
className="admin-form-input"
className="admin-form-input fw-500"
placeholder="Popis položky..."
style={{ fontWeight: 500 }}
/>
</td>
<td>

View File

@@ -254,11 +254,11 @@ export default function InvoiceDetail() {
return (
<div className="admin-skeleton" style={{ padding: 0, gap: '1.5rem' }}>
<div className="admin-skeleton-row" style={{ justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-row-gap">
<div className="admin-skeleton-line" style={{ width: '32px', height: '32px', borderRadius: '8px' }} />
<div className="admin-skeleton-line h-8" style={{ width: '200px' }} />
</div>
<div className="admin-skeleton-row" style={{ gap: '0.5rem' }}>
<div className="admin-skeleton-row gap-2">
<div className="admin-skeleton-line h-10" style={{ width: '100px', borderRadius: '8px' }} />
<div className="admin-skeleton-line h-10" style={{ width: '100px', borderRadius: '8px' }} />
</div>
@@ -277,9 +277,9 @@ export default function InvoiceDetail() {
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
{[0, 1, 2].map(i => (
<div key={i} className="admin-skeleton-row">
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-full" /></div>
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-3/4" /></div>
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-1/2" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-full" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-3/4" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-1/2" /></div>
</div>
))}
</div>
@@ -302,14 +302,14 @@ export default function InvoiceDetail() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div className="flex-row gap-4">
<Link to="/invoices" className="admin-btn-icon" title="Zpět" aria-label="Zpět">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
</Link>
<div>
<h1 className="admin-page-title" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<h1 className="admin-page-title flex-row-gap">
Faktura {invoice.invoice_number}
<span className={`admin-badge ${STATUS_CLASSES[invoice.status] || ''}`}>
{STATUS_LABELS[invoice.status] || invoice.status}
@@ -326,7 +326,7 @@ export default function InvoiceDetail() {
>
{pdfLoading ? (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
PDF...
</>
) : (
@@ -376,9 +376,9 @@ export default function InvoiceDetail() {
>
<h3 className="admin-card-title">Informace</h3>
<div className="admin-form">
<div className="offers-form-row-3" style={{ marginBottom: '0.5rem' }}>
<div className="offers-form-row-3 mb-2">
<FormField label="Zákazník">
<div style={{ fontWeight: 500 }}>{invoice.customer_name || '—'}</div>
<div className="fw-500">{invoice.customer_name || '—'}</div>
{invoice.customer && (
<div className="text-tertiary" style={{ fontSize: '0.8rem', marginTop: '0.2rem' }}>
{invoice.customer.company_id && `IČ: ${invoice.customer.company_id}`}
@@ -399,7 +399,7 @@ export default function InvoiceDetail() {
<div>{invoice.currency}</div>
</FormField>
</div>
<div className="offers-form-row-3" style={{ marginBottom: '0.5rem' }}>
<div className="offers-form-row-3 mb-2">
<FormField label="Datum vystavení">
<div>{formatDate(invoice.issue_date)}</div>
</FormField>
@@ -424,7 +424,7 @@ export default function InvoiceDetail() {
</FormField>
</div>
{invoice.paid_date && (
<div className="admin-form-row" style={{ marginTop: '0.5rem' }}>
<div className="admin-form-row mt-2">
<FormField label="Datum úhrady">
<div style={{ color: 'var(--success)', fontWeight: 500 }}>{formatDate(invoice.paid_date)}</div>
</FormField>
@@ -440,11 +440,11 @@ export default function InvoiceDetail() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.2 }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<div className="flex-between mb-4">
<h3 className="admin-card-title" style={{ margin: 0 }}>Položky</h3>
{isDraft && hasPermission('invoices.edit') && (
editingItems ? (
<div style={{ display: 'flex', gap: '0.5rem' }}>
<div className="flex-row gap-2">
<button type="button" onClick={addEditItem} className="admin-btn admin-btn-secondary admin-btn-sm">
+ Přidat položku
</button>
@@ -487,9 +487,8 @@ export default function InvoiceDetail() {
type="text"
value={item.description}
onChange={(e) => updateEditItem(index, 'description', e.target.value)}
className="admin-form-input"
className="admin-form-input fw-500"
placeholder="Popis položky..."
style={{ fontWeight: 500 }}
/>
</td>
<td>
@@ -578,10 +577,10 @@ export default function InvoiceDetail() {
return (
<tr key={item.id || index}>
<td className="text-tertiary" style={{ textAlign: 'center', fontWeight: 500 }}>{index + 1}</td>
<td style={{ fontWeight: 500 }}>{item.description || '—'}</td>
<td className="fw-500">{item.description || '—'}</td>
<td style={{ textAlign: 'center' }}>{item.quantity} {item.unit && <span className="text-tertiary">{item.unit}</span>}</td>
<td style={{ textAlign: 'center' }}>{item.unit || '—'}</td>
<td className="admin-mono" style={{ textAlign: 'right' }}>{formatCurrency(item.unit_price, invoice.currency)}</td>
<td className="admin-mono text-right">{formatCurrency(item.unit_price, invoice.currency)}</td>
<td style={{ textAlign: 'center' }}>{Number(invoice.apply_vat) ? Number(item.vat_rate) : 0}%</td>
<td className="admin-mono" style={{ textAlign: 'right', fontWeight: 600 }}>{formatCurrency(lineSubtotal + lineVat, invoice.currency)}</td>
</tr>
@@ -644,7 +643,7 @@ export default function InvoiceDetail() {
/>
</Suspense>
{hasPermission('invoices.edit') && (
<div style={{ marginTop: '0.5rem' }}>
<div className="mt-2">
<button
onClick={handleSaveNotes}
className="admin-btn admin-btn-secondary admin-btn-sm"

View File

@@ -313,7 +313,7 @@ export default function Invoices() {
</button>
</div>
<div className="offers-tabs" style={{ marginBottom: '1rem', justifyContent: 'center' }}>
<div className="offers-tabs mb-4" style={{ justifyContent: 'center' }}>
<button className={`offers-tab ${activeTab === 'issued' ? 'active' : ''}`} onClick={() => setActiveTab('issued')}>
Vydané
</button>
@@ -427,7 +427,7 @@ export default function Invoices() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.2 }}
>
<div className="offers-tabs" style={{ marginBottom: '1.5rem' }}>
<div className="offers-tabs mb-6">
{STATUS_FILTERS.map(f => (
<button
key={f.value}
@@ -448,7 +448,7 @@ export default function Invoices() {
transition={{ duration: 0.4, delay: 0.25 }}
>
<div className="admin-card-body">
<div className="admin-search-bar" style={{ marginBottom: '1rem' }}>
<div className="admin-search-bar mb-4">
<input
type="text"
value={search}
@@ -494,7 +494,7 @@ export default function Invoices() {
<th style={{ cursor: 'pointer' }} onClick={() => handleSort('due_date')}>
Splatnost <SortIcon column="due_date" sort={activeSort} order={order} />
</th>
<th style={{ textAlign: 'right' }}>Celkem</th>
<th className="text-right">Celkem</th>
<th>Akce</th>
</tr>
</thead>

View File

@@ -179,8 +179,8 @@ export default function LeaveApproval() {
{[0, 1, 2, 3, 4].map(i => (
<div key={i} className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="flex-1">
<div className="admin-skeleton-line w-1/3 mb-2" />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
<div className="admin-skeleton-line w-1/4" />
@@ -217,7 +217,7 @@ export default function LeaveApproval() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="offers-tabs" style={{ marginBottom: '1.5rem' }}>
<div className="offers-tabs mb-6">
<button
className={`offers-tab ${activeTab === 'pending' ? 'active' : ''}`}
onClick={() => setActiveTab('pending')}
@@ -249,7 +249,7 @@ export default function LeaveApproval() {
<div className="admin-card">
<div className="admin-card-body">
<div className="admin-empty-state">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-muted" style={{ marginBottom: '1rem' }}>
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-muted mb-4">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
@@ -263,8 +263,8 @@ export default function LeaveApproval() {
<div key={req.id} className="admin-card">
<div className="admin-card-body" style={{ padding: '1.25rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: '1rem' }}>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.5rem' }}>
<div className="flex-1">
<div className="flex-row-gap mb-2">
<strong style={{ fontSize: '1rem' }}>{req.employee_name}</strong>
<span className={`attendance-leave-badge ${leaveTypeClasses[req.leave_type] || ''}`}>
{leaveTypeLabels[req.leave_type] || req.leave_type}
@@ -413,7 +413,7 @@ export default function LeaveApproval() {
</div>
<div className="admin-modal-body">
{rejectModal.request && (
<p className="text-secondary" style={{ marginBottom: '1rem' }}>
<p className="text-secondary mb-4">
{rejectModal.request.employee_name} {leaveTypeLabels[rejectModal.request.leave_type]},{' '}
{formatDate(rejectModal.request.date_from)} {formatDate(rejectModal.request.date_to)} ({rejectModal.request.total_days} dnů)
</p>

View File

@@ -106,40 +106,40 @@ export default function LeaveRequests() {
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="flex-1">
<div className="admin-skeleton-line w-1/3 mb-2" />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
<div className="admin-skeleton-line w-1/4" />
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="admin-skeleton-line w-1/2" style={{ marginBottom: '0.5rem' }} />
<div className="flex-1">
<div className="admin-skeleton-line w-1/2 mb-2" />
<div className="admin-skeleton-line w-1/3" style={{ height: '10px' }} />
</div>
<div className="admin-skeleton-line w-1/4" />
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="admin-skeleton-line w-3/4" style={{ marginBottom: '0.5rem' }} />
<div className="flex-1">
<div className="admin-skeleton-line w-3/4 mb-2" />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
<div className="admin-skeleton-line w-1/4" />
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="admin-skeleton-line w-1/2" style={{ marginBottom: '0.5rem' }} />
<div className="flex-1">
<div className="admin-skeleton-line w-1/2 mb-2" />
<div className="admin-skeleton-line w-1/3" style={{ height: '10px' }} />
</div>
<div className="admin-skeleton-line w-1/4" />
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="flex-1">
<div className="admin-skeleton-line w-1/3 mb-2" />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
<div className="admin-skeleton-line w-1/4" />

View File

@@ -163,11 +163,11 @@ export default function OfferDetail() {
return (
<div className="admin-skeleton" style={{ padding: 0, gap: '1.5rem' }}>
<div className="admin-skeleton-row" style={{ justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-row-gap">
<div className="admin-skeleton-line" style={{ width: '32px', height: '32px', borderRadius: '8px' }} />
<div className="admin-skeleton-line h-8" style={{ width: '200px' }} />
</div>
<div className="admin-skeleton-row" style={{ gap: '0.5rem' }}>
<div className="admin-skeleton-row gap-2">
<div className="admin-skeleton-line h-10" style={{ width: '100px', borderRadius: '8px' }} />
<div className="admin-skeleton-line h-10" style={{ width: '100px', borderRadius: '8px' }} />
</div>
@@ -186,9 +186,9 @@ export default function OfferDetail() {
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
{[0, 1, 2].map(i => (
<div key={i} className="admin-skeleton-row">
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-full" /></div>
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-3/4" /></div>
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-1/2" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-full" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-3/4" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-1/2" /></div>
</div>
))}
</div>
@@ -216,7 +216,7 @@ export default function OfferDetail() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div className="flex-row gap-4">
<Link to="/offers" className="admin-btn-icon" title="Zpět" aria-label="Zpět">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 12H5M12 19l-7-7 7-7" />
@@ -246,7 +246,7 @@ export default function OfferDetail() {
<button onClick={handlePdf} className="admin-btn admin-btn-secondary" disabled={pdfLoading}>
{pdfLoading ? (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
PDF...
</>
) : (
@@ -293,7 +293,7 @@ export default function OfferDetail() {
<button onClick={handleSave} className="admin-btn admin-btn-primary" disabled={saving}>
{saving ? (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
Ukládání...
</>
) : 'Uložit'}
@@ -410,14 +410,13 @@ export default function OfferDetail() {
<div className="offers-form-row-3">
<FormField label="Sazba DPH (%)">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-row-gap">
<input
type="number"
value={form.vat_rate}
onChange={(e) => updateForm('vat_rate', parseFloat(e.target.value) || 0)}
className="admin-form-input"
className="admin-form-input flex-1"
step="0.1"
style={{ flex: 1 }}
readOnly={isInvalidated}
/>
<label className="admin-form-checkbox" style={{ whiteSpace: 'nowrap' }}>
@@ -514,7 +513,7 @@ export default function OfferDetail() {
</FormField>
<FormField label="Příloha (PDF)">
{orderAttachment ? (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<div className="flex-row gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--accent-color)" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />

View File

@@ -211,7 +211,7 @@ export default function Offers() {
{[0, 1, 2, 3, 4].map(i => (
<div key={i} className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -267,7 +267,7 @@ export default function Offers() {
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="admin-card-body">
<div className="admin-search-bar" style={{ marginBottom: '1rem' }}>
<div className="admin-search-bar mb-4">
<input
type="text"
value={search}
@@ -315,7 +315,7 @@ export default function Offers() {
<th style={{ cursor: 'pointer' }} onClick={() => handleSort('currency')}>
Měna <SortIcon column="currency" sort={activeSort} order={order} />
</th>
<th style={{ textAlign: 'right' }}>Celkem</th>
<th className="text-right">Celkem</th>
<th>Akce</th>
</tr>
</thead>
@@ -395,7 +395,7 @@ export default function Offers() {
{q.currency}
</span>
</td>
<td className="admin-mono" style={{ textAlign: 'right', fontWeight: 500 }}>
<td className="admin-mono text-right fw-500">
{formatCurrency(q.total, q.currency)}
</td>
<td>
@@ -578,7 +578,7 @@ export default function Offers() {
</FormField>
<FormField label="Příloha (PDF)">
{orderAttachment ? (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<div className="flex-row gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--accent-color)" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />

View File

@@ -244,7 +244,7 @@ export default function OffersCustomers() {
{[0, 1, 2, 3, 4].map(i => (
<div key={i} className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -289,7 +289,7 @@ export default function OffersCustomers() {
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="admin-card-body">
<div className="admin-search-bar" style={{ marginBottom: '1rem' }}>
<div className="admin-search-bar mb-4">
<input
type="text"
value={search}
@@ -606,7 +606,7 @@ export default function OffersCustomers() {
<button type="button" onClick={handleSubmit} className="admin-btn admin-btn-primary" disabled={saving}>
{saving && (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
Ukládání...
</>
)}

View File

@@ -151,7 +151,7 @@ function ItemTemplatesTab() {
{[0, 1, 2, 3, 4].map(i => (
<div key={i} className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -171,7 +171,7 @@ function ItemTemplatesTab() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="admin-card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="admin-card-header flex-between">
<h3 className="admin-card-title">Šablony položek ({templates.length})</h3>
<button onClick={openCreate} className="admin-btn admin-btn-primary admin-btn-sm">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
@@ -199,7 +199,7 @@ function ItemTemplatesTab() {
<tbody>
{templates.map((t) => (
<tr key={t.id}>
<td style={{ fontWeight: 500 }}>{t.name}</td>
<td className="fw-500">{t.name}</td>
<td style={{ color: 'var(--text-secondary)' }}>{t.description || '—'}</td>
<td>{Number(t.default_price).toFixed(2)}</td>
<td style={{ color: 'var(--text-secondary)' }}>{t.category || '—'}</td>
@@ -258,7 +258,7 @@ function ItemTemplatesTab() {
<div className="admin-modal-footer">
<button type="button" onClick={() => setShowModal(false)} className="admin-btn admin-btn-secondary" disabled={saving}>Zrušit</button>
<button type="button" onClick={handleSubmit} className="admin-btn admin-btn-primary" disabled={saving}>
{saving && (<><div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />Ukládání...</>)}
{saving && (<><div className="admin-spinner admin-spinner-sm" />Ukládání...</>)}
{!saving && (editingTemplate ? 'Uložit' : 'Vytvořit')}
</button>
</div>
@@ -424,7 +424,7 @@ function ScopeTemplatesTab() {
{[0, 1, 2, 3, 4].map(i => (
<div key={i} className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -444,7 +444,7 @@ function ScopeTemplatesTab() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="admin-card-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="admin-card-header flex-between">
<h3 className="admin-card-title">Šablony rozsahu ({templates.length})</h3>
<button onClick={openCreate} className="admin-btn admin-btn-primary admin-btn-sm">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
@@ -469,7 +469,7 @@ function ScopeTemplatesTab() {
<tbody>
{templates.map((t) => (
<tr key={t.id}>
<td style={{ fontWeight: 500 }}>{t.name}</td>
<td className="fw-500">{t.name}</td>
<td>
<div className="admin-table-actions">
<button onClick={() => openEdit(t)} className="admin-btn-icon" title="Upravit" aria-label="Upravit">
@@ -511,7 +511,7 @@ function ScopeTemplatesTab() {
</FormField>
<div className="admin-form-group">
<label className="admin-form-label" style={{ marginBottom: '0.5rem' }}>Sekce</label>
<label className="admin-form-label mb-2">Sekce</label>
<div className="offers-scope-list">
{form.sections.map((section, index) => (
<div key={section._key} className="offers-scope-section">
@@ -564,7 +564,7 @@ function ScopeTemplatesTab() {
<div className="admin-modal-footer">
<button type="button" onClick={() => setShowModal(false)} className="admin-btn admin-btn-secondary" disabled={saving}>Zrušit</button>
<button type="button" onClick={handleSubmit} className="admin-btn admin-btn-primary" disabled={saving}>
{saving && (<><div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />Ukládání...</>)}
{saving && (<><div className="admin-spinner admin-spinner-sm" />Ukládání...</>)}
{!saving && (editingTemplate ? 'Uložit' : 'Vytvořit')}
</button>
</div>

View File

@@ -217,11 +217,11 @@ export default function OrderDetail() {
return (
<div className="admin-skeleton" style={{ padding: 0, gap: '1.5rem' }}>
<div className="admin-skeleton-row" style={{ justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-row-gap">
<div className="admin-skeleton-line" style={{ width: '32px', height: '32px', borderRadius: '8px' }} />
<div className="admin-skeleton-line h-8" style={{ width: '200px' }} />
</div>
<div className="admin-skeleton-row" style={{ gap: '0.5rem' }}>
<div className="admin-skeleton-row gap-2">
<div className="admin-skeleton-line h-10" style={{ width: '100px', borderRadius: '8px' }} />
<div className="admin-skeleton-line h-10" style={{ width: '100px', borderRadius: '8px' }} />
</div>
@@ -240,9 +240,9 @@ export default function OrderDetail() {
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
{[0, 1, 2].map(i => (
<div key={i} className="admin-skeleton-row">
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-full" /></div>
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-3/4" /></div>
<div style={{ flex: 1 }}><div className="admin-skeleton-line w-1/2" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-full" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-3/4" /></div>
<div className="flex-1"><div className="admin-skeleton-line w-1/2" /></div>
</div>
))}
</div>
@@ -262,14 +262,14 @@ export default function OrderDetail() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div className="flex-row gap-4">
<Link to="/orders" className="admin-btn-icon" title="Zpět" aria-label="Zpět">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
</Link>
<div>
<h1 className="admin-page-title" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<h1 className="admin-page-title flex-row-gap">
{editingNumber ? (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem' }}>
Objednávka
@@ -345,7 +345,7 @@ export default function OrderDetail() {
disabled={statusChanging === status}
>
{statusChanging === status ? (
<div className="admin-spinner" style={{ width: 14, height: 14, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
) : (
TRANSITION_LABELS[status] || status
)}
@@ -372,7 +372,7 @@ export default function OrderDetail() {
>
<div className="admin-card-body">
<h3 className="admin-card-title">Informace</h3>
<div className="admin-form-row" style={{ marginBottom: '0.5rem' }}>
<div className="admin-form-row mb-2">
<FormField label="Nabídka">
<div>
<Link to={`/offers/${order.quotation_id}`} className="link-accent">
@@ -393,9 +393,9 @@ export default function OrderDetail() {
</div>
</FormField>
</div>
<div className="admin-form-row admin-form-row-3" style={{ marginBottom: '0.5rem' }}>
<div className="admin-form-row admin-form-row-3 mb-2">
<FormField label="Zákazník">
<div style={{ fontWeight: 500 }}>{order.customer_name || '—'}</div>
<div className="fw-500">{order.customer_name || '—'}</div>
</FormField>
<FormField label="Číslo obj. zákazníka">
<div>{order.customer_order_number || '—'}</div>
@@ -404,7 +404,7 @@ export default function OrderDetail() {
<div>{order.currency}</div>
</FormField>
</div>
<div className="admin-form-row admin-form-row-3" style={{ marginBottom: '0.5rem' }}>
<div className="admin-form-row admin-form-row-3 mb-2">
<FormField label="Datum vytvoření">
<div>{formatDate(order.created_at)}</div>
</FormField>
@@ -418,7 +418,7 @@ export default function OrderDetail() {
disabled={attachmentLoading}
>
{attachmentLoading ? (
<div className="admin-spinner" style={{ width: 14, height: 14, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
@@ -464,7 +464,7 @@ export default function OrderDetail() {
<tr key={item.id || index}>
<td style={{ color: 'var(--text-tertiary)', textAlign: 'center', fontWeight: 500 }}>{index + 1}</td>
<td>
<div style={{ fontWeight: 500 }}>{item.description || '—'}</div>
<div className="fw-500">{item.description || '—'}</div>
{item.item_description && (
<div style={{ fontSize: '0.8rem', color: 'var(--text-tertiary)', marginTop: '0.25rem' }}>{item.item_description}</div>
)}
@@ -561,7 +561,7 @@ export default function OrderDetail() {
/>
</FormField>
{hasPermission('orders.edit') && (
<div style={{ marginTop: '0.5rem' }}>
<div className="mt-2">
<button
onClick={handleSaveNotes}
className="admin-btn admin-btn-secondary admin-btn-sm"

View File

@@ -83,7 +83,7 @@ export default function Orders() {
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -91,7 +91,7 @@ export default function Orders() {
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/2" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/3" style={{ height: '10px' }} />
</div>
@@ -99,7 +99,7 @@ export default function Orders() {
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-3/4" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -107,7 +107,7 @@ export default function Orders() {
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/2" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/3" style={{ height: '10px' }} />
</div>
@@ -115,7 +115,7 @@ export default function Orders() {
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -151,7 +151,7 @@ export default function Orders() {
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="admin-card-body">
<div className="admin-search-bar" style={{ marginBottom: '1rem' }}>
<div className="admin-search-bar mb-4">
<input
type="text"
value={search}
@@ -191,7 +191,7 @@ export default function Orders() {
<th style={{ cursor: 'pointer' }} onClick={() => handleSort('created_at')}>
Datum <SortIcon column="created_at" sort={activeSort} order={order} />
</th>
<th style={{ textAlign: 'right' }}>Celkem</th>
<th className="text-right">Celkem</th>
<th>Akce</th>
</tr>
</thead>
@@ -217,7 +217,7 @@ export default function Orders() {
<td className="admin-mono">
{formatDate(o.created_at)}
</td>
<td className="admin-mono" style={{ textAlign: 'right', fontWeight: 500 }}>
<td className="admin-mono text-right fw-500">
{formatCurrency(o.total, o.currency)}
</td>
<td>

View File

@@ -158,7 +158,7 @@ export default function ProjectCreate() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div className="flex-row gap-4">
<Link to="/projects" className="admin-btn-icon" title="Zpět" aria-label="Zpět">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 12H5M12 19l-7-7 7-7" />

View File

@@ -210,7 +210,7 @@ export default function ProjectDetail() {
return (
<div className="admin-skeleton" style={{ padding: 0, gap: '1.5rem' }}>
<div className="admin-skeleton-row" style={{ justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-row-gap">
<div className="admin-skeleton-line" style={{ width: '32px', height: '32px', borderRadius: '8px' }} />
<div className="admin-skeleton-line h-8" style={{ width: '200px' }} />
</div>
@@ -273,7 +273,7 @@ export default function ProjectDetail() {
<button onClick={handleSave} className="admin-btn admin-btn-primary" disabled={saving}>
{saving ? (
<>
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
Ukládání...
</>
) : 'Uložit'}
@@ -380,7 +380,7 @@ export default function ProjectDetail() {
<h3 className="admin-card-title">Poznámky</h3>
{/* Add note */}
<div style={{ marginBottom: '1rem' }}>
<div className="mb-4">
<textarea
value={newNote}
onChange={(e) => setNewNote(e.target.value)}
@@ -394,14 +394,14 @@ export default function ProjectDetail() {
}
}}
/>
<div style={{ marginTop: '0.5rem' }}>
<div className="mt-2">
<button
onClick={handleAddNote}
className="admin-btn admin-btn-secondary admin-btn-sm"
disabled={addingNote || !newNote.trim()}
>
{addingNote ? (
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
) : (
'Přidat poznámku'
)}
@@ -452,7 +452,7 @@ export default function ProjectDetail() {
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: '0.5rem' }}>
<div style={{ flex: 1 }}>
<div className="flex-1">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.25rem' }}>
<span style={{ fontWeight: 600, fontSize: '0.85rem' }}>
{note.user_name}

View File

@@ -78,7 +78,7 @@ export default function Projects() {
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -86,7 +86,7 @@ export default function Projects() {
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/2" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/3" style={{ height: '10px' }} />
</div>
@@ -94,7 +94,7 @@ export default function Projects() {
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-3/4" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -102,7 +102,7 @@ export default function Projects() {
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/2" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/3" style={{ height: '10px' }} />
</div>
@@ -110,7 +110,7 @@ export default function Projects() {
</div>
<div className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
@@ -155,7 +155,7 @@ export default function Projects() {
transition={{ duration: 0.4, delay: 0.1 }}
>
<div className="admin-card-body">
<div className="admin-search-bar" style={{ marginBottom: '1rem' }}>
<div className="admin-search-bar mb-4">
<input
type="text"
value={search}
@@ -210,7 +210,7 @@ export default function Projects() {
{p.project_number}
</Link>
</td>
<td style={{ fontWeight: 500 }}>{p.name || '—'}</td>
<td className="fw-500">{p.name || '—'}</td>
<td>{p.customer_name || '—'}</td>
<td>
<span className={`admin-badge ${STATUS_CLASSES[p.status] || ''}`}>
@@ -242,7 +242,7 @@ export default function Projects() {
disabled={deletingId === p.id}
>
{deletingId === p.id ? (
<div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />
<div className="admin-spinner admin-spinner-sm" />
) : (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="3 6 5 6 21 6" />

View File

@@ -369,7 +369,7 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
const renderKpi = () => {
if (!hasLoadedOnce.current && statsLoading) {
return (
<div className="dash-kpi-grid dash-kpi-4" style={{ marginBottom: '1.5rem' }}>
<div className="dash-kpi-grid dash-kpi-4 mb-6">
{[0, 1, 2, 3].map(i => (
<div key={i} className="admin-stat-card">
<div className="admin-skeleton-line" style={{ width: '60%', height: '11px', marginBottom: '0.5rem' }} />
@@ -456,7 +456,7 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
transition={{ duration: 0.4, delay: 0.15 }}
>
<div className="admin-card-body">
<div className="admin-search-bar" style={{ marginBottom: '1rem' }}>
<div className="admin-search-bar mb-4">
<input
type="text"
value={search}
@@ -549,7 +549,7 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
</td>
<td className="admin-mono">{formatDate(inv.issue_date)}</td>
<td className="admin-mono">{formatDate(inv.due_date)}</td>
<td className="admin-mono" style={{ textAlign: 'right', fontWeight: 500 }}>
<td className="admin-mono text-right fw-500">
{formatCurrency(inv.amount, inv.currency)}
</td>
<td>
@@ -615,7 +615,7 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
<h2 className="admin-modal-title">Nahrát přijaté faktury</h2>
</div>
<div className="admin-modal-body">
<div style={{ marginBottom: '1rem' }}>
<div className="mb-4">
<input
ref={fileInputRef}
type="file"

View File

@@ -276,8 +276,8 @@ export default function Settings() {
{[0, 1, 2, 3, 4].map(i => (
<div key={i} className="admin-skeleton-row">
<div className="admin-skeleton-line circle" />
<div style={{ flex: 1 }}>
<div className="admin-skeleton-line w-1/3" style={{ marginBottom: '0.5rem' }} />
<div className="flex-1">
<div className="admin-skeleton-line w-1/3 mb-2" />
<div className="admin-skeleton-line w-1/4" style={{ height: '10px' }} />
</div>
<div className="admin-skeleton-line w-1/4" />
@@ -306,7 +306,7 @@ export default function Settings() {
function renderRoleButtonContent() {
if (saving) {
return <><div className="admin-spinner" style={{ width: 16, height: 16, borderWidth: 2 }} />Ukládání...</>
return <><div className="admin-spinner admin-spinner-sm" />Ukládání...</>
}
return editingRole ? 'Uložit změny' : 'Vytvořit roli'
}
@@ -347,7 +347,7 @@ export default function Settings() {
</div>
<div className="admin-card-body">
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div className="flex-row-gap">
<div style={{
width: 36, height: 36, borderRadius: '50%',
display: 'flex', alignItems: 'center', justifyContent: 'center',

Some files were not shown because too many files have changed in this diff Show More