Aug 5 - Add new design (switchable)
This commit is contained in:
+170
-57
@@ -1,68 +1,181 @@
|
||||
JQC WEB — phase44: scheduled inspection End Date
|
||||
================================================
|
||||
Repo: lt_janitorial_quality_control
|
||||
Deploy root: /home/jqc/janitorial_qc/
|
||||
WEB ONLY. No iOS changes, no iPad rebuild.
|
||||
════════════════════════════════════════════════════════════════════════════
|
||||
JQC — phase48: Modern web portal design (A/B test with user switch)
|
||||
════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
NEW FILE
|
||||
migrations/versions/phase44_scheduled_end_date.py
|
||||
revision = 'phase44_sched_end_date'
|
||||
down_revision = 'phase43_sched_recurrence' <- verified current HEAD
|
||||
WHAT THIS DOES
|
||||
──────────────
|
||||
Adds a second, complete web portal design (sidebar shell, teal-blue palette
|
||||
from JQC_design.pptx) alongside the existing one. Each user picks which design
|
||||
they see; the choice is stored on their account and doubles as their vote.
|
||||
|
||||
OVERWRITE
|
||||
app/models/scheduled_inspection.py end_date column; is_within_end_date();
|
||||
is_expired; expire_if_past_end_date();
|
||||
fulfill() deactivates past the boundary
|
||||
app/utils/forms.py end_date DateField + validate() rules
|
||||
app/routes/scheduled_inspections.py _apply_recurrence() sets/clears end_date;
|
||||
_reject_if_past_end_date() guard;
|
||||
per-context next_due_date label;
|
||||
run_reminders() expiry sweep; audit detail
|
||||
app/api/scheduled.py 'end_date' in _scheduled_payload
|
||||
app/templates/scheduled_inspections/form.html End Date field + JS toggle
|
||||
app/templates/scheduled_inspections/list.html "Ends" column + "Ended" badge
|
||||
CLAUDE.md
|
||||
The classic design is byte-for-byte unchanged apart from one added menu item
|
||||
("Try the New Design"). No route, endpoint, function, model or column was
|
||||
renamed, and no functionality was removed.
|
||||
|
||||
DEPLOY (migration step first, then code)
|
||||
|
||||
HOW IT WORKS (root of the design, not a workaround)
|
||||
───────────────────────────────────────────────────
|
||||
1. `app/templates/base.html` used to hold the entire page chrome. That markup
|
||||
moved verbatim to `app/templates/layouts/classic.html`.
|
||||
|
||||
2. `base.html` is now a single line — `{% extends jqc_layout %}`. Jinja resolves
|
||||
`{% block %}` overrides through the whole inheritance chain, so all 69 page
|
||||
templates keep `{% extends "base.html" %}` and needed ZERO edits.
|
||||
|
||||
3. `jqc_layout` is supplied by the new `inject_ui_theme()` context processor in
|
||||
`app/__init__.py`, driven by the new `users.ui_theme` column
|
||||
('classic' | 'modern').
|
||||
|
||||
4. Pages whose layout genuinely differs in the deck get an override file under
|
||||
`app/templates/modern/<same path>.html`. `ThemedEnvironment.get_template()`
|
||||
(app/__init__.py) swaps `dashboard.html` → `modern/dashboard.html` only when
|
||||
`g.jqc_theme == 'modern'`. The swap happens in `get_template()` rather than
|
||||
in the loader **on purpose**: Jinja's template cache is keyed on the name
|
||||
that `get_template()` receives, so a cached modern template can never be
|
||||
served to a classic user or vice versa. A loader-level swap would have that
|
||||
bug.
|
||||
|
||||
5. Every other page renders its existing markup inside the modern shell and is
|
||||
restyled by `static/css/theme_modern.css`, which loads after `theme.css` and
|
||||
is scoped to `body.jqc-modern`. Classic pages never load that file.
|
||||
|
||||
|
||||
FILES — PLACEMENT MAP
|
||||
─────────────────────
|
||||
NEW
|
||||
app/templates/layouts/classic.html ← old base.html verbatim + one
|
||||
"Try the New Design" menu item in
|
||||
the user dropdown
|
||||
app/templates/layouts/modern.html ← new sidebar shell (top bar, search,
|
||||
bell, avatar, sidebar nav, switch)
|
||||
app/static/css/theme_modern.css ← modern skin, scoped to .jqc-modern
|
||||
app/routes/ui.py ← blueprint `ui`
|
||||
POST /ui/theme switch_theme()
|
||||
GET /ui/about about()
|
||||
GET /ui/support-center support_center()
|
||||
GET /ui/theme-votes theme_votes() (admin)
|
||||
app/templates/modern/dashboard.html ← deck slide 1
|
||||
app/templates/modern/facilities/list.html ← deck slide 5 (hub cards + the
|
||||
original list, unchanged, below)
|
||||
app/templates/ui/about.html ← new About Us page
|
||||
app/templates/ui/support_center.html ← deck slide 6 support hub
|
||||
app/templates/ui/theme_votes.html ← admin vote tally
|
||||
migrations/versions/phase48_user_ui_theme.py
|
||||
migrations/versions/0003_add_user_active.py
|
||||
NO-OP stub. Repairs a PRE-EXISTING break in the Alembic revision graph:
|
||||
phase1_projects_roles.py declares down_revision = '0003_add_user_active'
|
||||
but that script is not in the repo (the early 0001-0003 files were lost).
|
||||
Alembic warns while walking the graph but raises KeyError as soon as it
|
||||
builds the full revision map, which any `flask db upgrade <target>` does.
|
||||
The stub restores the node with down_revision = None and empty
|
||||
upgrade()/downgrade(). No schema effect. Do not delete it.
|
||||
|
||||
MODIFIED
|
||||
app/templates/base.html
|
||||
Entire file replaced by the one-line dispatcher (old content now lives in
|
||||
layouts/classic.html).
|
||||
app/models/user.py
|
||||
class User — added `ui_theme` column after `active`.
|
||||
VARCHAR(16) NOT NULL DEFAULT 'classic'.
|
||||
app/__init__.py
|
||||
+ `ThemedEnvironment` class above create_app()
|
||||
+ `app.jinja_environment = ThemedEnvironment` as the FIRST statement in
|
||||
create_app() (must precede any touch of app.jinja_env — it is a cached
|
||||
property)
|
||||
+ modern-template index built at boot, before_request `resolve_ui_theme()`,
|
||||
context processor `inject_ui_theme()` (also exposes `now_display`)
|
||||
+ `from app.routes import ui` and `app.register_blueprint(ui.bp)`
|
||||
|
||||
|
||||
DEPLOY — STEP 1: CODE
|
||||
─────────────────────
|
||||
cd /home/jqc/janitorial_qc
|
||||
git pull
|
||||
# back up the two files being replaced wholesale
|
||||
cp app/templates/base.html /tmp/base.html.bak
|
||||
cp app/__init__.py /tmp/__init__.py.bak
|
||||
|
||||
# 1. MIGRATION
|
||||
source venv/bin/activate
|
||||
flask db current # expect phase43_sched_recurrence
|
||||
# unzip the package over the repo root (paths already match)
|
||||
unzip -o jqc_phase48_modern_design.zip -d /home/jqc/janitorial_qc
|
||||
|
||||
chown -R jqc:jqc /home/jqc/janitorial_qc/app
|
||||
|
||||
DEPLOY — STEP 2: MIGRATION (run separately, after the code is in place)
|
||||
───────────────────────────────────────────────────────────────────────
|
||||
cd /home/jqc/janitorial_qc
|
||||
source venv/bin/activate # adjust if your venv path differs
|
||||
flask db upgrade
|
||||
flask db current # expect phase44_sched_end_date
|
||||
|
||||
# 2. CODE
|
||||
sudo systemctl restart janitorial_qc
|
||||
sudo systemctl status janitorial_qc --no-pager
|
||||
Expect: phase47_sched_acknowledged → phase48_user_ui_theme
|
||||
With the 0003 stub in place, `flask db heads` reports exactly one head
|
||||
(phase48_user_ui_theme) and no "Revision ... is not present" warning.
|
||||
The migration uses an INFORMATION_SCHEMA existence check and an idempotent
|
||||
backfill — safe to re-run.
|
||||
|
||||
Verify:
|
||||
mysql -e "SHOW COLUMNS FROM users LIKE 'ui_theme';" janitorial_qc
|
||||
|
||||
DEPLOY — STEP 3: RESTART
|
||||
────────────────────────
|
||||
sudo systemctl restart jqc # or your unit name
|
||||
journalctl -u jqc -n 40 --no-pager
|
||||
|
||||
Look for: "UI themes | modern overrides indexed: 2"
|
||||
|
||||
No Nginx change is required — no new external host, no CSP change.
|
||||
|
||||
|
||||
VERIFICATION
|
||||
────────────
|
||||
1. Log in. Portal looks exactly as before (everyone starts on classic).
|
||||
2. Account menu (top right) → "Try the New Design" → same page reloads in the
|
||||
sidebar design, flash message confirms.
|
||||
3. Dashboard: 4 KPI tiles + Inspection / Open Issues / SLA Issues cards +
|
||||
Scheduled + Recent Activities. Click each number — it lands on the same
|
||||
filtered list the classic dashboard links to.
|
||||
4. Facility: 4 hub cards, then the full grouped facility list underneath.
|
||||
Add Facility / Print All QR / Delete modal all still work.
|
||||
5. Sidebar → Supports and About Us render.
|
||||
6. Bell icon: badge count and dropdown behave as on classic.
|
||||
7. Sidebar → "Classic Design" button (or account menu) → returns to classic.
|
||||
8. Log out and back in — the design choice persists.
|
||||
9. Admin account menu → "Design Vote Tally" shows the split.
|
||||
10. Audit Trail shows UPDATE / User / "ui_theme=classic→modern" for each switch.
|
||||
11. Narrow the browser below 992px — the sidebar becomes an off-canvas drawer
|
||||
behind the hamburger.
|
||||
|
||||
|
||||
ROLLBACK
|
||||
flask db downgrade phase43_sched_recurrence # drops end_date, nothing else
|
||||
────────
|
||||
Fastest (no deploy): reset everyone to classic —
|
||||
mysql -e "UPDATE users SET ui_theme='classic';" janitorial_qc
|
||||
The modern design becomes unreachable; nothing else changes.
|
||||
|
||||
VERIFY
|
||||
1. New Schedule -> frequency "One-time": End Date row is HIDDEN,
|
||||
date field reads "Start Date"
|
||||
2. Switch frequency to Weekly: End Date row appears
|
||||
3. Edit an existing schedule: date field reads "Next Due Date"
|
||||
4. Validation:
|
||||
- end date before the due date -> rejected
|
||||
- end date on a one-time schedule (via curl/devtools) -> rejected
|
||||
- Mon/Wed/Fri, pick a Tuesday, end date that same Tuesday
|
||||
-> rejected, message names the Wednesday
|
||||
5. List: "Ends" column shows the date, "No end" when blank, "—" for one-time
|
||||
6. Existing schedules: unchanged, "No end", still Active, cadence identical
|
||||
7. Boundary: set end date = next due date, complete the inspection
|
||||
-> schedule goes Inactive, badge reads "Ended"
|
||||
8. Cron sweep:
|
||||
curl -X POST "https://jqc.ltservicesinc.com/scheduled-inspections/run?token=$DIGEST_SECRET"
|
||||
-> JSON now includes "expired": N
|
||||
-> a schedule past its end date that was never completed goes Inactive
|
||||
and stops generating overdue alerts
|
||||
Full rollback:
|
||||
cp /tmp/base.html.bak app/templates/base.html
|
||||
cp /tmp/__init__.py.bak app/__init__.py
|
||||
rm -rf app/templates/layouts app/templates/modern app/templates/ui \
|
||||
app/static/css/theme_modern.css app/routes/ui.py
|
||||
flask db downgrade phase47_sched_acknowledged
|
||||
sudo systemctl restart jqc
|
||||
|
||||
NOTES
|
||||
- Migration follows the phase42/phase43 idiom in this repo
|
||||
(op.get_bind() + sa.text() + INFORMATION_SCHEMA). Flag if you want the
|
||||
stricter plain-string-only form instead; 62 existing migrations use this one.
|
||||
- api/scheduled.py now returns "end_date". Additive and safe: the iPad
|
||||
decodes explicit CodingKeys, so current builds ignore the new key.
|
||||
|
||||
KNOWN SCOPE LIMITS (deliberate)
|
||||
───────────────────────────────
|
||||
• Deck slides 2 (Reports), 3 (Inspections) and 4 (Issues) are NOT rebuilt as
|
||||
separate templates. Their existing structure already matches the deck
|
||||
(title + subtitle, filter row, KPI row, cards, table) and theme_modern.css
|
||||
restyles them — dark-teal table headers, pill filters, rounded cards. Building
|
||||
parallel copies of those three templates would duplicate several hundred lines
|
||||
of filter/permission/export logic and double the maintenance surface during a
|
||||
vote. Say the word after the vote and I will rebuild whichever ones you keep.
|
||||
• Slide 3's "Scheduled Inspection In Progress" panel appears on the modern
|
||||
DASHBOARD (where the route already supplies that data). Putting it on the
|
||||
Inspections page as well needs an additive query in `inspections.index` —
|
||||
small, but it is a route change, so it is not in this package.
|
||||
• The deck's "Overall Score" and "Avg. Score" KPI tiles are not on the modern
|
||||
dashboard: `dashboard.index` does not compute either value today. Adding them
|
||||
means new aggregate queries in the route — flag it and I will send that
|
||||
separately.
|
||||
• "Customize" on the Facility hub points at Templates (inspection templates),
|
||||
the closest existing feature. There is no facility field/tag configuration
|
||||
screen in the app yet.
|
||||
|
||||
Reference in New Issue
Block a user