Compare commits

...

2 Commits

Author SHA1 Message Date
Flatlogic Bot
70a9003080 even better 2025-11-29 09:36:40 +00:00
Flatlogic Bot
fd9ccfc89a good version 1 2025-11-29 07:48:50 +00:00
21 changed files with 2135 additions and 145 deletions

216
add_client.php Normal file
View File

@ -0,0 +1,216 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$message = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$db = db();
$required_fields = ['full_legal_name'];
foreach ($required_fields as $field) {
if (empty($_POST[$field])) {
throw new Exception("'$field' is a required field.");
}
}
$sql = "INSERT INTO clients (
full_legal_name, ndis_client_number, date_of_birth, preferred_contact_method,
primary_phone, email, address, emergency_contact_name, emergency_contact_phone,
ndis_plan_start_date, ndis_plan_end_date, plan_manager_name, plan_manager_contact,
ndis_funding_budget_total, primary_disability, support_needs_summary,
communication_aids_methods, behaviours_of_concern, risk_assessment_summary,
safety_plan, consent_for_info_sharing, intake_notes
) VALUES (
:full_legal_name, :ndis_client_number, :date_of_birth, :preferred_contact_method,
:primary_phone, :email, :address, :emergency_contact_name, :emergency_contact_phone,
:ndis_plan_start_date, :ndis_plan_end_date, :plan_manager_name, :plan_manager_contact,
:ndis_funding_budget_total, :primary_disability, :support_needs_summary,
:communication_aids_methods, :behaviours_of_concern, :risk_assessment_summary,
:safety_plan, :consent_for_info_sharing, :intake_notes
)";
$stmt = $db->prepare($sql);
$consent = isset($_POST['consent_for_info_sharing']) ? 1 : 0;
$stmt->bindParam(':full_legal_name', $_POST['full_legal_name']);
$stmt->bindParam(':ndis_client_number', $_POST['ndis_client_number']);
$stmt->bindParam(':date_of_birth', $_POST['date_of_birth']);
$stmt->bindParam(':preferred_contact_method', $_POST['preferred_contact_method']);
$stmt->bindParam(':primary_phone', $_POST['primary_phone']);
$stmt->bindParam(':email', $_POST['email']);
$stmt->bindParam(':address', $_POST['address']);
$stmt->bindParam(':emergency_contact_name', $_POST['emergency_contact_name']);
$stmt->bindParam(':emergency_contact_phone', $_POST['emergency_contact_phone']);
$stmt->bindParam(':ndis_plan_start_date', $_POST['ndis_plan_start_date']);
$stmt->bindParam(':ndis_plan_end_date', $_POST['ndis_plan_end_date']);
$stmt->bindParam(':plan_manager_name', $_POST['plan_manager_name']);
$stmt->bindParam(':plan_manager_contact', $_POST['plan_manager_contact']);
$stmt->bindParam(':ndis_funding_budget_total', $_POST['ndis_funding_budget_total']);
$stmt->bindParam(':primary_disability', $_POST['primary_disability']);
$stmt->bindParam(':support_needs_summary', $_POST['support_needs_summary']);
$stmt->bindParam(':communication_aids_methods', $_POST['communication_aids_methods']);
$stmt->bindParam(':behaviours_of_concern', $_POST['behaviours_of_concern']);
$stmt->bindParam(':risk_assessment_summary', $_POST['risk_assessment_summary']);
$stmt->bindParam(':safety_plan', $_POST['safety_plan']);
$stmt->bindParam(':consent_for_info_sharing', $consent, PDO::PARAM_INT);
$stmt->bindParam(':intake_notes', $_POST['intake_notes']);
$stmt->execute();
$message = "Client successfully added!";
} catch (Exception $e) {
$error = "Error: " . $e->getMessage();
}
}
?>
<style>
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }
.form-section { background-color: #fdfdfd; padding: 1.5rem; border-radius: 8px; border: 1px solid #eee; }
.form-section h3 { font-size: 1.2rem; margin-bottom: 1.5rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; }
.ai-section { background-color: #eaf5ff; padding: 1.5rem; border-radius: 8px; margin-bottom: 2rem; border: 1px solid #c7dfff; }
</style>
<header>
<h1>Add New Client</h1>
</header>
<?php if ($message): ?>
<div class="feedback success"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="feedback error"><?php echo $error; ?></div>
<?php endif; ?>
<form action="add_client.php" method="POST">
<div class="ai-section">
<h3>AI-Assisted Intake</h3>
<div class="form-group">
<label for="intake_notes">Raw Intake Notes</label>
<textarea id="intake_notes" name="intake_notes" rows="6" placeholder="Paste the full, unstructured intake notes here..."></textarea>
</div>
<button type="button" class="btn btn-secondary" id="summarize-with-ai">Summarize with AI</button>
</div>
<div class="form-grid">
<div class="form-section">
<h3>Client Details</h3>
<div class="form-group">
<label for="full_legal_name">Full Legal Name *</label>
<input type="text" id="full_legal_name" name="full_legal_name" required>
</div>
<div class="form-group">
<label for="ndis_client_number">NDIS Client Number</label>
<input type="text" id="ndis_client_number" name="ndis_client_number">
</div>
<div class="form-group">
<label for="date_of_birth">Date of Birth</label>
<input type="date" id="date_of_birth" name="date_of_birth">
</div>
<div class="form-group">
<label for="preferred_contact_method">Preferred Contact Method</label>
<input type="text" id="preferred_contact_method" name="preferred_contact_method">
</div>
</div>
<div class="form-section">
<h3>Contact Info</h3>
<div class="form-group">
<label for="primary_phone">Primary Phone</label>
<input type="tel" id="primary_phone" name="primary_phone">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email">
</div>
<div class="form-group">
<label for="address">Address</label>
<textarea id="address" name="address" rows="1"></textarea>
</div>
<div class="form-group">
<label for="emergency_contact_name">Emergency Contact Name</label>
<input type="text" id="emergency_contact_name" name="emergency_contact_name">
</div>
<div class="form-group">
<label for="emergency_contact_phone">Emergency Contact Phone</label>
<input type="tel" id="emergency_contact_phone" name="emergency_contact_phone">
</div>
</div>
<div class="form-section">
<h3>Plan Details</h3>
<div class="form-group">
<label for="ndis_plan_start_date">NDIS Plan Start Date</label>
<input type="date" id="ndis_plan_start_date" name="ndis_plan_start_date">
</div>
<div class="form-group">
<label for="ndis_plan_end_date">NDIS Plan End Date</label>
<input type="date" id="ndis_plan_end_date" name="ndis_plan_end_date">
</div>
<div class="form-group">
<label for="plan_manager_name">Plan Manager Name</label>
<input type="text" id="plan_manager_name" name="plan_manager_name">
</div>
<div class="form-group">
<label for="plan_manager_contact">Plan Manager Contact</label>
<input type="text" id="plan_manager_contact" name="plan_manager_contact">
</div>
<div class="form-group">
<label for="ndis_funding_budget_total">NDIS Funding Budget (Total)</label>
<input type="number" step="0.01" id="ndis_funding_budget_total" name="ndis_funding_budget_total">
</div>
</div>
<div class="form-section">
<h3>Disability & Needs</h3>
<div class="form-group">
<label for="primary_disability">Primary Disability</label>
<textarea id="primary_disability" name="primary_disability" rows="2"></textarea>
</div>
<div class="form-group">
<label for="support_needs_summary">Support Needs Summary</label>
<textarea id="support_needs_summary" name="support_needs_summary" rows="2"></textarea>
</div>
<div class="form-group">
<label for="communication_aids_methods">Communication Aids/Methods</label>
<textarea id="communication_aids_methods" name="communication_aids_methods" rows="2"></textarea>
</div>
</div>
<div class="form-section">
<h3>Risk & Safety</h3>
<div class="form-group">
<label for="behaviours_of_concern">Known Behaviours of Concern</label>
<textarea id="behaviours_of_concern" name="behaviours_of_concern" rows="3"></textarea>
</div>
<div class="form-group">
<label for="risk_assessment_summary">Detailed Risk Assessment Summary</label>
<textarea id="risk_assessment_summary" name="risk_assessment_summary" rows="3"></textarea>
</div>
<div class="form-group">
<label for="safety_plan">Safety/Restrictive Practices Plan</label>
<textarea id="safety_plan" name="safety_plan" rows="3"></textarea>
</div>
</div>
<div class="form-section">
<h3>Consent</h3>
<div class="form-group">
<input type="checkbox" id="consent_for_info_sharing" name="consent_for_info_sharing" value="1" style="width: auto; margin-right: 10px;">
<label for="consent_for_info_sharing">Consent for information sharing has been recorded.</label>
</div>
</div>
</div>
<div style="margin-top: 2rem;">
<button type="submit" class="btn btn-primary">Add Client</button>
<a href="clients.php" class="btn btn-secondary">Cancel</a>
</div>
</form>
<?php require_once 'footer.php'; ?>

94
add_staff.php Normal file
View File

@ -0,0 +1,94 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$message = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$db = db();
if (empty($_POST['full_name'])) {
throw new Exception("Full Name is a required field.");
}
$sql = "INSERT INTO care_staff (
full_name, contact_info, ndis_worker_screening_number,
ndis_worker_screening_expiry, first_aid_expiry, qualifications, hourly_rate
) VALUES (
:full_name, :contact_info, :ndis_worker_screening_number,
:ndis_worker_screening_expiry, :first_aid_expiry, :qualifications, :hourly_rate
)";
$stmt = $db->prepare($sql);
$stmt->bindParam(':full_name', $_POST['full_name']);
$stmt->bindParam(':contact_info', $_POST['contact_info']);
$stmt->bindParam(':ndis_worker_screening_number', $_POST['ndis_worker_screening_number']);
$stmt->bindParam(':ndis_worker_screening_expiry', $_POST['ndis_worker_screening_expiry']);
$stmt->bindParam(':first_aid_expiry', $_POST['first_aid_expiry']);
$stmt->bindParam(':qualifications', $_POST['qualifications']);
$stmt->bindParam(':hourly_rate', $_POST['hourly_rate']);
$stmt->execute();
$message = "Care staff member successfully added!";
} catch (Exception $e) {
$error = "Error: " . $e->getMessage();
}
}
?>
<header>
<h1>Add New Staff Member</h1>
</header>
<?php if ($message): ?>
<div class="feedback success"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="feedback error"><?php echo $error; ?></div>
<?php endif; ?>
<form action="add_staff.php" method="POST">
<div class="form-grid" style="grid-template-columns: 1fr;">
<div class="form-group">
<label for="full_name">Full Name *</label>
<input type="text" id="full_name" name="full_name" required>
</div>
<div class="form-group">
<label for="contact_info">Contact Info (Phone/Email)</label>
<textarea id="contact_info" name="contact_info" rows="2"></textarea>
</div>
<div class="form-group">
<label for="qualifications">Qualifications/Certificates</label>
<textarea id="qualifications" name="qualifications" rows="3"></textarea>
</div>
<div class="form-group">
<label for="ndis_worker_screening_number">NDIS Staff Screening Check Number</label>
<input type="text" id="ndis_worker_screening_number" name="ndis_worker_screening_number">
</div>
<div class="form-group">
<label for="ndis_worker_screening_expiry">Screening Check Expiry Date</label>
<input type="date" id="ndis_worker_screening_expiry" name="ndis_worker_screening_expiry">
</div>
<div class="form-group">
<label for="first_aid_expiry">First Aid Certificate Expiry Date</label>
<input type="date" id="first_aid_expiry" name="first_aid_expiry">
</div>
<div class="form-group">
<label for="hourly_rate">Hourly Rate</label>
<input type="number" step="0.01" id="hourly_rate" name="hourly_rate">
</div>
</div>
<div style="margin-top: 2rem;">
<button type="submit" class="btn btn-primary">Add Staff Member</button>
<a href="care_staff.php" class="btn">Cancel</a>
</div>
</form>
<?php require_once 'footer.php'; ?>

55
api/summarize_notes.php Normal file
View File

@ -0,0 +1,55 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../ai/LocalAIApi.php';
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['notes'])) {
echo json_encode(['error' => 'No notes provided.']);
exit;
}
$notes = $input['notes'];
$prompt = <<<PROMPT
You are an expert NDIS support coordinator. A new participant's initial intake notes are provided below.
Your tasks are:
1. Summarize the participant's primary disability and support needs into a concise paragraph. This will be used for the "Support Needs Summary" field.
2. Based on the summary, extract any specific communication aids or methods mentioned (e.g., uses Auslan, requires a translator, non-verbal). This will be for the "Communication Aids/Methods" field.
3. Based on the summary, identify any mentioned "Behaviours of Concern" and list them.
Return the output as a JSON object with the following keys: "support_needs_summary", "communication_aids_methods", "behaviours_of_concern".
---
INTAKE NOTES:
{$notes}
---
PROMPT;
try {
$resp = LocalAIApi::createResponse([
'input' => [
['role' => 'system', 'content' => $prompt],
],
]);
if (!empty($resp['success'])) {
$text = LocalAIApi::extractText($resp);
$json_output = LocalAIApi::decodeJsonFromResponse($resp);
if ($json_output) {
echo json_encode($json_output);
} else {
// If the model didn't return valid JSON, try to wrap its text output in a JSON structure.
echo json_encode(['support_needs_summary' => $text]);
}
} else {
throw new Exception($resp['error'] ?? 'Unknown AI error');
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'AI Service Error: ' . $e->getMessage()]);
}

340
assets/css/style.css Normal file
View File

@ -0,0 +1,340 @@
/* General Styles */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
:root {
--primary-color: #0A2240; /* Navy Blue */
--secondary-color: #F0F2F5; /* Soft Grey */
--accent-color: #48E5C2; /* Teal */
--text-color: #333333;
--text-secondary-color: #6c757d;
--background-color: #F8F9FA;
--surface-color: #FFFFFF;
--border-color: #E9ECEF;
--danger-color: #e74c3c;
--font-family-sans-serif: 'Inter', sans-serif;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 32px;
--space-7: 48px;
--space-8: 64px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family-sans-serif);
background-color: var(--background-color);
color: var(--text-color);
display: flex;
min-height: 100vh;
}
a {
text-decoration: none;
color: var(--accent-color);
transition: color 0.3s ease;
}
a:hover {
color: #36c1a2;
}
/* Sidebar Navigation */
.sidebar {
width: 240px;
background-color: var(--primary-color);
padding: var(--space-5);
position: fixed;
height: 100%;
display: flex;
flex-direction: column;
color: var(--secondary-color);
}
.sidebar .logo {
font-size: 1.8rem;
font-weight: 700;
text-align: center;
margin-bottom: var(--space-6);
color: var(--surface-color);
}
.sidebar nav a {
display: block;
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-2);
border-radius: var(--space-1);
color: var(--secondary-color);
font-weight: 500;
transition: background-color 0.2s ease, color 0.2s ease;
}
.sidebar nav a:hover,
.sidebar nav a.active {
background-color: rgba(255, 255, 255, 0.1);
color: var(--surface-color);
}
.sidebar .footer {
margin-top: auto;
text-align: center;
font-size: 0.8rem;
color: #a0a8b3;
}
/* Main Content Area */
.main-content {
margin-left: 240px;
flex-grow: 1;
padding: var(--space-6);
display: flex;
flex-direction: column;
}
.main-content header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-6);
}
.main-content header h1 {
font-size: 2.2rem;
font-weight: 700;
}
/* Buttons */
.btn,
button,
input[type="submit"] {
background-color: var(--accent-color);
color: var(--primary-color);
padding: var(--space-3) var(--space-5);
border: none;
border-radius: var(--space-2);
cursor: pointer;
font-family: var(--font-family-sans-serif);
font-weight: 600;
transition: background-color 0.3s ease, transform 0.2s ease;
display: inline-block;
text-align: center;
}
.btn:hover,
button:hover,
input[type="submit"]:hover {
background-color: #36c1a2;
transform: translateY(-2px);
}
.btn-primary {
background-color: var(--primary-color);
color: var(--surface-color);
}
.btn-primary:hover {
background-color: #081b33;
}
.btn-secondary {
background-color: var(--secondary-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background-color: #e2e6ea;
}
.btn-danger {
background-color: var(--danger-color);
color: var(--surface-color);
}
.btn-danger:hover {
background-color: #c0392b;
}
/* Info Cards (Dashboard) & Data Cards (Lists) */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--space-5);
}
.card {
background-color: var(--surface-color);
border-radius: var(--space-2);
padding: var(--space-5);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
border: 1px solid var(--border-color);
transition: transform 0.3s ease, box-shadow 0.3s ease;
display: flex;
flex-direction: column;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.06);
}
.card-header {
margin-bottom: var(--space-4);
padding-bottom: var(--space-3);
border-bottom: 1px solid var(--border-color);
}
.card-header h3 {
font-size: 1.1rem;
font-weight: 600;
margin: 0;
}
.card-body p {
font-size: 2.2rem;
font-weight: 700;
color: var(--primary-color);
margin-bottom: var(--space-2);
}
.card-footer {
margin-top: auto;
padding-top: var(--space-4);
}
.card-footer a {
font-weight: 600;
}
.status-tag {
display: inline-block;
padding: var(--space-1) var(--space-2);
font-size: 0.75rem;
font-weight: 600;
border-radius: var(--space-1);
color: var(--surface-color);
}
.status-tag.active { background-color: #2ecc71; }
.status-tag.inactive { background-color: var(--text-secondary-color); }
.status-tag.expiring { background-color: #f39c12; }
.status-tag.unbilled { background-color: var(--danger-color); }
/* Forms */
form {
background-color: var(--surface-color);
padding: var(--space-6);
border-radius: var(--space-2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
border: 1px solid var(--border-color);
}
.form-group {
margin-bottom: var(--space-4);
}
.form-group label {
display: block;
margin-bottom: var(--space-2);
font-weight: 600;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: var(--space-3);
border: 1px solid var(--border-color);
border-radius: var(--space-2);
font-family: var(--font-family-sans-serif);
background-color: var(--background-color);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(72, 229, 194, 0.2);
}
/* Detail Views */
.detail-container {
background-color: var(--surface-color);
padding: var(--space-6);
border-radius: var(--space-2);
box-shadow: 0 4px 12px rgba(0,0,0,0.04);
border: 1px solid var(--border-color);
}
.detail-actions {
margin-top: var(--space-5);
display: flex;
gap: var(--space-3);
}
/* Tables (legacy, for reference) */
table {
width: 100%;
border-collapse: collapse;
background-color: var(--surface-color);
border-radius: var(--space-2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
overflow: hidden;
border: 1px solid var(--border-color);
}
th, td {
padding: var(--space-4);
text-align: left;
border-bottom: 1px solid var(--border-color);
}
th {
background-color: var(--secondary-color);
font-weight: 600;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background-color: #fcfcfd; }
/* Empty States */
.empty-state {
text-align: center;
padding: var(--space-8) var(--space-5);
background-color: var(--surface-color);
border: 2px dashed var(--border-color);
border-radius: var(--space-2);
}
.empty-state h3 {
font-size: 1.5rem;
margin-bottom: var(--space-3);
}
.empty-state p {
color: var(--text-secondary-color);
margin-bottom: var(--space-4);
}
/* Charts */
.charts-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: var(--space-5);
margin-top: var(--space-5);
}
.chart-card {
background-color: var(--surface-color);
border-radius: var(--space-2);
padding: var(--space-5);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
border: 1px solid var(--border-color);
}
.chart-card h3 {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: var(--space-4);
}

69
assets/js/main.js Normal file
View File

@ -0,0 +1,69 @@
document.addEventListener('DOMContentLoaded', function () {
const summarizeBtn = document.getElementById('summarize-with-ai');
const intakeNotes = document.getElementById('intake_notes');
if (summarizeBtn && intakeNotes) {
summarizeBtn.addEventListener('click', async () => {
const notes = intakeNotes.value;
if (!notes.trim()) {
alert('Please enter some intake notes first.');
return;
}
summarizeBtn.disabled = true;
summarizeBtn.innerHTML = 'Summarizing...';
try {
const response = await fetch('api/summarize_notes.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ notes: notes })
});
if (!response.ok) {
throw new Error('Network response was not ok.');
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
if (data.summary) {
// This is a simple example. We'll populate fields based on a hypothetical structured response.
// A real implementation would need to parse the summary more intelligently.
const summary = data.summary;
// Example of populating fields:
if (summary.full_legal_name) {
document.getElementById('full_legal_name').value = summary.full_legal_name;
}
if (summary.email) {
document.getElementById('email').value = summary.email;
}
if (summary.primary_phone) {
document.getElementById('primary_phone').value = summary.primary_phone;
}
if (summary.primary_disability) {
document.getElementById('primary_disability').value = summary.primary_disability;
}
if (summary.support_needs_summary) {
document.getElementById('support_needs_summary').value = summary.support_needs_summary;
}
alert('AI summarization complete. Please review the populated fields.');
}
} catch (error) {
console.error('AI Summarization Error:', error);
alert('An error occurred while summarizing the notes. Please check the console.');
} finally {
summarizeBtn.disabled = false;
summarizeBtn.innerHTML = 'Summarize with AI';
}
});
}
});

99
bookings.php Normal file
View File

@ -0,0 +1,99 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$bookings = [];
try {
$db = db();
$sql = "SELECT
sl.id,
sl.service_date,
p.full_legal_name AS client_name,
w.full_name AS staff_name,
sl.ndis_line_item,
sl.duration_minutes,
sl.billing_status
FROM
bookings sl
JOIN
clients p ON sl.client_id = p.id
JOIN
care_staff w ON sl.staff_id = w.id
ORDER BY
sl.service_date DESC";
$stmt = $db->query($sql);
$bookings = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo '<div class="feedback error">Error: ' . $e->getMessage() . '</div>';
}
function get_status_chip_class($status) {
switch ($status) {
case 'Paid':
return 'status-paid';
case 'Billed':
return 'status-billed';
case 'Pending':
default:
return 'status-pending';
}
}
?>
<style>
.status-chip {
padding: 0.3rem 0.8rem;
border-radius: 16px;
font-weight: 600;
font-size: 0.8rem;
text-transform: uppercase;
display: inline-block;
color: #fff;
}
.status-paid { background-color: #2ecc71; }
.status-billed { background-color: #f1c40f; color: #333; }
.status-pending { background-color: #95a5a6; }
</style>
<header>
<h1>Bookings</h1>
<a href="log_booking.php" class="btn btn-primary">Log New Booking</a>
</header>
<table>
<thead>
<tr>
<th>Service Date</th>
<th>Client</th>
<th>Care Staff</th>
<th>Duration (mins)</th>
<th>NDIS Line Item</th>
<th>Billing Status</th>
</tr>
</thead>
<tbody>
<?php if (empty($bookings)):
<tr>
<td colspan="6" style="text-align: center;">No bookings found.</td>
</tr>
<?php else:
<?php foreach ($bookings as $log):
<tr>
<td><?php echo htmlspecialchars(date('d M Y, H:i', strtotime($log['service_date']))); ?></td>
<td><?php echo htmlspecialchars($log['client_name']); ?></td>
<td><?php echo htmlspecialchars($log['staff_name']); ?></td>
<td><?php echo htmlspecialchars($log['duration_minutes']); ?></td>
<td><?php echo htmlspecialchars($log['ndis_line_item']); ?></td>
<td>
<span class="status-chip <?php echo get_status_chip_class($log['billing_status']); ?>">
<?php echo htmlspecialchars($log['billing_status']); ?>
</span>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php require_once 'footer.php'; ?>

50
care_staff.php Normal file
View File

@ -0,0 +1,50 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$care_staff = [];
try {
$db = db();
$stmt = $db->query("SELECT id, full_name, ndis_worker_screening_number, first_aid_expiry FROM care_staff ORDER BY created_at DESC");
$care_staff = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo '<div class="feedback error">Error: ' . $e->getMessage() . '</div>';
}
?>
<header>
<h1>Care Staff</h1>
<a href="add_staff.php" class="btn btn-primary">Add New Staff Member</a>
</header>
<table>
<thead>
<tr>
<th>Name</th>
<th>Screening Number</th>
<th>First Aid Expiry</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($care_staff)):
<tr>
<td colspan="4" style="text-align: center;">No care staff found.</td>
</tr>
<?php else:
<?php foreach ($care_staff as $w):
<tr>
<td><?php echo htmlspecialchars($w['full_name']); ?></td>
<td><?php echo htmlspecialchars($w['ndis_worker_screening_number']); ?></td>
<td><?php echo htmlspecialchars(date("d M Y", strtotime($w['first_aid_expiry']))); ?></td>
<td>
<a href="staff_detail.php?id=<?php echo $w['id']; ?>" class="btn">View</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php require_once 'footer.php'; ?>

113
client_detail.php Normal file
View File

@ -0,0 +1,113 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
if (!isset($_GET['id']) || !filter_var($_GET['id'], FILTER_VALIDATE_INT)) {
echo '<div class="feedback error">Invalid client ID.</div>';
require_once 'footer.php';
exit;
}
$client_id = $_GET['id'];
$client = null;
try {
$db = db();
$stmt = $db->prepare("SELECT * FROM clients WHERE id = :id");
$stmt->bindParam(':id', $client_id, PDO::PARAM_INT);
$stmt->execute();
$client = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo '<div class="feedback error">Error: ' . $e->getMessage() . '</div>';
}
if (!$client) {
echo '<div class="feedback error">Client not found.</div>';
require_once 'footer.php';
exit;
}
function render_detail_item($label, $value, $is_currency = false) {
if (!empty($value)) {
$display_value = htmlspecialchars($value);
if ($is_currency) {
$display_value = '$' . number_format((float)$value, 2);
}
echo "<div class='detail-item'><span class='label'>{$label}</span><span class='value'>{$display_value}</span></div>";
}
}
function render_detail_area($label, $value) {
if (!empty($value)) {
echo "<div class='detail-area'><span class='label'>{$label}</span><pre class='value'>" . htmlspecialchars($value) . "</pre></div>";
}
}
?>
<style>
.detail-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
.detail-actions { display: flex; gap: 1rem; }
.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; }
.detail-section { background: #fff; border-radius: 12px; padding: 1.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.detail-section h2 { font-size: 1.3rem; margin-bottom: 1.5rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; }
.detail-item { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #f5f5f5; }
.detail-item:last-child { border-bottom: none; }
.detail-item .label { font-weight: 600; color: #555; }
.detail-area { margin-top: 1rem; }
.detail-area .label { font-weight: 600; display: block; margin-bottom: 0.5rem; }
.detail-area pre { background: #f9f9f9; padding: 1rem; border-radius: 8px; white-space: pre-wrap; word-wrap: break-word; font-family: inherit; }
</style>
<div class="detail-header">
<h1><?php echo htmlspecialchars($client['full_legal_name']); ?></h1>
<div class="detail-actions">
<a href="edit_client.php?id=<?php echo $client['id']; ?>" class="btn btn-primary">Edit</a>
<form action="delete_client.php" method="POST" onsubmit="return confirm('Are you sure you want to delete this client? This cannot be undone.');">
<input type="hidden" name="id" value="<?php echo $client['id']; ?>">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
<a href="clients.php" class="btn">Back to List</a>
</div>
</div>
<div class="detail-grid">
<div class="detail-section">
<h2>Client Details</h2>
<?php render_detail_item('NDIS Number', $client['ndis_client_number']); ?>
<?php render_detail_item('Date of Birth', date("d M Y", strtotime($client['date_of_birth']))); ?>
<?php render_detail_item('Contact Method', $client['preferred_contact_method']); ?>
</div>
<div class="detail-section">
<h2>Contact Info</h2>
<?php render_detail_item('Phone', $client['primary_phone']); ?>
<?php render_detail_item('Email', $client['email']); ?>
<?php render_detail_item('Address', $client['address']); ?>
<?php render_detail_item('Emergency Contact', $client['emergency_contact_name']); ?>
<?php render_detail_item('Emergency Phone', $client['emergency_contact_phone']); ?>
</div>
<div class="detail-section">
<h2>NDIS Plan</h2>
<?php render_detail_item('Plan Start', date("d M Y", strtotime($client['ndis_plan_start_date']))); ?>
<?php render_detail_item('Plan End', date("d M Y", strtotime($client['ndis_plan_end_date']))); ?>
<?php render_detail_item('Plan Manager', $client['plan_manager_name']); ?>
<?php render_detail_item('Manager Contact', $client['plan_manager_contact']); ?>
<?php render_detail_item('Total Budget', $client['ndis_funding_budget_total'], true); ?>
</div>
</div>
<div class="detail-section" style="margin-top: 2rem;">
<h2>Disability, Needs & Risks</h2>
<?php render_detail_area('Primary Disability', $client['primary_disability']); ?>
<?php render_detail_area('Support Needs', $client['support_needs_summary']); ?>
<?php render_detail_area('Communication Aids', $client['communication_aids_methods']); ?>
<?php render_detail_area('Behaviours of Concern', $client['behaviours_of_concern']); ?>
<?php render_detail_area('Risk Summary', $client['risk_assessment_summary']); ?>
<?php render_detail_area('Safety Plan', $client['safety_plan']); ?>
</div>
<div class="detail-section" style="margin-top: 2rem;">
<h2>Intake Notes</h2>
<?php render_detail_area('Raw notes from intake', $client['intake_notes']); ?>
</div>
<?php require_once 'footer.php'; ?>

62
clients.php Normal file
View File

@ -0,0 +1,62 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$logs = [];
try {
$db = db();
$stmt = $db->query("SELECT id, full_legal_name, ndis_client_number, primary_phone, email FROM clients ORDER BY created_at DESC");
$clients = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo '<div class="feedback error">Error: ' . $e->getMessage() . '</div>';
}
$message = $_GET['message'] ?? '';
$error = $_GET['error'] ?? '';
?>
<header>
<h1>Clients</h1>
<a href="add_client.php" class="btn btn-primary">Add New Client</a>
</header>
<?php if ($message === 'deleted'): ?>
<div class="feedback success">Client successfully deleted.</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="feedback error">An error occurred. <?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<table>
<thead>
<tr>
<th>Name</th>
<th>NDIS Number</th>
<th>Phone</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($clients)): ?>
<tr>
<td colspan="5" style="text-align: center;">No clients found.</td>
</tr>
<?php else: ?>
<?php foreach ($clients as $p): ?>
<tr>
<td><?php echo htmlspecialchars($p['full_legal_name']); ?></td>
<td><?php echo htmlspecialchars($p['ndis_client_number']); ?></td>
<td><?php echo htmlspecialchars($p['primary_phone']); ?></td>
<td><?php echo htmlspecialchars($p['email']); ?></td>
<td>
<a href="client_detail.php?id=<?php echo $p['id']; ?>" class="btn">View</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php require_once 'footer.php'; ?>

152
compliance.php Normal file
View File

@ -0,0 +1,152 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$plan_alerts = [];
$screening_alerts = [];
$first_aid_alerts = [];
$error = '';
try {
$db = db();
$ninety_days_from_now = date('Y-m-d', strtotime('+90 days'));
// NDIS Plan Reviews
$plan_stmt = $db->prepare("SELECT id, full_legal_name, ndis_plan_end_date FROM clients WHERE ndis_plan_end_date <= :end_date AND ndis_plan_end_date >= CURDATE() ORDER BY ndis_plan_end_date ASC");
$plan_stmt->bindParam(':end_date', $ninety_days_from_now);
$plan_stmt->execute();
$plan_alerts = $plan_stmt->fetchAll(PDO::FETCH_ASSOC);
// Staff Screening Expiries
$screening_stmt = $db->prepare("SELECT id, full_name, ndis_worker_screening_expiry FROM care_staff WHERE ndis_worker_screening_expiry <= :end_date AND ndis_worker_screening_expiry >= CURDATE() ORDER BY ndis_worker_screening_expiry ASC");
$screening_stmt->bindParam(':end_date', $ninety_days_from_now);
$screening_stmt->execute();
$screening_alerts = $screening_stmt->fetchAll(PDO::FETCH_ASSOC);
// First Aid Expiries
$fa_stmt = $db->prepare("SELECT id, full_name, first_aid_expiry FROM care_staff WHERE first_aid_expiry <= :end_date AND first_aid_expiry >= CURDATE() ORDER BY first_aid_expiry ASC");
$fa_stmt->bindParam(':end_date', $ninety_days_from_now);
$fa_stmt->execute();
$first_aid_alerts = $fa_stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error = "Database Error: " . $e->getMessage();
}
function get_days_until_badge($date) {
$now = time();
$your_date = strtotime($date);
$datediff = $your_date - $now;
$days = round($datediff / (60 * 60 * 24));
$class = ' ';
if ($days < 0) $class = 'expired';
elseif ($days <= 30) $class = 'urgent';
elseif ($days <= 60) $class = 'soon';
else $class = 'safe';
$text = ($days < 0) ? "Expired" : "{$days} days";
return "<span class=\"days-badge {$class}\">{$text}</span>";
}
?>
<style>
.alert-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
}
.alert-card {
background: #fff;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
.alert-card h2 {
margin-bottom: 1.5rem;
font-size: 1.4rem;
}
.alert-list li {
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
border-bottom: 1px solid #e0e0e0;
}
.alert-list li:last-child { border-bottom: none; }
.days-badge {
padding: 0.3rem 0.8rem;
border-radius: 16px;
font-weight: 600;
font-size: 0.8rem;
color: #fff;
}
.days-badge.expired { background-color: #e74c3c; }
.days-badge.urgent { background-color: #f39c12; }
.days-badge.soon { background-color: #3498db; }
.days-badge.safe { background-color: #2ecc71; }
</style>
<header>
<h1>Compliance Dashboard</h1>
</header>
<?php if ($error): ?>
<div class="feedback error"><?php echo $error; ?></div>
<?php endif; ?>
<div class="alert-grid">
<div class="alert-card">
<h2>NDIS Plan Reviews (Next 90 Days)</h2>
<ul class="alert-list">
<?php if (empty($plan_alerts)): ?>
<li>No upcoming plan reviews.</li>
<?php else: ?>
<?php foreach ($plan_alerts as $alert): ?>
<li>
<a href="client_detail.php?id=<?php echo $alert['id']; ?>"><?php echo htmlspecialchars($alert['full_legal_name']); ?></a>
<div><?php echo htmlspecialchars(date("d M Y", strtotime($alert['ndis_plan_end_date']))); ?> &nbsp; <?php echo get_days_until_badge($alert['ndis_plan_end_date']); ?></div>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
<div class="alert-card">
<h2>Staff Screening Expiries</h2>
<ul class="alert-list">
<?php if (empty($screening_alerts)): ?>
<li>No upcoming screening expiries.</li>
<?php else: ?>
<?php foreach ($screening_alerts as $alert): ?>
<li>
<a href="staff_detail.php?id=<?php echo $alert['id']; ?>"><?php echo htmlspecialchars($alert['full_name']); ?></a>
<div><?php echo htmlspecialchars(date("d M Y", strtotime($alert['ndis_worker_screening_expiry']))); ?> &nbsp; <?php echo get_days_until_badge($alert['ndis_worker_screening_expiry']); ?></div>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
<div class="alert-card">
<h2>First Aid Certificate Expiries</h2>
<ul class="alert-list">
<?php if (empty($first_aid_alerts)): ?>
<li>No upcoming First Aid expiries.</li>
<?php else: ?>
<?php foreach ($first_aid_alerts as $alert): ?>
<li>
<a href="staff_detail.php?id=<?php echo $alert['id']; ?>"><?php echo htmlspecialchars($alert['full_name']); ?></a>
<div><?php echo htmlspecialchars(date("d M Y", strtotime($alert['first_aid_expiry']))); ?> &nbsp; <?php echo get_days_until_badge($alert['first_aid_expiry']); ?></div>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
</div>
<?php require_once 'footer.php'; ?>

38
db/setup.php Normal file
View File

@ -0,0 +1,38 @@
<?php
require_once 'config.php';
try {
$db = db();
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `clients` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`full_legal_name` VARCHAR(255) NOT NULL,
`ndis_client_number` VARCHAR(50),
`date_of_birth` DATE,
`preferred_contact_method` VARCHAR(100),
`primary_phone` VARCHAR(50),
`email` VARCHAR(255),
`address` TEXT,
`emergency_contact_name` VARCHAR(255),
`emergency_contact_phone` VARCHAR(50),
`ndis_plan_start_date` DATE,
`ndis_plan_end_date` DATE,
`plan_manager_name` VARCHAR(255),
`plan_manager_contact` VARCHAR(255),
`ndis_funding_budget_total` DECIMAL(10, 2),
`primary_disability` TEXT,
`support_needs_summary` TEXT,
`communication_aids_methods` TEXT,
`behaviours_of_concern` TEXT,
`risk_assessment_summary` TEXT,
`safety_plan` TEXT,
`consent_for_info_sharing` BOOLEAN DEFAULT FALSE,
`intake_notes` TEXT
);
SQL;
$db->exec($sql);
echo "Table `clients` created successfully." . PHP_EOL;
} catch (PDOException $e) {
die("DB ERROR: " . $e->getMessage());
}

25
db/setup_bookings.php Normal file
View File

@ -0,0 +1,25 @@
<?php
require_once 'config.php';
try {
$db = db();
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `bookings` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`client_id` INT NOT NULL,
`staff_id` INT NOT NULL,
`service_date` DATETIME NOT NULL,
`ndis_line_item` VARCHAR(255),
`duration_minutes` INT,
`service_notes` TEXT,
`billing_status` ENUM('Pending', 'Billed', 'Paid') DEFAULT 'Pending',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`staff_id`) REFERENCES `care_staff`(`id`) ON DELETE CASCADE
);
SQL;
$db->exec($sql);
echo "Table `bookings` created successfully." . PHP_EOL;
} catch (PDOException $e) {
die("DB ERROR: " . $e->getMessage());
}

23
db/setup_care_staff.php Normal file
View File

@ -0,0 +1,23 @@
<?php
require_once 'config.php';
try {
$db = db();
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `care_staff` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`full_name` VARCHAR(255) NOT NULL,
`contact_info` TEXT,
`ndis_worker_screening_number` VARCHAR(100),
`ndis_worker_screening_expiry` DATE,
`first_aid_expiry` DATE,
`qualifications` TEXT,
`hourly_rate` DECIMAL(10, 2)
);
SQL;
$db->exec($sql);
echo "Table `care_staff` created successfully." . PHP_EOL;
} catch (PDOException $e) {
die("DB ERROR: " . $e->getMessage());
}

33
delete_client.php Normal file
View File

@ -0,0 +1,33 @@
<?php
require_once 'db/config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: clients.php');
exit;
}
if (!isset($_POST['id']) || !filter_var($_POST['id'], FILTER_VALIDATE_INT)) {
header('Location: clients.php?error=invalid_id');
exit;
}
$client_id = $_POST['id'];
try {
$db = db();
$stmt = $db->prepare("DELETE FROM clients WHERE id = :id");
$stmt->bindParam(':id', $client_id, PDO::PARAM_INT);
$stmt->execute();
if ($stmt->rowCount() > 0) {
header('Location: clients.php?message=deleted');
} else {
header('Location: clients.php?error=not_found');
}
exit;
} catch (PDOException $e) {
// In a real app, you'd log this error, not expose it
header('Location: clients.php?error=db_error');
exit;
}

256
edit_client.php Normal file
View File

@ -0,0 +1,256 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$message = '';
$error = '';
$client = null;
if (!isset($_GET['id']) && !isset($_POST['id'])) {
echo '<div class="feedback error">No client ID specified.</div>';
require_once 'footer.php';
exit;
}
$client_id = $_GET['id'] ?? $_POST['id'];
try {
$db = db();
$stmt = $db->prepare("SELECT * FROM clients WHERE id = :id");
$stmt->bindParam(':id', $client_id, PDO::PARAM_INT);
$stmt->execute();
$client = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$client) {
throw new Exception("Client not found.");
}
} catch (Exception $e) {
$error = "Error: " . $e->getMessage();
echo "<div class='feedback error'>$error</div>";
require_once 'footer.php';
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$db = db();
$required_fields = ['full_legal_name', 'id'];
foreach ($required_fields as $field) {
if (empty($_POST[$field])) {
throw new Exception("'$field' is a required field.");
}
}
$sql = "UPDATE clients SET
full_legal_name = :full_legal_name,
ndis_client_number = :ndis_client_number,
date_of_birth = :date_of_birth,
preferred_contact_method = :preferred_contact_method,
primary_phone = :primary_phone,
email = :email,
address = :address,
emergency_contact_name = :emergency_contact_name,
emergency_contact_phone = :emergency_contact_phone,
ndis_plan_start_date = :ndis_plan_start_date,
ndis_plan_end_date = :ndis_plan_end_date,
plan_manager_name = :plan_manager_name,
plan_manager_contact = :plan_manager_contact,
ndis_funding_budget_total = :ndis_funding_budget_total,
primary_disability = :primary_disability,
support_needs_summary = :support_needs_summary,
communication_aids_methods = :communication_aids_methods,
behaviours_of_concern = :behaviours_of_concern,
risk_assessment_summary = :risk_assessment_summary,
safety_plan = :safety_plan,
consent_for_info_sharing = :consent_for_info_sharing,
intake_notes = :intake_notes
WHERE id = :id";
$stmt = $db->prepare($sql);
$consent = isset($_POST['consent_for_info_sharing']) ? 1 : 0;
$client_id = $_POST['id'];
$stmt->bindParam(':id', $client_id, PDO::PARAM_INT);
$stmt->bindParam(':full_legal_name', $_POST['full_legal_name']);
$stmt->bindParam(':ndis_client_number', $_POST['ndis_client_number']);
$stmt->bindParam(':date_of_birth', $_POST['date_of_birth']);
$stmt->bindParam(':preferred_contact_method', $_POST['preferred_contact_method']);
$stmt->bindParam(':primary_phone', $_POST['primary_phone']);
$stmt->bindParam(':email', $_POST['email']);
$stmt->bindParam(':address', $_POST['address']);
$stmt->bindParam(':emergency_contact_name', $_POST['emergency_contact_name']);
$stmt->bindParam(':emergency_contact_phone', $_POST['emergency_contact_phone']);
$stmt->bindParam(':ndis_plan_start_date', $_POST['ndis_plan_start_date']);
$stmt->bindParam(':ndis_plan_end_date', $_POST['ndis_plan_end_date']);
$stmt->bindParam(':plan_manager_name', $_POST['plan_manager_name']);
$stmt->bindParam(':plan_manager_contact', $_POST['plan_manager_contact']);
$stmt->bindParam(':ndis_funding_budget_total', $_POST['ndis_funding_budget_total']);
$stmt->bindParam(':primary_disability', $_POST['primary_disability']);
$stmt->bindParam(':support_needs_summary', $_POST['support_needs_summary']);
$stmt->bindParam(':communication_aids_methods', $_POST['communication_aids_methods']);
$stmt->bindParam(':behaviours_of_concern', $_POST['behaviours_of_concern']);
$stmt->bindParam(':risk_assessment_summary', $_POST['risk_assessment_summary']);
$stmt->bindParam(':safety_plan', $_POST['safety_plan']);
$stmt->bindParam(':consent_for_info_sharing', $consent, PDO::PARAM_INT);
$stmt->bindParam(':intake_notes', $_POST['intake_notes']);
$stmt->execute();
header("Location: client_detail.php?id=" . $client_id . "&message=updated");
exit;
} catch (Exception $e) {
$error = "Error: " . $e->getMessage();
}
}
?>
<style>
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }
.form-section { background-color: #fdfdfd; padding: 1.5rem; border-radius: 8px; border: 1px solid #eee; }
.form-section h3 { font-size: 1.2rem; margin-bottom: 1.5rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; }
.ai-section { background-color: #eaf5ff; padding: 1.5rem; border-radius: 8px; margin-bottom: 2rem; border: 1px solid #c7dfff; }
</style>
<header>
<h1>Edit Client: <?php echo htmlspecialchars($client['full_legal_name']); ?></h1>
</header>
<?php if ($message): ?>
<div class="feedback success"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="feedback error"><?php echo $error; ?></div>
<?php endif; ?>
<form action="edit_client.php" method="POST">
<input type="hidden" name="id" value="<?php echo $client['id']; ?>">
<div class="ai-section">
<h3>AI-Assisted Intake</h3>
<div class="form-group">
<label for="intake_notes">Raw Intake Notes</label>
<textarea id="intake_notes" name="intake_notes" rows="6" placeholder="Paste the full, unstructured intake notes here..."><?php echo htmlspecialchars($client['intake_notes']); ?></textarea>
</div>
<button type="button" class="btn btn-secondary" id="summarize-with-ai">Summarize with AI</button>
</div>
<div class="form-grid">
<div class="form-section">
<h3>Client Details</h3>
<div class="form-group">
<label for="full_legal_name">Full Legal Name *</label>
<input type="text" id="full_legal_name" name="full_legal_name" value="<?php echo htmlspecialchars($client['full_legal_name']); ?>" required>
</div>
<div class="form-group">
<label for="ndis_client_number">NDIS Client Number</label>
<input type="text" id="ndis_client_number" name="ndis_client_number" value="<?php echo htmlspecialchars($client['ndis_client_number']); ?>">
</div>
<div class="form-group">
<label for="date_of_birth">Date of Birth</label>
<input type="date" id="date_of_birth" name="date_of_birth" value="<?php echo htmlspecialchars($client['date_of_birth']); ?>">
</div>
<div class="form-group">
<label for="preferred_contact_method">Preferred Contact Method</label>
<input type="text" id="preferred_contact_method" name="preferred_contact_method" value="<?php echo htmlspecialchars($client['preferred_contact_method']); ?>">
</div>
</div>
<div class="form-section">
<h3>Contact Info</h3>
<div class="form-group">
<label for="primary_phone">Primary Phone</label>
<input type="tel" id="primary_phone" name="primary_phone" value="<?php echo htmlspecialchars($client['primary_phone']); ?>">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" value="<?php echo htmlspecialchars($client['email']); ?>">
</div>
<div class="form-group">
<label for="address">Address</label>
<textarea id="address" name="address" rows="1"><?php echo htmlspecialchars($client['address']); ?></textarea>
</div>
<div class="form-group">
<label for="emergency_contact_name">Emergency Contact Name</label>
<input type="text" id="emergency_contact_name" name="emergency_contact_name" value="<?php echo htmlspecialchars($client['emergency_contact_name']); ?>">
</div>
<div class="form-group">
<label for="emergency_contact_phone">Emergency Contact Phone</label>
<input type="tel" id="emergency_contact_phone" name="emergency_contact_phone" value="<?php echo htmlspecialchars($client['emergency_contact_phone']); ?>">
</div>
</div>
<div class="form-section">
<h3>Plan Details</h3>
<div class="form-group">
<label for="ndis_plan_start_date">NDIS Plan Start Date</label>
<input type="date" id="ndis_plan_start_date" name="ndis_plan_start_date" value="<?php echo htmlspecialchars($client['ndis_plan_start_date']); ?>">
</div>
<div class="form-group">
<label for="ndis_plan_end_date">NDIS Plan End Date</label>
<input type="date" id="ndis_plan_end_date" name="ndis_plan_end_date" value="<?php echo htmlspecialchars($client['ndis_plan_end_date']); ?>">
</div>
<div class="form-group">
<label for="plan_manager_name">Plan Manager Name</label>
<input type="text" id="plan_manager_name" name="plan_manager_name" value="<?php echo htmlspecialchars($client['plan_manager_name']); ?>">
</div>
<div class="form-group">
<label for="plan_manager_contact">Plan Manager Contact</label>
<input type="text" id="plan_manager_contact" name="plan_manager_contact" value="<?php echo htmlspecialchars($client['plan_manager_contact']); ?>">
</div>
<div class="form-group">
<label for="ndis_funding_budget_total">NDIS Funding Budget (Total)</label>
<input type="number" step="0.01" id="ndis_funding_budget_total" name="ndis_funding_budget_total" value="<?php echo htmlspecialchars($client['ndis_funding_budget_total']); ?>">
</div>
</div>
<div class="form-section">
<h3>Disability & Needs</h3>
<div class="form-group">
<label for="primary_disability">Primary Disability</label>
<textarea id="primary_disability" name="primary_disability" rows="2"><?php echo htmlspecialchars($client['primary_disability']); ?></textarea>
</div>
<div class="form-group">
<label for="support_needs_summary">Support Needs Summary</label>
<textarea id="support_needs_summary" name="support_needs_summary" rows="2"><?php echo htmlspecialchars($client['support_needs_summary']); ?></textarea>
</div>
<div class="form-group">
<label for="communication_aids_methods">Communication Aids/Methods</label>
<textarea id="communication_aids_methods" name="communication_aids_methods" rows="2"><?php echo htmlspecialchars($client['communication_aids_methods']); ?></textarea>
</div>
</div>
<div class="form-section">
<h3>Risk & Safety</h3>
<div class="form-group">
<label for="behaviours_of_concern">Known Behaviours of Concern</label>
<textarea id="behaviours_of_concern" name="behaviours_of_concern" rows="3"><?php echo htmlspecialchars($client['behaviours_of_concern']); ?></textarea>
</div>
<div class="form-group">
<label for="risk_assessment_summary">Detailed Risk Assessment Summary</label>
<textarea id="risk_assessment_summary" name="risk_assessment_summary" rows="3"><?php echo htmlspecialchars($client['risk_assessment_summary']); ?></textarea>
</div>
<div class="form-group">
<label for="safety_plan">Safety/Restrictive Practices Plan</label>
<textarea id="safety_plan" name="safety_plan" rows="3"><?php echo htmlspecialchars($client['safety_plan']); ?></textarea>
</div>
</div>
<div class="form-section">
<h3>Consent</h3>
<div class="form-group">
<input type="checkbox" id="consent_for_info_sharing" name="consent_for_info_sharing" value="1" <?php echo $client['consent_for_info_sharing'] ? 'checked' : ''; ?> style="width: auto; margin-right: 10px;">
<label for="consent_for_info_sharing">Consent for information sharing has been recorded.</label>
</div>
</div>
</div>
<div style="margin-top: 2rem;">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="client_detail.php?id=<?php echo $client['id']; ?>" class="btn">Cancel</a>
</div>
</form>
<?php require_once 'footer.php'; ?>

127
edit_staff.php Normal file
View File

@ -0,0 +1,127 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$message = '';
$error = '';
$staff = null;
if (!isset($_GET['id']) && !isset($_POST['id'])) {
echo '<div class="feedback error">No staff ID specified.</div>';
require_once 'footer.php';
exit;
}
$staff_id = $_GET['id'] ?? $_POST['id'];
try {
$db = db();
$stmt = $db->prepare("SELECT * FROM care_staff WHERE id = :id");
$stmt->bindParam(':id', $staff_id, PDO::PARAM_INT);
$stmt->execute();
$staff = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$staff) {
throw new Exception("Care staff member not found.");
}
} catch (Exception $e) {
$error = "Error: " . $e->getMessage();
echo "<div class='feedback error'>$error</div>";
require_once 'footer.php';
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$db = db();
if (empty($_POST['full_name']) || empty($_POST['id'])) {
throw new Exception("Full Name and ID are required fields.");
}
$sql = "UPDATE care_staff SET
full_name = :full_name,
contact_info = :contact_info,
ndis_worker_screening_number = :ndis_worker_screening_number,
ndis_worker_screening_expiry = :ndis_worker_screening_expiry,
first_aid_expiry = :first_aid_expiry,
qualifications = :qualifications,
hourly_rate = :hourly_rate
WHERE id = :id";
$stmt = $db->prepare($sql);
$staff_id = $_POST['id'];
$stmt->bindParam(':id', $staff_id, PDO::PARAM_INT);
$stmt->bindParam(':full_name', $_POST['full_name']);
$stmt->bindParam(':contact_info', $_POST['contact_info']);
$stmt->bindParam(':ndis_worker_screening_number', $_POST['ndis_worker_screening_number']);
$stmt->bindParam(':ndis_worker_screening_expiry', $_POST['ndis_worker_screening_expiry']);
$stmt->bindParam(':first_aid_expiry', $_POST['first_aid_expiry']);
$stmt->bindParam(':qualifications', $_POST['qualifications']);
$stmt->bindParam(':hourly_rate', $_POST['hourly_rate']);
$stmt->execute();
header("Location: staff_detail.php?id=" . $staff_id . "&message=updated");
exit;
} catch (Exception $e) {
$error = "Error: " . $e->getMessage();
}
}
?>
<header>
<h1>Edit Staff Member: <?php echo htmlspecialchars($staff['full_name']); ?></h1>
</header>
<?php if ($message): ?>
<div class="feedback success"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="feedback error"><?php echo $error; ?></div>
<?php endif; ?>
<form action="edit_staff.php" method="POST">
<input type="hidden" name="id" value="<?php echo $staff['id']; ">
<div class="form-grid" style="grid-template-columns: 1fr;">
<div class="form-group">
<label for="full_name">Full Name *</label>
<input type="text" id="full_name" name="full_name" value="<?php echo htmlspecialchars($staff['full_name']); ?>" required>
</div>
<div class="form-group">
<label for="contact_info">Contact Info (Phone/Email)</label>
<textarea id="contact_info" name="contact_info" rows="2"><?php echo htmlspecialchars($staff['contact_info']); ?></textarea>
</div>
<div class="form-group">
<label for="qualifications">Qualifications/Certificates</label>
<textarea id="qualifications" name="qualifications" rows="3"><?php echo htmlspecialchars($staff['qualifications']); ?></textarea>
</div>
<div class="form-group">
<label for="ndis_worker_screening_number">NDIS Staff Screening Check Number</label>
<input type="text" id="ndis_worker_screening_number" name="ndis_worker_screening_number" value="<?php echo htmlspecialchars($staff['ndis_worker_screening_number']); ?>">
</div>
<div class="form-group">
<label for="ndis_worker_screening_expiry">Screening Check Expiry Date</label>
<input type="date" id="ndis_worker_screening_expiry" name="ndis_worker_screening_expiry" value="<?php echo htmlspecialchars($staff['ndis_worker_screening_expiry']); ?>">
</div>
<div class="form-group">
<label for="first_aid_expiry">First Aid Certificate Expiry Date</label>
<input type="date" id="first_aid_expiry" name="first_aid_expiry" value="<?php echo htmlspecialchars($staff['first_aid_expiry']); ?>">
</div>
<div class="form-group">
<label for="hourly_rate">Hourly Rate</label>
<input type="number" step="0.01" id="hourly_rate" name="hourly_rate" value="<?php echo htmlspecialchars($staff['hourly_rate']); ?>">
</div>
</div>
<div style="margin-top: 2rem;">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="staff_detail.php?id=<?php echo $staff['id']; ?>" class="btn">Cancel</a>
</div>
</form>
<?php require_once 'footer.php'; ?>

3
footer.php Normal file
View File

@ -0,0 +1,3 @@
</div>
</body>
</html>

37
header.php Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NDIS Mini CRM</title>
<link rel="stylesheet" href="assets/css/style.css?v=<?php echo time(); ?>">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="assets/js/main.js" defer></script>
</head>
<body>
<?php
$current_page = basename($_SERVER['PHP_SELF']);
$pages = [
'index.php' => 'Dashboard',
'clients.php' => 'Clients',
'care_staff.php' => 'Care Staff',
'bookings.php' => 'Bookings', 'compliance.php' => 'Compliance',
];
?>
<div class="sidebar">
<h1 class="logo">NDIS CRM</h1>
<nav>
<?php foreach ($pages as $url => $title): ?>
<a href="<?php echo $url; ?>" class="<?php echo ($current_page == $url) ? 'active' : ''; ?>">
<?php echo $title; ?>
</a>
<?php endforeach; ?>
</nav>
<div class="footer">
<p>&copy; <?php echo date("Y"); ?> NDIS CRM</p>
</div>
</div>
<div class="main-content">

280
index.php
View File

@ -1,150 +1,140 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
require_once 'db/config.php';
require_once 'header.php';
// Fetch counts for dashboard cards
$client_count = db()->query("SELECT COUNT(*) FROM clients")->fetchColumn();
$staff_count = db()->query("SELECT COUNT(*) FROM care_staff")->fetchColumn();
$booking_count = db()->query("SELECT COUNT(*) FROM bookings")->fetchColumn();
// Fetch upcoming compliance alerts (e.g., expiring in 90 days)
$ninety_days_from_now = date('Y-m-d', strtotime('+90 days'));
$compliance_alerts = db()->query("SELECT COUNT(*) FROM clients WHERE ndis_plan_end_date <= '$ninety_days_from_now'")->fetchColumn();
$worker_alerts = db()->query("SELECT COUNT(*) FROM care_staff WHERE ndis_worker_screening_expiry <= '$ninety_days_from_now' OR first_aid_expiry <= '$ninety_days_from_now'")->fetchColumn();
$total_alerts = $compliance_alerts + $worker_alerts;
// Chart Data
// Clients per month
$clients_per_month_q = db()->query("SELECT DATE_FORMAT(created_at, '%Y-%m') as month, COUNT(*) as count FROM clients GROUP BY month ORDER BY month");
$clients_per_month = $clients_per_month_q->fetchAll(PDO::FETCH_ASSOC);
$client_months = json_encode(array_column($clients_per_month, 'month'));
$client_counts = json_encode(array_column($clients_per_month, 'count'));
// Bookings per month
$bookings_per_month_q = db()->query("SELECT DATE_FORMAT(service_date, '%Y-%m') as month, COUNT(*) as count FROM bookings GROUP BY month ORDER BY month");
$bookings_per_month = $bookings_per_month_q->fetchAll(PDO::FETCH_ASSOC);
$booking_months = json_encode(array_column($bookings_per_month, 'month'));
$booking_counts = json_encode(array_column($bookings_per_month, 'count'));
// Billing status
$billing_status_q = db()->query("SELECT billing_status, COUNT(*) as count FROM bookings GROUP BY billing_status");
$billing_status = $billing_status_q->fetchAll(PDO::FETCH_ASSOC);
$billing_status_labels = json_encode(array_column($billing_status, 'billing_status'));
$billing_status_counts = json_encode(array_column($billing_status, 'count'));
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head>
<body>
<main>
<header>
<h1>Dashboard</h1>
<a href="log_booking.php" class="btn btn-primary">Log a New Booking</a>
</header>
<div class="card-container">
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
<h3>Total Clients</h3>
<p><?php echo $client_count; ?></p>
<a href="clients.php" class="card-link">Manage Clients &rarr;</a>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
</body>
</html>
<div class="card">
<h3>Total Care Staff</h3>
<p><?php echo $staff_count; ?></p>
<a href="care_staff.php" class="card-link">Manage Care Staff &rarr;</a>
</div>
<div class="card">
<h3>Bookings Logged</h3>
<p><?php echo $booking_count; ?></p>
<a href="bookings.php" class="card-link">View Bookings &rarr;</a>
</div>
<div class="card">
<h3>Compliance Alerts</h3>
<p><?php echo $total_alerts; ?></p>
<a href="compliance.php" class="card-link">View Alerts &rarr;</a>
</div>
</div>
<div class="charts-container">
<div class="chart-card">
<h3>Clients Added Per Month</h3>
<canvas id="clientsChart"></canvas>
</div>
<div class="chart-card">
<h3>Bookings Per Month</h3>
<canvas id="bookingsChart"></canvas>
</div>
<div class="chart-card">
<h3>Booking Billing Status</h3>
<canvas id="billingChart"></canvas>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Clients Chart
var clientsCtx = document.getElementById('clientsChart').getContext('2d');
new Chart(clientsCtx, {
type: 'bar',
data: {
labels: <?php echo $client_months; ?>,
datasets: [{
label: 'New Clients',
data: <?php echo $client_counts; ?>,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
}
});
// Bookings Chart
var bookingsCtx = document.getElementById('bookingsChart').getContext('2d');
new Chart(bookingsCtx, {
type: 'bar',
data: {
labels: <?php echo $booking_months; ?>,
datasets: [{
label: 'Bookings Logged',
data: <?php echo $booking_counts; ?>,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1
}]
}
});
// Billing Status Chart
var billingCtx = document.getElementById('billingChart').getContext('2d');
new Chart(billingCtx, {
type: 'pie',
data: {
labels: <?php echo $billing_status_labels; ?>,
datasets: [{
label: 'Billing Status',
data: <?php echo $billing_status_counts; ?>,
backgroundColor: [
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)'
],
borderColor: [
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 1
}]
}
});
});
</script>
<?php require_once 'footer.php'; ?>

117
log_booking.php Normal file
View File

@ -0,0 +1,117 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
$message = '';
$error = '';
$clients = [];
$care_staff = [];
try {
$db = db();
$clients = $db->query("SELECT id, full_legal_name FROM clients ORDER BY full_legal_name ASC")->fetchAll(PDO::FETCH_ASSOC);
$care_staff = $db->query("SELECT id, full_name FROM care_staff ORDER BY full_name ASC")->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
$error = "Failed to fetch clients or care staff: " . $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
if (empty($_POST['client_id']) || empty($_POST['staff_id']) || empty($_POST['service_date'])) {
throw new Exception("Client, Care Staff, and Service Date are required fields.");
}
$db = db();
$sql = "INSERT INTO bookings (
client_id, staff_id, service_date, ndis_line_item,
duration_minutes, service_notes, billing_status
) VALUES (
:client_id, :staff_id, :service_date, :ndis_line_item,
:duration_minutes, :service_notes, :billing_status
)";
$stmt = $db->prepare($sql);
$stmt->bindParam(':client_id', $_POST['client_id'], PDO::PARAM_INT);
$stmt->bindParam(':staff_id', $_POST['staff_id'], PDO::PARAM_INT);
$stmt->bindParam(':service_date', $_POST['service_date']);
$stmt->bindParam(':ndis_line_item', $_POST['ndis_line_item']);
$stmt->bindParam(':duration_minutes', $_POST['duration_minutes'], PDO::PARAM_INT);
$stmt->bindParam(':service_notes', $_POST['service_notes']);
$stmt->bindParam(':billing_status', $_POST['billing_status']);
$stmt->execute();
$message = "Booking successfully added!";
} catch (Exception $e) {
$error = "Error: " . $e->getMessage();
}
}
?>
<header>
<h1>Log a New Booking</h1>
</header>
<?php if ($message): ?>
<div class="feedback success"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="feedback error"><?php echo $error; ?></div>
<?php endif; ?>
<form action="log_booking.php" method="POST">
<div class="form-grid">
<div class="form-group">
<label for="client_id">Client *</label>
<select id="client_id" name="client_id" required>
<option value="">Select a client...</option>
<?php foreach ($clients as $p): ?>
<option value="<?php echo $p['id']; ?>"><?php echo htmlspecialchars($p['full_legal_name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="staff_id">Care Staff *</label>
<select id="staff_id" name="staff_id" required>
<option value="">Select a staff member...</option>
<?php foreach ($care_staff as $w): ?>
<option value="<?php echo $w['id']; ?>"><?php echo htmlspecialchars($w['full_name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="service_date">Service Date & Time *</label>
<input type="datetime-local" id="service_date" name="service_date" required>
</div>
<div class="form-group">
<label for="duration_minutes">Duration (minutes)</label>
<input type="number" id="duration_minutes" name="duration_minutes">
</div>
<div class="form-group">
<label for="ndis_line_item">NDIS Line Item Code</label>
<input type="text" id="ndis_line_item" name="ndis_line_item" placeholder="e.g., 01_001_0107_5_1">
</div>
<div class="form-group">
<label for="billing_status">Billing Status</label>
<select id="billing_status" name="billing_status">
<option value="Pending">Pending</option>
<option value="Billed">Billed</option>
<option value="Paid">Paid</option>
</select>
</div>
</div>
<div class="form-group" style="margin-top: 1.5rem;">
<label for="service_notes">Brief Service Note/Outcome</label>
<textarea id="service_notes" name="service_notes" rows="4"></textarea>
</div>
<div style="margin-top: 2rem;">
<button type="submit" class="btn btn-primary">Log Booking</button>
<a href="bookings.php" class="btn">Cancel</a>
</div>
</form>
<?php require_once 'footer.php'; ?>

91
staff_detail.php Normal file
View File

@ -0,0 +1,91 @@
<?php
require_once 'db/config.php';
require_once 'header.php';
if (!isset($_GET['id']) || !filter_var($_GET['id'], FILTER_VALIDATE_INT)) {
echo '<div class="feedback error">Invalid staff ID.</div>';
require_once 'footer.php';
exit;
}
$staff_id = $_GET['id'];
$staff = null;
try {
$db = db();
$stmt = $db->prepare("SELECT * FROM care_staff WHERE id = :id");
$stmt->bindParam(':id', $staff_id, PDO::PARAM_INT);
$stmt->execute();
$staff = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo '<div class="feedback error">Error: ' . $e->getMessage() . '</div>';
}
if (!$staff) {
echo '<div class="feedback error">Care staff member not found.</div>';
require_once 'footer.php';
exit;
}
function render_detail_item($label, $value, $is_currency = false) {
if (!empty($value)) {
$display_value = htmlspecialchars($value);
if ($is_currency) {
$display_value = '$' . number_format((float)$value, 2);
}
echo "<div class='detail-item'><span class='label'>{$label}</span><span class='value'>{$display_value}</span></div>";
}
}
function render_detail_area($label, $value) {
if (!empty($value)) {
echo "<div class='detail-area'><span class='label'>{$label}</span><pre class='value'>" . htmlspecialchars($value) . "</pre></div>";
}
}
?>
<style>
.detail-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
.detail-actions { display: flex; gap: 1rem; }
.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; }
.detail-section { background: #fff; border-radius: 12px; padding: 1.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.detail-section h2 { font-size: 1.3rem; margin-bottom: 1.5rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; }
.detail-item { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #f5f5f5; }
.detail-item:last-child { border-bottom: none; }
.detail-item .label { font-weight: 600; color: #555; }
.detail-area { margin-top: 1rem; }
.detail-area .label { font-weight: 600; display: block; margin-bottom: 0.5rem; }
.detail-area pre { background: #f9f9f9; padding: 1rem; border-radius: 8px; white-space: pre-wrap; word-wrap: break-word; font-family: inherit; }
</style>
<div class="detail-header">
<h1><?php echo htmlspecialchars($staff['full_name']); ?></h1>
<div class="detail-actions">
<a href="edit_staff.php?id=<?php echo $staff['id']; ?>" class="btn btn-primary">Edit</a>
<form action="delete_staff.php" method="POST" onsubmit="return confirm('Are you sure you want to delete this staff member? This cannot be undone.');">
<input type="hidden" name="id" value="<?php echo $staff['id']; ?>">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
<a href="care_staff.php" class="btn">Back to List</a>
</div>
</div>
<div class="detail-grid">
<div class="detail-section">
<h2>Personal & Contact</h2>
<?php render_detail_item('Full Name', $staff['full_name']); ?>
<?php render_detail_area('Contact Info', $staff['contact_info']); ?>
</div>
<div class="detail-section">
<h2>Compliance</h2>
<?php render_detail_item('Screening Number', $staff['ndis_worker_screening_number']); ?>
<?php render_detail_item('Screening Expiry', date("d M Y", strtotime($staff['ndis_worker_screening_expiry']))); ?>
<?php render_detail_item('First Aid Expiry', date("d M Y", strtotime($staff['first_aid_expiry']))); ?>
</div>
<div class="detail-section">
<h2>Work Details</h2>
<?php render_detail_item('Hourly Rate', $staff['hourly_rate'], true); ?>
<?php render_detail_area('Qualifications', $staff['qualifications']); ?>
</div>
</div>
<?php require_once 'footer.php'; ?>