diff --git a/src/MyPrayerJournal/Handlers.fs b/src/MyPrayerJournal/Handlers.fs index 691ec5b..a36a389 100644 --- a/src/MyPrayerJournal/Handlers.fs +++ b/src/MyPrayerJournal/Handlers.fs @@ -527,9 +527,9 @@ let routes = [ subRoute "/components/" [ GET_HEAD [ route "journal-items" Components.journalItems // done - routef "request/%s/add-notes" Components.addNotes - routef "request/%s/item" Components.requestItem - routef "request/%s/notes" Components.notes + routef "request/%s/add-notes" Components.addNotes // done + routef "request/%s/item" Components.requestItem // not used + routef "request/%s/notes" Components.notes // done routef "request/%s/snooze" Components.snooze ] ] @@ -558,7 +558,7 @@ let routes = [ ] POST [ route "" Request.add // done - routef "/%s/note" Request.addNote + routef "/%s/note" Request.addNote // done ] ] subRoute "/user/" [ diff --git a/src/convert-from-v3.php b/src/convert-from-v3.php index b6af213..03b3407 100644 --- a/src/convert-from-v3.php +++ b/src/convert-from-v3.php @@ -20,12 +20,18 @@ Configuration::$pdoDSN = 'sqlite:./data/mpj.db'; Definition::ensureTable(Table::REQUEST); +/** Convert dates to the same format */ +function convertDate(string $date): string +{ + return (new DateTimeImmutable($date))->format('c'); +} + foreach ($reqs as $reqJson) { $req = json_decode($reqJson['data']); - $notes = array_map(fn(stdClass $note) => new Note($note->asOf, $note->notes), $req->notes ?? []); + $notes = array_map(fn(stdClass $note) => new Note(convertDate($note->asOf), $note->notes), $req->notes ?? []); $history = array_map(fn(stdClass $hist) => new History( - asOf: $hist->asOf, + asOf: convertDate($hist->asOf), action: RequestAction::from($hist->status), text: property_exists($hist, 'text') ? $hist->text : null), $req->history); @@ -37,10 +43,10 @@ foreach ($reqs as $reqJson) { }; $v4Req = new Request( id: $req->id, - enteredOn: $req->enteredOn, + enteredOn: convertDate($req->enteredOn), userId: $req->userId, - snoozedUntil: property_exists($req, 'snoozedUntil') ? $req->snoozedUntil : null, - showAfter: property_exists($req, 'showAfter') ? $req->showAfter : null, + snoozedUntil: property_exists($req, 'snoozedUntil') ? convertDate($req->snoozedUntil) : null, + showAfter: property_exists($req, 'showAfter') ? convertDate($req->showAfter) : null, recurrence: $recur, history: $history, notes: $notes); diff --git a/src/lib/Request.php b/src/lib/Request.php index 4ab7839..8fd9743 100644 --- a/src/lib/Request.php +++ b/src/lib/Request.php @@ -3,6 +3,7 @@ namespace MyPrayerJournal; use BitBadger\PDODocument\{Custom, DocumentException, DocumentList, Find, Mapper\DocumentMapper}; +use DateTimeImmutable; use Exception; use JsonSerializable; use Visus\Cuid2\Cuid2; @@ -65,6 +66,30 @@ class Request implements JsonSerializable return $this->history[0]->action == RequestAction::Answered; } + /** + * Is this request currently snoozed? + * + * @return bool True if the request is snoozed, false if not + * @throws Exception If the snoozed until date/time is not valid + */ + public function isSnoozed(): bool + { + return isset($this->snoozedUntil) && new DateTimeImmutable($this->snoozedUntil) > new DateTimeImmutable('now'); + } + + /** + * Is this request currently not shown due to recurrence? + * + * @return bool True if the request is pending, false if not + * @throws Exception If the snoozed or show-after date/times are not valid + */ + public function isPending(): bool + { + return !$this->isSnoozed() + && isset($this->showAfter) + && new DateTimeImmutable($this->showAfter) > new DateTimeImmutable('now'); + } + public function jsonSerialize(): mixed { $values = [ diff --git a/src/lib/UI.php b/src/lib/UI.php index c8745a3..3eae728 100644 --- a/src/lib/UI.php +++ b/src/lib/UI.php @@ -2,8 +2,7 @@ namespace MyPrayerJournal; -use BitBadger\PDODocument\DocumentException; -use BitBadger\PDODocument\DocumentList; +use BitBadger\PDODocument\{DocumentException, DocumentList}; use DateTimeImmutable; use DateTimeZone; use Exception; @@ -44,7 +43,7 @@ class UI ['class' => 'btn btn-secondary', 'title' => 'Edit Request']); ?> '; /// 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 isSnoozed = isFuture req.SnoozedUntil let isPending = (not isSnoozed) && isFuture req.ShowAfter let restoreBtn (link : string) title = button [ btnClass; _hxPatch $"/request/{reqId}/{link}"; _title title ] [ icon "restore" ] */ ?> @@ -190,9 +191,11 @@ class UI self::pageLink("/request/edit?id=$req->id", self::icon('edit'), ['class' => $btnClass, 'title' => 'Edit Request']); } -// if isSnoozed then restoreBtn "cancel-snooze" "Cancel Snooze" -// elif isPending then restoreBtn "show" "Show Now" - + if ($req->isSnoozed()) { + echo $restoreBtn($req->id, 'cancel-snooze', 'Cancel Snooze'); + } elseif ($req->isPending()) { + echo $restoreBtn($req->id, 'show', 'Show Now'); + } echo '

' . $req->currentText(); // if isSnoozed || isPending || isAnswered then // br [] diff --git a/src/public/components/request/add-note.php b/src/public/components/request/add-note.php new file mode 100644 index 0000000..4ce73da --- /dev/null +++ b/src/public/components/request/add-note.php @@ -0,0 +1,28 @@ + 'GET') not_found(); + +Auth::requireUser(false); + +$req = Request::byId($_GET['id']); +if (!$req) not_found(); + +Layout::bareHead(); ?> +

+
+ + +
+

+

+
+
+

+ +

'GET') not_found(); + +Auth::requireUser(false); + +$req = Request::byId($_GET['id']); +if (!$req) not_found(); + +Layout::bareHead();?> +

Prior Notes for This Requestnotes) > 0) { + foreach ($req->notes as $note) { ?> +

asOf); ?>
+ text)?> +

There are no prior notes for this request 'GET') not_found(); + +Auth::requireUser(false); + +$req = Request::byId($_GET['id']); +if (!$req) not_found(); + +Layout::bareHead(); ?> +

+
+ + +
+

+

'POST') not_found(); + +Auth::requireUser(false); + +$req = Request::byId($_GET['id']); +if (!$req) not_found(); + +array_unshift($req->notes, new Note((new DateTimeImmutable('now'))->format('c'), $_POST['notes'])); +Patch::byId(Table::REQUEST, $req->id, ['notes' => $req->notes]); + +hide_modal('notes'); +http_response_code(202); diff --git a/src/public/request/save.php b/src/public/request/save.php index 8031bdd..d1e0196 100644 --- a/src/public/request/save.php +++ b/src/public/request/save.php @@ -22,9 +22,7 @@ switch ($_SERVER['REQUEST_METHOD']) { recurrence: $recurrence, history: [new History($now->format('c'), RequestAction::Created, $_POST['requestText'])])); //Messages.pushSuccess ctx "Added prayer request" "/journal" - header('Location: /journal'); - http_response_code(303); - exit; + see_other('/journal'); case 'PATCH': $req = Request::byId($_PATCH['requestId']); @@ -44,8 +42,5 @@ switch ($_SERVER['REQUEST_METHOD']) { $patch['history'] = $req->history; Patch::byId(Table::REQUEST, $req->id, $patch); //Messages.pushSuccess ctx "Prayer request updated successfully" nextUrl - // TODO: make redirect that filters out non-local URLs - header('Location: ' . $_PATCH['returnTo']); - http_response_code(303); - exit; + see_other($_PATCH['returnTo']); } diff --git a/src/public/request/snooze.php b/src/public/request/snooze.php new file mode 100644 index 0000000..6b1257b --- /dev/null +++ b/src/public/request/snooze.php @@ -0,0 +1,21 @@ + 'PATCH') not_found(); + +Auth::requireUser(false); + +$req = Request::byId($_GET['id']); +if (!$req) not_found(); + +$until = (new DateTimeImmutable($_PATCH['until'] . 'T00:00:00', new DateTimeZone($_REQUEST['time_zone']))) + ->setTimezone(new DateTimeZone('Etc/UTC')); +Patch::byId(Table::REQUEST, $req->id, ['snoozedUntil' => $until->format('c')]); + +// TODO: message + +hide_modal('snooze'); +UI::journal(); diff --git a/src/start.php b/src/start.php index d84afc5..a775f75 100644 --- a/src/start.php +++ b/src/start.php @@ -18,6 +18,8 @@ if (php_sapi_name() != 'cli') { if (!is_null($auth0_user)) { $_SESSION['user_id'] = $auth0_user['sub']; } + + $_REQUEST['time_zone'] = $_SERVER['HTTP_X_TIME_ZONE'] ?? 'Etc/UTC'; } Configuration::$pdoDSN = 'sqlite:' . implode(DIRECTORY_SEPARATOR, [__DIR__, 'data', 'mpj.db']); @@ -35,3 +37,25 @@ function not_found(): never http_response_code(404); die('Not found'); } + +/** + * Return a 303 redirect ("see other" - redirects to a GET) + * + * @param string $url The URL to which the browser should be redirected + */ +function see_other(string $url): never +{ + header('Location: ' . (str_starts_with($url, 'http') ? '/' : $url)); + http_response_code(303); + exit; +} + +/** + * Add a header that instructs the browser to close an open modal dialog + * + * @param string $name The name of the dialog to be closed + */ +function hide_modal(string $name): void +{ + header("X-Hide-Modal: $name"); +}