webtime service fixes

This commit is contained in:
Bruno Eckhardt committed 2026-06-24 12:05:16 +02:00
1 parent 9ff26f0f59
commit 6336442f2e
7 files changed
+476 -50

No files matched your search

@@ -0,0 +1,104 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Zeiterfassung;
use MVC\Model\Users;
class WebtimeController extends Controller {
public function webtime() {
// User aus Session + DB laden
$username = $_SESSION['username'] ?? null;
if (!$username) {
header('Location: ?service=login');
exit;
}
$user = Users::getByUsername($username);
$userId = (int)($user['ID'] ?? 0);
if (!$userId) {
header('Location: ?service=login');
exit;
}
// --- Einstempeln ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'start') {
if (!Zeiterfassung::isRunning($userId)) {
Zeiterfassung::insert([
'DATUM' => date('d.m.Y'),
'BEGIN' => date('H:i'),
'USER_ID' => $userId,
]);
}
header('Location: ?service=webtime');
exit;
}
// --- Ausstempeln + Aufgaben speichern ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'stop') {
$open = Zeiterfassung::getOpenEntry($userId);
if ($open) {
Zeiterfassung::update(
[
'ENDE' => date('H:i'),
'A_1' => $_POST['a_1'] ?? null,
'A_2' => $_POST['a_2'] ?? null,
'A_3' => $_POST['a_3'] ?? null,
'A_4' => $_POST['a_4'] ?? null,
],
"ID = ?",
$open['ID']
);
}
header('Location: ?service=webtime');
exit;
}
// --- Eintrag bearbeiten ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'edit') {
$id = (int)($_POST['entry_id'] ?? 0);
if ($id) {
Zeiterfassung::update(
[
'DATUM' => $_POST['datum'],
'BEGIN' => $_POST['begin'],
'ENDE' => $_POST['ende'] !== '' ? $_POST['ende'] : null,
'A_1' => $_POST['a_1'] ?? null,
'A_2' => $_POST['a_2'] ?? null,
'A_3' => $_POST['a_3'] ?? null,
'A_4' => $_POST['a_4'] ?? null,
],
"ID = ? AND USER_ID = ?",
$id, $userId
);
}
header('Location: ?service=webtime');
exit;
}
// --- Eintrag löschen ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete') {
$id = (int)($_POST['entry_id'] ?? 0);
if ($id) {
Zeiterfassung::delete("ID = ? AND USER_ID = ?", $id, $userId);
}
header('Location: ?service=webtime');
exit;
}
$isRunning = Zeiterfassung::isRunning($userId);
$openEntry = Zeiterfassung::getOpenEntry($userId);
$eintraege = Zeiterfassung::getByUser($userId);
return $this->render('webtime', [
'isRunning' => $isRunning,
'openEntry' => $openEntry,
'eintraege' => $eintraege,
]);
}
}
@@ -1,22 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Client;
use MVC\Classes\Cookie;
use MVC\Classes\Request;
use MVC\Classes\Server;
// use MVC\Model\YourModel; // Importiere dein Model
class ZeiterfassungController extends Controller {
public function zeiterfassung() {
// Nutze Models für den Datenbankzugriff
// $data = YourModel::all();
return $this->render('zeiterfassung', [
'title' => 'Zeiterfassung Modul',
'message' => 'Willkommen in deinem neuen Modul Zeiterfassung!'
]);
}
}
+19 -12
View File
@@ -4,19 +4,26 @@ namespace MVC\Model;
use MVC\Core\Model;
/**
* Model für die Tabelle 'Zeiterfassung'
* Verfügbare Spalten: 'ID', ''
*/
class Zeiterfassung extends Model {
protected static $table = "Zeiterfassung";
// Hier kannst du spezifische Methoden für dieses Model hinzufügen
/**
* Beispiel: Suche nach einer spezifischen Spalte
*/
public static function getByFirstColumn($value) {
return self::where("ID = ?", $value);
public static function getByUser(int $userId): array {
return self::where("USER_ID = ? ORDER BY ID DESC", $userId);
}
}
public static function isRunning(int $userId): bool {
$result = self::where(
"USER_ID = ? AND ENDE IS NULL LIMIT 1",
$userId
);
return !empty($result);
}
public static function getOpenEntry(int $userId): ?array {
$result = self::where(
"USER_ID = ? AND ENDE IS NULL ORDER BY ID DESC LIMIT 1",
$userId
);
return $result[0] ?? null;
}
}
+351
View File
@@ -0,0 +1,351 @@
<h2>⏱️ WebTime – Zeiterfassung</h2>
<!-- STATUS-KARTE -->
<div class="status-card <?= $isRunning ? 'running' : 'stopped' ?>">
<?php if ($isRunning && $openEntry): ?>
<div class="status-info">
<span class="status-dot"></span>
<strong>Läuft seit <?= htmlspecialchars($openEntry['BEGIN']) ?> Uhr</strong>
</div>
<form method="POST">
<input type="hidden" name="action" value="stop">
<div class="aufgaben-grid">
<input type="text" name="a_1"
value="<?= htmlspecialchars($openEntry['A_1'] ?? '') ?>"
placeholder="Aufgabe 1" class="aufgabe-input">
<input type="text" name="a_2"
value="<?= htmlspecialchars($openEntry['A_2'] ?? '') ?>"
placeholder="Aufgabe 2" class="aufgabe-input">
<input type="text" name="a_3"
value="<?= htmlspecialchars($openEntry['A_3'] ?? '') ?>"
placeholder="Aufgabe 3" class="aufgabe-input">
<input type="text" name="a_4"
value="<?= htmlspecialchars($openEntry['A_4'] ?? '') ?>"
placeholder="Aufgabe 4" class="aufgabe-input">
</div>
<button type="submit" class="btn btn-stop">⏹ Ausstempeln</button>
</form>
<?php else: ?>
<div class="status-info">
<span class="status-dot"></span>
<strong>Nicht eingestempelt</strong>
</div>
<form method="POST">
<input type="hidden" name="action" value="start">
<button type="submit" class="btn btn-start">▶ Einstempeln</button>
</form>
<?php endif; ?>
</div>
<!-- TABELLE -->
<div class="card" style="margin-top: 30px;">
<table class="data-table">
<thead>
<tr>
<th>Datum</th>
<th>Beginn</th>
<th>Ende</th>
<th>Dauer</th>
<th>Aufgaben</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php foreach ($eintraege as $e):
$dauer = '–';
if (!empty($e['BEGIN']) && !empty($e['ENDE'])) {
$diff = strtotime($e['ENDE']) - strtotime($e['BEGIN']);
if ($diff > 0) {
$dauer = sprintf('%02d:%02d h', floor($diff / 3600), floor(($diff % 3600) / 60));
}
}
$aufgaben = array_filter([
$e['A_1'] ?? null,
$e['A_2'] ?? null,
$e['A_3'] ?? null,
$e['A_4'] ?? null,
]);
?>
<tr>
<td><?= htmlspecialchars($e['DATUM'] ?? '') ?></td>
<td><?= htmlspecialchars($e['BEGIN'] ?? '') ?></td>
<td>
<?php if (!empty($e['ENDE'])): ?>
<?= htmlspecialchars($e['ENDE']) ?>
<?php else: ?>
<span class="badge-running">läuft</span>
<?php endif; ?>
</td>
<td><?= $dauer ?></td>
<td>
<?php foreach ($aufgaben as $a): ?>
<span class="tag"><?= htmlspecialchars($a) ?></span>
<?php endforeach; ?>
</td>
<td>
<button class="btn-icon"
onclick='openEdit(<?= htmlspecialchars(json_encode($e), ENT_QUOTES) ?>)'>✏️</button>
<form method="POST" style="display:inline"
onsubmit="return confirm('Eintrag löschen?')">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="entry_id" value="<?= (int)$e['ID'] ?>">
<button type="submit" class="btn-icon">🗑️</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($eintraege)): ?>
<tr>
<td colspan="6" style="text-align:center; padding: 20px; opacity: 0.5;">
Noch keine Einträge vorhanden.
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- EDIT MODAL -->
<div id="editModal" class="modal" style="display:none">
<div class="modal-box">
<h3 style="margin-top:0">✏️ Eintrag bearbeiten</h3>
<form method="POST">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="entry_id" id="edit_id">
<div class="form-row">
<div class="form-group">
<label>Datum</label>
<input type="text" name="datum" id="edit_datum"
class="form-control" placeholder="01.01.2025">
</div>
<div class="form-group">
<label>Beginn</label>
<input type="text" name="begin" id="edit_begin"
class="form-control" placeholder="08:00">
</div>
<div class="form-group">
<label>Ende</label>
<input type="text" name="ende" id="edit_ende"
class="form-control" placeholder="17:00">
</div>
</div>
<div class="form-group">
<label>Aufgabe 1</label>
<input type="text" name="a_1" id="edit_a_1" class="form-control">
</div>
<div class="form-group">
<label>Aufgabe 2</label>
<input type="text" name="a_2" id="edit_a_2" class="form-control">
</div>
<div class="form-group">
<label>Aufgabe 3</label>
<input type="text" name="a_3" id="edit_a_3" class="form-control">
</div>
<div class="form-group">
<label>Aufgabe 4</label>
<input type="text" name="a_4" id="edit_a_4" class="form-control">
</div>
<div style="display:flex; gap:10px; margin-top:20px">
<button type="submit" class="btn btn-start">💾 Speichern</button>
<button type="button" class="btn btn-stop"
onclick="document.getElementById('editModal').style.display='none'">
Abbrechen
</button>
</div>
</form>
</div>
</div>
<script>
function openEdit(e) {
document.getElementById('edit_id').value = e.ID ?? '';
document.getElementById('edit_datum').value = e.DATUM ?? '';
document.getElementById('edit_begin').value = e.BEGIN ?? '';
document.getElementById('edit_ende').value = e.ENDE ?? '';
document.getElementById('edit_a_1').value = e.A_1 ?? '';
document.getElementById('edit_a_2').value = e.A_2 ?? '';
document.getElementById('edit_a_3').value = e.A_3 ?? '';
document.getElementById('edit_a_4').value = e.A_4 ?? '';
document.getElementById('editModal').style.display = 'flex';
}
// Modal schließen bei Klick außerhalb
document.getElementById('editModal').addEventListener('click', function(e) {
if (e.target === this) this.style.display = 'none';
});
</script>
<style>
.status-card {
padding: 25px 30px;
border-radius: 10px;
border: 2px solid var(--border);
background: var(--sidebar);
display: flex;
align-items: center;
gap: 30px;
flex-wrap: wrap;
}
.status-card.running { border-color: #2ecc71; }
.status-card.stopped { border-color: var(--border); }
.status-info {
display: flex;
align-items: center;
gap: 8px;
min-width: 220px;
}
.status-dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background: #95a5a6;
flex-shrink: 0;
}
.running .status-dot {
background: #2ecc71;
animation: pulse 1.2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.aufgaben-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin: 12px 0;
}
.aufgabe-input {
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
width: 100%;
box-sizing: border-box;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
font-size: 0.95rem;
}
.btn-start { background: #2ecc71; color: white; }
.btn-stop { background: #e74c3c; color: white; }
.btn:hover { opacity: 0.85; }
.card {
background: var(--sidebar);
border-radius: 8px;
border: 1px solid var(--border);
overflow: hidden;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
text-align: left;
padding: 13px 15px;
border-bottom: 1px solid var(--border);
}
.data-table th {
background: rgba(0,0,0,0.04);
font-weight: bold;
}
.data-table tr:last-child td {
border-bottom: none;
}
.data-table tr:hover td {
background: rgba(0,0,0,0.02);
}
.badge-running {
color: #2ecc71;
font-weight: bold;
font-size: 0.8rem;
}
.tag {
display: inline-block;
background: rgba(0,123,255,0.1);
color: var(--accent);
border-radius: 4px;
padding: 2px 8px;
font-size: 0.78rem;
margin: 2px;
}
.btn-icon {
background: none;
border: none;
cursor: pointer;
font-size: 1.1rem;
padding: 4px 6px;
border-radius: 4px;
transition: background 0.15s;
}
.btn-icon:hover {
background: rgba(0,0,0,0.07);
}
/* Modal */
.modal {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.55);
display: flex;
align-items: center;
justify-content: center;
z-index: 200;
}
.modal-box {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 30px;
width: 520px;
max-width: 95vw;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
}
.form-row {
display: flex;
gap: 12px;
}
.form-group {
flex: 1;
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 600;
font-size: 0.9rem;
}
.form-control {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
box-sizing: border-box;
font-size: 0.95rem;
}
.form-control:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(0,123,255,0.2);
}
</style>
@@ -1,14 +0,0 @@
<h2><?= htmlspecialchars($title) ?></h2>
<div class="card">
<p><?= htmlspecialchars($message) ?></p>
<p>Hier kannst du nun dein Projekt aufbauen.</p>
</div>
<style>
.card {
background: var(--card-bg);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--card-border);
}
</style>
+1 -1
View File
@@ -14,5 +14,5 @@ return [
'template_form' => ['label' => '📝 Form Template'],
'template_table' => ['label' => '📊 Table Template'],
'docs' => ['label' => '📚 Dokumentation'],
'zeiterfassung' => ['label' => 'Zeiterfassung'],
'webtime' => ['label' => '⏱️ WebTime'],
];
+1 -1
View File
@@ -23,8 +23,8 @@ $router->add('template_download', \MVC\Controller\TemplateController::class, 'do
$router->add('template_form', \MVC\Controller\TemplateController::class, 'data_form', ['auth']);
$router->add('template_table', \MVC\Controller\TemplateController::class, 'data_table', ['auth']);
$router->add('download_file', \MVC\Controller\TemplateController::class, 'download_file', ['auth']);
$router->add('webtime',\MVC\Controller\WebtimeController::class, 'webtime', ['auth']);
// API Routes (Path-based)
$router->add('api/v1/test', \MVC\Controller\ApiController::class, 'get');
$router->add('api/v1/become', \MVC\Controller\ApiController::class, 'become');
$router->add('zeiterfassung', \MVC\Controller\ZeiterfassungController::class, 'zeiterfassung');