From 308db66c9bbbe11a919a2e4fb1b067af8da8ad4b Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 25 Sep 2025 15:02:30 +0000 Subject: [PATCH] v1 --- api/track_time.php | 86 ++++++++++ assets/css/custom.css | 110 ++++++++++++ assets/js/main.js | 92 ++++++++++ db/config.php | 14 ++ .../001_create_time_records_table.sql | 8 + index.php | 158 ++++-------------- 6 files changed, 342 insertions(+), 126 deletions(-) create mode 100644 api/track_time.php create mode 100644 assets/css/custom.css create mode 100644 assets/js/main.js create mode 100644 db/migrations/001_create_time_records_table.sql diff --git a/api/track_time.php b/api/track_time.php new file mode 100644 index 0000000..d59fa98 --- /dev/null +++ b/api/track_time.php @@ -0,0 +1,86 @@ + false, + 'message' => 'Invalid request' +]; + +function get_last_status($pdo, $employee_id) { + $stmt = $pdo->prepare("SELECT * FROM time_records WHERE employee_id = ? ORDER BY id DESC LIMIT 1"); + $stmt->execute([$employee_id]); + return $stmt->fetch(PDO::FETCH_ASSOC); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $data = json_decode(file_get_contents('php://input'), true); + $action = $data['action'] ?? null; + $employee_id = $data['employee_id'] ?? null; + + if ($action && $employee_id) { + try { + $pdo = db(); + $last_record = get_last_status($pdo, $employee_id); + + if ($action === 'clock_in') { + if ($last_record && $last_record['clock_out'] === null) { + $response['message'] = 'Ya has fichado la entrada. Debes fichar la salida primero.'; + } else { + $stmt = $pdo->prepare("INSERT INTO time_records (employee_id, clock_in) VALUES (?, NOW())"); + $stmt->execute([$employee_id]); + $response['success'] = true; + $response['message'] = 'Entrada registrada con éxito.'; + $response['status'] = 'Fichado a las ' . date('H:i:s'); + $response['action'] = 'clock_in'; + } + } elseif ($action === 'clock_out') { + if (!$last_record || $last_record['clock_out'] !== null) { + $response['message'] = 'No has fichado la entrada. Debes fichar la entrada primero.'; + } else { + $stmt = $pdo->prepare("UPDATE time_records SET clock_out = NOW() WHERE id = ?"); + $stmt->execute([$last_record['id']]); + $response['success'] = true; + $response['message'] = 'Salida registrada con éxito.'; + $response['status'] = 'Salida registrada a las ' . date('H:i:s'); + $response['action'] = 'clock_out'; + } + } else { + $response['message'] = 'Acción no válida.'; + } + } catch (PDOException $e) { + $response['message'] = 'Error de base de datos: ' . $e->getMessage(); + } + } else { + $response['message'] = 'Faltan datos en la solicitud.'; + } +} elseif ($_SERVER['REQUEST_METHOD'] === 'GET') { + $employee_id = $_GET['employee_id'] ?? null; + if ($employee_id) { + try { + $pdo = db(); + $last_record = get_last_status($pdo, $employee_id); + if ($last_record) { + if($last_record['clock_out'] === null) { + $response['status'] = 'Fichado a las ' . date('H:i:s', strtotime($last_record['clock_in'])); + $response['last_action'] = 'clock_in'; + } else { + $response['status'] = 'Salida registrada a las ' . date('H:i:s', strtotime($last_record['clock_out'])); + $response['last_action'] = 'clock_out'; + } + } else { + $response['status'] = 'Listo para fichar la entrada.'; + $response['last_action'] = 'clock_out'; + } + $response['success'] = true; + } catch (PDOException $e) { + $response['message'] = 'Error de base de datos: ' . $e->getMessage(); + } + } +} + +echo json_encode($response); diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..f774fc6 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,110 @@ + +body { + background-color: #F4F7F6; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + margin: 0; + color: #333; +} + +.clock-container { + background-color: #FFFFFF; + padding: 40px; + border-radius: 12px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + text-align: center; + width: 100%; + max-width: 400px; +} + +h1 { + font-size: 24px; + margin-bottom: 10px; +} + +#current-time { + font-size: 48px; + font-weight: 600; + margin-bottom: 20px; + letter-spacing: 2px; +} + +#status-message { + font-size: 16px; + color: #555; + margin-bottom: 30px; + min-height: 24px; +} + +.btn { + border: none; + padding: 15px 30px; + font-size: 18px; + font-weight: 500; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + width: 100%; + margin-bottom: 10px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +.btn-primary { + background-color: #4A90E2; + color: white; +} + +.btn-primary:hover { + background-color: #357ABD; +} + +.btn-danger { + background-color: #D0021B; + color: white; +} + +.btn-danger:hover { + background-color: #A80115; +} + +.btn:disabled { + background-color: #D8D8D8; + cursor: not-allowed; +} + +.toast { + position: fixed; + top: 20px; + right: 20px; + background-color: #333; + color: white; + padding: 15px 20px; + border-radius: 8px; + box-shadow: 0 5px 15px rgba(0,0,0,0.2); + opacity: 0; + visibility: hidden; + transition: opacity 0.3s, visibility 0.3s, transform 0.3s; + transform: translateY(-20px); + z-index: 1000; +} + +.toast.show { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.toast.success { + background-color: #50E3C2; + color: #333; +} + +.toast.error { + background-color: #D0021B; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..a180530 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,92 @@ +document.addEventListener('DOMContentLoaded', function () { + const timeElement = document.getElementById('current-time'); + const statusMessage = document.getElementById('status-message'); + const clockInButton = document.getElementById('clock-in-btn'); + const clockOutButton = document.getElementById('clock-out-btn'); + + function updateTime() { + const now = new Date(); + timeElement.textContent = now.toLocaleTimeString('es-ES'); + } + + setInterval(updateTime, 1000); + updateTime(); + + function showToast(message, type = 'success') { + const toast = document.createElement('div'); + toast.className = `toast ${type}`; + toast.textContent = message; + document.body.appendChild(toast); + + setTimeout(() => { + toast.classList.add('show'); + }, 100); + + setTimeout(() => { + toast.classList.remove('show'); + setTimeout(() => { + document.body.removeChild(toast); + }, 300); + }, 3000); + } + + async function handleClockAction(action) { + clockInButton.disabled = true; + clockOutButton.disabled = true; + + try { + const response = await fetch('api/track_time.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ action: action, employee_id: 1 }) // Hardcoded employee_id + }); + + const result = await response.json(); + + if (result.success) { + showToast(result.message); + statusMessage.textContent = result.status; + if (result.action === 'clock_in') { + clockOutButton.disabled = false; + } else { + clockInButton.disabled = false; + } + } else { + showToast(result.message, 'error'); + // Re-enable buttons based on assumed last state if error + if(action === 'clock_in') clockInButton.disabled = false; else clockOutButton.disabled = false; + } + } catch (error) { + showToast('Error de conexión con el servidor.', 'error'); + if(action === 'clock_in') clockInButton.disabled = false; else clockOutButton.disabled = false; + } + } + + clockInButton.addEventListener('click', () => handleClockAction('clock_in')); + clockOutButton.addEventListener('click', () => handleClockAction('clock_out')); + + // Initial state check + async function checkInitialState() { + try { + const response = await fetch('api/track_time.php?employee_id=1'); + const result = await response.json(); + if(result.status) { + statusMessage.textContent = result.status; + if(result.last_action === 'clock_in'){ + clockInButton.disabled = true; + clockOutButton.disabled = false; + } else { + clockInButton.disabled = false; + clockOutButton.disabled = true; + } + } + } catch(e) { + // assume default state + clockOutButton.disabled = true; + } + } + + checkInitialState(); +}); diff --git a/db/config.php b/db/config.php index 62ed3b7..5a5b782 100644 --- a/db/config.php +++ b/db/config.php @@ -15,3 +15,17 @@ function db() { } return $pdo; } + +function run_migrations() { + $pdo = db(); + $migration_files = glob(__DIR__ . '/migrations/*.sql'); + foreach ($migration_files as $file) { + try { + $sql = file_get_contents($file); + $pdo->exec($sql); + } catch (PDOException $e) { + // Optionally log this error instead of dying + error_log("Migration failed for file: $file. Error: " . $e->getMessage()); + } + } +} diff --git a/db/migrations/001_create_time_records_table.sql b/db/migrations/001_create_time_records_table.sql new file mode 100644 index 0000000..4fa672c --- /dev/null +++ b/db/migrations/001_create_time_records_table.sql @@ -0,0 +1,8 @@ +-- 001_create_time_records_table.sql +CREATE TABLE IF NOT EXISTS `time_records` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `employee_id` INT NOT NULL, + `clock_in` DATETIME NOT NULL, + `clock_out` DATETIME DEFAULT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/index.php b/index.php index 6f7ffab..11c5059 100644 --- a/index.php +++ b/index.php @@ -1,131 +1,37 @@ - - - + + - - - New Style - - - - + + + Control de Presencia + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ +
+

Control de Presencia

+
--:--:--
+
Cargando estado...
+ + + +
-
- + + + - + \ No newline at end of file