feat: CNB exchange rates, multi-currency KPI stats, invoice PDF VAT in CZK
- ČNB exchange rate service with date-specific rates and caching - Invoice/received invoice stats convert foreign currencies to CZK - Dashboard revenue converts all currencies to CZK - Invoice PDF: VAT recap table always in CZK with CNB rate footer - Inline styles replaced with utility classes (step 4 cleanup) - Spinner animation exempt from prefers-reduced-motion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -576,10 +576,7 @@ export default function Attendance() {
|
||||
<div className="attendance-project-header">
|
||||
<span className="attendance-shift-label">Projekt</span>
|
||||
{activeProjectId ? (
|
||||
<span
|
||||
className="admin-badge admin-badge-wrap"
|
||||
style={{ fontSize: "0.8125rem" }}
|
||||
>
|
||||
<span className="admin-badge admin-badge-wrap text-sm">
|
||||
{projects.find(
|
||||
(p) => String(p.id) === String(activeProjectId),
|
||||
)
|
||||
@@ -587,12 +584,7 @@ export default function Attendance() {
|
||||
: `Projekt #${activeProjectId}`}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className="text-muted"
|
||||
style={{ fontSize: "0.8125rem" }}
|
||||
>
|
||||
Žádný
|
||||
</span>
|
||||
<span className="text-muted text-sm">Žádný</span>
|
||||
)}
|
||||
</div>
|
||||
<select
|
||||
@@ -601,8 +593,7 @@ export default function Attendance() {
|
||||
handleSwitchProject(e.target.value || null)
|
||||
}
|
||||
disabled={switchingProject}
|
||||
className="admin-form-select"
|
||||
style={{ fontSize: "0.875rem" }}
|
||||
className="admin-form-select text-md"
|
||||
>
|
||||
<option value="">— Bez projektu —</option>
|
||||
{projects.map((p) => (
|
||||
@@ -654,8 +645,7 @@ export default function Attendance() {
|
||||
<button
|
||||
onClick={handleBreak}
|
||||
disabled={submitting}
|
||||
className="admin-btn admin-btn-secondary"
|
||||
style={{ width: "100%" }}
|
||||
className="admin-btn admin-btn-secondary w-full"
|
||||
>
|
||||
Pauza (30 min)
|
||||
</button>
|
||||
@@ -663,15 +653,13 @@ export default function Attendance() {
|
||||
<button
|
||||
onClick={() => handlePunch("departure")}
|
||||
disabled={submitting}
|
||||
className="admin-btn admin-btn-primary"
|
||||
style={{ width: "100%" }}
|
||||
className="admin-btn admin-btn-primary w-full"
|
||||
>
|
||||
{submitting ? "Zpracovávám..." : "Odchod"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowLeaveModal(true)}
|
||||
className="admin-btn admin-btn-secondary"
|
||||
style={{ width: "100%" }}
|
||||
className="admin-btn admin-btn-secondary w-full"
|
||||
>
|
||||
Žádost o nepřítomnost
|
||||
</button>
|
||||
@@ -703,16 +691,14 @@ export default function Attendance() {
|
||||
<button
|
||||
onClick={() => handlePunch("arrival")}
|
||||
disabled={submitting}
|
||||
className="admin-btn admin-btn-primary"
|
||||
style={{ width: "100%" }}
|
||||
className="admin-btn admin-btn-primary w-full"
|
||||
>
|
||||
{submitting ? "Zpracovávám..." : "Příchod"}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowLeaveModal(true)}
|
||||
className="admin-btn admin-btn-secondary"
|
||||
style={{ width: "100%" }}
|
||||
className="admin-btn admin-btn-secondary w-full"
|
||||
>
|
||||
Žádost o nepřítomnost
|
||||
</button>
|
||||
@@ -877,11 +863,10 @@ export default function Attendance() {
|
||||
</div>
|
||||
<div style={{ marginTop: "0.75rem" }}>
|
||||
<div
|
||||
className="text-secondary"
|
||||
className="text-secondary text-sm"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
fontSize: "0.8125rem",
|
||||
marginBottom: "0.5rem",
|
||||
}}
|
||||
>
|
||||
@@ -905,8 +890,8 @@ export default function Attendance() {
|
||||
</div>
|
||||
{data.monthly_fund.leave_hours > 0 && (
|
||||
<div
|
||||
className="text-muted"
|
||||
style={{ fontSize: "0.75rem", marginTop: "0.375rem" }}
|
||||
className="text-muted text-xs"
|
||||
style={{ marginTop: "0.375rem" }}
|
||||
>
|
||||
{"Pokryto: "}
|
||||
{data.monthly_fund.covered}h (práce{" "}
|
||||
|
||||
Reference in New Issue
Block a user