05/07 Phase 3: initial codes

This commit is contained in:
2026-05-07 12:17:19 -04:00
parent 6e5518c103
commit ce462c57b2
107 changed files with 6365 additions and 208 deletions
+47
View File
@@ -0,0 +1,47 @@
{% extends "tenant/layouts/base.html" %}
{% block title %}{{ 'Edit' if mode == 'edit' else 'New' }} Customer{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header fw-semibold">{{ 'Edit' if mode == 'edit' else 'New' }} Customer</div>
<div class="card-body">
{% if error %}<div class="alert alert-danger py-2">{{ error }}</div>{% endif %}
<form method="POST" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Name <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="name"
value="{{ customer.name if customer else '' }}" required autofocus>
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Phone</label>
<input type="tel" class="form-control" name="phone"
value="{{ customer.phone if customer else '' }}">
</div>
<div class="col">
<label class="form-label">Email</label>
<input type="email" class="form-control" name="email"
value="{{ customer.email if customer else '' }}">
</div>
</div>
<div class="mb-3">
<label class="form-label">Date of Birth</label>
<input type="date" class="form-control" name="date_of_birth"
value="{{ customer.date_of_birth.isoformat() if customer and customer.date_of_birth else '' }}">
</div>
<div class="mb-3">
<label class="form-label">Notes</label>
<textarea class="form-control" name="notes" rows="3">{{ customer.notes if customer else '' }}</textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Save</button>
<a href="{{ url_for('customers.index') }}" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
+41
View File
@@ -0,0 +1,41 @@
{% extends "tenant/layouts/base.html" %}
{% block title %}Customers{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="fw-bold mb-0"><i class="bi bi-people me-2"></i>Customers</h4>
<a href="{{ url_for('customers.create') }}" class="btn btn-primary btn-sm">
<i class="bi bi-plus-lg me-1"></i>New Customer
</a>
</div>
<div class="card shadow-sm mb-3">
<div class="card-body py-2">
<form method="GET" class="d-flex gap-2">
<input type="search" class="form-control form-control-sm" name="q"
value="{{ q }}" placeholder="Search by name, phone, or email…" autofocus>
<button class="btn btn-outline-secondary btn-sm">Search</button>
{% if q %}<a href="{{ url_for('customers.index') }}" class="btn btn-outline-danger btn-sm">Clear</a>{% endif %}
</form>
</div>
</div>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light"><tr><th>Name</th><th>Phone</th><th>Email</th><th>Loyalty</th><th>No-Shows</th><th></th></tr></thead>
<tbody>
{% for c in customers %}
<tr>
<td><a href="{{ url_for('customers.view', customer_id=c.id) }}" class="text-decoration-none fw-semibold">{{ c.name }}</a></td>
<td class="text-muted small">{{ c.phone or '—' }}</td>
<td class="text-muted small">{{ c.email or '—' }}</td>
<td><span class="badge bg-warning text-dark">{{ c.loyalty_points }} pts</span></td>
<td>{% if c.no_show_count > 0 %}<span class="badge bg-danger">{{ c.no_show_count }}</span>{% else %}0{% endif %}</td>
<td><a href="{{ url_for('customers.edit', customer_id=c.id) }}" class="btn btn-outline-secondary btn-sm">Edit</a></td>
</tr>
{% else %}
<tr><td colspan="6" class="text-center text-muted py-4">No customers found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
+58
View File
@@ -0,0 +1,58 @@
{% extends "tenant/layouts/base.html" %}
{% block title %}{{ customer.name }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="fw-bold mb-0">{{ customer.name }}</h4>
<div class="d-flex gap-2">
<a href="{{ url_for('customers.edit', customer_id=customer.id) }}" class="btn btn-outline-secondary btn-sm">Edit</a>
<a href="{{ url_for('appointments.create') }}?customer_id={{ customer.id }}" class="btn btn-primary btn-sm">New Appointment</a>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card shadow-sm">
<div class="card-body">
<p class="mb-1"><i class="bi bi-telephone me-2 text-muted"></i>{{ customer.phone or '—' }}</p>
<p class="mb-1"><i class="bi bi-envelope me-2 text-muted"></i>{{ customer.email or '—' }}</p>
{% if customer.date_of_birth %}
<p class="mb-1"><i class="bi bi-cake me-2 text-muted"></i>{{ customer.date_of_birth.strftime('%B %d') }}</p>
{% endif %}
<p class="mb-1"><i class="bi bi-star me-2 text-warning"></i>{{ customer.loyalty_points }} loyalty points</p>
{% if customer.no_show_count > 0 %}
<p class="mb-0"><i class="bi bi-x-circle me-2 text-danger"></i>{{ customer.no_show_count }} no-show(s)</p>
{% endif %}
{% if customer.notes %}
<hr><p class="text-muted small mb-0">{{ customer.notes }}</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-8">
<div class="card shadow-sm mb-3">
<div class="card-header fw-semibold">Recent Appointments</div>
{% if appointments %}
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead><tr><th>Date</th><th>Service</th><th>Staff</th><th>Status</th></tr></thead>
<tbody>
{% for a in appointments %}
<tr>
<td class="small">{{ a.start_time.strftime('%Y-%m-%d %I:%M %p') }}</td>
<td class="small">{{ a.service.name if a.service else '—' }}</td>
<td class="small">{{ a.staff.name if a.staff else '—' }}</td>
<td><span class="badge bg-{{ {'completed':'success','cancelled':'danger','no_show':'warning'}.get(a.status,'secondary') }} small">{{ a.status }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card-body text-muted small">No appointments yet.</div>
{% endif %}
</div>
</div>
</div>
<a href="{{ url_for('customers.index') }}" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-arrow-left me-1"></i>Back
</a>
{% endblock %}