fix: oprava kritických bezpečnostních chyb a bugů z code review
- SEC-1: nahrazen exec('fsutil') za PHP-native is_link()+realpath() v NasFileManager - eliminace command injection
- SEC-2: přidáno ověření aktuálního hesla při změně hesla (profile.php + DashProfile.jsx)
- BUG-1: attendance punch obalen do transakce s SELECT FOR UPDATE - prevence race condition při dvojkliku
- BUG-2: eliminován N+1 SQL dotaz pro VAT v invoice listu - výpočet přesunut do subquery
- BUG-5/6: delete a update attendance záznamů obaleny do transakcí - prevence nekonzistentního stavu
- BUG-7: opravena duplikace nabídky - přidáno chybějící pole unit v offer items
ESLint: 0 errors | PHPCS: 0 errors | Build: OK
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -261,14 +261,17 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
$lat = isset($input['latitude']) && $input['latitude'] !== '' ? (float)$input['latitude'] : null;
|
$lat = isset($input['latitude']) && $input['latitude'] !== '' ? (float)$input['latitude'] : null;
|
||||||
$lng = isset($input['longitude']) && $input['longitude'] !== '' ? (float)$input['longitude'] : null;
|
$lng = isset($input['longitude']) && $input['longitude'] !== '' ? (float)$input['longitude'] : null;
|
||||||
$accuracy = isset($input['accuracy']) && $input['accuracy'] !== '' ? (float)$input['accuracy'] : null;
|
$accuracy = isset($input['accuracy']) && $input['accuracy'] !== '' ? (float)$input['accuracy'] : null;
|
||||||
$address = !empty($input['address']) ? $input['address'] : null;
|
$address = !empty($input['address']) ? mb_substr($input['address'], 0, 500) : null;
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT id, user_id, shift_date, arrival_time, break_start, break_end,
|
SELECT id, user_id, shift_date, arrival_time, break_start, break_end,
|
||||||
departure_time, notes, project_id, leave_type, created_at
|
departure_time, notes, project_id, leave_type, created_at
|
||||||
FROM attendance
|
FROM attendance
|
||||||
WHERE user_id = ? AND departure_time IS NULL AND (leave_type IS NULL OR leave_type = 'work')
|
WHERE user_id = ? AND departure_time IS NULL AND (leave_type IS NULL OR leave_type = 'work')
|
||||||
ORDER BY created_at DESC LIMIT 1
|
ORDER BY created_at DESC LIMIT 1
|
||||||
|
FOR UPDATE
|
||||||
");
|
");
|
||||||
$stmt->execute([$userId]);
|
$stmt->execute([$userId]);
|
||||||
$ongoingShift = $stmt->fetch();
|
$ongoingShift = $stmt->fetch();
|
||||||
@@ -287,6 +290,7 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
'location' => $address,
|
'location' => $address,
|
||||||
], 'Příchod zaznamenán');
|
], 'Příchod zaznamenán');
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
successResponse(null, 'Příchod zaznamenán');
|
successResponse(null, 'Příchod zaznamenán');
|
||||||
} elseif ($ongoingShift) {
|
} elseif ($ongoingShift) {
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
@@ -298,8 +302,10 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
|
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
|
||||||
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
successResponse(null, 'Pauza zaznamenána');
|
successResponse(null, 'Pauza zaznamenána');
|
||||||
} else {
|
} else {
|
||||||
|
$pdo->rollBack();
|
||||||
errorResponse('Nelze zadat pauzu');
|
errorResponse('Nelze zadat pauzu');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -319,14 +325,16 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
$breakStart = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint - (30 * 60)));
|
$breakStart = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint - (30 * 60)));
|
||||||
$breakEnd = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint + (30 * 60)));
|
$breakEnd = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint + (30 * 60)));
|
||||||
|
|
||||||
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
|
$sql = 'UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
||||||
} elseif ($hoursWorked > 6) {
|
} elseif ($hoursWorked > 6) {
|
||||||
$midPoint = $arrivalTime + (($departureTime - $arrivalTime) / 2);
|
$midPoint = $arrivalTime + (($departureTime - $arrivalTime) / 2);
|
||||||
$breakStart = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint - (15 * 60)));
|
$breakStart = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint - (15 * 60)));
|
||||||
$breakEnd = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint + (15 * 60)));
|
$breakEnd = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint + (15 * 60)));
|
||||||
|
|
||||||
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
|
$sql = 'UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,18 +358,26 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
'location' => $address,
|
'location' => $address,
|
||||||
], 'Odchod zaznamenán');
|
], 'Odchod zaznamenán');
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
successResponse(null, 'Odchod zaznamenán');
|
successResponse(null, 'Odchod zaznamenán');
|
||||||
} else {
|
} else {
|
||||||
|
$pdo->rollBack();
|
||||||
errorResponse('Nelze zadat odchod');
|
errorResponse('Nelze zadat odchod');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
$pdo->rollBack();
|
||||||
errorResponse('Neplatná akce');
|
errorResponse('Neplatná akce');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
$pdo->rollBack();
|
||||||
errorResponse('Neplatná akce - nemáte aktivní směnu');
|
errorResponse('Neplatná akce - nemáte aktivní směnu');
|
||||||
}
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUpdateAddress(PDO $pdo, int $userId): void
|
function handleUpdateAddress(PDO $pdo, int $userId): void
|
||||||
|
|||||||
@@ -213,6 +213,8 @@ function handleGetList(PDO $pdo): void
|
|||||||
c.name as customer_name,
|
c.name as customer_name,
|
||||||
(SELECT COALESCE(SUM(ii.quantity * ii.unit_price), 0)
|
(SELECT COALESCE(SUM(ii.quantity * ii.unit_price), 0)
|
||||||
FROM invoice_items ii WHERE ii.invoice_id = i.id) as subtotal,
|
FROM invoice_items ii WHERE ii.invoice_id = i.id) as subtotal,
|
||||||
|
(SELECT COALESCE(SUM(ii.quantity * ii.unit_price * ii.vat_rate / 100), 0)
|
||||||
|
FROM invoice_items ii WHERE ii.invoice_id = i.id) as vat_amount,
|
||||||
o.order_number
|
o.order_number
|
||||||
{$from} {$where}
|
{$from} {$where}
|
||||||
ORDER BY {$p['sort']} {$p['order']}",
|
ORDER BY {$p['sort']} {$p['order']}",
|
||||||
@@ -222,20 +224,11 @@ function handleGetList(PDO $pdo): void
|
|||||||
|
|
||||||
$invoices = $result['items'];
|
$invoices = $result['items'];
|
||||||
|
|
||||||
// Dopocitat celkovou castku s DPH
|
|
||||||
foreach ($invoices as &$inv) {
|
foreach ($invoices as &$inv) {
|
||||||
$subtotal = (float) $inv['subtotal'];
|
$subtotal = (float) $inv['subtotal'];
|
||||||
if ($inv['apply_vat']) {
|
$inv['total'] = $inv['apply_vat']
|
||||||
$vatStmt = $pdo->prepare('
|
? $subtotal + (float) $inv['vat_amount']
|
||||||
SELECT COALESCE(SUM(quantity * unit_price * vat_rate / 100), 0)
|
: $subtotal;
|
||||||
FROM invoice_items WHERE invoice_id = ?
|
|
||||||
');
|
|
||||||
$vatStmt->execute([$inv['id']]);
|
|
||||||
$vatAmount = (float) $vatStmt->fetchColumn();
|
|
||||||
$inv['total'] = $subtotal + $vatAmount;
|
|
||||||
} else {
|
|
||||||
$inv['total'] = $subtotal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unset($inv);
|
unset($inv);
|
||||||
|
|
||||||
|
|||||||
@@ -428,6 +428,7 @@ function handleDuplicate(PDO $pdo, int $sourceId): void
|
|||||||
'description' => $item['description'],
|
'description' => $item['description'],
|
||||||
'item_description' => $item['item_description'],
|
'item_description' => $item['item_description'],
|
||||||
'quantity' => $item['quantity'],
|
'quantity' => $item['quantity'],
|
||||||
|
'unit' => $item['unit'] ?? '',
|
||||||
'unit_price' => $item['unit_price'],
|
'unit_price' => $item['unit_price'],
|
||||||
'is_included_in_total' => $item['is_included_in_total'],
|
'is_included_in_total' => $item['is_included_in_total'],
|
||||||
'position' => $item['position'],
|
'position' => $item['position'],
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ try {
|
|||||||
$pdo = db();
|
$pdo = db();
|
||||||
$userId = $authData['user_id'];
|
$userId = $authData['user_id'];
|
||||||
|
|
||||||
// Get existing user
|
// Get existing user (vcetne password_hash pro overeni aktualniho hesla)
|
||||||
$stmt = $pdo->prepare('
|
$stmt = $pdo->prepare('
|
||||||
SELECT id, username, email, first_name, last_name, role_id, is_active,
|
SELECT id, username, email, first_name, last_name, role_id, is_active,
|
||||||
last_login, created_at
|
last_login, created_at, password_hash
|
||||||
FROM users WHERE id = ?
|
FROM users WHERE id = ?
|
||||||
');
|
');
|
||||||
$stmt->execute([$userId]);
|
$stmt->execute([$userId]);
|
||||||
@@ -75,6 +75,14 @@ try {
|
|||||||
|
|
||||||
// Update user
|
// Update user
|
||||||
if (!empty($input['password'])) {
|
if (!empty($input['password'])) {
|
||||||
|
// Overeni aktualniho hesla
|
||||||
|
if (empty($input['current_password'])) {
|
||||||
|
errorResponse('Pro změnu hesla je nutné zadat aktuální heslo');
|
||||||
|
}
|
||||||
|
if (!password_verify($input['current_password'], $existingUser['password_hash'])) {
|
||||||
|
errorResponse('Aktuální heslo není správné');
|
||||||
|
}
|
||||||
|
|
||||||
// Validate password length
|
// Validate password length
|
||||||
if (strlen($input['password']) < 8) {
|
if (strlen($input['password']) < 8) {
|
||||||
errorResponse('Heslo musí mít alespoň 8 znaků');
|
errorResponse('Heslo musí mít alespoň 8 znaků');
|
||||||
|
|||||||
@@ -579,6 +579,8 @@ function handleUpdateAttendance(PDO $pdo, int $recordId): void
|
|||||||
|
|
||||||
$projectLogs = $input['project_logs'] ?? null;
|
$projectLogs = $input['project_logs'] ?? null;
|
||||||
if ($projectLogs !== null) {
|
if ($projectLogs !== null) {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
$stmt = $pdo->prepare('DELETE FROM attendance_project_logs WHERE attendance_id = ?');
|
$stmt = $pdo->prepare('DELETE FROM attendance_project_logs WHERE attendance_id = ?');
|
||||||
$stmt->execute([$recordId]);
|
$stmt->execute([$recordId]);
|
||||||
|
|
||||||
@@ -601,6 +603,11 @@ function handleUpdateAttendance(PDO $pdo, int $recordId): void
|
|||||||
$logStmt->execute([$recordId, $pid, $h, $m]);
|
$logStmt->execute([$recordId, $pid, $h, $m]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$pdo->commit();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::logUpdate('attendance', $recordId, $record, $input, 'Admin upravil záznam docházky');
|
AuditLog::logUpdate('attendance', $recordId, $record, $input, 'Admin upravil záznam docházky');
|
||||||
@@ -621,6 +628,8 @@ function handleDeleteAttendance(PDO $pdo, int $recordId): void
|
|||||||
errorResponse('Záznam nebyl nalezen', 404);
|
errorResponse('Záznam nebyl nalezen', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
$leaveType = $record['leave_type'] ?? 'work';
|
$leaveType = $record['leave_type'] ?? 'work';
|
||||||
$leaveHours = $record['leave_hours'] ?? 0;
|
$leaveHours = $record['leave_hours'] ?? 0;
|
||||||
if ($leaveType !== 'work' && $leaveHours > 0) {
|
if ($leaveType !== 'work' && $leaveHours > 0) {
|
||||||
@@ -633,6 +642,12 @@ function handleDeleteAttendance(PDO $pdo, int $recordId): void
|
|||||||
$stmt = $pdo->prepare('DELETE FROM attendance WHERE id = ?');
|
$stmt = $pdo->prepare('DELETE FROM attendance WHERE id = ?');
|
||||||
$stmt->execute([$recordId]);
|
$stmt->execute([$recordId]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
AuditLog::logDelete('attendance', $recordId, $record, 'Admin smazal záznam docházky');
|
AuditLog::logDelete('attendance', $recordId, $record, 'Admin smazal záznam docházky');
|
||||||
|
|
||||||
successResponse(null, 'Záznam byl smazán');
|
successResponse(null, 'Záznam byl smazán');
|
||||||
|
|||||||
@@ -560,12 +560,12 @@ class NasFileManager
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$attr = @exec('fsutil reparsepoint query "' . str_replace('/', '\\', $path) . '" 2>NUL');
|
// PHP is_link detekuje symlinky
|
||||||
if ($attr !== false && $attr !== '') {
|
if (is_link($path)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback - realpath se lisi od puvodniho path u junction
|
// Junction detekce pres porovnani realpath vs zadana cesta
|
||||||
$real = realpath($path);
|
$real = realpath($path);
|
||||||
$normalized = str_replace('\\', '/', $path);
|
$normalized = str_replace('\\', '/', $path);
|
||||||
$normalReal = str_replace('\\', '/', (string) $real);
|
$normalReal = str_replace('\\', '/', (string) $real);
|
||||||
|
|||||||
19
dist/api/admin/handlers/attendance-handlers.php
vendored
19
dist/api/admin/handlers/attendance-handlers.php
vendored
@@ -261,14 +261,17 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
$lat = isset($input['latitude']) && $input['latitude'] !== '' ? (float)$input['latitude'] : null;
|
$lat = isset($input['latitude']) && $input['latitude'] !== '' ? (float)$input['latitude'] : null;
|
||||||
$lng = isset($input['longitude']) && $input['longitude'] !== '' ? (float)$input['longitude'] : null;
|
$lng = isset($input['longitude']) && $input['longitude'] !== '' ? (float)$input['longitude'] : null;
|
||||||
$accuracy = isset($input['accuracy']) && $input['accuracy'] !== '' ? (float)$input['accuracy'] : null;
|
$accuracy = isset($input['accuracy']) && $input['accuracy'] !== '' ? (float)$input['accuracy'] : null;
|
||||||
$address = !empty($input['address']) ? $input['address'] : null;
|
$address = !empty($input['address']) ? mb_substr($input['address'], 0, 500) : null;
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT id, user_id, shift_date, arrival_time, break_start, break_end,
|
SELECT id, user_id, shift_date, arrival_time, break_start, break_end,
|
||||||
departure_time, notes, project_id, leave_type, created_at
|
departure_time, notes, project_id, leave_type, created_at
|
||||||
FROM attendance
|
FROM attendance
|
||||||
WHERE user_id = ? AND departure_time IS NULL AND (leave_type IS NULL OR leave_type = 'work')
|
WHERE user_id = ? AND departure_time IS NULL AND (leave_type IS NULL OR leave_type = 'work')
|
||||||
ORDER BY created_at DESC LIMIT 1
|
ORDER BY created_at DESC LIMIT 1
|
||||||
|
FOR UPDATE
|
||||||
");
|
");
|
||||||
$stmt->execute([$userId]);
|
$stmt->execute([$userId]);
|
||||||
$ongoingShift = $stmt->fetch();
|
$ongoingShift = $stmt->fetch();
|
||||||
@@ -287,6 +290,7 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
'location' => $address,
|
'location' => $address,
|
||||||
], 'Příchod zaznamenán');
|
], 'Příchod zaznamenán');
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
successResponse(null, 'Příchod zaznamenán');
|
successResponse(null, 'Příchod zaznamenán');
|
||||||
} elseif ($ongoingShift) {
|
} elseif ($ongoingShift) {
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
@@ -298,8 +302,10 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
|
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
|
||||||
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
successResponse(null, 'Pauza zaznamenána');
|
successResponse(null, 'Pauza zaznamenána');
|
||||||
} else {
|
} else {
|
||||||
|
$pdo->rollBack();
|
||||||
errorResponse('Nelze zadat pauzu');
|
errorResponse('Nelze zadat pauzu');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -326,7 +332,8 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
$breakStart = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint - (15 * 60)));
|
$breakStart = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint - (15 * 60)));
|
||||||
$breakEnd = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint + (15 * 60)));
|
$breakEnd = roundToNearest10Minutes(date('Y-m-d H:i:s', $midPoint + (15 * 60)));
|
||||||
|
|
||||||
$stmt = $pdo->prepare('UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?');
|
$sql = 'UPDATE attendance SET break_start = ?, break_end = ? WHERE id = ?';
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
$stmt->execute([$breakStart, $breakEnd, $ongoingShift['id']]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,18 +357,26 @@ function handlePunch(PDO $pdo, int $userId): void
|
|||||||
'location' => $address,
|
'location' => $address,
|
||||||
], 'Odchod zaznamenán');
|
], 'Odchod zaznamenán');
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
successResponse(null, 'Odchod zaznamenán');
|
successResponse(null, 'Odchod zaznamenán');
|
||||||
} else {
|
} else {
|
||||||
|
$pdo->rollBack();
|
||||||
errorResponse('Nelze zadat odchod');
|
errorResponse('Nelze zadat odchod');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
$pdo->rollBack();
|
||||||
errorResponse('Neplatná akce');
|
errorResponse('Neplatná akce');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
$pdo->rollBack();
|
||||||
errorResponse('Neplatná akce - nemáte aktivní směnu');
|
errorResponse('Neplatná akce - nemáte aktivní směnu');
|
||||||
}
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUpdateAddress(PDO $pdo, int $userId): void
|
function handleUpdateAddress(PDO $pdo, int $userId): void
|
||||||
|
|||||||
17
dist/api/admin/handlers/invoices-handlers.php
vendored
17
dist/api/admin/handlers/invoices-handlers.php
vendored
@@ -213,6 +213,8 @@ function handleGetList(PDO $pdo): void
|
|||||||
c.name as customer_name,
|
c.name as customer_name,
|
||||||
(SELECT COALESCE(SUM(ii.quantity * ii.unit_price), 0)
|
(SELECT COALESCE(SUM(ii.quantity * ii.unit_price), 0)
|
||||||
FROM invoice_items ii WHERE ii.invoice_id = i.id) as subtotal,
|
FROM invoice_items ii WHERE ii.invoice_id = i.id) as subtotal,
|
||||||
|
(SELECT COALESCE(SUM(ii.quantity * ii.unit_price * ii.vat_rate / 100), 0)
|
||||||
|
FROM invoice_items ii WHERE ii.invoice_id = i.id) as vat_amount,
|
||||||
o.order_number
|
o.order_number
|
||||||
{$from} {$where}
|
{$from} {$where}
|
||||||
ORDER BY {$p['sort']} {$p['order']}",
|
ORDER BY {$p['sort']} {$p['order']}",
|
||||||
@@ -222,20 +224,11 @@ function handleGetList(PDO $pdo): void
|
|||||||
|
|
||||||
$invoices = $result['items'];
|
$invoices = $result['items'];
|
||||||
|
|
||||||
// Dopocitat celkovou castku s DPH
|
|
||||||
foreach ($invoices as &$inv) {
|
foreach ($invoices as &$inv) {
|
||||||
$subtotal = (float) $inv['subtotal'];
|
$subtotal = (float) $inv['subtotal'];
|
||||||
if ($inv['apply_vat']) {
|
$inv['total'] = $inv['apply_vat']
|
||||||
$vatStmt = $pdo->prepare('
|
? $subtotal + (float) $inv['vat_amount']
|
||||||
SELECT COALESCE(SUM(quantity * unit_price * vat_rate / 100), 0)
|
: $subtotal;
|
||||||
FROM invoice_items WHERE invoice_id = ?
|
|
||||||
');
|
|
||||||
$vatStmt->execute([$inv['id']]);
|
|
||||||
$vatAmount = (float) $vatStmt->fetchColumn();
|
|
||||||
$inv['total'] = $subtotal + $vatAmount;
|
|
||||||
} else {
|
|
||||||
$inv['total'] = $subtotal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unset($inv);
|
unset($inv);
|
||||||
|
|
||||||
|
|||||||
1
dist/api/admin/handlers/offers-handlers.php
vendored
1
dist/api/admin/handlers/offers-handlers.php
vendored
@@ -428,6 +428,7 @@ function handleDuplicate(PDO $pdo, int $sourceId): void
|
|||||||
'description' => $item['description'],
|
'description' => $item['description'],
|
||||||
'item_description' => $item['item_description'],
|
'item_description' => $item['item_description'],
|
||||||
'quantity' => $item['quantity'],
|
'quantity' => $item['quantity'],
|
||||||
|
'unit' => $item['unit'] ?? '',
|
||||||
'unit_price' => $item['unit_price'],
|
'unit_price' => $item['unit_price'],
|
||||||
'is_included_in_total' => $item['is_included_in_total'],
|
'is_included_in_total' => $item['is_included_in_total'],
|
||||||
'position' => $item['position'],
|
'position' => $item['position'],
|
||||||
|
|||||||
12
dist/api/admin/profile.php
vendored
12
dist/api/admin/profile.php
vendored
@@ -34,10 +34,10 @@ try {
|
|||||||
$pdo = db();
|
$pdo = db();
|
||||||
$userId = $authData['user_id'];
|
$userId = $authData['user_id'];
|
||||||
|
|
||||||
// Get existing user
|
// Get existing user (vcetne password_hash pro overeni aktualniho hesla)
|
||||||
$stmt = $pdo->prepare('
|
$stmt = $pdo->prepare('
|
||||||
SELECT id, username, email, first_name, last_name, role_id, is_active,
|
SELECT id, username, email, first_name, last_name, role_id, is_active,
|
||||||
last_login, created_at
|
last_login, created_at, password_hash
|
||||||
FROM users WHERE id = ?
|
FROM users WHERE id = ?
|
||||||
');
|
');
|
||||||
$stmt->execute([$userId]);
|
$stmt->execute([$userId]);
|
||||||
@@ -75,6 +75,14 @@ try {
|
|||||||
|
|
||||||
// Update user
|
// Update user
|
||||||
if (!empty($input['password'])) {
|
if (!empty($input['password'])) {
|
||||||
|
// Overeni aktualniho hesla
|
||||||
|
if (empty($input['current_password'])) {
|
||||||
|
errorResponse('Pro změnu hesla je nutné zadat aktuální heslo');
|
||||||
|
}
|
||||||
|
if (!password_verify($input['current_password'], $existingUser['password_hash'])) {
|
||||||
|
errorResponse('Aktuální heslo není správné');
|
||||||
|
}
|
||||||
|
|
||||||
// Validate password length
|
// Validate password length
|
||||||
if (strlen($input['password']) < 8) {
|
if (strlen($input['password']) < 8) {
|
||||||
errorResponse('Heslo musí mít alespoň 8 znaků');
|
errorResponse('Heslo musí mít alespoň 8 znaků');
|
||||||
|
|||||||
15
dist/api/includes/AttendanceAdmin.php
vendored
15
dist/api/includes/AttendanceAdmin.php
vendored
@@ -579,6 +579,8 @@ function handleUpdateAttendance(PDO $pdo, int $recordId): void
|
|||||||
|
|
||||||
$projectLogs = $input['project_logs'] ?? null;
|
$projectLogs = $input['project_logs'] ?? null;
|
||||||
if ($projectLogs !== null) {
|
if ($projectLogs !== null) {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
$stmt = $pdo->prepare('DELETE FROM attendance_project_logs WHERE attendance_id = ?');
|
$stmt = $pdo->prepare('DELETE FROM attendance_project_logs WHERE attendance_id = ?');
|
||||||
$stmt->execute([$recordId]);
|
$stmt->execute([$recordId]);
|
||||||
|
|
||||||
@@ -601,6 +603,11 @@ function handleUpdateAttendance(PDO $pdo, int $recordId): void
|
|||||||
$logStmt->execute([$recordId, $pid, $h, $m]);
|
$logStmt->execute([$recordId, $pid, $h, $m]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$pdo->commit();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLog::logUpdate('attendance', $recordId, $record, $input, 'Admin upravil záznam docházky');
|
AuditLog::logUpdate('attendance', $recordId, $record, $input, 'Admin upravil záznam docházky');
|
||||||
@@ -621,6 +628,8 @@ function handleDeleteAttendance(PDO $pdo, int $recordId): void
|
|||||||
errorResponse('Záznam nebyl nalezen', 404);
|
errorResponse('Záznam nebyl nalezen', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
$leaveType = $record['leave_type'] ?? 'work';
|
$leaveType = $record['leave_type'] ?? 'work';
|
||||||
$leaveHours = $record['leave_hours'] ?? 0;
|
$leaveHours = $record['leave_hours'] ?? 0;
|
||||||
if ($leaveType !== 'work' && $leaveHours > 0) {
|
if ($leaveType !== 'work' && $leaveHours > 0) {
|
||||||
@@ -633,6 +642,12 @@ function handleDeleteAttendance(PDO $pdo, int $recordId): void
|
|||||||
$stmt = $pdo->prepare('DELETE FROM attendance WHERE id = ?');
|
$stmt = $pdo->prepare('DELETE FROM attendance WHERE id = ?');
|
||||||
$stmt->execute([$recordId]);
|
$stmt->execute([$recordId]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
AuditLog::logDelete('attendance', $recordId, $record, 'Admin smazal záznam docházky');
|
AuditLog::logDelete('attendance', $recordId, $record, 'Admin smazal záznam docházky');
|
||||||
|
|
||||||
successResponse(null, 'Záznam byl smazán');
|
successResponse(null, 'Záznam byl smazán');
|
||||||
|
|||||||
6
dist/api/includes/NasFileManager.php
vendored
6
dist/api/includes/NasFileManager.php
vendored
@@ -560,12 +560,12 @@ class NasFileManager
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$attr = @exec('fsutil reparsepoint query "' . str_replace('/', '\\', $path) . '" 2>NUL');
|
// PHP is_link detekuje symlinky
|
||||||
if ($attr !== false && $attr !== '') {
|
if (is_link($path)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback - realpath se lisi od puvodniho path u junction
|
// Junction detekce pres porovnani realpath vs zadana cesta
|
||||||
$real = realpath($path);
|
$real = realpath($path);
|
||||||
$normalized = str_replace('\\', '/', $path);
|
$normalized = str_replace('\\', '/', $path);
|
||||||
$normalReal = str_replace('\\', '/', (string) $real);
|
$normalReal = str_replace('\\', '/', (string) $real);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
import{j as e,m as f}from"./vendor-animation-0s3FMHwK.js";import{r as m}from"./vendor-react-BVs3cwbi.js";import{a9 as T}from"./vendor-utils-Dyr8OjFr.js";import{a as C,u as A,c as O,F as B,A as H}from"./index-fMsy8JiX.js";import{F as I}from"./Forbidden-D25jV3Oq.js";import{c as W,b as k,g as w,d as z,e as S,a as v,h as E,i as y,f as b}from"./attendanceHelpers-D6sLEw0q.js";const L="/api/admin",R=s=>s.break_start&&s.break_end?`${b(s.break_start)} - ${b(s.break_end)}`:s.break_start?`${b(s.break_start)} - ?`:"—",Z=s=>s.project_logs&&s.project_logs.length>0?e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"0.125rem"},children:s.project_logs.map((n,g)=>{let d,c,o=!1;if(n.hours!==null&&n.hours!==void 0)d=parseInt(n.hours)||0,c=parseInt(n.minutes)||0;else{o=!n.ended_at;const x=n.ended_at?new Date(n.ended_at):new Date,p=Math.floor((x-new Date(n.started_at))/6e4);d=Math.floor(p/60),c=p%60}return e.jsxs("span",{className:"admin-badge",style:{fontSize:"0.7rem",display:"inline-block",background:o?"var(--accent-light)":void 0},children:[n.project_name||`#${n.project_id}`," (",d,":",String(c).padStart(2,"0"),"h",o?" ▸":"",")"]},n.id||g)})}):s.project_name?e.jsx("span",{className:"admin-badge admin-badge-wrap",style:{fontSize:"0.75rem"},children:s.project_name}):"—",Y=s=>s.overtime>0?e.jsxs("span",{className:"leave-badge badge-overtime",children:["+",s.overtime,"h přesčas"]}):s.remaining>0?e.jsxs("span",{style:{color:"#dc2626"},children:["−",s.remaining,"h"]}):e.jsx("span",{style:{color:"#16a34a"},children:"splněno"});function Q(){const s=C(),{user:n,hasPermission:g}=A(),[d,c]=m.useState(!0),o=m.useRef(null),[x,p]=m.useState(()=>{const a=new Date;return`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`}),[t,D]=m.useState({records:[],month_name:"",year:new Date().getFullYear(),total_minutes:0,vacation_hours:0,sick_hours:0,holiday_hours:0,unpaid_hours:0,leave_balance:null,monthly_fund:null}),_=m.useCallback(async()=>{c(!0);try{const a=await O(`${L}/attendance.php?action=history&month=${x}`);if(a.status===401)return;const i=await a.json();i.success&&D(i.data)}catch{s.error("Nepodařilo se načíst data")}finally{c(!1)}},[x,s]);if(m.useEffect(()=>{_()},[_]),!g("attendance.history"))return e.jsx(I,{});const $=()=>{if(!o.current)return;const a=window.open("","_blank");a.document.write(`
|
import{j as e,m as f}from"./vendor-animation-0s3FMHwK.js";import{r as m}from"./vendor-react-BVs3cwbi.js";import{a9 as T}from"./vendor-utils-Dyr8OjFr.js";import{a as C,u as A,c as O,F as B,A as H}from"./index-CCZhiEoc.js";import{F as I}from"./Forbidden-D25jV3Oq.js";import{c as W,b as k,g as w,d as z,e as S,a as v,h as E,i as y,f as b}from"./attendanceHelpers-D6sLEw0q.js";const L="/api/admin",R=s=>s.break_start&&s.break_end?`${b(s.break_start)} - ${b(s.break_end)}`:s.break_start?`${b(s.break_start)} - ?`:"—",Z=s=>s.project_logs&&s.project_logs.length>0?e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"0.125rem"},children:s.project_logs.map((n,g)=>{let d,c,o=!1;if(n.hours!==null&&n.hours!==void 0)d=parseInt(n.hours)||0,c=parseInt(n.minutes)||0;else{o=!n.ended_at;const x=n.ended_at?new Date(n.ended_at):new Date,p=Math.floor((x-new Date(n.started_at))/6e4);d=Math.floor(p/60),c=p%60}return e.jsxs("span",{className:"admin-badge",style:{fontSize:"0.7rem",display:"inline-block",background:o?"var(--accent-light)":void 0},children:[n.project_name||`#${n.project_id}`," (",d,":",String(c).padStart(2,"0"),"h",o?" ▸":"",")"]},n.id||g)})}):s.project_name?e.jsx("span",{className:"admin-badge admin-badge-wrap",style:{fontSize:"0.75rem"},children:s.project_name}):"—",Y=s=>s.overtime>0?e.jsxs("span",{className:"leave-badge badge-overtime",children:["+",s.overtime,"h přesčas"]}):s.remaining>0?e.jsxs("span",{style:{color:"#dc2626"},children:["−",s.remaining,"h"]}):e.jsx("span",{style:{color:"#16a34a"},children:"splněno"});function Q(){const s=C(),{user:n,hasPermission:g}=A(),[d,c]=m.useState(!0),o=m.useRef(null),[x,p]=m.useState(()=>{const a=new Date;return`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`}),[t,D]=m.useState({records:[],month_name:"",year:new Date().getFullYear(),total_minutes:0,vacation_hours:0,sick_hours:0,holiday_hours:0,unpaid_hours:0,leave_balance:null,monthly_fund:null}),_=m.useCallback(async()=>{c(!0);try{const a=await O(`${L}/attendance.php?action=history&month=${x}`);if(a.status===401)return;const i=await a.json();i.success&&D(i.data)}catch{s.error("Nepodařilo se načíst data")}finally{c(!1)}},[x,s]);if(m.useEffect(()=>{_()},[_]),!g("attendance.history"))return e.jsx(I,{});const $=()=>{if(!o.current)return;const a=window.open("","_blank");a.document.write(`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="cs">
|
<html lang="cs">
|
||||||
<head>
|
<head>
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
import{j as e,m as p,A as Z}from"./vendor-animation-0s3FMHwK.js";import{r as i,L as J}from"./vendor-react-BVs3cwbi.js";import{a9 as G}from"./vendor-utils-Dyr8OjFr.js";import{a as q,u as Q,c as b,b as X,F as r,A as C,f as l,C as ee}from"./index-fMsy8JiX.js";import{F as se}from"./Forbidden-D25jV3Oq.js";import{b as $}from"./attendanceHelpers-D6sLEw0q.js";const N="/api/admin";function de(){const d=q(),{hasPermission:L}=Q(),[k,D]=i.useState(!0),[j,V]=i.useState(()=>{const s=new Date;return`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-01`}),[g,A]=i.useState(()=>{const s=new Date,t=new Date(s.getFullYear(),s.getMonth()+1,0).getDate();return`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-${String(t).padStart(2,"0")}`}),[m,F]=i.useState(""),[h,E]=i.useState(""),[P,B]=i.useState({trips:[],vehicles:[],users:[],totals:{total:0,business:0,count:0}}),[n,I]=i.useState(null),w=i.useRef(null),[T,v]=i.useState(!1),[_,U]=i.useState(null),[a,o]=i.useState({vehicle_id:"",trip_date:"",start_km:"",end_km:"",route_from:"",route_to:"",is_business:1,notes:""}),[u,z]=i.useState({show:!1,trip:null}),y=i.useCallback(async(s=!0)=>{s&&D(!0);try{let t=`${N}/trips.php?action=admin&date_from=${j}&date_to=${g}`;m&&(t+=`&vehicle_id=${m}`),h&&(t+=`&user_id=${h}`);const c=await(await b(t)).json();c.success&&B(c.data)}catch{d.error("Nepodařilo se načíst data")}finally{s&&D(!1)}},[j,g,m,h,d]);if(i.useEffect(()=>{y()},[y]),X(T),!L("trips.admin"))return e.jsx(se,{});const H=s=>{U(s),o({vehicle_id:s.vehicle_id,trip_date:s.trip_date,start_km:s.start_km,end_km:s.end_km,route_from:s.route_from,route_to:s.route_to,is_business:s.is_business,notes:s.notes||""}),v(!0)},O=async()=>{if(parseInt(a.end_km)<=parseInt(a.start_km)){d.error("Konečný stav km musí být větší než počáteční");return}try{const t=await(await b(`${N}/trips.php?id=${_.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json();t.success?(v(!1),await y(!1),await new Promise(x=>setTimeout(x,300)),d.success(t.message)):d.error(t.error)}catch{d.error("Chyba připojení")}},W=async()=>{if(u.trip)try{const t=await(await b(`${N}/trips.php?id=${u.trip.id}`,{method:"DELETE"})).json();t.success?(z({show:!1,trip:null}),await y(!1),d.success(t.message)):d.error(t.error)}catch{d.error("Chyba připojení")}},K=async()=>{try{let s=`${N}/trips.php?action=print&date_from=${j}&date_to=${g}`;m&&(s+=`&vehicle_id=${m}`),h&&(s+=`&user_id=${h}`);const x=await(await b(s)).json();x.success&&(I(x.data),setTimeout(()=>{if(w.current){const c=window.open("","_blank");c.document.write(`
|
import{j as e,m as p,A as Z}from"./vendor-animation-0s3FMHwK.js";import{r as i,L as J}from"./vendor-react-BVs3cwbi.js";import{a9 as G}from"./vendor-utils-Dyr8OjFr.js";import{a as q,u as Q,c as b,b as X,F as r,A as C,f as l,C as ee}from"./index-CCZhiEoc.js";import{F as se}from"./Forbidden-D25jV3Oq.js";import{b as $}from"./attendanceHelpers-D6sLEw0q.js";const N="/api/admin";function de(){const d=q(),{hasPermission:L}=Q(),[k,D]=i.useState(!0),[j,V]=i.useState(()=>{const s=new Date;return`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-01`}),[g,A]=i.useState(()=>{const s=new Date,t=new Date(s.getFullYear(),s.getMonth()+1,0).getDate();return`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-${String(t).padStart(2,"0")}`}),[m,F]=i.useState(""),[h,E]=i.useState(""),[P,B]=i.useState({trips:[],vehicles:[],users:[],totals:{total:0,business:0,count:0}}),[n,I]=i.useState(null),w=i.useRef(null),[T,v]=i.useState(!1),[_,U]=i.useState(null),[a,o]=i.useState({vehicle_id:"",trip_date:"",start_km:"",end_km:"",route_from:"",route_to:"",is_business:1,notes:""}),[u,z]=i.useState({show:!1,trip:null}),y=i.useCallback(async(s=!0)=>{s&&D(!0);try{let t=`${N}/trips.php?action=admin&date_from=${j}&date_to=${g}`;m&&(t+=`&vehicle_id=${m}`),h&&(t+=`&user_id=${h}`);const c=await(await b(t)).json();c.success&&B(c.data)}catch{d.error("Nepodařilo se načíst data")}finally{s&&D(!1)}},[j,g,m,h,d]);if(i.useEffect(()=>{y()},[y]),X(T),!L("trips.admin"))return e.jsx(se,{});const H=s=>{U(s),o({vehicle_id:s.vehicle_id,trip_date:s.trip_date,start_km:s.start_km,end_km:s.end_km,route_from:s.route_from,route_to:s.route_to,is_business:s.is_business,notes:s.notes||""}),v(!0)},O=async()=>{if(parseInt(a.end_km)<=parseInt(a.start_km)){d.error("Konečný stav km musí být větší než počáteční");return}try{const t=await(await b(`${N}/trips.php?id=${_.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json();t.success?(v(!1),await y(!1),await new Promise(x=>setTimeout(x,300)),d.success(t.message)):d.error(t.error)}catch{d.error("Chyba připojení")}},W=async()=>{if(u.trip)try{const t=await(await b(`${N}/trips.php?id=${u.trip.id}`,{method:"DELETE"})).json();t.success?(z({show:!1,trip:null}),await y(!1),d.success(t.message)):d.error(t.error)}catch{d.error("Chyba připojení")}},K=async()=>{try{let s=`${N}/trips.php?action=print&date_from=${j}&date_to=${g}`;m&&(s+=`&vehicle_id=${m}`),h&&(s+=`&user_id=${h}`);const x=await(await b(s)).json();x.success&&(I(x.data),setTimeout(()=>{if(w.current){const c=window.open("","_blank");c.document.write(`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="cs">
|
<html lang="cs">
|
||||||
<head>
|
<head>
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{j as x}from"./vendor-animation-0s3FMHwK.js";import{r as t}from"./vendor-react-BVs3cwbi.js";import{a as L,c as O}from"./index-fMsy8JiX.js";function J({column:e,sort:r,order:n}){return r!==e?null:x.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",style:{marginLeft:4,verticalAlign:"middle"},children:x.jsx("path",{d:n==="ASC"?"M18 15l-6-6-6 6":"M6 9l6 6 6-6"})})}function V(e,r="DESC"){const[n,a]=t.useState(e),[o,c]=t.useState(r),i=t.useRef(!1),S=t.useCallback(u=>{i.current=!0,a(m=>m===u?(c(h=>h==="ASC"?"DESC":"ASC"),m):(c("DESC"),u))},[]),d=i.current?n:null;return{sort:n,order:o,handleSort:S,activeSort:d}}function I(e,r=300){const[n,a]=t.useState(e);return t.useEffect(()=>{const o=setTimeout(()=>a(e),r);return()=>clearTimeout(o)},[e,r]),n}const N="/api/admin";function _(e,{dataKey:r,search:n,sort:a,order:o,page:c,perPage:i,extraParams:S,errorMsg:d="Nepodařilo se načíst data"}={}){const u=L(),[m,h]=t.useState([]),[j,D]=t.useState(!0),[w,k]=t.useState(null),l=t.useRef(null),p=S?JSON.stringify(S):"",b=I(n,300),C=t.useCallback(async()=>{l.current&&l.current.abort();const g=new AbortController;l.current=g;try{const s=new URLSearchParams;if(b&&s.set("search",b),a&&s.set("sort",a),o&&s.set("order",o),c&&s.set("page",c),i&&s.set("per_page",i),p){const R=JSON.parse(p);Object.entries(R).forEach(([y,A])=>{A&&s.set(y,A)})}const E=await O(`${N}/${e}?${s}`,{signal:g.signal});if(E.status===401)return;const f=await E.json();f.success?(h(f.data[r]||[]),f.data.pagination&&k(f.data.pagination)):u.error(f.error||d)}catch(s){if(s.name==="AbortError")return;u.error("Chyba připojení")}finally{D(!1)}},[u,e,r,b,a,o,c,i,p,d]);return t.useEffect(()=>(C(),()=>{l.current&&l.current.abort()}),[C]),{items:m,setItems:h,loading:j,pagination:w,refetch:C}}export{J as S,_ as a,V as u};
|
import{j as x}from"./vendor-animation-0s3FMHwK.js";import{r as t}from"./vendor-react-BVs3cwbi.js";import{a as L,c as O}from"./index-CCZhiEoc.js";function J({column:e,sort:r,order:n}){return r!==e?null:x.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",style:{marginLeft:4,verticalAlign:"middle"},children:x.jsx("path",{d:n==="ASC"?"M18 15l-6-6-6 6":"M6 9l6 6 6-6"})})}function V(e,r="DESC"){const[n,a]=t.useState(e),[o,c]=t.useState(r),i=t.useRef(!1),S=t.useCallback(u=>{i.current=!0,a(m=>m===u?(c(h=>h==="ASC"?"DESC":"ASC"),m):(c("DESC"),u))},[]),d=i.current?n:null;return{sort:n,order:o,handleSort:S,activeSort:d}}function I(e,r=300){const[n,a]=t.useState(e);return t.useEffect(()=>{const o=setTimeout(()=>a(e),r);return()=>clearTimeout(o)},[e,r]),n}const N="/api/admin";function _(e,{dataKey:r,search:n,sort:a,order:o,page:c,perPage:i,extraParams:S,errorMsg:d="Nepodařilo se načíst data"}={}){const u=L(),[m,h]=t.useState([]),[j,D]=t.useState(!0),[w,k]=t.useState(null),l=t.useRef(null),p=S?JSON.stringify(S):"",b=I(n,300),C=t.useCallback(async()=>{l.current&&l.current.abort();const g=new AbortController;l.current=g;try{const s=new URLSearchParams;if(b&&s.set("search",b),a&&s.set("sort",a),o&&s.set("order",o),c&&s.set("page",c),i&&s.set("per_page",i),p){const R=JSON.parse(p);Object.entries(R).forEach(([y,A])=>{A&&s.set(y,A)})}const E=await O(`${N}/${e}?${s}`,{signal:g.signal});if(E.status===401)return;const f=await E.json();f.success?(h(f.data[r]||[]),f.data.pagination&&k(f.data.pagination)):u.error(f.error||d)}catch(s){if(s.name==="AbortError")return;u.error("Chyba připojení")}finally{D(!1)}},[u,e,r,b,a,o,c,i,p,d]);return t.useEffect(()=>(C(),()=>{l.current&&l.current.abort()}),[C]),{items:m,setItems:h,loading:j,pagination:w,refetch:C}}export{J as S,_ as a,V as u};
|
||||||
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -29,7 +29,7 @@
|
|||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Urbanist:wght@400;500;600;700;800&display=swap"
|
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Urbanist:wght@400;500;600;700;800&display=swap"
|
||||||
rel="stylesheet" />
|
rel="stylesheet" />
|
||||||
<script type="module" crossorigin src="/assets/index-fMsy8JiX.js"></script>
|
<script type="module" crossorigin src="/assets/index-CCZhiEoc.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BVs3cwbi.js">
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BVs3cwbi.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/vendor-animation-0s3FMHwK.js">
|
<link rel="modulepreload" crossorigin href="/assets/vendor-animation-0s3FMHwK.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/vendor-utils-Dyr8OjFr.js">
|
<link rel="modulepreload" crossorigin href="/assets/vendor-utils-Dyr8OjFr.js">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function DashProfile({
|
|||||||
|
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
username: '', email: '', password: '', first_name: '', last_name: ''
|
username: '', email: '', password: '', current_password: '', first_name: '', last_name: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
useModalLock(showModal)
|
useModalLock(showModal)
|
||||||
@@ -33,6 +33,7 @@ export default function DashProfile({
|
|||||||
username: user?.username || '',
|
username: user?.username || '',
|
||||||
email: user?.email || '',
|
email: user?.email || '',
|
||||||
password: '',
|
password: '',
|
||||||
|
current_password: '',
|
||||||
first_name: nameParts[0] || '',
|
first_name: nameParts[0] || '',
|
||||||
last_name: nameParts.slice(1).join(' ') || ''
|
last_name: nameParts.slice(1).join(' ') || ''
|
||||||
})
|
})
|
||||||
@@ -179,6 +180,12 @@ export default function DashProfile({
|
|||||||
<label className="admin-form-label">Nové heslo (ponechte prázdné pro zachování stávajícího)</label>
|
<label className="admin-form-label">Nové heslo (ponechte prázdné pro zachování stávajícího)</label>
|
||||||
<input type="password" value={formData.password} onChange={(e) => setFormData({ ...formData, password: e.target.value })} className="admin-form-input" />
|
<input type="password" value={formData.password} onChange={(e) => setFormData({ ...formData, password: e.target.value })} className="admin-form-input" />
|
||||||
</div>
|
</div>
|
||||||
|
{formData.password && (
|
||||||
|
<div className="admin-form-group">
|
||||||
|
<label className="admin-form-label required">Aktuální heslo</label>
|
||||||
|
<input type="password" value={formData.current_password} onChange={(e) => setFormData({ ...formData, current_password: e.target.value })} className="admin-form-input" placeholder="Zadejte aktuální heslo pro potvrzení" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-modal-footer">
|
<div className="admin-modal-footer">
|
||||||
|
|||||||
Reference in New Issue
Block a user