weekly updates
This commit is contained in:
@@ -6,10 +6,10 @@
|
||||
- [[file:../tags/emacs.org][@@html:<span class="post-tag">emacs</span>@@]] (2)
|
||||
- [[file:../tags/insights.org][@@html:<span class="post-tag">insights</span>@@]] (4)
|
||||
- [[file:../tags/introduction.org][@@html:<span class="post-tag">introduction</span>@@]] (3)
|
||||
- [[file:../tags/learning.org][@@html:<span class="post-tag">learning</span>@@]] (14)
|
||||
- [[file:../tags/life.org][@@html:<span class="post-tag">life</span>@@]] (4)
|
||||
- [[file:../tags/learning.org][@@html:<span class="post-tag">learning</span>@@]] (16)
|
||||
- [[file:../tags/life.org][@@html:<span class="post-tag">life</span>@@]] (8)
|
||||
- [[file:../tags/maths.org][@@html:<span class="post-tag">maths</span>@@]] (1)
|
||||
- [[file:../tags/notes.org][@@html:<span class="post-tag">notes</span>@@]] (15)
|
||||
- [[file:../tags/notes.org][@@html:<span class="post-tag">notes</span>@@]] (17)
|
||||
- [[file:../tags/reading.org][@@html:<span class="post-tag">reading</span>@@]] (1)
|
||||
- [[file:../tags/review.org][@@html:<span class="post-tag">review</span>@@]] (20)
|
||||
- [[file:../tags/review.org][@@html:<span class="post-tag">review</span>@@]] (22)
|
||||
- [[file:../tags/website.org][@@html:<span class="post-tag">website</span>@@]] (2)
|
||||
332
home/guide/wird-tracker-guide.org
Normal file
332
home/guide/wird-tracker-guide.org
Normal file
@@ -0,0 +1,332 @@
|
||||
#+TITLE: Wird Tracker — Technical Guide
|
||||
#+OPTIONS: toc:t num:t
|
||||
#+SLUG: wird-tracker-guide
|
||||
|
||||
* Overview
|
||||
|
||||
The wird tracker is a full-stack feature built on top of the existing
|
||||
org-publish site. It consists of four layers:
|
||||
|
||||
- *Database* — a PostgreSQL table (~wird_entries~) storing every log event
|
||||
- *Backend* — a Spring Boot controller (~WirdController~) exposing a REST API at ~/api/wird/~
|
||||
- *Frontend JS* — ~wird-tracker.js~ handles all rendering and API calls
|
||||
- *Org page* — ~wird-tracker.org~ defines the HTML structure via ~#+BEGIN_EXPORT html~
|
||||
|
||||
The page has no sidenotes (~#+NO_SIDENOTES: t~), so ~body.no-sidenotes~ in
|
||||
~styles.css~ automatically widens the content area. ~wird-tracker.css~
|
||||
complements ~styles.css~ and defers to its CSS variables, so dark mode
|
||||
works without any extra work.
|
||||
|
||||
* File Locations
|
||||
|
||||
| File | Where it lives | Purpose |
|
||||
|--------------------------+---------------------------------------------+----------------------------------|
|
||||
| ~wird-tracker.org~ | your org source directory | page structure, HTML injection |
|
||||
| ~wird-tracker.css~ | ~static/css/wird-tracker.css~ | component styles |
|
||||
| ~wird-tracker.js~ | ~static/js/wird-tracker.js~ | all frontend logic |
|
||||
| ~WirdEntry.java~ | ~src/.../model/WirdEntry.java~ | JPA entity |
|
||||
| ~CreateWirdEntryDTO.java~ | ~src/.../dto/CreateWirdEntryDTO.java~ | request body shape |
|
||||
| ~WirdEntryRepository.java~ | ~src/.../repository/WirdEntryRepository.java~ | Spring Data queries |
|
||||
| ~WirdService.java~ | ~src/.../service/WirdService.java~ | business logic |
|
||||
| ~WirdController.java~ | ~src/.../controller/WirdController.java~ | REST endpoints |
|
||||
| ~wird_schema.sql~ | wherever you keep your SQL scripts | initial DB setup |
|
||||
|
||||
* Database Schema
|
||||
|
||||
** Tables
|
||||
|
||||
The main table is ~wird_entries~. Each row is a single log event — not
|
||||
one row per day. This means you can log 200 durood in the morning and 300
|
||||
in the evening; they aggregate to 500 on the frontend.
|
||||
|
||||
#+BEGIN_SRC sql
|
||||
CREATE TABLE wird_entries (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
wird_type VARCHAR NOT NULL,
|
||||
date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
value NUMERIC(10,2) NOT NULL CHECK (value >= 0),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
#+END_SRC
|
||||
|
||||
The ~wird_targets~ table stores daily minimums per wird type. It has an
|
||||
~effective_from~ column so you can change targets over time without
|
||||
losing history.
|
||||
|
||||
#+BEGIN_SRC sql
|
||||
CREATE TABLE wird_targets (
|
||||
id SERIAL PRIMARY KEY,
|
||||
wird_type VARCHAR NOT NULL,
|
||||
target NUMERIC NOT NULL,
|
||||
effective_from DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
UNIQUE (wird_type, effective_from)
|
||||
);
|
||||
#+END_SRC
|
||||
|
||||
There is also a convenience view ~wird_daily_totals~ which aggregates
|
||||
entries per day per type. It is not queried by the backend currently but
|
||||
is useful for ad-hoc psql inspection.
|
||||
|
||||
** Note on the Enum
|
||||
|
||||
The schema was originally written with a PostgreSQL ~CREATE TYPE wird_type AS ENUM~.
|
||||
This was dropped in favour of plain ~VARCHAR~ because Hibernate 6 cannot
|
||||
bind a ~String~ to a Postgres enum column without a custom ~PGobject~
|
||||
converter, which itself has compatibility issues with Hibernate 6's type
|
||||
system. ~VARCHAR~ with application-level validation is simpler and
|
||||
equally safe.
|
||||
|
||||
* REST API
|
||||
|
||||
All endpoints are under ~/api/wird/~.
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------+-----------------------------+---------------------------------------------|
|
||||
| ~GET~ | ~/api/wird/entries~ | All entries, newest first |
|
||||
| ~GET~ | ~/api/wird/entries/today~ | Today's entries only |
|
||||
| ~GET~ | ~/api/wird/entries/range~ | Entries between ~?from=YYYY-MM-DD&to=...~ |
|
||||
| ~GET~ | ~/api/wird/entries/type/:t~ | Entries for one type in a date range |
|
||||
| ~POST~ | ~/api/wird/entries~ | Create a new entry |
|
||||
|
||||
The ~POST~ body shape is:
|
||||
|
||||
#+BEGIN_SRC json
|
||||
{
|
||||
"wirdType": "durood",
|
||||
"date": "2026-03-19",
|
||||
"value": 500,
|
||||
"notes": "after fajr"
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
All fields except ~notes~ are required. ~date~ defaults to today on the
|
||||
backend if omitted, but the frontend always sends it explicitly.
|
||||
|
||||
* Frontend Architecture
|
||||
|
||||
** Key constants
|
||||
|
||||
At the top of ~wird-tracker.js~ there are two objects you will edit most often:
|
||||
|
||||
#+BEGIN_SRC javascript
|
||||
const DAILY_WIRD = {
|
||||
durood: { label: "Durood", unit: "count", target: 500 },
|
||||
istighfar: { label: "Istighfar", unit: "count", target: 200 },
|
||||
quran: { label: "Qurʾān", unit: "juz", target: 3 },
|
||||
muraqabah: { label: "Murāqabah", unit: "min", target: 20 },
|
||||
wuqoof_qalbi: { label: "Wuqūf Qalbī", unit: "min", target: 15 },
|
||||
};
|
||||
|
||||
const MEETING_CYCLE_DAYS = 21;
|
||||
#+END_SRC
|
||||
|
||||
~DAILY_WIRD~ drives the today cards, the progress bar count, the trend
|
||||
chart dropdown, and the history table. ~WERD_META~ is a superset of
|
||||
~DAILY_WIRD~ that also includes ~shaykh_meeting~ — used for labelling
|
||||
history entries and formatting values.
|
||||
|
||||
** Rendering pipeline
|
||||
|
||||
On page load, the sequence is:
|
||||
|
||||
1. ~loadAll()~ — fetches ~/api/wird/entries~, populates ~allEntries~
|
||||
2. ~buildTodayMap()~ — aggregates today's entries into ~todayMap~ (type → total)
|
||||
3. ~buildMeetingLog()~ — filters ~allEntries~ to attended shaykh meetings
|
||||
4. ~renderToday()~ — draws the 5 wird cards and progress bar
|
||||
5. ~renderMeetingPanel()~ — draws the meeting status, next due date, cycle table
|
||||
6. ~renderHistory()~ — populates the history table (meetings excluded)
|
||||
7. ~renderChart()~ — draws the Chart.js trend line for the selected wird
|
||||
|
||||
After any ~POST~ (new entry), steps 2–7 all re-run so the page updates
|
||||
without a reload.
|
||||
|
||||
** Shaykh meeting cycle logic
|
||||
|
||||
~buildCycleInsights()~ works by anchoring cycles to the date of your very
|
||||
first logged meeting and walking forward in 21-day windows until today.
|
||||
For each window it checks whether any attended meeting falls within it.
|
||||
This means the cycle boundaries are stable — they do not shift when you
|
||||
log a new meeting. If you want cycles to reset from the most recent
|
||||
meeting instead, change the anchor line:
|
||||
|
||||
#+BEGIN_SRC javascript
|
||||
// Current: anchored to first ever meeting
|
||||
const first = allMeetingDates[0];
|
||||
|
||||
// Alternative: rolling window from most recent
|
||||
const first = addDays(allMeetingDates[allMeetingDates.length - 1], 0);
|
||||
#+END_SRC
|
||||
|
||||
* How To: Common Tasks
|
||||
|
||||
** Change a daily target
|
||||
|
||||
Targets are currently hard-coded in ~DAILY_WIRD~ in ~wird-tracker.js~.
|
||||
Change the ~target~ value for the relevant entry:
|
||||
|
||||
#+BEGIN_SRC javascript
|
||||
durood: { label: "Durood", unit: "count", target: 700 },
|
||||
#+END_SRC
|
||||
|
||||
If you want targets to come from the database instead (so you can change
|
||||
them without redeploying), the ~wird_targets~ table already supports this.
|
||||
You would need to add a ~/api/wird/targets~ endpoint in ~WirdController~
|
||||
and fetch it in ~loadAll()~, then replace the hard-coded ~target~ values
|
||||
with the fetched ones.
|
||||
|
||||
** Add a new wird type
|
||||
|
||||
There are four places to update:
|
||||
|
||||
1. *~wird-tracker.js~* — add an entry to ~DAILY_WIRD~:
|
||||
|
||||
#+BEGIN_SRC javascript
|
||||
tawbah: { label: "Tawbah", unit: "count", target: 100 },
|
||||
#+END_SRC
|
||||
|
||||
2. *~wird-tracker.org~* — add an ~<option>~ to the modal ~<select>~:
|
||||
|
||||
#+BEGIN_SRC html
|
||||
<option value="tawbah">Tawbah</option>
|
||||
#+END_SRC
|
||||
|
||||
3. *~wird-tracker.org~* — add an ~<option>~ to the trend chart ~<select>~:
|
||||
|
||||
#+BEGIN_SRC html
|
||||
<option value="tawbah">Tawbah</option>
|
||||
#+END_SRC
|
||||
|
||||
4. *~wird_targets~ table* — insert a default target (optional but tidy):
|
||||
|
||||
#+BEGIN_SRC sql
|
||||
INSERT INTO wird_targets (wird_type, target)
|
||||
VALUES ('tawbah', 100);
|
||||
#+END_SRC
|
||||
|
||||
No backend changes are needed — ~WirdController~ accepts any string as
|
||||
~wirdType~ and stores it as-is.
|
||||
|
||||
** Change the shaykh meeting cycle length
|
||||
|
||||
One line in ~wird-tracker.js~:
|
||||
|
||||
#+BEGIN_SRC javascript
|
||||
const MEETING_CYCLE_DAYS = 21; // change to e.g. 14
|
||||
#+END_SRC
|
||||
|
||||
** Edit or delete an entry
|
||||
|
||||
There is currently no edit/delete UI. You can do it directly in psql:
|
||||
|
||||
#+BEGIN_SRC sql
|
||||
-- Find the entry
|
||||
SELECT * FROM wird_entries
|
||||
WHERE wird_type = 'durood' AND date = '2026-03-19'
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- Delete by id
|
||||
DELETE FROM wird_entries WHERE id = 42;
|
||||
|
||||
-- Correct a value
|
||||
UPDATE wird_entries SET value = 350 WHERE id = 42;
|
||||
#+END_SRC
|
||||
|
||||
If you want a delete button in the UI, the backend needs a ~DELETE~
|
||||
endpoint:
|
||||
|
||||
#+BEGIN_SRC java
|
||||
@DeleteMapping("/entries/{id}")
|
||||
public ResponseEntity<Void> deleteEntry(@PathVariable Long id) {
|
||||
repo.deleteById(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
Then in the JS, add a delete button to each history row and call:
|
||||
|
||||
#+BEGIN_SRC javascript
|
||||
await fetch(`${API}/entries/${id}`, { method: "DELETE" });
|
||||
#+END_SRC
|
||||
|
||||
** Inspect data directly
|
||||
|
||||
The ~wird_daily_totals~ view is useful for quick summaries:
|
||||
|
||||
#+BEGIN_SRC sql
|
||||
-- Today's totals
|
||||
SELECT wird_type, total, log_count
|
||||
FROM wird_daily_totals
|
||||
WHERE date = CURRENT_DATE;
|
||||
|
||||
-- Last 7 days of durood
|
||||
SELECT date, total
|
||||
FROM wird_daily_totals
|
||||
WHERE wird_type = 'durood'
|
||||
AND date >= CURRENT_DATE - INTERVAL '7 days'
|
||||
ORDER BY date DESC;
|
||||
|
||||
-- Check whether you met target each day
|
||||
SELECT date, wird_type, total,
|
||||
CASE WHEN total >= t.target THEN 'met' ELSE 'missed' END AS status
|
||||
FROM wird_daily_totals w
|
||||
JOIN wird_targets t USING (wird_type)
|
||||
WHERE t.effective_from = (
|
||||
SELECT MAX(effective_from) FROM wird_targets t2
|
||||
WHERE t2.wird_type = w.wird_type
|
||||
AND t2.effective_from <= w.date
|
||||
)
|
||||
ORDER BY date DESC, wird_type;
|
||||
#+END_SRC
|
||||
|
||||
* Extensibility Notes
|
||||
|
||||
** Adding a weekly/monthly summary endpoint
|
||||
|
||||
The backend is structured to make this easy. Add a method to
|
||||
~WirdEntryRepository~ using a ~@Query~ and expose it via a new
|
||||
~@GetMapping~ in ~WirdController~. The JS can then call it and render
|
||||
an additional panel without touching anything else.
|
||||
|
||||
** Making targets configurable via the DB
|
||||
|
||||
The ~wird_targets~ table already has ~effective_from~, which means you
|
||||
can version targets over time. A query like the one in the last section
|
||||
above shows the pattern for joining targets to entries correctly —
|
||||
finding the most recent target that was in effect on a given date.
|
||||
|
||||
** Adding authentication
|
||||
|
||||
Currently the API has no auth — it is assumed the page is on a
|
||||
personal/private site. If you ever need to restrict writes, the cleanest
|
||||
approach given the existing Spring Boot setup is to add a simple API key
|
||||
check in a ~HandlerInterceptor~ that only applies to ~POST~ and ~DELETE~
|
||||
methods on ~/api/wird/~.
|
||||
|
||||
** Porting the frontend to a proper framework
|
||||
|
||||
The JS is a self-contained IIFE with no build step, which suits the
|
||||
org-publish workflow. If you ever move to a build pipeline, the logic
|
||||
maps cleanly onto a React component tree:
|
||||
~<TodayPanel>~, ~<MeetingPanel>~, ~<TrendChart>~, ~<HistoryTable>~ — each
|
||||
taking ~allEntries~ as a prop and deriving their state from it.
|
||||
|
||||
* Deployment Checklist
|
||||
|
||||
When you deploy changes, the steps depend on what you changed:
|
||||
|
||||
| Changed file | Action needed |
|
||||
|------------------------+------------------------------------------------------------|
|
||||
| ~wird-tracker.org~ | Re-run org-publish; the HTML will be regenerated |
|
||||
| ~wird-tracker.css~ | Copy to ~static/css/~; hard-refresh browser cache |
|
||||
| ~wird-tracker.js~ | Copy to ~static/js/~; hard-refresh browser cache |
|
||||
| Any ~*.java~ file | Rebuild and restart the Spring Boot jar |
|
||||
| SQL schema changes | Run the migration manually in psql; restart Spring Boot |
|
||||
|
||||
For CSS/JS cache busting during development, append a query string to
|
||||
the ~<link>~ and ~<script>~ tags in the ~#+BEGIN_EXPORT html~ block:
|
||||
|
||||
#+BEGIN_SRC html
|
||||
<link rel="stylesheet" href="/css/wird-tracker.css?v=2">
|
||||
<script src="/js/wird-tracker.js?v=2"></script>
|
||||
#+END_SRC
|
||||
30
home/recently-updated.org
Executable file
30
home/recently-updated.org
Executable file
@@ -0,0 +1,30 @@
|
||||
#+TITLE: Recently Updated
|
||||
#+OPTIONS: toc:nil num:nil
|
||||
|
||||
* Recently Updated (top 26 files - per lima's request)
|
||||
- [[file:home/wird-tracker.org][Wird Tracker]] @@html:<span class="post-date">2026-03-19 16:05</span>@@
|
||||
- [[file:blogs/2026/03-march/setting-a-wird-tracker-19-03.org][Setting a Wird Tracker]] @@html:<span class="post-date">2026-03-19 13:57</span>@@
|
||||
- [[file:index.org][Home Page]] @@html:<span class="post-date">2026-03-19 13:10</span>@@
|
||||
- [[file:home/guide/wird-tracker-guide.org][Wird Tracker — Technical Guide]] @@html:<span class="post-date">2026-03-19 12:43</span>@@
|
||||
- [[file:blogs/2026/03-march/fixing-the-dag-18-03.org][DAG fixes]] @@html:<span class="post-date">2026-03-19 10:42</span>@@
|
||||
- [[file:blogs/2026/03-march/feeling-sleepy.org][Feeling extremely sleepy]] @@html:<span class="post-date">2026-03-19 10:41</span>@@
|
||||
- [[file:blogs/2026/03-march/oversleeping-16-03.org][Oversleeping and missing a meeting...]] @@html:<span class="post-date">2026-03-16 12:17</span>@@
|
||||
- [[file:blogs/2026/03-march/15-03-week-review.org][[15-03-2026] - Weekly Review]] @@html:<span class="post-date">2026-03-12 16:31</span>@@
|
||||
- [[file:blogs/2026/03-march/08-03-week-review.org][[08-03-2026] - Weekly Review]] @@html:<span class="post-date">2026-03-12 16:17</span>@@
|
||||
- [[file:posts/career/javascript.org][Understands the Javascript language]] @@html:<span class="post-date">2026-03-11 17:20</span>@@
|
||||
- [[file:posts/career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:<span class="post-date">2026-03-11 17:19</span>@@
|
||||
- [[file:posts/career/restful-api.org][Restful API]] @@html:<span class="post-date">2026-03-08 17:37</span>@@
|
||||
- [[file:home/status.org][Competency Status Board]] @@html:<span class="post-date">2026-03-08 16:28</span>@@
|
||||
- [[file:blogs/2026/02-february/27-02-26.org][Journeys rambles again...]] @@html:<span class="post-date">2026-03-07 16:33</span>@@
|
||||
- [[file:blogs/2026/03-march/01-03-week-review.org][[01-03-2026] - Weekly Review]] @@html:<span class="post-date">2026-03-07 12:45</span>@@
|
||||
- [[file:posts/career/database-permissions.org][Database Permissions, Roles, and Accounts]] @@html:<span class="post-date">2026-03-05 13:06</span>@@
|
||||
- [[file:posts/career/monitoring-and-logging.org][Monitoring and Logging]] @@html:<span class="post-date">2026-03-05 12:58</span>@@
|
||||
- [[file:posts/career/pipelines.org][Pipelines and how they work (as well as CI/CD)]] @@html:<span class="post-date">2026-03-05 11:52</span>@@
|
||||
- [[file:blogs/2026/02-february/third-meeting.org][Third Meeting with lima :)]] @@html:<span class="post-date">2026-03-03 12:02</span>@@
|
||||
- [[file:blogs/2026/02-february/22-02-week-review.org][[22-02-2026] - Weekly Review]] @@html:<span class="post-date">2026-02-27 17:07</span>@@
|
||||
- [[file:blogs/2026/02-february/26-02-26.org][Starting the Journeys Upgrade]] @@html:<span class="post-date">2026-02-26 17:37</span>@@
|
||||
- [[file:blogs/2026/02-february/24-02-26.org][Integration tests failing (sob)]] @@html:<span class="post-date">2026-02-24 17:00</span>@@
|
||||
- [[file:home/backlog.org][Backlog]] @@html:<span class="post-date">2026-02-23 16:40</span>@@
|
||||
- [[file:blogs/2026/02-february/15-02-week-review.org][[15-02-2026] - Weekly Review]] @@html:<span class="post-date">2026-02-23 10:02</span>@@
|
||||
- [[file:home/services.org][Service]] @@html:<span class="post-date">2026-02-11 13:26</span>@@
|
||||
- [[file:blogs/2026/02-february/08-02-week-review.org][[08-02-2026] - Weekly Review]] @@html:<span class="post-date">2026-02-11 12:20</span>@@
|
||||
159
home/wird-tracker.org
Normal file
159
home/wird-tracker.org
Normal file
@@ -0,0 +1,159 @@
|
||||
#+TITLE: Wird Tracker
|
||||
#+OPTIONS: toc:nil num:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+COMMENTS: nil
|
||||
#+SLUG: wird-tracker
|
||||
|
||||
This page tracks my daily awrād and spiritual practices.
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
|
||||
<div id="wird-app">
|
||||
|
||||
<!-- Today Panel -->
|
||||
<section id="today-panel" class="wird-panel">
|
||||
<div class="wird-panel-header">
|
||||
<h2 id="today-date">—</h2>
|
||||
<div id="today-progress-wrap">
|
||||
<div id="today-progress-bar"><div id="today-progress-fill"></div></div>
|
||||
<span id="today-progress-label">0 / 5 complete</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wird-cards"></div>
|
||||
</section>
|
||||
|
||||
<!-- Shaykh Meeting Panel -->
|
||||
<section id="meeting-panel" class="wird-panel">
|
||||
<div class="wird-panel-header">
|
||||
<h2>Meeting with the Shaykh</h2>
|
||||
<button id="log-meeting-btn" class="btn-primary">+ Log Meeting</button>
|
||||
</div>
|
||||
<div id="meeting-panel-body">
|
||||
<p style="color:var(--muted);font-style:italic">Loading…</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Log Entry Modal -->
|
||||
<dialog id="log-modal">
|
||||
<form id="log-form" method="dialog">
|
||||
<h3 id="modal-title">Log Entry</h3>
|
||||
|
||||
<div class="field-group">
|
||||
<label for="log-date">Date</label>
|
||||
<input type="date" id="log-date" required>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label for="log-type">Wird</label>
|
||||
<select id="log-type" required>
|
||||
<option value="durood">Durood</option>
|
||||
<option value="istighfar">Istighfar</option>
|
||||
<option value="quran">Qurʾān (juz)</option>
|
||||
<option value="muraqabah">Murāqabah (min)</option>
|
||||
<option value="wuqoof_qalbi">Wuqūf Qalbī</option>
|
||||
<option value="shaykh_meeting" style="display:none">Meeting with Shaykh</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Numeric amount — count/juz/min wirds -->
|
||||
<div class="field-group" id="value-group">
|
||||
<label for="log-value">Amount</label>
|
||||
<input type="number" id="log-value" min="0" step="0.5" placeholder="0">
|
||||
</div>
|
||||
|
||||
<!-- Rating picker — wuqoof qalbi only -->
|
||||
<div class="field-group" id="rating-group" style="display:none">
|
||||
<label>Quality of presence</label>
|
||||
<div class="modal-pips" id="modal-pips">
|
||||
<button type="button" class="modal-pip" data-val="1">
|
||||
<span class="pip-dot"></span>
|
||||
<span class="pip-lbl">distracted</span>
|
||||
</button>
|
||||
<button type="button" class="modal-pip" data-val="2">
|
||||
<span class="pip-dot"></span>
|
||||
<span class="pip-lbl">scattered</span>
|
||||
</button>
|
||||
<button type="button" class="modal-pip" data-val="3">
|
||||
<span class="pip-dot"></span>
|
||||
<span class="pip-lbl">present</span>
|
||||
</button>
|
||||
<button type="button" class="modal-pip" data-val="4">
|
||||
<span class="pip-dot"></span>
|
||||
<span class="pip-lbl">attentive</span>
|
||||
</button>
|
||||
<button type="button" class="modal-pip" data-val="5">
|
||||
<span class="pip-dot"></span>
|
||||
<span class="pip-lbl">absorbed</span>
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" id="log-rating-value">
|
||||
</div>
|
||||
|
||||
<!-- Shaykh meeting note -->
|
||||
<div class="field-group" id="shaykh-group" style="display:none">
|
||||
<p style="font-size:.82rem;color:var(--muted);margin:0">Set the date above to when the meeting took place.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label for="log-notes">Notes <span class="optional">(optional)</span></label>
|
||||
<textarea id="log-notes" rows="2" placeholder="Any reflections…"></textarea>
|
||||
</div>
|
||||
|
||||
<div id="modal-actions">
|
||||
<button type="button" id="modal-cancel">Cancel</button>
|
||||
<button type="submit" id="modal-save" class="btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<!-- Trends -->
|
||||
<section id="trends-panel" class="wird-panel">
|
||||
<div class="wird-panel-header">
|
||||
<h2>Trends</h2>
|
||||
<div id="trend-controls">
|
||||
<select id="trend-type">
|
||||
<option value="durood">Durood</option>
|
||||
<option value="istighfar">Istighfar</option>
|
||||
<option value="quran">Qurʾān</option>
|
||||
<option value="muraqabah">Murāqabah</option>
|
||||
<option value="wuqoof_qalbi">Wuqūf Qalbī</option>
|
||||
</select>
|
||||
<select id="trend-range">
|
||||
<option value="14">14 days</option>
|
||||
<option value="30" selected>30 days</option>
|
||||
<option value="90">90 days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="trend-chart"></canvas>
|
||||
<div id="trend-stats"></div>
|
||||
</section>
|
||||
|
||||
<!-- History -->
|
||||
<section id="history-panel" class="wird-panel">
|
||||
<div class="wird-panel-header">
|
||||
<h2>History</h2>
|
||||
<button id="open-log-btn" class="btn-primary">+ Log Entry</button>
|
||||
</div>
|
||||
<div id="history-table-wrap">
|
||||
<table id="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Wird</th>
|
||||
<th>Amount</th>
|
||||
<th>vs Target</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="history-body">
|
||||
<tr><td colspan="5" class="loading-cell">Loading…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
#+END_EXPORT
|
||||
Reference in New Issue
Block a user