| Client | Phone | Purchase | Expiry | Tool | Price | Plan | Payment | Days Left | Status | Actions | |
|---|---|---|---|---|---|---|---|---|---|---|---|
|
🗂️
No clients yet. Click “New Client” to add your first one. |
|||||||||||
| “> | |||||||||||
\n
}
if (!file_exists(DATA_FILE)) {
@file_put_contents(DATA_FILE, json_encode([], JSON_PRETTY_PRINT));
}
}
ensureStorage();
/* —————————————————————
DATA ACCESS HELPERS (with file locking)
————————————————————— */
function loadClients()
{
if (!file_exists(DATA_FILE)) return [];
$fp = @fopen(DATA_FILE, ‘r’);
if (!$fp) return [];
$data = [];
if (flock($fp, LOCK_SH)) {
$content = stream_get_contents($fp);
flock($fp, LOCK_UN);
$decoded = json_decode($content, true);
$data = is_array($decoded) ? $decoded : [];
}
fclose($fp);
return $data;
}
function saveClients($clients)
{
$fp = @fopen(DATA_FILE, ‘c’);
if (!$fp) return false;
$ok = false;
if (flock($fp, LOCK_EX)) {
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, json_encode(array_values($clients), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
fflush($fp);
flock($fp, LOCK_UN);
$ok = true;
}
fclose($fp);
return $ok;
}
/* —————————————————————
SECURITY / UTIL HELPERS
————————————————————— */
function cleanInput($v)
{
return trim(strip_tags((string)($v ?? ”)));
}
function e($v)
{
return htmlspecialchars((string)($v ?? ”), ENT_QUOTES, ‘UTF-8’);
}
function genId()
{
return ‘c_’ . bin2hex(random_bytes(8));
}
if (empty($_SESSION[‘csrf’])) {
$_SESSION[‘csrf’] = bin2hex(random_bytes(32));
}
function checkCsrf()
{
return isset($_POST[‘csrf’]) && hash_equals($_SESSION[‘csrf’], $_POST[‘csrf’]);
}
function computeStatus($expiryDate)
{
try {
$today = new DateTime(‘today’);
$expiry = new DateTime($expiryDate);
} catch (Exception $e) {
return [‘status’ => ‘Active’, ‘days’ => 0];
}
$interval = $today->diff($expiry);
$days = (int)$interval->format(‘%a’);
if ($interval->invert === 1) $days = -$days; // expiry is in the past
if ($days < 0) return ['status' => ‘Expired’, ‘days’ => $days];
if ($days <= EXPIRING_SOON_DAYS) return ['status' => ‘Expiring Soon’, ‘days’ => $days];
return [‘status’ => ‘Active’, ‘days’ => $days];
}
/* —————————————————————
GET ACTIONS — Export CSV / Backup JSON (file download)
————————————————————— */
if (isset($_GET[‘action’])) {
$action = $_GET[‘action’];
if ($action === ‘export_csv’) {
$clients = loadClients();
header(‘Content-Type: text/csv; charset=utf-8’);
header(‘Content-Disposition: attachment; filename=clients_export_’ . date(‘Y-m-d_His’) . ‘.csv’);
$out = fopen(‘php://output’, ‘w’);
fputcsv($out, [‘Name’, ‘Phone’, ‘Email’, ‘Purchase Date’, ‘Expiry Date’, ‘Tool Name’, ‘Tool Price’, ‘Plan Type’, ‘Payment Status’, ‘Days Remaining’, ‘Status’, ‘Notes’]);
foreach ($clients as $c) {
$st = computeStatus($c[‘expiry_date’] ?? ”);
fputcsv($out, [
$c[‘name’] ?? ”, $c[‘phone’] ?? ”, $c[’email’] ?? ”,
$c[‘purchase_date’] ?? ”, $c[‘expiry_date’] ?? ”,
$c[‘tool_name’] ?? ”, $c[‘tool_price’] ?? 0,
$c[‘plan_type’] ?? ”, $c[‘payment_status’] ?? ”,
$st[‘days’], $st[‘status’], $c[‘notes’] ?? ”
]);
}
fclose($out);
exit;
}
if ($action === ‘backup_json’) {
header(‘Content-Type: application/json’);
header(‘Content-Disposition: attachment; filename=clients_backup_’ . date(‘Y-m-d_His’) . ‘.json’);
header(‘Content-Length: ‘ . filesize(DATA_FILE));
readfile(DATA_FILE);
exit;
}
}
/* —————————————————————
POST ACTIONS — Add / Edit / Delete (Post/Redirect/Get pattern)
————————————————————— */
if ($_SERVER[‘REQUEST_METHOD’] === ‘POST’) {
$postAction = $_POST[‘action’] ?? ”;
if (!checkCsrf()) {
$_SESSION[‘flash’] = [‘msg’ => ‘Security check failed. Please refresh and try again.’, ‘type’ => ‘error’];
} else {
$clients = loadClients();
if ($postAction === ‘save_client’) {
$id = cleanInput($_POST[‘id’] ?? ”);
$name = cleanInput($_POST[‘name’] ?? ”);
$phone = cleanInput($_POST[‘phone’] ?? ”);
$email = cleanInput($_POST[’email’] ?? ”);
$purchase_date = cleanInput($_POST[‘purchase_date’] ?? ”);
$expiry_date = cleanInput($_POST[‘expiry_date’] ?? ”);
$tool_name = cleanInput($_POST[‘tool_name’] ?? ”);
$tool_price = floatval($_POST[‘tool_price’] ?? 0);
$plan_type = in_array($_POST[‘plan_type’] ?? ”, [‘Monthly’, ‘Yearly’, ‘Custom’], true) ? $_POST[‘plan_type’] : ‘Custom’;
$payment_status = in_array($_POST[‘payment_status’] ?? ”, [‘Paid’, ‘Pending’], true) ? $_POST[‘payment_status’] : ‘Pending’;
$notes = cleanInput($_POST[‘notes’] ?? ”);
$errors = [];
if ($name === ”) $errors[] = ‘Client name is required.’;
if ($purchase_date === ” || !strtotime($purchase_date)) $errors[] = ‘A valid purchase date is required.’;
if ($expiry_date === ” || !strtotime($expiry_date)) $errors[] = ‘A valid expiry date is required.’;
if ($email !== ” && !filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = ‘Email address is invalid.’;
if ($tool_price < 0) $errors[] = 'Tool price cannot be negative.';
if (empty($errors)) {
if ($id !== '') {
$found = false;
foreach ($clients as &$c) {
if (($c['id'] ?? '') === $id) {
$c['name'] = $name; $c['phone'] = $phone; $c['email'] = $email;
$c['purchase_date'] = $purchase_date; $c['expiry_date'] = $expiry_date;
$c['tool_name'] = $tool_name; $c['tool_price'] = $tool_price;
$c['plan_type'] = $plan_type; $c['payment_status'] = $payment_status;
$c['notes'] = $notes; $c['updated_at'] = date('c');
$found = true;
break;
}
}
unset($c);
saveClients($clients);
$_SESSION['flash'] = $found
? ['msg' => ‘Client updated successfully.’, ‘type’ => ‘success’]
: [‘msg’ => ‘Client not found.’, ‘type’ => ‘error’];
} else {
$clients[] = [
‘id’ => genId(), ‘name’ => $name, ‘phone’ => $phone, ’email’ => $email,
‘purchase_date’ => $purchase_date, ‘expiry_date’ => $expiry_date,
‘tool_name’ => $tool_name, ‘tool_price’ => $tool_price,
‘plan_type’ => $plan_type, ‘payment_status’ => $payment_status,
‘notes’ => $notes, ‘created_at’ => date(‘c’), ‘updated_at’ => date(‘c’),
];
saveClients($clients);
$_SESSION[‘flash’] = [‘msg’ => ‘Client added successfully.’, ‘type’ => ‘success’];
}
} else {
$_SESSION[‘flash’] = [‘msg’ => implode(‘ ‘, $errors), ‘type’ => ‘error’];
}
} elseif ($postAction === ‘delete_client’) {
$id = cleanInput($_POST[‘id’] ?? ”);
$before = count($clients);
$clients = array_values(array_filter($clients, function ($c) use ($id) {
return ($c[‘id’] ?? ”) !== $id;
}));
saveClients($clients);
$after = count($clients);
$_SESSION[‘flash’] = ($before > $after)
? [‘msg’ => ‘Client deleted successfully.’, ‘type’ => ‘success’]
: [‘msg’ => ‘Client not found.’, ‘type’ => ‘error’];
}
}
header(‘Location: ‘ . strtok($_SERVER[‘REQUEST_URI’], ‘?’));
exit;
}
/* —————————————————————
FLASH MESSAGE
————————————————————— */
$message = ”;
$messageType = ”;
if (isset($_SESSION[‘flash’])) {
$message = $_SESSION[‘flash’][‘msg’];
$messageType = $_SESSION[‘flash’][‘type’];
unset($_SESSION[‘flash’]);
}
/* —————————————————————
LOAD DATA + COMPUTE DASHBOARD STATS
————————————————————— */
$clients = loadClients();
$totalClients = count($clients);
$activeCount = 0;
$expiredCount = 0;
$expiringCount = 0;
$monthlyCount = 0;
$yearlyCount = 0;
$totalPaid = 0.0;
$totalPending = 0.0;
$rows = [];
foreach ($clients as $c) {
$st = computeStatus($c[‘expiry_date’] ?? ”);
$c[‘_status’] = $st[‘status’];
$c[‘_days’] = $st[‘days’];
if ($st[‘status’] === ‘Active’) $activeCount++;
if ($st[‘status’] === ‘Expired’) $expiredCount++;
if ($st[‘status’] === ‘Expiring Soon’) $expiringCount++;
if (($c[‘plan_type’] ?? ”) === ‘Monthly’) $monthlyCount++;
if (($c[‘plan_type’] ?? ”) === ‘Yearly’) $yearlyCount++;
if (($c[‘payment_status’] ?? ”) === ‘Paid’) $totalPaid += floatval($c[‘tool_price’] ?? 0);
if (($c[‘payment_status’] ?? ”) === ‘Pending’) $totalPending += floatval($c[‘tool_price’] ?? 0);
$rows[] = $c;
}
usort($rows, function ($a, $b) {
return strcmp($b[‘created_at’] ?? ”, $a[‘created_at’] ?? ”);
});
$csrfToken = $_SESSION[‘csrf’];
$clientsJson = json_encode($rows, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
?>
| Client | Phone | Purchase | Expiry | Tool | Price | Plan | Payment | Days Left | Status | Actions | |
|---|---|---|---|---|---|---|---|---|---|---|---|
|
🗂️
No clients yet. Click “New Client” to add your first one. |
|||||||||||
| “> | |||||||||||