mirror of
https://github.com/myronblair/do-server-config
synced 2026-07-27 21:18:32 -05:00
[orbis] Weekly backup 2026-07-07 — 318 files changed, 48597 insertions(+), 7 deletions(-)
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
AdminAuth::require();
|
||||
|
||||
$rows = db()->fetchAll("
|
||||
SELECT ws.worker_id, ws.week_start, ws.paid_at, w.name
|
||||
FROM week_settlements ws
|
||||
JOIN workers w ON w.id = ws.worker_id
|
||||
WHERE ws.paid = 1
|
||||
ORDER BY ws.week_start DESC, w.name ASC
|
||||
");
|
||||
|
||||
$owedByRow = [];
|
||||
foreach ($rows as $i => $r) {
|
||||
$dates = getWeekDates($r['week_start']);
|
||||
$placeholders = implode(',', array_fill(0, count($dates), '?'));
|
||||
$entries = db()->query("SELECT * FROM day_entries WHERE worker_id = ? AND work_date IN ({$placeholders})", array_merge([$r['worker_id']], $dates))->fetchAll();
|
||||
$owedByRow[$i] = calcOwed($entries);
|
||||
}
|
||||
|
||||
$pageTitle = 'Full History - ChuckCo Time Keeper';
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
<div class="topbar">
|
||||
<h1>Full Payment History</h1>
|
||||
<a href="/admin/index.php" class="logout">← Back</a>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<h3>All Paid Weeks</h3>
|
||||
<?php if (empty($rows)): ?>
|
||||
<p class="text-muted">No paid weeks yet.</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($rows as $i => $r): ?>
|
||||
<div class="worker-row">
|
||||
<a href="/admin/worker.php?id=<?= (int)$r['worker_id'] ?>" style="color:inherit; text-decoration:none;">
|
||||
<strong><?= e($r['name']) ?></strong> · Week of <?= e(dayLabel($r['week_start'])) ?>
|
||||
</a>
|
||||
<span class="worker-owed"><?= formatCurrency($owedByRow[$i] ?? 0) ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
AdminAuth::require();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'add_worker') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
if ($name !== '') {
|
||||
db()->insert('workers', [
|
||||
'name' => $name,
|
||||
'entry_token' => generateToken(),
|
||||
'share_token' => generateToken(),
|
||||
'payer_token' => generateToken(),
|
||||
]);
|
||||
}
|
||||
header('Location: /admin/index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete_worker') {
|
||||
$workerId = (int) ($_POST['worker_id'] ?? 0);
|
||||
if ($workerId > 0) {
|
||||
// day_entries and week_settlements both have ON DELETE CASCADE on worker_id,
|
||||
// so deleting the worker row removes all their related data too.
|
||||
db()->query("DELETE FROM workers WHERE id = :id", ['id' => $workerId]);
|
||||
}
|
||||
header('Location: /admin/index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$workers = db()->fetchAll("SELECT * FROM workers WHERE active = 1 ORDER BY name ASC");
|
||||
|
||||
$owedByWorker = [];
|
||||
$rows = db()->fetchAll("
|
||||
SELECT de.worker_id,
|
||||
SUM(CASE WHEN de.status='full' THEN " . FULL_DAY_RATE . " WHEN de.status='half' THEN " . HALF_DAY_RATE . " ELSE 0 END) AS owed
|
||||
FROM day_entries de
|
||||
LEFT JOIN week_settlements ws ON ws.worker_id = de.worker_id AND ws.week_start = de.week_start
|
||||
WHERE ws.paid IS NULL OR ws.paid = 0
|
||||
GROUP BY de.worker_id
|
||||
");
|
||||
foreach ($rows as $r) {
|
||||
$owedByWorker[$r['worker_id']] = (float) $r['owed'];
|
||||
}
|
||||
|
||||
$pageTitle = 'Admin Dashboard - ChuckCo Time Keeper';
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
<div class="topbar">
|
||||
<h1>ChuckCo Time Keeper</h1>
|
||||
<a href="/admin/logout.php" class="logout">Log out</a>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="card">
|
||||
<h3>All Workers Overview</h3>
|
||||
<p class="text-muted" style="font-size:0.85rem;">One link showing everyone's current week together.</p>
|
||||
<a href="/all.php?t=<?= e(ALL_WORKERS_TOKEN) ?>" class="btn btn-secondary btn-block" target="_blank">Open All-Workers View</a>
|
||||
<a href="/admin/history.php" class="btn btn-secondary btn-block" style="margin-top:0.5rem;">Full History (Everyone)</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Add Worker</h3>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="add_worker">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-input" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Add Worker</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Workers</h3>
|
||||
<?php if (empty($workers)): ?>
|
||||
<p class="text-muted">No workers yet. Add one above.</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($workers as $w): ?>
|
||||
<div class="worker-row" style="display:block;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<a href="/admin/worker.php?id=<?= (int)$w['id'] ?>" class="worker-name" style="color:inherit; text-decoration:none;"><?= e($w['name']) ?></a>
|
||||
<span class="worker-owed"><?= formatCurrency($owedByWorker[$w['id']] ?? 0) ?> owed</span>
|
||||
</div>
|
||||
<div class="link-row">
|
||||
<a class="btn btn-sm btn-secondary" href="/w.php?t=<?= e($w['entry_token']) ?>" target="_blank">Entry Link</a>
|
||||
<a class="btn btn-sm btn-secondary" href="/s.php?t=<?= e($w['share_token']) ?>" target="_blank">Their Screenshot</a>
|
||||
<a class="btn btn-sm btn-secondary" href="/p.php?t=<?= e($w['payer_token']) ?>" target="_blank">My Screenshot</a>
|
||||
<form method="POST" style="display:inline;" onsubmit="return confirm('Permanently delete <?= e(addslashes($w['name'])) ?> and ALL their time entries and history? This cannot be undone.');">
|
||||
<input type="hidden" name="action" value="delete_worker">
|
||||
<input type="hidden" name="worker_id" value="<?= (int)$w['id'] ?>">
|
||||
<button type="submit" class="btn btn-sm" style="background:rgba(220,38,38,.1); color:var(--error);">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
if (AdminAuth::isLoggedIn()) {
|
||||
header('Location: /admin/index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$error = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$password = $_POST['password'] ?? '';
|
||||
if (AdminAuth::attempt($password)) {
|
||||
header('Location: /admin/index.php');
|
||||
exit;
|
||||
}
|
||||
$error = AdminAuth::isLockedOut($_SERVER['REMOTE_ADDR'] ?? 'unknown')
|
||||
? 'Too many failed attempts. Try again in 15 minutes.'
|
||||
: 'Incorrect password';
|
||||
}
|
||||
|
||||
$pageTitle = 'Login - ChuckCo Time Keeper';
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
<div class="container" style="max-width:400px; padding-top: 4rem;">
|
||||
<div class="card">
|
||||
<h2 class="text-center">ChuckCo Time Keeper</h2>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-error"><?= e($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Admin Password</label>
|
||||
<input type="password" name="password" class="form-input" autofocus required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Log In</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
AdminAuth::logout();
|
||||
header('Location: /admin/login.php');
|
||||
exit;
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
AdminAuth::require();
|
||||
|
||||
$workerId = (int) ($_GET['id'] ?? 0);
|
||||
$worker = db()->fetch("SELECT * FROM workers WHERE id = :id", ['id' => $workerId]);
|
||||
if (!$worker) {
|
||||
header('Location: /admin/index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'set_day') {
|
||||
$date = $_POST['date'] ?? '';
|
||||
$status = $_POST['status'] ?? '';
|
||||
$reason = trim($_POST['reason'] ?? '');
|
||||
$location = trim($_POST['location'] ?? '');
|
||||
if (in_array($status, ['full', 'half', 'absent'], true) && $date) {
|
||||
$weekStart = getWeekStart($date);
|
||||
db()->query("
|
||||
INSERT INTO day_entries (worker_id, work_date, week_start, status, absence_reason, location)
|
||||
VALUES (:wid, :date, :week, :status, :reason, :location)
|
||||
ON DUPLICATE KEY UPDATE status = :status2, absence_reason = :reason2, location = :location2, week_start = :week2
|
||||
", [
|
||||
'wid' => $workerId, 'date' => $date, 'week' => $weekStart,
|
||||
'status' => $status, 'reason' => $reason ?: null, 'location' => $location ?: null,
|
||||
'status2' => $status, 'reason2' => $reason ?: null, 'location2' => $location ?: null, 'week2' => $weekStart,
|
||||
]);
|
||||
}
|
||||
header('Location: /admin/worker.php?id=' . $workerId);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'clear_day') {
|
||||
$date = $_POST['date'] ?? '';
|
||||
db()->query("DELETE FROM day_entries WHERE worker_id = :wid AND work_date = :date", ['wid' => $workerId, 'date' => $date]);
|
||||
header('Location: /admin/worker.php?id=' . $workerId);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'toggle_paid') {
|
||||
$weekStart = $_POST['week_start'] ?? '';
|
||||
$existing = db()->fetch("SELECT * FROM week_settlements WHERE worker_id = :wid AND week_start = :w", ['wid' => $workerId, 'w' => $weekStart]);
|
||||
if ($existing) {
|
||||
$newPaid = $existing['paid'] ? 0 : 1;
|
||||
db()->update('week_settlements', ['paid' => $newPaid, 'paid_at' => $newPaid ? date('Y-m-d H:i:s') : null], 'id = :id', ['id' => $existing['id']]);
|
||||
} else {
|
||||
db()->insert('week_settlements', ['worker_id' => $workerId, 'week_start' => $weekStart, 'paid' => 1, 'paid_at' => date('Y-m-d H:i:s')]);
|
||||
}
|
||||
header('Location: /admin/worker.php?id=' . $workerId);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Gather all weeks that have entries, plus the current week even if empty
|
||||
$weekStarts = db()->fetchAll("SELECT DISTINCT week_start FROM day_entries WHERE worker_id = :wid ORDER BY week_start DESC", ['wid' => $workerId]);
|
||||
$weekStartList = array_column($weekStarts, 'week_start');
|
||||
if (!in_array(currentWeekStart(), $weekStartList, true)) {
|
||||
array_unshift($weekStartList, currentWeekStart());
|
||||
}
|
||||
rsort($weekStartList);
|
||||
|
||||
$settlements = db()->fetchAll("SELECT * FROM week_settlements WHERE worker_id = :wid", ['wid' => $workerId]);
|
||||
$paidMap = [];
|
||||
foreach ($settlements as $s) { $paidMap[$s['week_start']] = (bool) $s['paid']; }
|
||||
|
||||
$allEntries = db()->fetchAll("SELECT * FROM day_entries WHERE worker_id = :wid", ['wid' => $workerId]);
|
||||
$entriesByDate = [];
|
||||
foreach ($allEntries as $en) { $entriesByDate[$en['work_date']] = $en; }
|
||||
|
||||
$pageTitle = $worker['name'] . ' - ChuckCo Time Keeper';
|
||||
require_once __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
<div class="topbar">
|
||||
<h1><?= e($worker['name']) ?></h1>
|
||||
<a href="/admin/index.php" class="logout">← Back</a>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="card">
|
||||
<h3>Links</h3>
|
||||
<div class="link-row">
|
||||
<a class="btn btn-sm btn-secondary" href="/w.php?t=<?= e($worker['entry_token']) ?>" target="_blank">Entry Link</a>
|
||||
<a class="btn btn-sm btn-secondary" href="/s.php?t=<?= e($worker['share_token']) ?>" target="_blank">Their Screenshot</a>
|
||||
<a class="btn btn-sm btn-secondary" href="/p.php?t=<?= e($worker['payer_token']) ?>" target="_blank">My Screenshot</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php foreach ($weekStartList as $weekStart):
|
||||
$dates = getWeekDates($weekStart);
|
||||
$weekEntries = [];
|
||||
foreach ($dates as $d) { if (isset($entriesByDate[$d])) $weekEntries[] = $entriesByDate[$d]; }
|
||||
$owed = calcOwed($weekEntries);
|
||||
$isPaid = $paidMap[$weekStart] ?? false;
|
||||
?>
|
||||
<div class="card">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<h3 style="margin:0;">Week of <?= e(dayLabel($weekStart)) ?></h3>
|
||||
<span class="badge <?= $isPaid ? 'badge-full' : 'badge-absent' ?>"><?= $isPaid ? 'Paid' : 'Unpaid' ?></span>
|
||||
</div>
|
||||
|
||||
<?php foreach ($dates as $date):
|
||||
$entry = $entriesByDate[$date] ?? null;
|
||||
$status = $entry['status'] ?? null;
|
||||
?>
|
||||
<div class="day-tap" style="flex-wrap:wrap;">
|
||||
<div style="flex:1; min-width:100%;">
|
||||
<div class="date"><?= e(dayLabel($date)) ?></div>
|
||||
<?php if (!empty($entry['location'])): ?>
|
||||
<div class="reason-text"><?= e($entry['location']) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($status === 'absent' && !empty($entry['absence_reason'])): ?>
|
||||
<div class="reason-text"><?= e($entry['absence_reason']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<form method="POST" style="width:100%; margin-top:0.5rem;">
|
||||
<input type="hidden" name="action" value="set_day">
|
||||
<input type="hidden" name="date" value="<?= e($date) ?>">
|
||||
<div class="day-btn-row">
|
||||
<button type="submit" name="status" value="full" class="day-btn <?= $status === 'full' ? 'active-full' : '' ?>">Full</button>
|
||||
<button type="submit" name="status" value="half" class="day-btn <?= $status === 'half' ? 'active-half' : '' ?>">Half</button>
|
||||
<button type="submit" name="status" value="absent" class="day-btn <?= $status === 'absent' ? 'active-absent' : '' ?>">Off</button>
|
||||
</div>
|
||||
<div class="day-field-row">
|
||||
<input type="text" name="location" class="form-input" placeholder="Location" value="<?= e($entry['location'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="day-field-row">
|
||||
<input type="text" name="reason" class="form-input" placeholder="Reason if off" value="<?= e($entry['absence_reason'] ?? '') ?>">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="owed-total">
|
||||
<span>Owed</span>
|
||||
<span><?= formatCurrency($owed) ?></span>
|
||||
</div>
|
||||
<form method="POST" style="margin-top:0.75rem;">
|
||||
<input type="hidden" name="action" value="toggle_paid">
|
||||
<input type="hidden" name="week_start" value="<?= e($weekStart) ?>">
|
||||
<button type="submit" class="btn <?= $isPaid ? 'btn-secondary' : 'btn-success' ?> btn-block">
|
||||
<?= $isPaid ? 'Mark as Unpaid' : 'Mark as Paid' ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
Reference in New Issue
Block a user