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", "type": "project",
"require": { "require": {
"php": ">=8.1", "php": ">=8.1",
"firebase/php-jwt": "^6.11", "firebase/php-jwt": "^7.0",
"robthree/twofactorauth": "^3.0", "robthree/twofactorauth": "^3.0",
"chillerlan/php-qrcode": "^5.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", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "452831f603fe18144b4f4557d0c4ea01", "content-hash": "081e51355832fa208c1fe8833cb07d49",
"packages": [ "packages": [
{ {
"name": "chillerlan/php-qrcode", "name": "chillerlan/php-qrcode",
@@ -167,16 +167,16 @@
}, },
{ {
"name": "firebase/php-jwt", "name": "firebase/php-jwt",
"version": "v6.11.1", "version": "v7.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/firebase/php-jwt.git", "url": "https://github.com/firebase/php-jwt.git",
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", "url": "https://api.github.com/repos/firebase/php-jwt/zipball/28aa0694bcfdfa5e2959c394d5a1ee7a5083629e",
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -224,9 +224,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/firebase/php-jwt/issues", "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", "name": "robthree/twofactorauth",
@@ -308,77 +308,6 @@
} }
], ],
"time": "2026-01-05T13:17:41+00:00" "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": [ "packages-dev": [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import { useAuth } from '../context/AuthContext'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import ConfirmModal from '../components/ConfirmModal' import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import useModalLock from '../hooks/useModalLock' import useModalLock from '../hooks/useModalLock'
import apiFetch from '../utils/api' import apiFetch from '../utils/api'
@@ -496,8 +497,7 @@ export default function Settings() {
</div> </div>
)} )}
<div className="admin-form-group"> <FormField label="Zobrazovaný název">
<label className="admin-form-label">Zobrazovaný název</label>
<input <input
type="text" type="text"
value={form.display_name} value={form.display_name}
@@ -506,10 +506,9 @@ export default function Settings() {
placeholder="např. Manažer" placeholder="např. Manažer"
disabled={editingRole && isAdminRole(editingRole)} disabled={editingRole && isAdminRole(editingRole)}
/> />
</div> </FormField>
<div className="admin-form-group"> <FormField label="Systémový název (slug)">
<label className="admin-form-label">Systémový název (slug)</label>
<input <input
type="text" type="text"
value={form.name} value={form.name}
@@ -523,10 +522,9 @@ export default function Settings() {
Pouze malá písmena, čísla a pomlčky. Nelze později změnit. Pouze malá písmena, čísla a pomlčky. Nelze později změnit.
</small> </small>
)} )}
</div> </FormField>
<div className="admin-form-group"> <FormField label="Popis">
<label className="admin-form-label">Popis</label>
<textarea <textarea
value={form.description} value={form.description}
onChange={(e) => setForm(prev => ({ ...prev, description: e.target.value }))} onChange={(e) => setForm(prev => ({ ...prev, description: e.target.value }))}
@@ -535,7 +533,7 @@ export default function Settings() {
placeholder="Volitelný popis role" placeholder="Volitelný popis role"
disabled={editingRole && isAdminRole(editingRole)} disabled={editingRole && isAdminRole(editingRole)}
/> />
</div> </FormField>
<div className="admin-form-group"> <div className="admin-form-group">
<label className="admin-form-label" style={{ marginBottom: '0.75rem' }}>Oprávnění</label> <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 AdminDatePicker from '../components/AdminDatePicker'
import ConfirmModal from '../components/ConfirmModal' import ConfirmModal from '../components/ConfirmModal'
import FormField from '../components/FormField'
import useModalLock from '../hooks/useModalLock' import useModalLock from '../hooks/useModalLock'
import Forbidden from '../components/Forbidden' import Forbidden from '../components/Forbidden'
import { formatDate } from '../utils/attendanceHelpers' import { formatDate } from '../utils/attendanceHelpers'
@@ -479,8 +480,7 @@ export default function Trips() {
<div className="admin-modal-body"> <div className="admin-modal-body">
<div className="admin-form"> <div className="admin-form">
<div className="admin-form-row"> <div className="admin-form-row">
<div className={`admin-form-group${errors.vehicle_id ? ' has-error' : ''}`}> <FormField label="Vozidlo" error={errors.vehicle_id} required>
<label className="admin-form-label required">Vozidlo</label>
<select <select
value={form.vehicle_id} value={form.vehicle_id}
onChange={(e) => { onChange={(e) => {
@@ -496,11 +496,9 @@ export default function Trips() {
</option> </option>
))} ))}
</select> </select>
{errors.vehicle_id && <span className="admin-form-error">{errors.vehicle_id}</span>} </FormField>
</div>
<div className={`admin-form-group${errors.trip_date ? ' has-error' : ''}`}> <FormField label="Datum jízdy" error={errors.trip_date} required>
<label className="admin-form-label required">Datum jízdy</label>
<AdminDatePicker <AdminDatePicker
mode="date" mode="date"
value={form.trip_date} value={form.trip_date}
@@ -509,13 +507,11 @@ export default function Trips() {
setErrors(prev => ({ ...prev, trip_date: undefined })) setErrors(prev => ({ ...prev, trip_date: undefined }))
}} }}
/> />
{errors.trip_date && <span className="admin-form-error">{errors.trip_date}</span>} </FormField>
</div>
</div> </div>
<div className="admin-form-row admin-form-row-3"> <div className="admin-form-row admin-form-row-3">
<div className={`admin-form-group${errors.start_km ? ' has-error' : ''}`}> <FormField label="Počáteční stav km" error={errors.start_km} required>
<label className="admin-form-label required">Počáteční stav km</label>
<input <input
type="number" type="number"
inputMode="numeric" inputMode="numeric"
@@ -527,11 +523,9 @@ export default function Trips() {
className="admin-form-input" className="admin-form-input"
min="0" min="0"
/> />
{errors.start_km && <span className="admin-form-error">{errors.start_km}</span>} </FormField>
</div>
<div className={`admin-form-group${errors.end_km ? ' has-error' : ''}`}> <FormField label="Konečný stav km" error={errors.end_km} required>
<label className="admin-form-label required">Konečný stav km</label>
<input <input
type="number" type="number"
inputMode="numeric" inputMode="numeric"
@@ -543,11 +537,9 @@ export default function Trips() {
className="admin-form-input" className="admin-form-input"
min="0" min="0"
/> />
{errors.end_km && <span className="admin-form-error">{errors.end_km}</span>} </FormField>
</div>
<div className="admin-form-group"> <FormField label="Vzdálenost">
<label className="admin-form-label">Vzdálenost</label>
<input <input
type="text" type="text"
value={`${formatKm(calculateDistance())} km`} value={`${formatKm(calculateDistance())} km`}
@@ -555,12 +547,11 @@ export default function Trips() {
readOnly readOnly
disabled disabled
/> />
</div> </FormField>
</div> </div>
<div className="admin-form-row"> <div className="admin-form-row">
<div className={`admin-form-group${errors.route_from ? ' has-error' : ''}`}> <FormField label="Místo odjezdu" error={errors.route_from} required>
<label className="admin-form-label required">Místo odjezdu</label>
<input <input
type="text" type="text"
value={form.route_from} value={form.route_from}
@@ -571,11 +562,9 @@ export default function Trips() {
className="admin-form-input" className="admin-form-input"
placeholder="Např. Praha" placeholder="Např. Praha"
/> />
{errors.route_from && <span className="admin-form-error">{errors.route_from}</span>} </FormField>
</div>
<div className={`admin-form-group${errors.route_to ? ' has-error' : ''}`}> <FormField label="Místo příjezdu" error={errors.route_to} required>
<label className="admin-form-label required">Místo příjezdu</label>
<input <input
type="text" type="text"
value={form.route_to} value={form.route_to}
@@ -586,12 +575,10 @@ export default function Trips() {
className="admin-form-input" className="admin-form-input"
placeholder="Např. Brno" placeholder="Např. Brno"
/> />
{errors.route_to && <span className="admin-form-error">{errors.route_to}</span>} </FormField>
</div>
</div> </div>
<div className="admin-form-group"> <FormField label="Typ jízdy">
<label className="admin-form-label">Typ jízdy</label>
<select <select
value={form.is_business} value={form.is_business}
onChange={(e) => setForm({ ...form, is_business: parseInt(e.target.value) })} 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={1}>Služební</option>
<option value={0}>Soukromá</option> <option value={0}>Soukromá</option>
</select> </select>
</div> </FormField>
<div className="admin-form-group"> <FormField label="Poznámky">
<label className="admin-form-label">Poznámky</label>
<textarea <textarea
value={form.notes} value={form.notes}
onChange={(e) => setForm({ ...form, notes: e.target.value })} onChange={(e) => setForm({ ...form, notes: e.target.value })}
@@ -611,7 +597,7 @@ export default function Trips() {
rows={2} rows={2}
placeholder="Volitelné poznámky..." placeholder="Volitelné poznámky..."
/> />
</div> </FormField>
</div> </div>
</div> </div>

View File

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

View File

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

View File

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