refactor: migrace vsech formularu na FormField komponentu + JWT upgrade

- FormField.jsx: pridana podpora style prop
- 23 stranek migrovano na FormField (166 vyskytu, -246 radku)
- firebase/php-jwt upgrade v6.11 -> v7.0.3 (security advisory fix)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 19:33:36 +01:00
parent 37122579d0
commit 2328ba2840
26 changed files with 366 additions and 612 deletions

View File

@@ -4,7 +4,7 @@
"type": "project",
"require": {
"php": ">=8.1",
"firebase/php-jwt": "^6.11",
"firebase/php-jwt": "^7.0",
"robthree/twofactorauth": "^3.0",
"chillerlan/php-qrcode": "^5.0"
},

85
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "452831f603fe18144b4f4557d0c4ea01",
"content-hash": "081e51355832fa208c1fe8833cb07d49",
"packages": [
{
"name": "chillerlan/php-qrcode",
@@ -167,16 +167,16 @@
},
{
"name": "firebase/php-jwt",
"version": "v6.11.1",
"version": "v7.0.3",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
"reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/28aa0694bcfdfa5e2959c394d5a1ee7a5083629e",
"reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e",
"shasum": ""
},
"require": {
@@ -224,9 +224,9 @@
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v6.11.1"
"source": "https://github.com/firebase/php-jwt/tree/v7.0.3"
},
"time": "2025-04-09T20:32:01+00:00"
"time": "2026-02-25T22:16:40+00:00"
},
{
"name": "robthree/twofactorauth",
@@ -308,77 +308,6 @@
}
],
"time": "2026-01-05T13:17:41+00:00"
},
{
"name": "tecnickcom/tcpdf",
"version": "6.11.2",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/TCPDF.git",
"reference": "e1e2ade18e574e963473f53271591edd8c0033ec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/e1e2ade18e574e963473f53271591edd8c0033ec",
"reference": "e1e2ade18e574e963473f53271591edd8c0033ec",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=7.1.0"
},
"type": "library",
"autoload": {
"classmap": [
"config",
"include",
"tcpdf.php",
"tcpdf_barcodes_1d.php",
"tcpdf_barcodes_2d.php",
"include/tcpdf_colors.php",
"include/tcpdf_filters.php",
"include/tcpdf_font_data.php",
"include/tcpdf_fonts.php",
"include/tcpdf_images.php",
"include/tcpdf_static.php",
"include/barcodes/datamatrix.php",
"include/barcodes/pdf417.php",
"include/barcodes/qrcode.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "Nicola Asuni",
"email": "info@tecnick.com",
"role": "lead"
}
],
"description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
"homepage": "http://www.tcpdf.org/",
"keywords": [
"PDFD32000-2008",
"TCPDF",
"barcodes",
"datamatrix",
"pdf",
"pdf417",
"qrcode"
],
"support": {
"issues": "https://github.com/tecnickcom/TCPDF/issues",
"source": "https://github.com/tecnickcom/TCPDF/tree/6.11.2"
},
"funding": [
{
"url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ",
"type": "custom"
}
],
"time": "2026-03-03T08:58:10+00:00"
}
],
"packages-dev": [

View File

@@ -6,13 +6,14 @@
* @param {string} [error] - Chybova zprava (zobrazi se pod inputem)
* @param {boolean} [required] - Zobrazi cervenu hvezdicku
* @param {string} [className] - Extra CSS trida na wrapperu
* @param {object} [style] - Inline styly na wrapperu
* @param {React.ReactNode} children - Input/select/textarea element
*/
export default function FormField({ label, error, required, className, children }) {
export default function FormField({ label, error, required, className, style, children }) {
const groupClass = `admin-form-group${error ? ' has-error' : ''}${className ? ` ${className}` : ''}`
return (
<div className={groupClass}>
<div className={groupClass} style={style}>
<label className={`admin-form-label${required ? ' required' : ''}`}>
{label}
</label>

View File

@@ -7,6 +7,7 @@ import AdminDatePicker from '../components/AdminDatePicker'
import ConfirmModal from '../components/ConfirmModal'
import useModalLock from '../hooks/useModalLock'
import { formatTime, calculateWorkMinutes, formatMinutes } from '../utils/attendanceHelpers'
import FormField from '../components/FormField'
import Forbidden from '../components/Forbidden'
import apiFetch from '../utils/api'
@@ -767,8 +768,7 @@ export default function Attendance() {
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label">Typ nepřítomnosti</label>
<FormField label="Typ nepřítomnosti">
<select
value={leaveForm.leave_type}
onChange={(e) => setLeaveForm({ ...leaveForm, leave_type: e.target.value })}
@@ -778,11 +778,10 @@ export default function Attendance() {
<option value="sick">Nemoc</option>
<option value="unpaid">Neplacené volno</option>
</select>
</div>
</FormField>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div className="admin-form-group">
<label className="admin-form-label">Od</label>
<FormField label="Od">
<AdminDatePicker
mode="date"
value={leaveForm.date_from}
@@ -794,16 +793,15 @@ export default function Attendance() {
}))
}}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Do</label>
</FormField>
<FormField label="Do">
<AdminDatePicker
mode="date"
value={leaveForm.date_to}
minDate={leaveForm.date_from}
onChange={(val) => setLeaveForm({ ...leaveForm, date_to: val })}
/>
</div>
</FormField>
</div>
{leaveForm.date_from && leaveForm.date_to && (
@@ -832,8 +830,7 @@ export default function Attendance() {
</div>
)}
<div className="admin-form-group">
<label className="admin-form-label">Poznámka</label>
<FormField label="Poznámka">
<textarea
value={leaveForm.notes}
onChange={(e) => setLeaveForm({ ...leaveForm, notes: e.target.value })}
@@ -841,7 +838,7 @@ export default function Attendance() {
className="admin-form-textarea"
rows={2}
/>
</div>
</FormField>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import ShiftFormModal from '../components/ShiftFormModal'
import AttendanceShiftTable from '../components/AttendanceShiftTable'
import useModalLock from '../hooks/useModalLock'
import useAttendanceAdmin from '../hooks/useAttendanceAdmin'
import FormField from '../components/FormField'
import { formatMinutes } from '../utils/attendanceHelpers'
function getFundBarBackground(data) {
@@ -104,16 +105,14 @@ export default function AttendanceAdmin() {
>
<div className="admin-card-body">
<div className="admin-form-row">
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Měsíc</label>
<FormField label="Měsíc">
<AdminDatePicker
mode="month"
value={month}
onChange={(val) => setMonth(val)}
/>
</div>
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Zaměstnanec</label>
</FormField>
<FormField label="Zaměstnanec">
<select
value={filterUserId}
onChange={(e) => setFilterUserId(e.target.value)}
@@ -124,7 +123,7 @@ export default function AttendanceAdmin() {
<option key={user.id} value={user.id}>{user.name}</option>
))}
</select>
</div>
</FormField>
</div>
</div>
</motion.div>

View File

@@ -5,6 +5,7 @@ import Forbidden from '../components/Forbidden'
import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import useModalLock from '../hooks/useModalLock'
import FormField from '../components/FormField'
import apiFetch from '../utils/api'
const API_BASE = '/api/admin'
@@ -594,8 +595,7 @@ export default function AttendanceBalances() {
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label">Nárok na dovolenou (hodiny)</label>
<FormField label="Nárok na dovolenou (hodiny)">
<input
type="number"
value={editForm.vacation_total}
@@ -605,10 +605,9 @@ export default function AttendanceBalances() {
step="1"
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Čerpáno dovolené (hodiny)</label>
<FormField label="Čerpáno dovolené (hodiny)">
<input
type="number"
value={editForm.vacation_used}
@@ -618,10 +617,9 @@ export default function AttendanceBalances() {
step="0.5"
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Čerpáno nemocenské (hodiny)</label>
<FormField label="Čerpáno nemocenské (hodiny)">
<input
type="number"
value={editForm.sick_used}
@@ -631,7 +629,7 @@ export default function AttendanceBalances() {
step="0.5"
className="admin-form-input"
/>
</div>
</FormField>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import { useNavigate, Link } from 'react-router-dom'
import { motion } from 'framer-motion'
import AdminDatePicker from '../components/AdminDatePicker'
import FormField from '../components/FormField'
import apiFetch from '../utils/api'
const API_BASE = '/api/admin'
@@ -152,8 +153,7 @@ export default function AttendanceCreate() {
<div className="admin-card-body">
<form onSubmit={handleSubmit} className="admin-form">
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label required">Zaměstnanec</label>
<FormField label="Zaměstnanec" required>
<select
value={form.user_id}
onChange={(e) => setForm({ ...form, user_id: e.target.value })}
@@ -165,20 +165,18 @@ export default function AttendanceCreate() {
<option key={user.id} value={user.id}>{user.name}</option>
))}
</select>
</div>
<div className="admin-form-group">
<label className="admin-form-label required">Datum směny</label>
</FormField>
<FormField label="Datum směny" required>
<AdminDatePicker
mode="date"
value={form.shift_date}
onChange={(val) => handleShiftDateChange(val)}
required
/>
</div>
</FormField>
</div>
<div className="admin-form-group">
<label className="admin-form-label required">Typ záznamu</label>
<FormField label="Typ záznamu" required>
<select
value={form.leave_type}
onChange={(e) => setForm({ ...form, leave_type: e.target.value })}
@@ -190,11 +188,10 @@ export default function AttendanceCreate() {
<option value="holiday">Svátek</option>
<option value="unpaid">Neplacené volno</option>
</select>
</div>
</FormField>
{!isWorkType && (
<div className="admin-form-group">
<label className="admin-form-label">Počet hodin</label>
<FormField label="Počet hodin">
<input
type="number"
value={form.leave_hours}
@@ -205,98 +202,89 @@ export default function AttendanceCreate() {
className="admin-form-input"
/>
<small className="admin-form-hint">Výchozí 8 hodin pro celý den</small>
</div>
</FormField>
)}
{isWorkType && (
<>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Příchod - datum</label>
<FormField label="Příchod - datum">
<AdminDatePicker
mode="date"
value={form.arrival_date}
onChange={(val) => setForm({ ...form, arrival_date: val })}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Příchod - čas</label>
</FormField>
<FormField label="Příchod - čas">
<AdminDatePicker
mode="time"
value={form.arrival_time}
onChange={(val) => setForm({ ...form, arrival_time: val })}
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Začátek pauzy - datum</label>
<FormField label="Začátek pauzy - datum">
<AdminDatePicker
mode="date"
value={form.break_start_date}
onChange={(val) => setForm({ ...form, break_start_date: val })}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Začátek pauzy - čas</label>
</FormField>
<FormField label="Začátek pauzy - čas">
<AdminDatePicker
mode="time"
value={form.break_start_time}
onChange={(val) => setForm({ ...form, break_start_time: val })}
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Konec pauzy - datum</label>
<FormField label="Konec pauzy - datum">
<AdminDatePicker
mode="date"
value={form.break_end_date}
onChange={(val) => setForm({ ...form, break_end_date: val })}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Konec pauzy - čas</label>
</FormField>
<FormField label="Konec pauzy - čas">
<AdminDatePicker
mode="time"
value={form.break_end_time}
onChange={(val) => setForm({ ...form, break_end_time: val })}
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Odchod - datum</label>
<FormField label="Odchod - datum">
<AdminDatePicker
mode="date"
value={form.departure_date}
onChange={(val) => setForm({ ...form, departure_date: val })}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Odchod - čas</label>
</FormField>
<FormField label="Odchod - čas">
<AdminDatePicker
mode="time"
value={form.departure_time}
onChange={(val) => setForm({ ...form, departure_time: val })}
/>
</div>
</FormField>
</div>
</>
)}
<div className="admin-form-group">
<label className="admin-form-label">Poznámka</label>
<FormField label="Poznámka">
<textarea
value={form.notes}
onChange={(e) => setForm({ ...form, notes: e.target.value })}
className="admin-form-textarea"
rows={3}
/>
</div>
</FormField>
<div className="admin-form-actions">
<Link to="/attendance/admin" className="admin-btn admin-btn-secondary">

View File

@@ -6,6 +6,7 @@ import Forbidden from '../components/Forbidden'
import { motion } from 'framer-motion'
import AdminDatePicker from '../components/AdminDatePicker'
import { formatDate, formatDatetime, formatTime, calculateWorkMinutes, formatMinutes, getLeaveTypeName, getLeaveTypeBadgeClass, calculateWorkMinutesPrint, formatTimeOrDatetimePrint } from '../utils/attendanceHelpers'
import FormField from '../components/FormField'
import apiFetch from '../utils/api'
const API_BASE = '/api/admin'
@@ -242,14 +243,13 @@ export default function AttendanceHistory() {
>
<div className="admin-card-body">
<div className="admin-form-row">
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Měsíc</label>
<FormField label="Měsíc">
<AdminDatePicker
mode="month"
value={month}
onChange={(val) => setMonth(val)}
/>
</div>
</FormField>
</div>
</div>
</motion.div>

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'
import { useAlert } from '../context/AlertContext'
import { useAuth } from '../context/AuthContext'
import Forbidden from '../components/Forbidden'
import FormField from '../components/FormField'
import { motion } from 'framer-motion'
import apiFetch from '../utils/api'
@@ -370,82 +371,74 @@ export default function CompanySettings() {
</div>
<div className="admin-card-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label">Název firmy</label>
<FormField label="Název firmy">
<input
type="text"
value={form.company_name}
onChange={(e) => updateField('company_name', e.target.value)}
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Ulice</label>
<FormField label="Ulice">
<input
type="text"
value={form.street}
onChange={(e) => updateField('street', e.target.value)}
className="admin-form-input"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Město</label>
</FormField>
<FormField label="Město">
<input
type="text"
value={form.city}
onChange={(e) => updateField('city', e.target.value)}
className="admin-form-input"
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">PSČ</label>
<FormField label="PSČ">
<input
type="text"
value={form.postal_code}
onChange={(e) => updateField('postal_code', e.target.value)}
className="admin-form-input"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Země</label>
</FormField>
<FormField label="Země">
<input
type="text"
value={form.country}
onChange={(e) => updateField('country', e.target.value)}
className="admin-form-input"
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">IČO</label>
<FormField label="IČO">
<input
type="text"
value={form.company_id}
onChange={(e) => updateField('company_id', e.target.value)}
className="admin-form-input"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">DIČ</label>
</FormField>
<FormField label="DIČ">
<input
type="text"
value={form.vat_id}
onChange={(e) => updateField('vat_id', e.target.value)}
className="admin-form-input"
/>
</div>
</FormField>
</div>
<div style={{ marginTop: 4 }}>
<label className="admin-form-label" style={{ display: 'block', marginBottom: 4 }}>Vlastní pole</label>
{customFields.map((field, idx) => (
<div key={field._key} style={{ marginBottom: 8 }}>
<div className="admin-form-row" style={{ marginBottom: 0, alignItems: 'flex-end' }}>
<div className="admin-form-group" style={{ flex: 1 }}>
{idx === 0 && <label className="admin-form-label" style={{ fontSize: '0.75rem' }}>Název</label>}
<FormField label={idx === 0 ? 'Název' : '\u00A0'} style={{ flex: 1 }}>
<input
type="text"
value={field.name}
@@ -457,9 +450,8 @@ export default function CompanySettings() {
className="admin-form-input"
placeholder="Např. Tel."
/>
</div>
<div className="admin-form-group" style={{ flex: 1 }}>
{idx === 0 && <label className="admin-form-label" style={{ fontSize: '0.75rem' }}>Hodnota</label>}
</FormField>
<FormField label={idx === 0 ? 'Hodnota' : '\u00A0'} style={{ flex: 1 }}>
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
<input
type="text"
@@ -498,7 +490,7 @@ export default function CompanySettings() {
</svg>
</button>
</div>
</div>
</FormField>
</div>
<label className="admin-form-checkbox" style={{ marginTop: 4 }}>
<input
@@ -622,8 +614,7 @@ export default function CompanySettings() {
</h4>
<div className="admin-form">
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label required">Název účtu</label>
<FormField label="Název účtu" required>
<input
type="text"
value={bankForm.account_name}
@@ -631,9 +622,8 @@ export default function CompanySettings() {
className="admin-form-input"
placeholder="Např. Hlavní CZK účet"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Název banky</label>
</FormField>
<FormField label="Název banky">
<input
type="text"
value={bankForm.bank_name}
@@ -641,11 +631,10 @@ export default function CompanySettings() {
className="admin-form-input"
placeholder="Např. MONETA Money Bank, a.s."
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Číslo účtu</label>
<FormField label="Číslo účtu">
<input
type="text"
value={bankForm.account_number}
@@ -653,9 +642,8 @@ export default function CompanySettings() {
className="admin-form-input"
placeholder="123456789/0600"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Měna</label>
</FormField>
<FormField label="Měna">
<select
value={bankForm.currency}
onChange={e => setBankForm(f => ({ ...f, currency: e.target.value }))}
@@ -666,11 +654,10 @@ export default function CompanySettings() {
<option value="USD">USD</option>
<option value="GBP">GBP</option>
</select>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">IBAN</label>
<FormField label="IBAN">
<input
type="text"
value={bankForm.iban}
@@ -678,9 +665,8 @@ export default function CompanySettings() {
className="admin-form-input"
placeholder="CZ65 0800 0000 1920 0014 5399"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">BIC / SWIFT</label>
</FormField>
<FormField label="BIC / SWIFT">
<input
type="text"
value={bankForm.bic}
@@ -688,7 +674,7 @@ export default function CompanySettings() {
className="admin-form-input"
placeholder="GIBACZPX"
/>
</div>
</FormField>
</div>
<label className="admin-form-checkbox">
<input
@@ -833,8 +819,7 @@ export default function CompanySettings() {
<div className="admin-card-body">
<div className="admin-form">
{/* Nabídky */}
<div className="admin-form-group">
<label className="admin-form-label">Nabídky prefix</label>
<FormField label="Nabídky — prefix">
<input
type="text"
value={form.quotation_prefix}
@@ -846,13 +831,12 @@ export default function CompanySettings() {
<small className="admin-form-hint">
Formát: ROK/PREFIX/ČÍSLO ukázka: {new Date().getFullYear()}/{form.quotation_prefix || 'N'}/001
</small>
</div>
</FormField>
<hr style={{ border: 'none', borderTop: '1px solid var(--border-color)', margin: '0.75rem 0' }} />
{/* Objednávky / Projekty */}
<div className="admin-form-group">
<label className="admin-form-label">Objednávky a projekty typový kód</label>
<FormField label="Objednávky a projekty — typový kód">
<input
type="text"
value={form.order_type_code}
@@ -864,13 +848,12 @@ export default function CompanySettings() {
<small className="admin-form-hint">
Formát: RRKÓD#### ukázka: {currentYear}{form.order_type_code || '71'}0001
</small>
</div>
</FormField>
<hr style={{ border: 'none', borderTop: '1px solid var(--border-color)', margin: '0.75rem 0' }} />
{/* Faktury */}
<div className="admin-form-group">
<label className="admin-form-label">Faktury typový kód</label>
<FormField label="Faktury — typový kód">
<input
type="text"
value={form.invoice_type_code}
@@ -882,7 +865,7 @@ export default function CompanySettings() {
<small className="admin-form-hint">
Formát: RRKÓD#### ukázka: {currentYear}{form.invoice_type_code || '81'}0001
</small>
</div>
</FormField>
</div>
</div>
</motion.div>
@@ -900,8 +883,7 @@ export default function CompanySettings() {
<div className="admin-card-body">
<div className="admin-form">
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Výchozí měna</label>
<FormField label="Výchozí měna">
<select
value={form.default_currency}
onChange={(e) => updateField('default_currency', e.target.value)}
@@ -912,9 +894,8 @@ export default function CompanySettings() {
<option value="CZK">CZK ()</option>
<option value="GBP">GBP (£)</option>
</select>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Výchozí sazba DPH (%)</label>
</FormField>
<FormField label="Výchozí sazba DPH (%)">
<input
type="number"
value={form.default_vat_rate}
@@ -922,7 +903,7 @@ export default function CompanySettings() {
className="admin-form-input"
step="0.1"
/>
</div>
</FormField>
</div>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import { useNavigate, useSearchParams, Link } from 'react-router-dom'
import { useAlert } from '../context/AlertContext'
import { useAuth } from '../context/AuthContext'
import Forbidden from '../components/Forbidden'
import FormField from '../components/FormField'
import AdminDatePicker from '../components/AdminDatePicker'
import { motion } from 'framer-motion'
import apiFetch from '../utils/api'
@@ -462,17 +463,15 @@ export default function InvoiceCreate() {
<h3 className="admin-card-title">Základní údaje</h3>
<div className="admin-form">
<div className="offers-form-row-3">
<div className="admin-form-group">
<label className="admin-form-label">Číslo faktury</label>
<FormField label="Číslo faktury">
<input
type="text"
value={invoiceNumber}
onChange={(e) => setInvoiceNumber(e.target.value)}
className="admin-form-input"
/>
</div>
<div className={`admin-form-group${errors.customer_id ? ' has-error' : ''}`}>
<label className="admin-form-label required">Odběratel</label>
</FormField>
<FormField label="Odběratel" error={errors.customer_id} required>
{form.customer_id ? (
<div className="offers-customer-selected">
<span>{form.customer_name}</span>
@@ -526,10 +525,8 @@ export default function InvoiceCreate() {
)}
</div>
)}
{errors.customer_id && <span className="admin-form-error">{errors.customer_id}</span>}
</div>
<div className="admin-form-group">
<label className="admin-form-label">Vystavil</label>
</FormField>
<FormField label="Vystavil">
<input
type="text"
value={form.issued_by}
@@ -537,12 +534,11 @@ export default function InvoiceCreate() {
readOnly
style={{ backgroundColor: 'var(--bg-secondary)', cursor: 'default' }}
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className={`admin-form-group${errors.issue_date ? ' has-error' : ''}`}>
<label className="admin-form-label required">Datum vystavení</label>
<FormField label="Datum vystavení" error={errors.issue_date} required>
<AdminDatePicker
mode="date"
value={form.issue_date}
@@ -551,10 +547,8 @@ export default function InvoiceCreate() {
setErrors(prev => ({ ...prev, issue_date: undefined }))
}}
/>
{errors.issue_date && <span className="admin-form-error">{errors.issue_date}</span>}
</div>
<div className="admin-form-group">
<label className="admin-form-label">Splatnost (dny)</label>
</FormField>
<FormField label="Splatnost (dny)">
<select
value={dueDays}
onChange={(e) => setDueDays(Number(e.target.value))}
@@ -569,9 +563,8 @@ export default function InvoiceCreate() {
Splatnost: {new Date(form.due_date).toLocaleDateString('cs-CZ')}
</span>
)}
</div>
<div className={`admin-form-group${errors.tax_date ? ' has-error' : ''}`}>
<label className="admin-form-label required">DÚZP</label>
</FormField>
<FormField label="DÚZP" error={errors.tax_date} required>
<AdminDatePicker
mode="date"
value={form.tax_date}
@@ -580,13 +573,11 @@ export default function InvoiceCreate() {
setErrors(prev => ({ ...prev, tax_date: undefined }))
}}
/>
{errors.tax_date && <span className="admin-form-error">{errors.tax_date}</span>}
</div>
</FormField>
</div>
<div className="offers-form-row-3">
<div className="admin-form-group">
<label className="admin-form-label">Forma úhrady</label>
<FormField label="Forma úhrady">
<select
value={form.payment_method}
onChange={(e) => setForm(prev => ({ ...prev, payment_method: e.target.value }))}
@@ -596,9 +587,8 @@ export default function InvoiceCreate() {
<option value="Hotově">Hotově</option>
<option value="Dobírka">Dobírka</option>
</select>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Měna</label>
</FormField>
<FormField label="Měna">
<select
value={form.currency}
onChange={(e) => setForm(prev => ({ ...prev, currency: e.target.value }))}
@@ -608,9 +598,8 @@ export default function InvoiceCreate() {
<option value="EUR">EUR ()</option>
<option value="USD">USD ($)</option>
</select>
</div>
<div className="admin-form-group">
<label className="admin-form-label">DPH</label>
</FormField>
<FormField label="DPH">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<label className="admin-form-checkbox" style={{ whiteSpace: 'nowrap' }}>
<input
@@ -621,11 +610,10 @@ export default function InvoiceCreate() {
<span>Uplatnit DPH</span>
</label>
</div>
</div>
</FormField>
</div>
<div className={`admin-form-group${errors.bank_account_id ? ' has-error' : ''}`}>
<label className="admin-form-label required">Bankovní účet</label>
<FormField label="Bankovní účet" error={errors.bank_account_id} required>
<select
value={form.bank_account_id}
onChange={(e) => {
@@ -641,8 +629,7 @@ export default function InvoiceCreate() {
</option>
))}
</select>
{errors.bank_account_id && <span className="admin-form-error">{errors.bank_account_id}</span>}
</div>
</FormField>
</div>
</motion.div>

View File

@@ -5,6 +5,7 @@ import { useParams, useNavigate, Link } from 'react-router-dom'
import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import Forbidden from '../components/Forbidden'
import FormField from '../components/FormField'
import apiFetch from '../utils/api'
import DOMPurify from 'dompurify'
@@ -376,8 +377,7 @@ 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="admin-form-group">
<label className="admin-form-label">Zákazník</label>
<FormField label="Zákazník">
<div style={{ fontWeight: 500 }}>{invoice.customer_name || '—'}</div>
{invoice.customer && (
<div className="text-tertiary" style={{ fontSize: '0.8rem', marginTop: '0.2rem' }}>
@@ -385,9 +385,8 @@ export default function InvoiceDetail() {
{invoice.customer.vat_id && ` · DIČ: ${invoice.customer.vat_id}`}
</div>
)}
</div>
<div className="admin-form-group">
<label className="admin-form-label">Objednávka</label>
</FormField>
<FormField label="Objednávka">
<div>
{invoice.order_id ? (
<Link to={`/orders/${invoice.order_id}`} className="link-accent">
@@ -395,48 +394,40 @@ export default function InvoiceDetail() {
</Link>
) : '—'}
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Měna</label>
</FormField>
<FormField label="Měna">
<div>{invoice.currency}</div>
</div>
</FormField>
</div>
<div className="offers-form-row-3" style={{ marginBottom: '0.5rem' }}>
<div className="admin-form-group">
<label className="admin-form-label">Datum vystavení</label>
<FormField label="Datum vystavení">
<div>{formatDate(invoice.issue_date)}</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Datum splatnosti</label>
</FormField>
<FormField label="Datum splatnosti">
<div className={invoice.status === 'overdue' ? 'text-danger fw-600' : ''}>
{formatDate(invoice.due_date)}
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">DÚZP</label>
</FormField>
<FormField label="DÚZP">
<div>{formatDate(invoice.tax_date)}</div>
</div>
</FormField>
</div>
<div className="offers-form-row-3">
<div className="admin-form-group">
<label className="admin-form-label">Forma úhrady</label>
<FormField label="Forma úhrady">
<div>{invoice.payment_method}</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Variabilní symbol</label>
</FormField>
<FormField label="Variabilní symbol">
<div>{invoice.invoice_number}</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Vystavil</label>
</FormField>
<FormField label="Vystavil">
<div>{invoice.issued_by || '—'}</div>
</div>
</FormField>
</div>
{invoice.paid_date && (
<div className="admin-form-row" style={{ marginTop: '0.5rem' }}>
<div className="admin-form-group">
<label className="admin-form-label">Datum úhrady</label>
<FormField label="Datum úhrady">
<div style={{ color: 'var(--success)', fontWeight: 500 }}>{formatDate(invoice.paid_date)}</div>
</div>
</FormField>
</div>
)}
</div>

View File

@@ -8,6 +8,7 @@ import { czechPlural } from '../utils/formatters'
import ConfirmModal from '../components/ConfirmModal'
import Forbidden from '../components/Forbidden'
import useModalLock from '../hooks/useModalLock'
import FormField from '../components/FormField'
const API_BASE = '/api/admin'
@@ -417,8 +418,7 @@ export default function LeaveApproval() {
{formatDate(rejectModal.request.date_from)} {formatDate(rejectModal.request.date_to)} ({rejectModal.request.total_days} dnů)
</p>
)}
<div className="admin-form-group">
<label className="admin-form-label required">Důvod zamítnutí</label>
<FormField label="Důvod zamítnutí" required>
<textarea
value={rejectNote}
onChange={(e) => setRejectNote(e.target.value)}
@@ -427,7 +427,7 @@ export default function LeaveApproval() {
rows={3}
autoFocus
/>
</div>
</FormField>
</div>
<div className="admin-modal-footer">
<button

View File

@@ -5,6 +5,7 @@ import { useAuth } from '../context/AuthContext'
import { useAlert } from '../context/AlertContext'
import { useTheme } from '../../context/ThemeContext'
import { shouldShowSessionExpiredAlert, shouldShowLogoutAlert } from '../utils/api'
import FormField from '../components/FormField'
export default function Login() {
const { login, verify2FA, isAuthenticated, loading: authLoading } = useAuth()
@@ -166,10 +167,7 @@ export default function Login() {
</div>
<form onSubmit={handleSubmit} className="admin-form">
<div className="admin-form-group">
<label htmlFor="username" className="admin-form-label">
Uživatelské jméno nebo e-mail
</label>
<FormField label="Uživatelské jméno nebo e-mail">
<input
id="username"
type="text"
@@ -180,12 +178,9 @@ export default function Login() {
className="admin-form-input"
placeholder="Zadejte uživatelské jméno"
/>
</div>
</FormField>
<div className="admin-form-group">
<label htmlFor="password" className="admin-form-label">
Heslo
</label>
<FormField label="Heslo">
<input
id="password"
type="password"
@@ -196,7 +191,7 @@ export default function Login() {
className="admin-form-input"
placeholder="Zadejte heslo"
/>
</div>
</FormField>
<label className="admin-form-checkbox">
<input
@@ -256,10 +251,7 @@ export default function Login() {
</div>
<form onSubmit={handle2FASubmit} className="admin-form">
<div className="admin-form-group">
<label htmlFor="totp-code" className="admin-form-label">
{useBackupCode ? 'Záložní kód' : 'Ověřovací kód'}
</label>
<FormField label={useBackupCode ? 'Záložní kód' : 'Ověřovací kód'}>
<input
ref={totpInputRef}
id="totp-code"
@@ -283,7 +275,7 @@ export default function Login() {
fontFamily: 'monospace'
}}
/>
</div>
</FormField>
<button
type="submit"

View File

@@ -5,6 +5,7 @@ import { useParams, useNavigate, Link } from 'react-router-dom'
import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import Forbidden from '../components/Forbidden'
import AdminDatePicker from '../components/AdminDatePicker'
import OfferItemsSection from '../components/OfferItemsSection'
@@ -319,8 +320,7 @@ export default function OfferDetail() {
<h3 className="admin-card-title">Základní údaje</h3>
<div className="admin-form">
<div className="offers-form-row-3">
<div className="admin-form-group">
<label className="admin-form-label">Číslo nabídky</label>
<FormField label="Číslo nabídky">
<input
type="text"
value={form.quotation_number}
@@ -328,9 +328,8 @@ export default function OfferDetail() {
readOnly
style={{ backgroundColor: 'var(--bg-secondary)', cursor: 'default' }}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Kód projektu</label>
</FormField>
<FormField label="Kód projektu">
<input
type="text"
value={form.project_code}
@@ -339,7 +338,7 @@ export default function OfferDetail() {
placeholder="Volitelný kód projektu"
readOnly={isInvalidated}
/>
</div>
</FormField>
<OfferCustomerPicker
customers={customers}
customerId={form.customer_id}
@@ -352,8 +351,7 @@ export default function OfferDetail() {
</div>
<div className="admin-form-row">
<div className={`admin-form-group${errors.created_at ? ' has-error' : ''}`}>
<label className="admin-form-label required">Datum vytvoření</label>
<FormField label="Datum vytvoření" error={errors.created_at} required>
{isInvalidated ? (
<input type="text" value={form.created_at} className="admin-form-input" readOnly />
) : (
@@ -366,10 +364,8 @@ export default function OfferDetail() {
}}
/>
)}
{errors.created_at && <span className="admin-form-error">{errors.created_at}</span>}
</div>
<div className={`admin-form-group${errors.valid_until ? ' has-error' : ''}`}>
<label className="admin-form-label required">Platnost do</label>
</FormField>
<FormField label="Platnost do" error={errors.valid_until} required>
{isInvalidated ? (
<input type="text" value={form.valid_until} className="admin-form-input" readOnly />
) : (
@@ -382,13 +378,11 @@ export default function OfferDetail() {
}}
/>
)}
{errors.valid_until && <span className="admin-form-error">{errors.valid_until}</span>}
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Měna</label>
<FormField label="Měna">
<select
value={form.currency}
onChange={(e) => updateForm('currency', e.target.value)}
@@ -400,9 +394,8 @@ export default function OfferDetail() {
<option value="CZK">CZK ()</option>
<option value="GBP">GBP (&pound;)</option>
</select>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Jazyk nabídky</label>
</FormField>
<FormField label="Jazyk nabídky">
<select
value={form.language}
onChange={(e) => updateForm('language', e.target.value)}
@@ -412,12 +405,11 @@ export default function OfferDetail() {
<option value="EN">English</option>
<option value="CZ">Čeština</option>
</select>
</div>
</FormField>
</div>
<div className="offers-form-row-3">
<div className="admin-form-group">
<label className="admin-form-label">Sazba DPH (%)</label>
<FormField label="Sazba DPH (%)">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<input
type="number"
@@ -438,9 +430,8 @@ export default function OfferDetail() {
<span>Účtovat DPH</span>
</label>
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Směnný kurz</label>
</FormField>
<FormField label="Směnný kurz">
<input
type="number"
value={form.exchange_rate}
@@ -450,7 +441,7 @@ export default function OfferDetail() {
step="0.0001"
readOnly={isInvalidated}
/>
</div>
</FormField>
</div>
</div>
</motion.div>
@@ -510,8 +501,7 @@ export default function OfferDetail() {
</div>
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label required">Číslo objednávky zákazníka</label>
<FormField label="Číslo objednávky zákazníka" required>
<input
type="text"
value={customerOrderNumber}
@@ -521,9 +511,8 @@ export default function OfferDetail() {
placeholder="Např. PO-2026-001"
autoFocus
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Příloha (PDF)</label>
</FormField>
<FormField label="Příloha (PDF)">
{orderAttachment ? (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--accent-color)" strokeWidth="2">
@@ -562,7 +551,7 @@ export default function OfferDetail() {
</label>
)}
<small className="admin-form-hint" style={{ marginTop: '0.25rem' }}>Max 10 MB</small>
</div>
</FormField>
</div>
</div>
<div className="admin-modal-footer">

View File

@@ -13,6 +13,7 @@ import useTableSort from '../hooks/useTableSort'
import useListData from '../hooks/useListData'
import useModalLock from '../hooks/useModalLock'
import Pagination from '../components/Pagination'
import FormField from '../components/FormField'
const API_BASE = '/api/admin'
const DRAFT_KEY = 'boha_offer_draft'
@@ -564,8 +565,7 @@ export default function Offers() {
</div>
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label required">Číslo objednávky zákazníka</label>
<FormField label="Číslo objednávky zákazníka" required>
<input
type="text"
value={customerOrderNumber}
@@ -575,9 +575,8 @@ export default function Offers() {
placeholder="Např. PO-2026-001"
autoFocus
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Příloha (PDF)</label>
</FormField>
<FormField label="Příloha (PDF)">
{orderAttachment ? (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--accent-color)" strokeWidth="2">
@@ -616,7 +615,7 @@ export default function Offers() {
</label>
)}
<small className="admin-form-hint" style={{ marginTop: '0.25rem' }}>Max 10 MB</small>
</div>
</FormField>
</div>
</div>
<div className="admin-modal-footer">

View File

@@ -3,6 +3,7 @@ import { useAlert } from '../context/AlertContext'
import { useAuth } from '../context/AuthContext'
import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import Forbidden from '../components/Forbidden'
import useModalLock from '../hooks/useModalLock'
@@ -408,8 +409,7 @@ export default function OffersCustomers() {
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label required">Název</label>
<FormField label="Název" required>
<input
type="text"
value={form.name}
@@ -417,64 +417,58 @@ export default function OffersCustomers() {
className="admin-form-input"
placeholder="Název firmy / jméno"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Ulice</label>
</FormField>
<FormField label="Ulice">
<input
type="text"
value={form.street}
onChange={(e) => setForm(prev => ({ ...prev, street: e.target.value }))}
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Město</label>
<FormField label="Město">
<input
type="text"
value={form.city}
onChange={(e) => setForm(prev => ({ ...prev, city: e.target.value }))}
className="admin-form-input"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">PSČ</label>
</FormField>
<FormField label="PSČ">
<input
type="text"
value={form.postal_code}
onChange={(e) => setForm(prev => ({ ...prev, postal_code: e.target.value }))}
className="admin-form-input"
/>
</FormField>
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Země</label>
<FormField label="Země">
<input
type="text"
value={form.country}
onChange={(e) => setForm(prev => ({ ...prev, country: e.target.value }))}
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">IČO</label>
<FormField label="IČO">
<input
type="text"
value={form.company_id}
onChange={(e) => setForm(prev => ({ ...prev, company_id: e.target.value }))}
className="admin-form-input"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">DIČ</label>
</FormField>
<FormField label="DIČ">
<input
type="text"
value={form.vat_id}
onChange={(e) => setForm(prev => ({ ...prev, vat_id: e.target.value }))}
className="admin-form-input"
/>
</div>
</FormField>
</div>
{/* Dynamic custom fields */}
@@ -483,8 +477,7 @@ export default function OffersCustomers() {
{customFields.map((field, idx) => (
<div key={field._key} style={{ marginBottom: 8 }}>
<div className="admin-form-row" style={{ marginBottom: 0, alignItems: 'flex-end' }}>
<div className="admin-form-group" style={{ flex: 1 }}>
{idx === 0 && <label className="admin-form-label" style={{ fontSize: '0.75rem' }}>Název</label>}
<FormField label={idx === 0 ? 'Název' : '\u00A0'} style={{ flex: 1 }}>
<input
type="text"
value={field.name}
@@ -496,9 +489,8 @@ export default function OffersCustomers() {
className="admin-form-input"
placeholder="Např. Kontakt"
/>
</div>
<div className="admin-form-group" style={{ flex: 1 }}>
{idx === 0 && <label className="admin-form-label" style={{ fontSize: '0.75rem' }}>Hodnota</label>}
</FormField>
<FormField label={idx === 0 ? 'Hodnota' : '\u00A0'} style={{ flex: 1 }}>
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
<input
type="text"
@@ -537,7 +529,7 @@ export default function OffersCustomers() {
</svg>
</button>
</div>
</div>
</FormField>
</div>
<label className="admin-form-checkbox" style={{ marginTop: 4 }}>
<input

View File

@@ -3,6 +3,7 @@ import { useAlert } from '../context/AlertContext'
import { useAuth } from '../context/AuthContext'
import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import Forbidden from '../components/Forbidden'
import RichEditor from '../components/RichEditor'
import useModalLock from '../hooks/useModalLock'
@@ -238,23 +239,19 @@ function ItemTemplatesTab() {
</div>
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label required">Název</label>
<FormField label="Název" required>
<input type="text" value={form.name} onChange={(e) => setForm(p => ({ ...p, name: e.target.value }))} className="admin-form-input" />
</div>
<div className="admin-form-group">
<label className="admin-form-label">Popis</label>
</FormField>
<FormField label="Popis">
<textarea value={form.description} onChange={(e) => setForm(p => ({ ...p, description: e.target.value }))} className="admin-form-input" rows={2} />
</div>
</FormField>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Výchozí cena</label>
<FormField label="Výchozí cena">
<input type="number" value={form.default_price} onChange={(e) => setForm(p => ({ ...p, default_price: parseFloat(e.target.value) || 0 }))} className="admin-form-input" step="0.01" />
</div>
<div className="admin-form-group">
<label className="admin-form-label">Kategorie</label>
</FormField>
<FormField label="Kategorie">
<input type="text" value={form.category} onChange={(e) => setForm(p => ({ ...p, category: e.target.value }))} className="admin-form-input" />
</div>
</FormField>
</div>
</div>
</div>
@@ -509,10 +506,9 @@ function ScopeTemplatesTab() {
</div>
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label required">Název šablony</label>
<FormField label="Název šablony" required>
<input type="text" value={form.name} onChange={(e) => setForm(p => ({ ...p, name: e.target.value }))} className="admin-form-input" />
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label" style={{ marginBottom: '0.5rem' }}>Sekce</label>
@@ -538,30 +534,21 @@ function ScopeTemplatesTab() {
</div>
<div className="admin-form">
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">
<span className="offers-lang-badge">EN</span>
Název sekce
</label>
<FormField label={<><span className="offers-lang-badge">EN</span> Název sekce</>}>
<input type="text" value={section.title} onChange={(e) => updateSection(index, 'title', e.target.value)} className="admin-form-input" placeholder="Název sekce (anglicky)" />
</div>
<div className="admin-form-group">
<label className="admin-form-label">
<span className="offers-lang-badge offers-lang-badge-cz">CZ</span>
Název sekce
</label>
</FormField>
<FormField label={<><span className="offers-lang-badge offers-lang-badge-cz">CZ</span> Název sekce</>}>
<input type="text" value={section.title_cz} onChange={(e) => updateSection(index, 'title_cz', e.target.value)} className="admin-form-input" placeholder="Název sekce (česky)" />
</FormField>
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Obsah</label>
<FormField label="Obsah">
<RichEditor
value={section.content}
onChange={(val) => updateSection(index, 'content', val)}
placeholder="Obsah sekce..."
minHeight="150px"
/>
</div>
</FormField>
</div>
</div>
))}

View File

@@ -5,6 +5,7 @@ import { useAuth } from '../context/AuthContext'
import { useParams, useNavigate, Link } from 'react-router-dom'
import { motion } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import Forbidden from '../components/Forbidden'
import apiFetch from '../utils/api'
@@ -372,8 +373,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-group">
<label className="admin-form-label">Nabídka</label>
<FormField label="Nabídka">
<div>
<Link to={`/offers/${order.quotation_id}`} className="link-accent">
{order.quotation_number}
@@ -382,9 +382,8 @@ export default function OrderDetail() {
<span className="text-tertiary" style={{ marginLeft: '0.5rem' }}>({order.project_code})</span>
)}
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Projekt</label>
</FormField>
<FormField label="Projekt">
<div>
{order.project ? (
<Link to={`/projects/${order.project.id}`} className="link-accent">
@@ -392,29 +391,24 @@ export default function OrderDetail() {
</Link>
) : '—'}
</div>
</div>
</FormField>
</div>
<div className="admin-form-row admin-form-row-3" style={{ marginBottom: '0.5rem' }}>
<div className="admin-form-group">
<label className="admin-form-label">Zákazník</label>
<FormField label="Zákazník">
<div style={{ fontWeight: 500 }}>{order.customer_name || '—'}</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Číslo obj. zákazníka</label>
</FormField>
<FormField label="Číslo obj. zákazníka">
<div>{order.customer_order_number || '—'}</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Měna</label>
</FormField>
<FormField label="Měna">
<div>{order.currency}</div>
</div>
</FormField>
</div>
<div className="admin-form-row admin-form-row-3" style={{ marginBottom: '0.5rem' }}>
<div className="admin-form-group">
<label className="admin-form-label">Datum vytvoření</label>
<FormField label="Datum vytvoření">
<div>{formatDate(order.created_at)}</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Příloha</label>
</FormField>
<FormField label="Příloha">
<div>
{order.attachment_name ? (
<button
@@ -435,7 +429,7 @@ export default function OrderDetail() {
</button>
) : '—'}
</div>
</div>
</FormField>
</div>
</div>
</motion.div>
@@ -556,7 +550,7 @@ export default function OrderDetail() {
>
<div className="admin-card-body">
<h3 className="admin-card-title">Poznámky</h3>
<div className="admin-form-group">
<FormField label="Poznámky">
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
@@ -565,7 +559,7 @@ export default function OrderDetail() {
placeholder="Interní poznámky k objednávce..."
disabled={!hasPermission('orders.edit')}
/>
</div>
</FormField>
{hasPermission('orders.edit') && (
<div style={{ marginTop: '0.5rem' }}>
<button

View File

@@ -3,6 +3,7 @@ import { useNavigate, Link } from 'react-router-dom'
import { useAlert } from '../context/AlertContext'
import { useAuth } from '../context/AuthContext'
import { motion } from 'framer-motion'
import FormField from '../components/FormField'
import Forbidden from '../components/Forbidden'
import AdminDatePicker from '../components/AdminDatePicker'
import apiFetch from '../utils/api'
@@ -190,8 +191,7 @@ export default function ProjectCreate() {
<h3 className="admin-card-title">Základní údaje</h3>
<div className="admin-form">
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Číslo projektu</label>
<FormField label="Číslo projektu">
<input
type="text"
value={form.project_number}
@@ -199,9 +199,8 @@ export default function ProjectCreate() {
className="admin-form-input"
placeholder="Ponechte prázdné pro automatické"
/>
</div>
<div className={`admin-form-group${errors.name ? ' has-error' : ''}`}>
<label className="admin-form-label required">Název</label>
</FormField>
<FormField label="Název" error={errors.name} required>
<input
type="text"
value={form.name}
@@ -209,13 +208,11 @@ export default function ProjectCreate() {
className="admin-form-input"
placeholder="Název projektu"
/>
{errors.name && <span className="admin-form-error">{errors.name}</span>}
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className={`admin-form-group${errors.customer_id ? ' has-error' : ''}`}>
<label className="admin-form-label required">Zákazník</label>
<FormField label="Zákazník" error={errors.customer_id} required>
{form.customer_id ? (
<div className="offers-customer-selected">
<span>{form.customer_name}</span>
@@ -257,16 +254,14 @@ export default function ProjectCreate() {
)}
</div>
)}
{errors.customer_id && <span className="admin-form-error">{errors.customer_id}</span>}
</div>
<div className="admin-form-group">
<label className="admin-form-label">Datum zahájení</label>
</FormField>
<FormField label="Datum zahájení">
<AdminDatePicker
mode="date"
value={form.start_date}
onChange={(val) => updateForm('start_date', val)}
/>
</div>
</FormField>
</div>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import { motion } from 'framer-motion'
import Forbidden from '../components/Forbidden'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import AdminDatePicker from '../components/AdminDatePicker'
import apiFetch from '../utils/api'
const API_BASE = '/api/admin'
@@ -300,8 +301,7 @@ export default function ProjectDetail() {
<h3 className="admin-card-title">Základní údaje</h3>
<div className="admin-form">
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Číslo projektu</label>
<FormField label="Číslo projektu">
<input
type="text"
value={project.project_number}
@@ -309,9 +309,8 @@ export default function ProjectDetail() {
readOnly
style={{ backgroundColor: 'var(--bg-secondary)', cursor: 'default' }}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Název</label>
</FormField>
<FormField label="Název">
<input
type="text"
value={form.name}
@@ -320,12 +319,11 @@ export default function ProjectDetail() {
placeholder="Název projektu"
disabled={!canEdit}
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Zákazník</label>
<FormField label="Zákazník">
<input
type="text"
value={project.customer_name || '—'}
@@ -333,9 +331,8 @@ export default function ProjectDetail() {
readOnly
style={{ backgroundColor: 'var(--bg-secondary)', cursor: 'default' }}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Stav</label>
</FormField>
<FormField label="Stav">
<select
value={form.status}
onChange={(e) => updateForm('status', e.target.value)}
@@ -346,28 +343,26 @@ export default function ProjectDetail() {
<option value="dokonceny">Dokončený</option>
<option value="zruseny">Zrušený</option>
</select>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Datum zahájení</label>
<FormField label="Datum zahájení">
<AdminDatePicker
mode="date"
value={form.start_date}
onChange={(val) => updateForm('start_date', val)}
disabled={!canEdit}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Datum ukončení</label>
</FormField>
<FormField label="Datum ukončení">
<AdminDatePicker
mode="date"
value={form.end_date}
onChange={(val) => updateForm('end_date', val)}
disabled={!canEdit}
/>
</div>
</FormField>
</div>
</div>
@@ -505,8 +500,7 @@ export default function ProjectDetail() {
<div className="admin-card-body">
<h3 className="admin-card-title">Propojení</h3>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Objednávka</label>
<FormField label="Objednávka">
<div>
{project.order_id ? (
<Link to={`/orders/${project.order_id}`} className="link-accent">
@@ -519,9 +513,8 @@ export default function ProjectDetail() {
</Link>
) : '—'}
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Nabídka</label>
</FormField>
<FormField label="Nabídka">
<div>
{project.quotation_id ? (
<Link to={`/offers/${project.quotation_id}`} className="link-accent">
@@ -529,7 +522,7 @@ export default function ProjectDetail() {
</Link>
) : '—'}
</div>
</div>
</FormField>
</div>
</div>
</motion.div>

View File

@@ -3,6 +3,7 @@ import { useAlert } from '../context/AlertContext'
import { useAuth } from '../context/AuthContext'
import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import apiFetch from '../utils/api'
import { formatCurrency, formatDate, czechPlural } from '../utils/formatters'
import SortIcon from '../components/SortIcon'
@@ -664,30 +665,24 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
</button>
</div>
<div className="received-upload-card-fields">
<div className="admin-form-group">
<label className="admin-form-label required">Dodavatel</label>
<FormField label="Dodavatel" error={uploadErrors[idx]?.supplier_name} required>
<input
type="text"
className={`admin-form-input${uploadErrors[idx]?.supplier_name ? ' has-error' : ''}`}
value={uploadMeta[idx]?.supplier_name || ''}
onChange={(e) => updateMeta(idx, 'supplier_name', e.target.value)}
/>
{uploadErrors[idx]?.supplier_name && (
<div className="admin-form-error">{uploadErrors[idx].supplier_name}</div>
)}
</div>
<div className="admin-form-group">
<label className="admin-form-label">Č. faktury</label>
</FormField>
<FormField label="Č. faktury">
<input
type="text"
className="admin-form-input"
value={uploadMeta[idx]?.invoice_number || ''}
onChange={(e) => updateMeta(idx, 'invoice_number', e.target.value)}
/>
</div>
</FormField>
<div className="received-upload-row">
<div className="admin-form-group" style={{ flex: 1 }}>
<label className="admin-form-label required">Částka</label>
<FormField label="Částka" error={uploadErrors[idx]?.amount} required style={{ flex: 1 }}>
<input
type="number"
step="0.01"
@@ -696,12 +691,8 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
value={uploadMeta[idx]?.amount || ''}
onChange={(e) => updateMeta(idx, 'amount', e.target.value)}
/>
{uploadErrors[idx]?.amount && (
<div className="admin-form-error">{uploadErrors[idx].amount}</div>
)}
</div>
<div className="admin-form-group" style={{ width: '90px' }}>
<label className="admin-form-label">Měna</label>
</FormField>
<FormField label="Měna" style={{ width: '90px' }}>
<select
className="admin-form-select"
value={uploadMeta[idx]?.currency || 'CZK'}
@@ -709,9 +700,8 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
>
{CURRENCY_OPTIONS.map(c => <option key={c} value={c}>{c}</option>)}
</select>
</div>
<div className="admin-form-group" style={{ width: '90px' }}>
<label className="admin-form-label">DPH %</label>
</FormField>
<FormField label="DPH %" style={{ width: '90px' }}>
<select
className="admin-form-select"
value={uploadMeta[idx]?.vat_rate || '21'}
@@ -719,7 +709,7 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
>
{VAT_RATE_OPTIONS.map(r => <option key={r} value={String(r)}>{r}%</option>)}
</select>
</div>
</FormField>
</div>
{uploadMeta[idx]?.amount && (
<div style={{ fontSize: '0.75rem', color: 'var(--text-tertiary)', marginTop: '-0.25rem', marginBottom: '0.5rem' }}>
@@ -730,32 +720,29 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
</div>
)}
<div className="received-upload-row">
<div className="admin-form-group" style={{ flex: 1 }}>
<label className="admin-form-label">Datum vystavení</label>
<FormField label="Datum vystavení" style={{ flex: 1 }}>
<AdminDatePicker
mode="date"
value={uploadMeta[idx]?.issue_date || ''}
onChange={(val) => updateMeta(idx, 'issue_date', val)}
/>
</div>
<div className="admin-form-group" style={{ flex: 1 }}>
<label className="admin-form-label">Datum splatnosti</label>
</FormField>
<FormField label="Datum splatnosti" style={{ flex: 1 }}>
<AdminDatePicker
mode="date"
value={uploadMeta[idx]?.due_date || ''}
onChange={(val) => updateMeta(idx, 'due_date', val)}
/>
</FormField>
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Poznámka</label>
<FormField label="Poznámka">
<input
type="text"
className="admin-form-input"
value={uploadMeta[idx]?.notes || ''}
onChange={(e) => updateMeta(idx, 'notes', e.target.value)}
/>
</div>
</FormField>
</div>
</div>
))}
@@ -801,8 +788,7 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
</div>
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-group">
<label className="admin-form-label required">Dodavatel</label>
<FormField label="Dodavatel" required>
<input
type="text"
className="admin-form-input"
@@ -810,9 +796,8 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
onChange={(e) => setEditInvoice(prev => ({ ...prev, supplier_name: e.target.value }))}
readOnly={ro}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Č. faktury</label>
</FormField>
<FormField label="Č. faktury">
<input
type="text"
className="admin-form-input"
@@ -820,10 +805,9 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
onChange={(e) => setEditInvoice(prev => ({ ...prev, invoice_number: e.target.value }))}
readOnly={ro}
/>
</div>
</FormField>
<div className="admin-form-row admin-form-row-3">
<div className="admin-form-group">
<label className="admin-form-label required">Částka</label>
<FormField label="Částka" required>
<input
type="number"
step="0.01"
@@ -833,9 +817,8 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
onChange={(e) => setEditInvoice(prev => ({ ...prev, amount: e.target.value }))}
readOnly={ro}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Měna</label>
</FormField>
<FormField label="Měna">
<select
className="admin-form-select"
value={editInvoice.currency}
@@ -844,9 +827,8 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
>
{CURRENCY_OPTIONS.map(c => <option key={c} value={c}>{c}</option>)}
</select>
</div>
<div className="admin-form-group">
<label className="admin-form-label">DPH %</label>
</FormField>
<FormField label="DPH %">
<select
className="admin-form-select"
value={editInvoice.vat_rate}
@@ -855,7 +837,7 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
>
{VAT_RATE_OPTIONS.map(r => <option key={r} value={String(r)}>{r}%</option>)}
</select>
</div>
</FormField>
</div>
{editInvoice.amount && (
<div style={{ fontSize: '0.75rem', color: 'var(--text-tertiary)', marginBottom: '0.75rem' }}>
@@ -866,27 +848,24 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
</div>
)}
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Datum vystavení</label>
<FormField label="Datum vystavení">
<AdminDatePicker
mode="date"
value={editInvoice.issue_date || ''}
onChange={(val) => setEditInvoice(prev => ({ ...prev, issue_date: val }))}
disabled={ro}
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Datum splatnosti</label>
</FormField>
<FormField label="Datum splatnosti">
<AdminDatePicker
mode="date"
value={editInvoice.due_date || ''}
onChange={(val) => setEditInvoice(prev => ({ ...prev, due_date: val }))}
disabled={ro}
/>
</FormField>
</div>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Stav</label>
<FormField label="Stav">
<select
className="admin-form-select"
value={editInvoice.status}
@@ -896,9 +875,8 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
<option value="unpaid">Neuhrazena</option>
<option value="paid">Uhrazena</option>
</select>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Poznámka</label>
</FormField>
<FormField label="Poznámka">
<textarea
className="admin-form-input"
rows={3}
@@ -906,7 +884,7 @@ export default function ReceivedInvoicesProps({ statsMonth, statsYear, uploadOpe
onChange={(e) => setEditInvoice(prev => ({ ...prev, notes: e.target.value }))}
readOnly={ro}
/>
</div>
</FormField>
</div>
</div>
<div className="admin-modal-footer">

View File

@@ -4,6 +4,7 @@ import { useAuth } from '../context/AuthContext'
import { useNavigate } from 'react-router-dom'
import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import useModalLock from '../hooks/useModalLock'
import apiFetch from '../utils/api'
@@ -496,8 +497,7 @@ export default function Settings() {
</div>
)}
<div className="admin-form-group">
<label className="admin-form-label">Zobrazovaný název</label>
<FormField label="Zobrazovaný název">
<input
type="text"
value={form.display_name}
@@ -506,10 +506,9 @@ export default function Settings() {
placeholder="např. Manažer"
disabled={editingRole && isAdminRole(editingRole)}
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Systémový název (slug)</label>
<FormField label="Systémový název (slug)">
<input
type="text"
value={form.name}
@@ -523,10 +522,9 @@ export default function Settings() {
Pouze malá písmena, čísla a pomlčky. Nelze později změnit.
</small>
)}
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Popis</label>
<FormField label="Popis">
<textarea
value={form.description}
onChange={(e) => setForm(prev => ({ ...prev, description: e.target.value }))}
@@ -535,7 +533,7 @@ export default function Settings() {
placeholder="Volitelný popis role"
disabled={editingRole && isAdminRole(editingRole)}
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label" style={{ marginBottom: '0.75rem' }}>Oprávnění</label>

View File

@@ -6,6 +6,7 @@ import { motion, AnimatePresence } from 'framer-motion'
import AdminDatePicker from '../components/AdminDatePicker'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import useModalLock from '../hooks/useModalLock'
import Forbidden from '../components/Forbidden'
import { formatDate } from '../utils/attendanceHelpers'
@@ -479,8 +480,7 @@ export default function Trips() {
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-row">
<div className={`admin-form-group${errors.vehicle_id ? ' has-error' : ''}`}>
<label className="admin-form-label required">Vozidlo</label>
<FormField label="Vozidlo" error={errors.vehicle_id} required>
<select
value={form.vehicle_id}
onChange={(e) => {
@@ -496,11 +496,9 @@ export default function Trips() {
</option>
))}
</select>
{errors.vehicle_id && <span className="admin-form-error">{errors.vehicle_id}</span>}
</div>
</FormField>
<div className={`admin-form-group${errors.trip_date ? ' has-error' : ''}`}>
<label className="admin-form-label required">Datum jízdy</label>
<FormField label="Datum jízdy" error={errors.trip_date} required>
<AdminDatePicker
mode="date"
value={form.trip_date}
@@ -509,13 +507,11 @@ export default function Trips() {
setErrors(prev => ({ ...prev, trip_date: undefined }))
}}
/>
{errors.trip_date && <span className="admin-form-error">{errors.trip_date}</span>}
</div>
</FormField>
</div>
<div className="admin-form-row admin-form-row-3">
<div className={`admin-form-group${errors.start_km ? ' has-error' : ''}`}>
<label className="admin-form-label required">Počáteční stav km</label>
<FormField label="Počáteční stav km" error={errors.start_km} required>
<input
type="number"
inputMode="numeric"
@@ -527,11 +523,9 @@ export default function Trips() {
className="admin-form-input"
min="0"
/>
{errors.start_km && <span className="admin-form-error">{errors.start_km}</span>}
</div>
</FormField>
<div className={`admin-form-group${errors.end_km ? ' has-error' : ''}`}>
<label className="admin-form-label required">Konečný stav km</label>
<FormField label="Konečný stav km" error={errors.end_km} required>
<input
type="number"
inputMode="numeric"
@@ -543,11 +537,9 @@ export default function Trips() {
className="admin-form-input"
min="0"
/>
{errors.end_km && <span className="admin-form-error">{errors.end_km}</span>}
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Vzdálenost</label>
<FormField label="Vzdálenost">
<input
type="text"
value={`${formatKm(calculateDistance())} km`}
@@ -555,12 +547,11 @@ export default function Trips() {
readOnly
disabled
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className={`admin-form-group${errors.route_from ? ' has-error' : ''}`}>
<label className="admin-form-label required">Místo odjezdu</label>
<FormField label="Místo odjezdu" error={errors.route_from} required>
<input
type="text"
value={form.route_from}
@@ -571,11 +562,9 @@ export default function Trips() {
className="admin-form-input"
placeholder="Např. Praha"
/>
{errors.route_from && <span className="admin-form-error">{errors.route_from}</span>}
</div>
</FormField>
<div className={`admin-form-group${errors.route_to ? ' has-error' : ''}`}>
<label className="admin-form-label required">Místo příjezdu</label>
<FormField label="Místo příjezdu" error={errors.route_to} required>
<input
type="text"
value={form.route_to}
@@ -586,12 +575,10 @@ export default function Trips() {
className="admin-form-input"
placeholder="Např. Brno"
/>
{errors.route_to && <span className="admin-form-error">{errors.route_to}</span>}
</div>
</FormField>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Typ jízdy</label>
<FormField label="Typ jízdy">
<select
value={form.is_business}
onChange={(e) => setForm({ ...form, is_business: parseInt(e.target.value) })}
@@ -600,10 +587,9 @@ export default function Trips() {
<option value={1}>Služební</option>
<option value={0}>Soukromá</option>
</select>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Poznámky</label>
<FormField label="Poznámky">
<textarea
value={form.notes}
onChange={(e) => setForm({ ...form, notes: e.target.value })}
@@ -611,7 +597,7 @@ export default function Trips() {
rows={2}
placeholder="Volitelné poznámky..."
/>
</div>
</FormField>
</div>
</div>

View File

@@ -8,6 +8,7 @@ import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal'
import AdminDatePicker from '../components/AdminDatePicker'
import FormField from '../components/FormField'
import useModalLock from '../hooks/useModalLock'
import { formatDate } from '../utils/attendanceHelpers'
import { formatKm } from '../utils/formatters'
@@ -302,24 +303,21 @@ export default function TripsAdmin() {
>
<div className="admin-card-body">
<div className="admin-form-row admin-form-row-4">
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Od</label>
<FormField label="Od" style={{ marginBottom: 0 }}>
<AdminDatePicker
mode="date"
value={dateFrom}
onChange={(val) => setDateFrom(val)}
/>
</div>
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Do</label>
</FormField>
<FormField label="Do" style={{ marginBottom: 0 }}>
<AdminDatePicker
mode="date"
value={dateTo}
onChange={(val) => setDateTo(val)}
/>
</div>
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Vozidlo</label>
</FormField>
<FormField label="Vozidlo" style={{ marginBottom: 0 }}>
<select
value={filterVehicleId}
onChange={(e) => setFilterVehicleId(e.target.value)}
@@ -332,9 +330,8 @@ export default function TripsAdmin() {
</option>
))}
</select>
</div>
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Řidič</label>
</FormField>
<FormField label="Řidič" style={{ marginBottom: 0 }}>
<select
value={filterUserId}
onChange={(e) => setFilterUserId(e.target.value)}
@@ -347,7 +344,7 @@ export default function TripsAdmin() {
</option>
))}
</select>
</div>
</FormField>
</div>
</div>
</motion.div>
@@ -527,8 +524,7 @@ export default function TripsAdmin() {
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Vozidlo</label>
<FormField label="Vozidlo">
<select
value={editForm.vehicle_id}
onChange={(e) => setEditForm({ ...editForm, vehicle_id: e.target.value })}
@@ -540,21 +536,19 @@ export default function TripsAdmin() {
</option>
))}
</select>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Datum jízdy</label>
<FormField label="Datum jízdy">
<AdminDatePicker
mode="date"
value={editForm.trip_date}
onChange={(val) => setEditForm({ ...editForm, trip_date: val })}
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Počáteční stav km</label>
<FormField label="Počáteční stav km">
<input
type="number"
inputMode="numeric"
@@ -563,10 +557,9 @@ export default function TripsAdmin() {
className="admin-form-input"
min="0"
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Konečný stav km</label>
<FormField label="Konečný stav km">
<input
type="number"
inputMode="numeric"
@@ -575,10 +568,9 @@ export default function TripsAdmin() {
className="admin-form-input"
min="0"
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Vzdálenost</label>
<FormField label="Vzdálenost">
<input
type="text"
value={`${formatKm(calculateDistance())} km`}
@@ -586,33 +578,30 @@ export default function TripsAdmin() {
readOnly
disabled
/>
</div>
</FormField>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Místo odjezdu</label>
<FormField label="Místo odjezdu">
<input
type="text"
value={editForm.route_from}
onChange={(e) => setEditForm({ ...editForm, route_from: e.target.value })}
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Místo příjezdu</label>
<FormField label="Místo příjezdu">
<input
type="text"
value={editForm.route_to}
onChange={(e) => setEditForm({ ...editForm, route_to: e.target.value })}
className="admin-form-input"
/>
</div>
</FormField>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Typ jízdy</label>
<FormField label="Typ jízdy">
<select
value={editForm.is_business}
onChange={(e) => setEditForm({ ...editForm, is_business: parseInt(e.target.value) })}
@@ -621,17 +610,16 @@ export default function TripsAdmin() {
<option value={1}>Služební</option>
<option value={0}>Soukromá</option>
</select>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Poznámky</label>
<FormField label="Poznámky">
<textarea
value={editForm.notes}
onChange={(e) => setEditForm({ ...editForm, notes: e.target.value })}
className="admin-form-textarea"
rows={2}
/>
</div>
</FormField>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import AdminDatePicker from '../components/AdminDatePicker'
import Forbidden from '../components/Forbidden'
import { formatDate } from '../utils/attendanceHelpers'
import { formatKm } from '../utils/formatters'
import FormField from '../components/FormField'
import apiFetch from '../utils/api'
const API_BASE = '/api/admin'
@@ -82,16 +83,14 @@ export default function TripsHistory() {
>
<div className="admin-card-body">
<div className="admin-form-row">
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Měsíc</label>
<FormField label="Měsíc">
<AdminDatePicker
mode="month"
value={month}
onChange={(val) => setMonth(val)}
/>
</div>
<div className="admin-form-group" style={{ marginBottom: 0 }}>
<label className="admin-form-label">Vozidlo</label>
</FormField>
<FormField label="Vozidlo">
<select
value={vehicleId}
onChange={(e) => setVehicleId(e.target.value)}
@@ -104,7 +103,7 @@ export default function TripsHistory() {
</option>
))}
</select>
</div>
</FormField>
</div>
</div>
</motion.div>

View File

@@ -3,6 +3,7 @@ import { motion, AnimatePresence } from 'framer-motion'
import { useAuth } from '../context/AuthContext'
import { useAlert } from '../context/AlertContext'
import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import Forbidden from '../components/Forbidden'
import useModalLock from '../hooks/useModalLock'
@@ -357,8 +358,7 @@ export default function Users() {
<div className="admin-modal-body">
<div className="admin-form">
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">Jméno</label>
<FormField label="Jméno">
<input
type="text"
value={formData.first_name}
@@ -366,9 +366,8 @@ export default function Users() {
required
className="admin-form-input"
/>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Příjmení</label>
</FormField>
<FormField label="Příjmení">
<input
type="text"
value={formData.last_name}
@@ -376,11 +375,10 @@ export default function Users() {
required
className="admin-form-input"
/>
</div>
</FormField>
</div>
<div className="admin-form-group">
<label className="admin-form-label">Uživatelské jméno</label>
<FormField label="Uživatelské jméno">
<input
type="text"
value={formData.username}
@@ -388,10 +386,9 @@ export default function Users() {
required
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">E-mail</label>
<FormField label="E-mail">
<input
type="email"
value={formData.email}
@@ -399,12 +396,9 @@ export default function Users() {
required
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">
Heslo {editingUser && '(ponechte prázdné pro zachování stávajícího)'}
</label>
<FormField label={`Heslo ${editingUser ? '(ponechte prázdné pro zachování stávajícího)' : ''}`}>
<input
type="password"
value={formData.password}
@@ -412,10 +406,9 @@ export default function Users() {
required={!editingUser}
className="admin-form-input"
/>
</div>
</FormField>
<div className="admin-form-group">
<label className="admin-form-label">Role</label>
<FormField label="Role">
<select
value={formData.role_id}
onChange={(e) => setFormData({ ...formData, role_id: e.target.value })}
@@ -428,7 +421,7 @@ export default function Users() {
</option>
))}
</select>
</div>
</FormField>
<label className="admin-form-checkbox">
<input