Finish req list, add snoozed list

This commit is contained in:
Daniel J. Summers 2024-06-22 22:48:31 -04:00
parent 2369827033
commit d6e8cf66cc
5 changed files with 76 additions and 25 deletions

View File

@ -530,7 +530,7 @@ let routes = [
routef "request/%s/add-notes" Components.addNotes // done routef "request/%s/add-notes" Components.addNotes // done
routef "request/%s/item" Components.requestItem // not used routef "request/%s/item" Components.requestItem // not used
routef "request/%s/notes" Components.notes // done routef "request/%s/notes" Components.notes // done
routef "request/%s/snooze" Components.snooze routef "request/%s/snooze" Components.snooze // done
] ]
] ]
GET_HEAD [ route "/docs" Home.docs ] // done GET_HEAD [ route "/docs" Home.docs ] // done
@ -547,14 +547,14 @@ let routes = [
routef "/%s/full" Request.getFull // done routef "/%s/full" Request.getFull // done
route "s/active" Request.active // done route "s/active" Request.active // done
route "s/answered" Request.answered // done route "s/answered" Request.answered // done
route "s/snoozed" Request.snoozed route "s/snoozed" Request.snoozed // done
] ]
PATCH [ PATCH [
route "" Request.update // done route "" Request.update // done
routef "/%s/cancel-snooze" Request.cancelSnooze routef "/%s/cancel-snooze" Request.cancelSnooze
routef "/%s/prayed" Request.prayed // done routef "/%s/prayed" Request.prayed // done
routef "/%s/show" Request.show routef "/%s/show" Request.show
routef "/%s/snooze" Request.snooze routef "/%s/snooze" Request.snooze // done
] ]
POST [ POST [
route "" Request.add // done route "" Request.add // done

View File

@ -2,6 +2,9 @@
namespace MyPrayerJournal; namespace MyPrayerJournal;
use BitBadger\PDODocument\Custom;
use BitBadger\PDODocument\Mapper\ExistsMapper;
class Layout class Layout
{ {
/** /**
@ -72,7 +75,15 @@ class Layout
* The default navigation bar, which will load the items on page load, and whenever a refresh event occurs * The default navigation bar, which will load the items on page load, and whenever a refresh event occurs
*/ */
public static function navBar(): void public static function navBar(): void
{ ?> {
$table = Table::REQUEST;
$hasSnoozed = key_exists('user_id', $_SESSION)
? Custom::scalar(<<<SQL
SELECT EXISTS (
SELECT 1 FROM $table
WHERE data->>'userId' = :userId AND datetime(data->>'snoozedUntil') > datetime('now'))
SQL, [':userId' => $_SESSION['user_id']], new ExistsMapper())
: false; ?>
<nav class="navbar navbar-dark" role="navigation"> <nav class="navbar navbar-dark" role="navigation">
<div class=container-fluid><?php <div class=container-fluid><?php
UI::pageLink('/', '<span class=m>my</span><span class=p>Prayer</span><span class=j>Journal</span>', UI::pageLink('/', '<span class=m>my</span><span class=p>Prayer</span><span class=j>Journal</span>',
@ -81,7 +92,7 @@ class Layout
if (key_exists('user_id', $_SESSION)) { if (key_exists('user_id', $_SESSION)) {
self::navLink('/journal', 'Journal'); self::navLink('/journal', 'Journal');
self::navLink('/requests/active', 'Active'); self::navLink('/requests/active', 'Active');
if (key_exists('has_snoozed', $_SESSION)) self::navLink('/requests/snoozed', 'Snoozed'); if ($hasSnoozed) self::navLink('/requests/snoozed', 'Snoozed');
self::navLink('/requests/answered', 'Answered'); ?> self::navLink('/requests/answered', 'Answered'); ?>
<li class=nav-item><a href=/user/log-off>Log Off</a><?php <li class=nav-item><a href=/user/log-off>Log Off</a><?php
} else { ?> } else { ?>

View File

@ -146,13 +146,15 @@ class Request implements JsonSerializable
* Get either the user's active or answered requests * Get either the user's active or answered requests
* *
* @param bool $active True to retrieve active requests, false to retrieve answered requests * @param bool $active True to retrieve active requests, false to retrieve answered requests
* @param bool $snoozed True to retrieve only snoozed requests
* @return DocumentList<Request> The requests matching the criteria * @return DocumentList<Request> The requests matching the criteria
* @throws DocumentException If any is encountered * @throws DocumentException If any is encountered
*/ */
private static function forUser(bool $active = true): DocumentList private static function forUser(bool $active = true, bool $snoozed = false): DocumentList
{ {
$table = Table::REQUEST; $table = Table::REQUEST;
$op = $active ? '<>' : '='; $op = $active ? '<>' : '=';
$extra = $snoozed ? "AND datetime(data->>'snoozedUntil') > datetime('now')" : '';
$order = $active $order = $active
? "coalesce(data->>'snoozedUntil', data->>'showAfter', last_prayed, data->>'$.history[0].asOf')" ? "coalesce(data->>'snoozedUntil', data->>'showAfter', last_prayed, data->>'$.history[0].asOf')"
: "data->>'$.history[0].asOf' DESC"; : "data->>'$.history[0].asOf' DESC";
@ -164,7 +166,7 @@ class Request implements JsonSerializable
LIMIT 1) last_prayed LIMIT 1) last_prayed
FROM $table r FROM $table r
WHERE data->>'userId' = :userId WHERE data->>'userId' = :userId
AND data->>'$.history[0].action' $op 'Answered' AND data->>'$.history[0].action' $op 'Answered' $extra
ORDER BY $order ORDER BY $order
SQL, [':userId' => $_SESSION['user_id']], new DocumentMapper(self::class)); SQL, [':userId' => $_SESSION['user_id']], new DocumentMapper(self::class));
} }
@ -190,4 +192,15 @@ class Request implements JsonSerializable
{ {
return self::forUser(false); return self::forUser(false);
} }
/**
* Get a list of snoozed requests for a user
*
* @return DocumentList<Request> The user's snoozed requests
* @throws DocumentException If any is encountered
*/
public static function snoozed(): DocumentList
{
return self::forUser(snoozed: true);
}
} }

View File

@ -168,19 +168,18 @@ class UI
return $dtFrom > $dtTo ? "$value ago" : "in $value"; return $dtFrom > $dtTo ? "$value ago" : "in $value";
} }
/**
* Render the given list of requests
*
* @param DocumentList<Request> $reqs The list of requests to render
* @throws Exception If date/time instances are not valid
*/
public static function requestList(DocumentList $reqs): void public static function requestList(DocumentList $reqs): void
{ {
$btnClass = "btn btn-light mx-2"; $btnClass = "btn btn-light mx-2";
$restoreBtn = fn(string $id, string $link, string $title) => $restoreBtn = fn(string $id, string $link, string $title) =>
'<button class="' . $btnClass. '" hx-patch="/request/' . $link . '?id=' . $id '<button class="' . $btnClass. '" hx-patch="/request/' . $link . '?id=' . $id
. '" title="' . htmlspecialchars($title) . '">' . self::icon('restore') . '</button>'; . '" title="' . htmlspecialchars($title) . '">' . self::icon('restore') . '</button>'; ?>
/// Create a request within the list
/* let reqListItem now tz req =
let isFuture instant = defaultArg (instant |> Option.map (fun it -> it > now)) false
let reqId = RequestId.toString req.RequestId
let isPending = (not isSnoozed) && isFuture req.ShowAfter
let restoreBtn (link : string) title =
button [ btnClass; _hxPatch $"/request/{reqId}/{link}"; _title title ] [ icon "restore" ] */ ?>
<div class=list-group><?php <div class=list-group><?php
foreach ($reqs->items() as /** @var Request $req */ $req) { ?> foreach ($reqs->items() as /** @var Request $req */ $req) { ?>
<div class="list-group-item px-0 d-flex flex-row align-items-start" hx-target=this <div class="list-group-item px-0 d-flex flex-row align-items-start" hx-target=this
@ -197,15 +196,21 @@ class UI
echo $restoreBtn($req->id, 'show', 'Show Now'); echo $restoreBtn($req->id, 'show', 'Show Now');
} }
echo '<p class="request-text mb-0">' . $req->currentText(); echo '<p class="request-text mb-0">' . $req->currentText();
// if isSnoozed || isPending || isAnswered then if ($req->isSnoozed() || $req->isPending() || $req->isAnswered()) { ?>
// br [] <br>
// small [ _class "text-muted" ] [ <small class=text-muted><em><?php
// if isSnoozed then [ str "Snooze expires "; relativeDate req.SnoozedUntil.Value now tz ] switch (true) {
// elif isPending then [ str "Request appears next "; relativeDate req.ShowAfter.Value now tz ] case $req->isSnoozed():
// else (* isAnswered *) [ str "Answered "; relativeDate req.AsOf now tz ] echo 'Snooze expires '; self::relativeDate($req->snoozedUntil);
// |> em [] break;
// ] case $req->isPending():
?> echo 'Request appears next '; self::relativeDate($req->showAfter);
break;
default:
echo 'Answered '; self::relativeDate($req->history[0]->asOf);
} ?>
</em></small><?php
} ?>
</div><?php </div><?php
} ?> } ?>
</div><?php </div><?php

View File

@ -0,0 +1,22 @@
<?php declare(strict_types=1);
use MyPrayerJournal\{Auth, Layout, Request, UI};
require '../../start.php';
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
Auth::requireUser();
$reqs = Request::snoozed();
Layout::pageHead('Snoozed Requests'); ?>
<article class="container mt-3">
<h2 class=pb-3>Snoozed Requests</h2><?php
if ($reqs->hasItems()) {
UI::requestList($reqs);
} else {
UI::noResults('No Snoozed Requests', '/journal', 'Return to your journal',
'Your prayer journal has no snoozed requests');
} ?>
</article><?php
Layout::pageFoot();