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:
@@ -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'
|
||||
|
||||
@@ -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
|
||||
============================================================================ */
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function AdminLayout() {
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div style={{ flex: 1 }} />
|
||||
<div className="flex-1" />
|
||||
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
|
||||
@@ -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í...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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' }}>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 →</Link>
|
||||
</div>
|
||||
|
||||
@@ -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 →</Link>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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' }} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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í...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -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 →</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 →</Link>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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í...
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -333,13 +333,12 @@ export default function Trips() {
|
||||
|
||||
{/* Recent Trips */}
|
||||
<motion.div
|
||||
className="admin-card"
|
||||
style={{ marginTop: '1.5rem' }}
|
||||
className="admin-card mt-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.2 }}
|
||||
>
|
||||
<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">Poslední jízdy</h2>
|
||||
<Link to="/trips/history" className="admin-btn admin-btn-secondary admin-btn-sm">
|
||||
Zobrazit historii
|
||||
|
||||
@@ -350,8 +350,7 @@ export default function TripsAdmin() {
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="admin-grid admin-grid-3"
|
||||
style={{ marginTop: '1.5rem' }}
|
||||
className="admin-grid admin-grid-3 mt-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.15 }}
|
||||
@@ -399,8 +398,7 @@ export default function TripsAdmin() {
|
||||
|
||||
{/* Trips Table */}
|
||||
<motion.div
|
||||
className="admin-card"
|
||||
style={{ marginTop: '1.5rem' }}
|
||||
className="admin-card mt-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.2 }}
|
||||
|
||||
@@ -109,8 +109,7 @@ export default function TripsHistory() {
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="admin-grid admin-grid-3"
|
||||
style={{ marginTop: '1.5rem' }}
|
||||
className="admin-grid admin-grid-3 mt-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.15 }}
|
||||
@@ -158,15 +157,14 @@ export default function TripsHistory() {
|
||||
|
||||
{/* Trips Table */}
|
||||
<motion.div
|
||||
className="admin-card"
|
||||
style={{ marginTop: '1.5rem' }}
|
||||
className="admin-card mt-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.2 }}
|
||||
>
|
||||
<div className="admin-card-body">
|
||||
{loading && (
|
||||
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
|
||||
<div className="admin-skeleton gap-5">
|
||||
{[0, 1, 2, 3, 4].map(i => (
|
||||
<div key={i} className="admin-skeleton-row">
|
||||
<div className="admin-skeleton-line w-1/4" />
|
||||
|
||||
@@ -214,8 +214,8 @@ export default function Users() {
|
||||
{[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" />
|
||||
|
||||
@@ -183,8 +183,8 @@ export default function Vehicles() {
|
||||
{[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" />
|
||||
@@ -259,7 +259,7 @@ export default function Vehicles() {
|
||||
<tbody>
|
||||
{vehicles.map((vehicle) => (
|
||||
<tr key={vehicle.id} className={!vehicle.is_active ? 'admin-table-row-inactive' : ''}>
|
||||
<td className="admin-mono" style={{ fontWeight: 500 }}>{vehicle.spz}</td>
|
||||
<td className="admin-mono fw-500">{vehicle.spz}</td>
|
||||
<td>{vehicle.name}</td>
|
||||
<td>
|
||||
{vehicle.brand || vehicle.model
|
||||
@@ -267,7 +267,7 @@ export default function Vehicles() {
|
||||
: '—'}
|
||||
</td>
|
||||
<td className="admin-mono">{formatKm(vehicle.initial_km)} km</td>
|
||||
<td className="admin-mono" style={{ fontWeight: 500 }}>{formatKm(vehicle.current_km)} km</td>
|
||||
<td className="admin-mono fw-500">{formatKm(vehicle.current_km)} km</td>
|
||||
<td className="admin-mono">{vehicle.trip_count}</td>
|
||||
<td>
|
||||
<button
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/* ============================================================================
|
||||
Project Status Badges
|
||||
============================================================================ */
|
||||
|
||||
.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);
|
||||
}
|
||||
Reference in New Issue
Block a user