### Database Models and RBAC Setup (Python) Source: https://context7.com/ivxt/clinic-app-local/llms.txt Defines User, Role, and Permission models for role-based access control. It demonstrates creating a user, setting a password, assigning roles, and checking user permissions. Includes legacy role support and a comprehensive list of permission codes. Dependencies: Flask-SQLAlchemy, Werkzeug. ```python from clinic_app.models_rbac import User, Role, Permission, Base from clinic_app.extensions import db from werkzeug.security import generate_password_hash from sqlalchemy.orm import Session import uuid # Create user with password session = Session(db.engine) user = User( id=str(uuid.uuid4()), username="doctor1", full_name="Dr. Sarah Johnson", phone="+1234567890", is_active=True ) user.set_password("SecurePass123!") # Assign role to user admin_role = session.query(Role).filter_by(name="admin").first() if admin_role: user.roles.append(admin_role) session.add(user) session.commit() # Check user permissions if user.has_permission("patients:edit"): print("User can edit patients") # Legacy role support (backward compatibility) # Permissions defined in LEGACY_ROLE_PERMISSIONS: # admin: full access including users:manage, backup operations # doctor: patients, appointments, payments, reports, receipts # assistant: view patients, appointments, issue receipts # Permission codes: # patients:view, patients:edit, patients:delete # appointments:view, appointments:edit # payments:view, payments:edit, payments:delete # receipts:view, receipts:issue, receipts:reprint # reports:view # expenses:view, expenses:create, expenses:edit, expenses:delete # suppliers:view, suppliers:manage # materials:view, materials:manage # backup:create, backup:restore # users:manage ``` -------------------------------- ### JavaScript Setup for Drag-and-Drop Initialization Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/multi_doctor_pro.html Initializes the drag-and-drop functionality by attaching event listeners to appointment items and doctor columns. It makes appointment items draggable and enables columns to receive dropped items. ```javascript function setupDragAndDrop() { const items = document.querySelectorAll('.appointment-item[draggable="true"]'); items.forEach(item => { item.addEventListener('dragstart', handleDragStart); item.addEventListener('dragend', handleDragEnd); }); const columns = document.querySelectorAll('.doctor-column'); columns.forEach(column => { column.addEventListener('dragover', handleDragOver); column.addEventListener('dragleave', handleDragLeave); column.addEventListener('drop', handleDrop); }); } ``` -------------------------------- ### Setup Filter Auto-Submit and Drag-and-Drop (JavaScript) Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/multi_doctor_pro.html This code snippet initializes two key functionalities when the DOM is fully loaded: setting up an auto-submitting filter and enabling drag-and-drop interactions. It assumes the existence of corresponding functions `setupFilterAutoSubmit` and `setupDragAndDrop` elsewhere in the codebase. ```javascript document.addEventListener('DOMContentLoaded', function() { setupFilterAutoSubmit(); setupDragAndDrop(); }); ``` -------------------------------- ### Process Payment with Installments Source: https://context7.com/ivxt/clinic-app-local/llms.txt Handles payment processing, including parsing monetary values to cents, validating fields, applying discounts, and calculating remaining balance. It inserts payment records into the database and formats currency for display. Dependencies include services for parsing, validation, and database access. ```python from clinic_app.services.payments import ( parse_money_to_cents, cents_guard, money, validate_payment_fields, overall_remaining ) from clinic_app.services.database import db import uuid from datetime import date # Parse user input to cents (integer arithmetic) total_amount = parse_money_to_cents("1500.00") # 150000 cents discount = parse_money_to_cents("200.50") # 20050 cents down_payment = parse_money_to_cents("500") # 50000 cents # Validate payment fields examination_fee = True # Auto-add 100 to total valid, due_or_error = validate_payment_fields( total_amount, discount, down_payment, examination_fee ) if not valid: print(f"Validation error: {due_or_error}") else: # Guard against overflow (max 10M) total_cents = cents_guard(total_amount, "Total amount") discount_cents = cents_guard(discount, "Discount") paid_cents = cents_guard(down_payment, "Payment") remaining_cents = total_cents - discount_cents - paid_cents conn = db() payment_id = str(uuid.uuid4()) conn.execute( """INSERT INTO payments( id, patient_id, paid_at, amount_cents, total_amount_cents, discount_cents, remaining_cents, method, treatment, examination_flag ) VALUES (?,?,?,?,?,?,?,?,?,?)""", (payment_id, "patient-uuid-here", date.today().isoformat(), paid_cents, total_cents, discount_cents, remaining_cents, "cash", "Root canal treatment", 1 if examination_fee else 0) ) conn.commit() conn.close() # Format for display print(f"Total: ${money(total_cents)}") print(f"Paid: ${money(paid_cents)}") print(f"Remaining: ${money(remaining_cents)}") # Calculate patient's overall balance across all treatments conn = db() patient_balance = overall_remaining(conn, "patient-uuid-here") conn.close() print(f"Overall balance: ${money(patient_balance)}") ``` -------------------------------- ### Format Appointment Start Time Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/simple_view.html Formats the start time of an appointment to display in a user-friendly 12-hour format with AM/PM. Includes error handling for invalid time formats. ```jinja {%- if appt.starts_at and appt.starts_at|length >= 16 -%} {%- set start_time = appt.starts_at[11:16] -%} {%- set start_hour = start_time[:2]|int -%} {%- set start_ampm = 'PM' if start_hour >= 12 else 'AM' -%} {%- set start_display = (start_hour % 12) if (start_hour % 12) != 0 else 12 -%} {{ start_display }}:{{ start_time[3:] }} {{ start_ampm }} {%- else -%} Invalid Time {%- endif -%} ``` -------------------------------- ### Format Appointment Start Date Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/simple_view.html Formats the start date of an appointment to display in YYYY-MM-DD format. Includes error handling for invalid date formats. ```jinja {%- if appt.starts_at and appt.starts_at|length >= 10 -%} {{ appt.starts_at[:10] }} {%- else -%} Invalid Date {%- endif -%} ``` -------------------------------- ### JavaScript for View Management with localStorage Source: https://github.com/ivxt/clinic-app-local/blob/main/%DEST%/index_enhanced.html This JavaScript code manages the user's preferred view ('table' or other) for appointments by using localStorage. It adds event listeners to view buttons to save preferences and updates the active button accordingly. It also handles initial view setup based on URL parameters or saved preferences. ```javascript document.addEventListener('DOMContentLoaded', function() { // Get saved view preference or default to table const savedView = localStorage.getItem('appointments_view') || 'table'; const currentView = '{{ current_view }}'; // Update view buttons based on saved preference const viewButtons = document.querySelectorAll('.view-button'); viewButtons.forEach(btn => { const viewType = btn.getAttribute('data-view'); if (viewType === currentView || (!currentView && viewType === savedView)) { btn.classList.add('active'); } // Add click handler to save preference btn.addEventListener('click', function() { localStorage.setItem('appointments_view', viewType); // Remove active class from all buttons viewButtons.forEach(b => b.classList.remove('active')); // Add active class to clicked button this.classList.add('active'); }); }); // Set default view on first visit const urlParams = new URLSearchParams(window.location.search); const urlView = urlParams.get('view'); if (!urlView && !savedView) { // First time user, redirect to table view const tableBtn = document.querySelector('a[data-view="table"]'); if (tableBtn) { window.location.href = tableBtn.href; } } }); ``` -------------------------------- ### SQL: Search Expense Receipts by Date Range Source: https://context7.com/ivxt/clinic-app-local/llms.txt Searches for expense receipts within a specified date range, joining with the suppliers table to include supplier names and counting the number of items per receipt. Requires start and end dates. The output is a list of expense receipt records. ```sql SELECT er.*, s.name as supplier_name, (SELECT COUNT(*) FROM expense_receipt_items WHERE expense_receipt_id = er.id) as item_count FROM expense_receipts er LEFT JOIN suppliers s ON er.supplier_id = s.id WHERE er.receipt_date BETWEEN ? AND ? ORDER BY er.receipt_date DESC ``` -------------------------------- ### Run Flask Application with Environment Configuration (Python) Source: https://context7.com/ivxt/clinic-app-local/llms.txt This snippet shows how to create and run the Flask application instance using the application factory pattern. It includes configuration details via environment variables and demonstrates WSGI deployment compatibility. Dependencies include Flask and its extensions. ```python from clinic_app import create_app, APP_HOST, APP_PORT # Create application with default configuration app = create_app() # Run development server if __name__ == "__main__": app.run(host=APP_HOST, port=APP_PORT, debug=False) # Configuration via environment variables: # CLINIC_DB_PATH - Custom database path (default: data/app.db) # CLINIC_SECRET_KEY - Flask secret key for sessions # CLINIC_DEFAULT_LOCALE - Default language: en or ar # CLINIC_DOCTORS - Comma-separated doctor names (default: "Dr. Lina,Dr. Omar") # APPOINTMENT_SLOT_MINUTES - Slot duration in minutes (default: 30) # APPOINTMENT_CONFLICT_GRACE_MINUTES - Overlap grace period (default: 5) # RECEIPT_SERIAL_PREFIX - Receipt number prefix (default: "R") # WSGI deployment (wsgi.py): from clinic_app import create_app app = create_app() # Compatible with gunicorn, uWSGI, Apache mod_wsgi ``` -------------------------------- ### Python: Create Role with Permissions using SQLAlchemy Source: https://context7.com/ivxt/clinic-app-local/llms.txt Demonstrates the creation of a new role ('Reception') with a description using SQLAlchemy models (`User`, `Role`, `Permission`) and extensions from `clinic_app.extensions`. This involves initializing a SQLAlchemy session and defining role attributes. ```python from clinic_app.models_rbac import User, Role, Permission from clinic_app.extensions import db from sqlalchemy.orm import Session import uuid session = Session(db.engine) # Create role with permissions reception_role = Role( name="Reception", description="Front desk staff with limited access" ) ``` -------------------------------- ### JavaScript Drag-and-Drop State Management Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/multi_doctor_pro.html Manages the state for drag-and-drop operations related to appointments. It includes functions to reset the drag state, handle the start of a drag operation, and manage the end of a drag operation. ```javascript function resetDragState() { if (dragState.originItem) { dragState.originItem.classList.remove('dragging'); } dragState.apptId = null; dragState.startTime = null; dragState.originDoctor = null; dragState.originItem = null; dragState.pendingMove = false; } function handleDragStart(event) { dragState.apptId = this.dataset.apptId; dragState.startTime = this.dataset.startTime; dragState.originDoctor = this.dataset.doctorId; dragState.originItem = this; dragState.pendingMove = false; this.classList.add('dragging'); if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', dragState.apptId || ''); } } function handleDragEnd() { this.classList.remove('dragging'); if (!dragState.pendingMove) { resetDragState(); } } ``` -------------------------------- ### Create User and Assign Role (Python) Source: https://context7.com/ivxt/clinic-app-local/llms.txt Creates a new user, sets their password, assigns them a role, and commits the changes to the database. Requires User model, uuid, and SQLAlchemy session. ```python new_user = User( id=str(uuid.uuid4()), username="receptionist1", full_name="Jane Doe", phone="+1234567890", is_active=True ) new_user.set_password("SecurePassword123!") new_user.roles.append(reception_role) session.add(new_user) session.commit() ``` -------------------------------- ### CLI Commands Source: https://context7.com/ivxt/clinic-app-local/llms.txt Command-line interface commands for database management, user creation, and data import. ```APIDOC ## Database Management ### `flask db upgrade` #### Description Applies pending database migrations to upgrade the schema. ### `flask db current` #### Description Shows the current database migration version. ### `flask db revision -m ""` #### Description Generates a new database migration script with the specified description. ### `flask legacy-import --source [--dry-run]` #### Description Imports legacy data from a specified SQLite database file. The `--dry-run` option simulates the import without making changes. ## User Management ### `flask bootstrap-admin --username ` #### Description Interactively creates an administrator user. Prompts for a password. ### `flask seed-admin --username --password ""` #### Description Creates an administrator user non-interactively with the specified username and password. ``` -------------------------------- ### Get Selected Checkbox Values in JavaScript Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/diag_plus/images.html Retrieves the values of all checked checkboxes with the class 'sel'. This is useful for actions like bulk export or delete. Returns an array of strings, where each string is the value of a checked checkbox. ```javascript function selectedNames(){ const boxes = document.querySelectorAll('.sel:checked'); return Array.from(boxes).map(x=>x.value); } ``` -------------------------------- ### Clear Flask Template Cache (Python) Source: https://github.com/ivxt/clinic-app-local/blob/main/troubleshooting_guide.md Temporarily clear Flask's Jinja2 template cache by resetting the cache object. This is useful when templates are not refreshing after modifications. Ensure this is only a temporary measure for debugging. ```python # Add this temporarily to your Flask app configuration app.jinja_env.cache = {} ``` -------------------------------- ### Bash: Flask CLI Commands for Database Management Source: https://context7.com/ivxt/clinic-app-local/llms.txt Provides essential Flask CLI commands for database management, including upgrading migrations, creating admin users (interactively and non-interactively), checking the current migration status, generating new migration scripts, and importing legacy data. These commands are crucial for setting up and maintaining the application's database. ```bash # Run database migrations (automatic on startup) flask db upgrade # Create admin user (interactive) flask bootstrap-admin --username admin # Prompts for password, creates user with admin role # Create admin user (non-interactive) flask seed-admin --username admin --password "SecurePass123!" # Check database status flask db current # Generate new migration flask db revision -m "description" # Import legacy data from old database flask legacy-import --source old_clinic.db --dry-run flask legacy-import --source old_clinic.db # Database automatically uses WAL mode for concurrency # Foreign key enforcement enabled # Auto-upgrades schema on application startup ``` -------------------------------- ### JavaScript Filter Auto-Submit Setup Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/multi_doctor_pro.html Sets up event listeners for filter inputs within a form to automatically submit the form when changes occur. This is useful for dynamically updating the displayed appointments based on filter criteria. ```javascript function setupFilterAutoSubmit() { const form = document.querySelector('.controls-section form'); if (!form) return; const filterInputs = form.querySelectorAll('input[name="day"], select[name="range"], select[name="show"]'); filterInputs.forEach(input => { input.addEventListener('change', () => { form.submit(); }); }); } ``` -------------------------------- ### Quick Action Links in Jinja Template Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/core/index_enhanced.html This Jinja template snippet defines a set of quick action links for a clinic application. These links allow users to quickly navigate to common tasks such as adding a patient, booking an appointment, recording a payment, and viewing reports. Each link is presented with an icon and a brief description. ```jinja [👤 Add Patient Register a new patient ]({{ url_for('patients.new_patient') }})[📅 Book Appointment Schedule a new appointment ]({{ url_for('appointments.new') }})[💰 Record Payment Add a new payment ]({{ url_for('receipts.new') }})[📊 View Reports Check collections & analytics ]({{ url_for('reports.collections') }}) ``` -------------------------------- ### Force Template Refresh (Bash) Source: https://github.com/ivxt/clinic-app-local/blob/main/troubleshooting_guide.md Force a refresh of template files by updating their modification timestamp using the 'touch' command. This can help if Flask is caching templates and not picking up changes. Navigate to the templates directory before running. ```bash # In your terminal, navigate to templates directory and run: touch appointments/simple_view.html ``` -------------------------------- ### JavaScript: Get CSRF Token Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/admin/settings/index.html Retrieves the Cross-Site Request Forgery (CSRF) token from a meta tag in the HTML document. This token is essential for security when making POST requests to the server. It handles cases where the meta tag might be missing. ```javascript const csrfMetaTag = document.querySelector('meta[name="csrf-token"]'); const CSRF_TOKEN = csrfMetaTag ? (csrfMetaTag.getAttribute('content') || '') : ''; ``` -------------------------------- ### CSS Media Queries for Responsive Design Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/simple_view.html Applies responsive styles for different screen sizes, adjusting layouts for headers, actions, filters, and appointment details to improve usability on smaller devices. ```css /* Responsive Design */ @media (max-width: 768px) { .header-content { flex-direction: column; align-items: stretch; text-align: center; } .primary-actions { justify-content: center; flex-wrap: wrap; } .filter-content { grid-template-columns: 1fr; gap: var(--spacing-md); } .date-nav { justify-content: center; } .appointment-header { grid-template-columns: 1fr; gap: var(--spacing-sm); } .appointment-time-section { flex-direction: row; align-items: center; justify-content: center; gap: var(--spacing-sm); } .appointment-meta-section { align-items: center; text-align: center; } .visit-title { text-align: center; max-width: none; } .appointment-actions { justify-content: center; flex-wrap: wrap; } .patient-details { margin-left: 0; align-items: center; } } @media (max-width: 480px) { .appointments-header, .filter-section, .main-content { padding: var(--spacing-md); } .appointment-card { padding: var(--spacing-md); } .btn { font-size: 0.75rem; padding: var(--spacing-xs) var(--spacing-sm); } } ``` -------------------------------- ### SQL: Generate Monthly Payment Summary Source: https://context7.com/ivxt/clinic-app-local/llms.txt Generates a monthly summary of payments, including the number of payments, total amount collected, and unique patient count. It requires a start and end date for filtering. The output is a list of tuples, each representing a month's summary. ```sql SELECT strftime('%Y-%m', paid_at) AS month, COUNT(*) AS payment_count, SUM(amount_cents) AS total_collected, COUNT(DISTINCT patient_id) AS patient_count FROM payments WHERE paid_at BETWEEN ? AND ? GROUP BY month ORDER BY month DESC ``` -------------------------------- ### Security and Permissions API Source: https://context7.com/ivxt/clinic-app-local/llms.txt Endpoints and decorators for managing user permissions, rate limiting, and security headers. ```APIDOC ## Route-based Permission Enforcement ### Description Applies permission checks to API routes using decorators. Access is granted only if the authenticated user possesses the required permission. ### Method Applies to GET, POST, PUT, DELETE, etc. ### Endpoint User-defined routes (e.g., `/patients/`) ### Decorators - **@require_permission(permission_name, no_store=False)**: Enforces that the current user must have the specified `permission_name` to access the route. If `no_store` is `True`, a `Cache-Control: no-store` header is automatically added to the response. ### Example Usage ```python from clinic_app.services.security import require_permission from flask import Blueprint bp = Blueprint("protected", __name__) @bp.route("/patients/", methods=["GET"]) @require_permission("patients:view") def view_patient(pid): return {"patient_id": pid} @bp.route("/patients//edit", methods=["POST"]) @require_permission("patients:edit", no_store=True) def edit_patient(pid): return {"success": True} ``` ## Programmatic Permission Checking ### Description Allows checking user permissions within application logic. ### Function - **user_has_permission(user, permission_name)**: Returns `True` if the given `user` object has the specified `permission_name`, `False` otherwise. ### Example Usage ```python from clinic_app.services.security import user_has_permission from flask_login import current_user if user_has_permission(current_user, "reports:view"): # Generate financial report pass ``` ## Rate Limiting ### Description Applies rate limiting to protect API endpoints from abuse. ### Configuration - **Login Rate Limiting**: Automatic. Limits login attempts to 5 per 15 minutes per IP address/username. Failed attempts are logged to the audit trail. - **POST/PUT/DELETE Rate Limiting**: Automatic. Limits requests to 60 per minute for POST, PUT, and DELETE methods. This is applied to routes decorated with `@require_permission(..., no_store=True)`. ### Example Usage (using Flask-Limiter) ```python from clinic_app.extensions import limiter # Rate limiting is typically configured globally or on specific routes ``` ## Automatic Security Headers ### Description Essential security headers are automatically applied to all responses to enhance application security. ### Headers Applied - **Content-Security-Policy**: `default-src 'self'; script-src 'self' 'unsafe-inline'` - **X-Frame-Options**: `DENY` - **X-Content-Type-Options**: `nosniff` - **Referrer-Policy**: `no-referrer` - **Permissions-Policy**: `geolocation=(), camera=(), microphone=()` ## CSRF Protection ### Description Cross-Site Request Forgery (CSRF) protection is enabled by default using Flask-WTF, ensuring all forms require a valid CSRF token. ``` -------------------------------- ### Link to Create New Appointment Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/simple_view.html Provides a button or link to create a new appointment. The URL is dynamically generated based on the current day and selected doctor. ```jinja [➕ New Appointment]({{ url_for('appointments.new', day=day, doctor=selected_doctor if selected_doctor and selected_doctor != 'all' else None) }}) ``` -------------------------------- ### Receipt Generation and PDF Export Source: https://context7.com/ivxt/clinic-app-local/llms.txt Python functions for issuing, retrieving, and reprinting receipts. Supports multi-language output and QR code generation. Generates PDF receipts with configurable options and formats. Dependencies include clinic_app.services.receipts and pathlib. ```python from clinic_app.services.receipts import ( issue_receipt, get_receipt_metadata, reprint_receipt, recent_receipts ) from pathlib import Path # Issue new receipt receipt_data = { "patient_id": "patient-uuid-here", "appointment_id": "appt-uuid-here", # Optional "amount": "500.00", "treatment": "Dental Cleaning", "payment_method": "cash" } receipt_id = issue_receipt( receipt_data, actor_id="user-uuid-here", locale="en" # or "ar" for Arabic ) # Receipt number auto-generated: R-2025-001, R-2025-002, etc. # Serial prefix configurable via RECEIPT_SERIAL_PREFIX env var # Retrieve receipt metadata meta = get_receipt_metadata(receipt_id) print(f"Receipt: {meta['number']}") print(f"Amount: ${meta['amount_cents']/100:.2f}") print(f"PDF: {meta['pdf_path']}") print(f"QR Payload: {meta['qr_payload']}") print(f"Reprints: {meta['reprint_count']}") # Reprint receipt (tracked for audit) reprint_receipt(receipt_id, actor_id="user-uuid-here") # Increments reprint_count and logs to audit trail # List recent receipts receipts = recent_receipts(limit=50) for r in receipts: print(f"{r['number']}: ${r['amount_cents']/100:.2f} - {r['patient_name']}") # PDF generation with options (via payments blueprint) # GET /patients//payments//print/?include_qr=1&watermark=COPY # Formats: 'full', 'summary', 'treatment', 'payment' # Options: include_qr, include_notes, watermark # Supports both English and Arabic with RTL layout ``` -------------------------------- ### Jinja Template for Timeline and Appointments Source: https://github.com/ivxt/clinic-app-local/blob/main/%DEST%/index_enhanced.html This Jinja template snippet renders a chronological timeline of appointments. It formats hours, displays appointment details (title, patient name, time, doctor, phone, notes), and includes controls for appointment status and saving. It handles cases where an hour might have no appointments. ```jinja {% for block in timeline %} {{ block.hour|int % 12 if block.hour|int % 12 != 0 else 12 }}:00 {{ 'PM' if block.hour|int >= 12 else 'AM' }} {% if block.entries %} {% for appt in block.entries %} ✏️ 🗑️ {% if appt.patient_id %} 👤 {% endif %} {{ appt.title }} {% if appt.patient_id %} {{ appt.patient_name }} {% else %} {{ appt.patient_name }} {% endif %} {%- set start_time = appt.starts_at[11:16] -%} {%- set end_time = appt.ends_at[11:16] -%} {%- set start_hour = start_time[:2]|int -%} {%- set end_hour = end_time[:2]|int -%} {%- set start_ampm = 'PM' if start_hour >= 12 else 'AM' -%} {%- set end_ampm = 'PM' if end_hour >= 12 else 'AM' -%} {%- set start_display = (start_hour % 12) if (start_hour % 12) != 0 else 12 -%} {%- set end_display = (end_hour % 12) if (end_hour % 12) != 0 else 12 -%} {{ start_display }}:{{ start_time[3:] }} {{ start_ampm }} → {{ end_display }}:{{ end_time[3:] }} {{ end_ampm }} {{ appt.doctor_label }} {% if appt.patient_phone %} {{ appt.patient_phone }} {% endif %} {% if appt.notes %} {{ appt.notes }} {% endif %} {% for key in ['scheduled','done'] %} {{ t('appointment_status_' + key) }} {% endfor %} {{ t('save') }} {% endfor %} {% else %} {{ t('appointments_empty_hour') }} {% endif %} {% endfor %} ``` -------------------------------- ### Formatting Appointment Times (Jinja2) Source: https://github.com/ivxt/clinic-app-local/blob/main/%DEST%/table_view_enhanced.html This code snippet formats appointment start and end times from a 24-hour format to a 12-hour format with AM/PM designation. It extracts hours and minutes, performs calculations for 12-hour conversion and AM/PM, and displays the formatted time. This is useful for presenting appointment schedules in a user-friendly way. ```jinja2 {%- set start_time = appt.starts_at[11:16] -%} {%- set end_time = appt.ends_at[11:16] -%} {%- set start_hour = start_time[:2]|int -%} {%- set end_hour = end_time[:2]|int -%} {%- set start_ampm = 'PM' if start_hour >= 12 else 'AM' -%} {%- set end_ampm = 'PM' if end_hour >= 12 else 'AM' -%} {%- set start_display = (start_hour % 12) if (start_hour % 12) != 0 else 12 -%} {%- set end_display = (end_hour % 12) if (end_hour % 12) != 0 else 12 -%} {{ start_display }}:{{ start_time[3:] }} {{ start_ampm }} {{ end_display }}:{{ end_time[3:] }} {{ end_ampm }} ``` -------------------------------- ### Payment Details Toggle and Input Autogrow (JavaScript) Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/_base.html This snippet includes a function to toggle the display of payment details and update a button's text. It also includes an event listener for DOMContentLoaded that makes textareas autogrow and sets up event listeners for financial input fields to update the remaining amount. ```javascript (function(){ window.__payToggle = function(id){ var d = document.getElementById('details-'+id); var b = document.getElementById('view-'+id); if(!d||!b) return; var show = d.style.display!=='block'; d.style.display = show ? 'block' : 'none'; b.classList.toggle('active', show); b.textContent = show ? '{{ t("hide") }}' : '{{ t("view") }}'; }; function autogrowTA(ta){ ta.style.height='auto'; ta.style.height=ta.scrollHeight+'px'; } document.addEventListener('DOMContentLoaded', function(){ document.querySelectorAll('textarea').forEach(function(ta){ autogrowTA(ta); ta.addEventListener('input', function(){ autogrowTA(ta); }); }); var total = document.getElementById('total_amount'); var vt = document.getElementById('visit_type'); var eff = document.getElementById('total_amount_effective'); var bonus = document.getElementById('bonus_badge'); var down = document.querySelector('input[name="down_payment"]'); var disc = document.querySelector('input[name="discount"]'); var rem = document.getElementById('remaining_amount'); function parseMoney(v){ try { return parseFloat((v|| equenz).replace(/,/g,'')) || 0; } catch(e){ return 0; } } function fmt2(n){ return (Math.round(n*100)/100).toFixed(2); } function update(){ if(!total || !rem) return; var base = parseMoney(total.value); var add = (vt && vt.value === 'exam') ? 100 : 0; var totalEff = base + add; if(eff) eff.value = fmt2(totalEff); if(bonus){ bonus.style.display = add ? 'inline-block' : 'none'; } var discount = parseMoney(disc ? disc.value : '0'); if(discount < 0) discount = 0; if(discount > totalEff) discount = totalEff; var due = totalEff - discount; var paid = parseMoney(down ? down.value : '0'); var remaining = due - paid; if(remaining < 0) remaining = 0; rem.value = fmt2(remaining); } ['input','change'].forEach(function(evt){ if(total) total.addEventListener(evt, update); if(vt) vt.addEventListener(evt, update); if(down) down.addEventListener(evt, update); if(disc) disc.addEventListener(evt, update); }); update(); }); })(); ``` -------------------------------- ### Security and Permission Control with Flask Source: https://context7.com/ivxt/clinic-app-local/llms.txt Python code using Flask decorators to enforce permissions and rate limiting on API routes. Includes programmatic permission checks and automatic security header configuration. Dependencies include clinic_app.services.security, clinic_app.extensions, Flask, and Flask-Login. ```python from clinic_app.services.security import require_permission, user_has_permission from clinic_app.extensions import limiter from flask import Blueprint, g from flask_login import current_user bp = Blueprint("protected", __name__) # Require specific permission for route @bp.route("/patients/", methods=["GET"]) @require_permission("patients:view") def view_patient(pid): # Only accessible if user has patients:view permission return {"patient_id": pid} # Require different permission for modification @bp.route("/patients//edit", methods=["POST"]) @require_permission("patients:edit", no_store=True) def edit_patient(pid): # Cache-Control: no-store header added automatically # Rate limited: 60 requests/minute on POST/PUT/DELETE return {"success": True} # Check permission programmatically if user_has_permission(current_user, "reports:view"): # Generate financial report pass # Login rate limiting (automatic) # Login endpoint: 5 attempts per 15 minutes per IP/username # Failed attempts logged to audit trail # Security headers applied automatically: # Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' # X-Frame-Options: DENY # X-Content-Type-Options: nosniff # Referrer-Policy: no-referrer # Permissions-Policy: geolocation=(), camera=(), microphone=() # CSRF protection enabled via Flask-WTF # All forms require CSRF token ``` -------------------------------- ### Appointment Form Structure (Jinja/HTML) Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/form.html Renders the structure for a new appointment form, including fields for patient selection, date, time, doctor, title, and notes. It dynamically displays patient information if a card exists and handles conditional rendering for the patient search input. ```html {% extends '\_base.html' %} {% block content %} {{ t('appointments_new') }} ============================ {% if patient_card %}[{{ patient_card.short_id or '—' }} {{ patient_card.name or '-' }} {{ patient_card.phone or 'No phone' }} {{ t('click_to_view_patient') if t('click_to_view_patient') != 'click_to_view_patient' else 'Click to open patient file' }}]({{ url_for('patients.patient_detail', pid=patient_card.id) }}) {% endif %} {{ t('appointments_day_label') }} {{ t('appointments_day_label') }} {{ t('appointments_slot_label') }} {{ t('appointments_doctor_label') }} {% for value, label in doctors %} {{ label }} {% endfor %} {{ t('appointments_title_label') }} {% if not editing %} Search Patient {% else %} {% endif %} Patient Name Patient Phone {{ t('appointments_notes_label') }} {{ defaults.notes if defaults else '' }} {{ t('appointments_submit') }} [{{ t('back') }}]({{ url_for('appointments.table') }}) document.addEventListener('DOMContentLoaded', function() { const searchInput = document.getElementById('patient-search'); const patientIdInput = document.getElementById('patient-id'); const patientNameInput = document.getElementById('patient-name'); const patientPhoneInput = document.getElementById('patient-phone'); const patientShortIdInput = document.getElementById('patient-short-id'); const suggestionsContainer = document.getElementById('patient-suggestions'); let searchTimeout; if (!searchInput) { return; } function showSuggestions(suggestions) { suggestionsContainer.innerHTML = ''; if (suggestions.length === 0) { suggestionsContainer.style.display = 'none'; return; } suggestions.forEach(patient => { const suggestion = document.createElement('div'); suggestion.className = 'patient-suggestion'; suggestion.innerHTML = `
${patient.full_name}
${patient.short_id || 'No ID'} | ${patient.phone || 'No phone'}
`; suggestion.addEventListener('click', () => selectPatient(patient)); suggestionsContainer.appendChild(suggestion); }); suggestionsContainer.style.display = 'block'; } function selectPatient(patient) { patientIdInput.value = patient.id; patientNameInput.value = patient.full_name; patientPhoneInput.value = patient.phone || ''; patientShortIdInput.value = patient.short_id || ''; searchInput.value = patient.full_name; suggestionsContainer.style.display = 'none'; } function hideSuggestions() { setTimeout(() => { suggestionsContainer.style.display = 'none'; }, 200); } searchInput.addEventListener('input', function() { const query = this.value.trim(); if (!query) { patientIdInput.value = ''; patientNameInput.value = ''; patientPhoneInput.value = ''; patientShortIdInput.value = ''; suggestionsContainer.style.display = 'none'; return; } clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { if (query.length < 2) { suggestionsContainer.style.display = 'none'; return; } // Make API call to search patients fetch(`/patients/search?q=${encodeURIComponent(query)}`) .then(response => response.json()) .then(patients => { showSuggestions(patients); }) .catch(error => { console.error('Error searching patients:', error); suggestionsContainer.style.display = 'none'; }); }, 300); }); // Hide suggestions when clicking outside document.addEventListener('click', function(e) { if (!searchInput.contains(e.target) && !suggestionsContainer.contains(e.target)) { suggestionsContainer.style.display = 'none'; } }); // Hide suggestions when search input loses focus searchInput.addEventListener('blur', hideSuggestions); }); {% endblock %} ``` -------------------------------- ### Create User Form (Jinja) Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/admin/settings/index.html A modal form for creating a new user. It includes input fields for username, full name, phone number, password, and a multi-select for assigning roles. It also has an 'Active' checkbox and 'Cancel' and 'Create User' buttons. ```jinja Username * Full Name Phone Password * Roles {% for role in roles %} {{ role.name }} {% endfor %} Active Cancel Create User ``` -------------------------------- ### Financial Reporting API with SQL Source: https://context7.com/ivxt/clinic-app-local/llms.txt Python code for generating daily and monthly financial collection reports using SQL queries. Includes functions for daily totals and detailed collection data within a specified date range. Dependencies include clinic_app.services.database and clinic_app.services.payments. ```python from clinic_app.services.database import db from clinic_app.services.payments import today_collected, money from datetime import date, timedelta conn = db() cursor = conn.cursor() # Today's collections today_total = today_collected(conn) print(f"Today's collections: ${money(today_total)}") # Daily collections report for date range start_date = "2025-11-01" end_date = "2025-11-30" daily_totals = cursor.execute( """SELECT paid_at AS day, COUNT(*) AS payment_count, SUM(amount_cents) AS total_collected, COUNT(DISTINCT patient_id) AS patient_count FROM payments WHERE paid_at BETWEEN ? AND ? GROUP BY paid_at ORDER BY paid_at DESC""", (start_date, end_date) ).fetchall() for row in daily_totals: print(f"{row['day']}: ${money(row['total_collected'])} " f"({row['payment_count']} payments, {row['patient_count']} patients)") ``` -------------------------------- ### CSS for Focus Styles Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/simple_view.html Provides clear visual outlines for focusable elements like buttons and form inputs, enhancing keyboard navigation and accessibility. ```css /* Focus styles for better accessibility */ .btn:focus, .filter-input:focus, .filter-select:focus { outline: 2px solid var(--primary-color); outline-offset: 2px; } ``` -------------------------------- ### Jinja Template for Displaying Stats Source: https://github.com/ivxt/clinic-app-local/blob/main/%DEST%/index_enhanced.html This Jinja template snippet iterates over a dictionary of statistics and displays each label and its corresponding value. It utilizes the 't' function for translation, likely for internationalization. ```jinja {% for label, value in stats %} {{ t(label) }} {{ value }} {% endfor %} ``` -------------------------------- ### Render Appointment Details and Actions (Jinja) Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/appointments/simple_view.html This snippet uses Jinja templating to conditionally display patient information, contact details, and appointment status. It also provides links for viewing and editing appointments, along with a delete action. ```jinja {% if appt.patient_id %} [{{ appt.patient_name }}]({{ url_for('patients.view', patient_id=appt.patient_id) }}) {% else %} {{ appt.patient_name }} {% endif %} {% if appt.patient_phone %} 📞 {{ appt.patient_phone }} {% endif %} {% if appt.patient_short_id %} 🆔 {{ appt.patient_short_id }} {% endif %} {{ appt.doctor_label }} {{ appt.title }} {% if appt.status == 'done' %} ✓ Completed {% else %} ⏰ Scheduled {% endif %} 👁️ View Details [✏️ Edit]({{ url_for('appointments.edit', appt_id=appt.id) }}) 🗑️ Delete {% endfor %} ``` -------------------------------- ### Admin Settings Container and Header Styles (CSS) Source: https://github.com/ivxt/clinic-app-local/blob/main/templates/admin/settings/index.html Defines the layout and appearance for the main admin settings container and its header. The container limits the width and centers content, while the header uses a gradient background and prominent typography for titles and descriptions. ```css .admin-settings-container { max-width: 1400px; margin: 0 auto; padding: 2rem; } .admin-settings-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2rem; border-radius: 12px; margin-bottom: 2rem; text-align: center; } .admin-settings-header h1 { margin: 0; font-size: 2.5rem; font-weight: 700; } .admin-settings-header p { margin: 0.5rem 0 0 0; opacity: 0.9; font-size: 1.1rem; } ``` -------------------------------- ### Retrieve Patient and Payment Data Source: https://context7.com/ivxt/clinic-app-local/llms.txt Fetches patient details, all payments for a patient, and calculates the overall balance from the database. It uses SQL queries and standard Python database connectors. The balance calculation handles cases where no payments exist. ```python patient_row = cursor.execute( "SELECT * FROM patients WHERE id=?", (patient_id,) ).fetchone() payments = cursor.execute( """SELECT * FROM payments WHERE patient_id=? ORDER BY paid_at DESC""", (patient_id,) ).fetchall() balance = cursor.execute( """SELECT COALESCE(SUM( CASE WHEN (total_amount_cents - discount_cents - amount_cents) < 0 THEN 0 ELSE (total_amount_cents - discount_cents - amount_cents) END ), 0) AS remaining FROM payments WHERE patient_id=?""", (patient_id,) ).fetchone()["remaining"] conn.close() print(f"Patient balance: ${balance/100:.2f}") ``` -------------------------------- ### JavaScript for Patient and Appointment Navigation Source: https://github.com/ivxt/clinic-app-local/blob/main/%DEST%/index_enhanced.html This JavaScript code provides functions for navigating to a patient's profile and for editing or deleting appointments. The `deleteAppointment` function includes a confirmation dialog and constructs a POST form to handle the deletion request, including CSRF token and redirect parameters. ```javascript // Patient navigation function function viewPatient(patientId) { window.location.href = `/patients/${patientId}`; } // Appointment management functions function editAppointment(appointmentId) { window.location.href = `/appointments/${appointmentId}/edit`; } function deleteAppointment(appointmentId, patientName) { if (confirm(`Are you sure you want to delete the appointment for ${patientName}?`)) { const form = document.createElement('form'); form.method = 'POST'; form.action = `/appointments/${appointmentId}/delete`; form.style.display = 'none'; form.innerHTML = ` `; document.body.appendChild(form); form.submit(); } } ``` -------------------------------- ### Python: Create Expense Receipt with Line Items Source: https://context7.com/ivxt/clinic-app-local/llms.txt Demonstrates how to create an expense receipt by first creating a supplier and a material, then constructing a detailed expense data dictionary including line items. It utilizes functions from `clinic_app.services.expense_receipts`. The output is a receipt ID and auto-generated serial number. ```python from clinic_app.services.expense_receipts import ( create_supplier, create_material, create_expense_receipt ) from datetime import date # Create supplier supplier_data = { "name": "Dental Supplies Inc", "contact_person": "John Smith", "phone": "+1234567890", "email": "sales@dentalsupplies.com", "address": "123 Medical Plaza", "tax_number": "TAX-12345", "is_active": True } supplier_id = create_supplier(supplier_data) # Create material material_data = { "name": "Composite Resin A3", "category_id": 1, # Restorative materials "unit": "syringe", "price_per_unit": 4500, # cents "description": "Tooth-colored filling material", "supplier_id": supplier_id, "is_active": True } material_id = create_material(material_data) # Create expense receipt with line items expense_data = { "supplier_id": supplier_id, "receipt_date": date.today().isoformat(), "notes": "Monthly restock order", "created_by": "user-uuid-here", "items": [ { "material_id": material_id, "material_name": "Composite Resin A3", "quantity": 10, "unit_price": 4500, "total_price": 45000, "notes": "10 syringes" }, { "material_id": None, # One-off item "material_name": "Disposable gloves (box)", "quantity": 5, "unit_price": 1200, "total_price": 6000, "notes": "" } ], "total_amount": 51000, # Sum of items "tax_amount": 7650, # 15% tax "receipt_image_path": "/path/to/scanned/receipt.jpg" } receipt_id = create_expense_receipt(expense_data) # Auto-generates serial: EXP-2025-001, EXP-2025-002, etc. ```