Notes / snooze, update data migration
- WIP on finishing out request list
This commit is contained in:
parent
3ebb03d470
commit
2369827033
|
@ -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/" [
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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']); ?>
|
||||
<?=$spacer?>
|
||||
<button type=button class="btn btn-secondary" title="Add Notes" data-bs-toggle=modal
|
||||
data-bs-target=#notesModal hx-get="/components/request/add-notes?id=<?=$req->id?>"
|
||||
data-bs-target=#notesModal hx-get="/components/request/add-note?id=<?=$req->id?>"
|
||||
hx-target=#notesBody hx-swap=innerHTML><?=self::icon('comment');?></button>
|
||||
<?=$spacer?>
|
||||
<button type=button class="btn btn-secondary" title="Snooze Request" data-bs-toggle=modal
|
||||
|
@ -110,7 +109,7 @@ class UI
|
|||
public static function relativeDate(string $date): void
|
||||
{
|
||||
$parsed = new DateTimeImmutable($date);
|
||||
$inZone = $parsed->setTimezone(new DateTimeZone($_SERVER['HTTP_X_TIME_ZONE'] ?? 'Etc/UTC'));
|
||||
$inZone = $parsed->setTimezone(new DateTimeZone($_REQUEST['time_zone']));
|
||||
echo '<span title="' . date_format($inZone, 'l, F j, Y \a\t g:ia T') . '">'
|
||||
. self::formatDistance('now', $parsed) . '</span>';
|
||||
}
|
||||
|
@ -172,11 +171,13 @@ class UI
|
|||
public static function requestList(DocumentList $reqs): void
|
||||
{
|
||||
$btnClass = "btn btn-light mx-2";
|
||||
$restoreBtn = fn(string $id, string $link, string $title) =>
|
||||
'<button class="' . $btnClass. '" hx-patch="/request/' . $link . '?id=' . $id
|
||||
. '" 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 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 '<p class="request-text mb-0">' . $req->currentText();
|
||||
// if isSnoozed || isPending || isAnswered then
|
||||
// br []
|
||||
|
|
28
src/public/components/request/add-note.php
Normal file
28
src/public/components/request/add-note.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
use MyPrayerJournal\{Auth, Layout, Request};
|
||||
|
||||
require '../../../start.php';
|
||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||
|
||||
Auth::requireUser(false);
|
||||
|
||||
$req = Request::byId($_GET['id']);
|
||||
if (!$req) not_found();
|
||||
|
||||
Layout::bareHead(); ?>
|
||||
<form hx-post="/request/note?id=<?=$req->id?>">
|
||||
<div class="form-floating pb-3">
|
||||
<textarea id=notes name=notes class=form-control style="min-height: 8rem;" placeholder=Notes autofocus
|
||||
required></textarea>
|
||||
<label for=notes>Notes</label>
|
||||
</div>
|
||||
<p class=text-end><button type=submit class="btn btn-primary">Add Notes</button>
|
||||
</form>
|
||||
<hr style="margin: .5rem -1rem">
|
||||
<div id=priorNotes>
|
||||
<p class="text-center pt-3">
|
||||
<button type=button class="btn btn-secondary" hx-get="/components/request/notes?id=<?=$req->id?>"
|
||||
hx-swap=outerHTML hx-target=#priorNotes>Load Prior Notes</button>
|
||||
</div><?php
|
||||
Layout::bareFoot();
|
23
src/public/components/request/notes.php
Normal file
23
src/public/components/request/notes.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
use MyPrayerJournal\{Auth, Layout, Request, UI};
|
||||
|
||||
require '../../../start.php';
|
||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||
|
||||
Auth::requireUser(false);
|
||||
|
||||
$req = Request::byId($_GET['id']);
|
||||
if (!$req) not_found();
|
||||
|
||||
Layout::bareHead();?>
|
||||
<p class=text-center><strong>Prior Notes for This Request</strong><?php
|
||||
if (sizeof($req->notes) > 0) {
|
||||
foreach ($req->notes as $note) { ?>
|
||||
<p><small class=text-muted><?php UI::relativeDate($note->asOf); ?></small><br>
|
||||
<?=htmlentities($note->text)?><?php
|
||||
}
|
||||
} else { ?>
|
||||
<p class="text-center text-muted">There are no prior notes for this request<?php
|
||||
}
|
||||
Layout::bareFoot();
|
22
src/public/components/request/snooze.php
Normal file
22
src/public/components/request/snooze.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
use MyPrayerJournal\{Auth, Layout, Request};
|
||||
|
||||
require '../../../start.php';
|
||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||
|
||||
Auth::requireUser(false);
|
||||
|
||||
$req = Request::byId($_GET['id']);
|
||||
if (!$req) not_found();
|
||||
|
||||
Layout::bareHead(); ?>
|
||||
<form hx-patch="/request/snooze?id=<?=$req->id?>" hx-target=#journalItems hx-swap=outerHTML>
|
||||
<div class="form-floating pb-3">
|
||||
<input type=date id=until name=until class=form-control
|
||||
min="<?=(new DateTimeImmutable('now'))->format('Y-m-d')?>" required>
|
||||
<label for=until>Until</label>
|
||||
</div>
|
||||
<p class="text-end mb-0"><button type=submit class="btn btn-primary">Snooze</button>
|
||||
</form><?php
|
||||
Layout::bareFoot();
|
18
src/public/request/note.php
Normal file
18
src/public/request/note.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
use MyPrayerJournal\{Auth, Note, Request, Table};
|
||||
use BitBadger\PDODocument\Patch;
|
||||
|
||||
require '../../start.php';
|
||||
if ($_SERVER['REQUEST_METHOD'] <> '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);
|
|
@ -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']);
|
||||
}
|
||||
|
|
21
src/public/request/snooze.php
Normal file
21
src/public/request/snooze.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
use MyPrayerJournal\{Auth, Request, Table, UI};
|
||||
use BitBadger\PDODocument\Patch;
|
||||
|
||||
require '../../start.php';
|
||||
if ($_SERVER['REQUEST_METHOD'] <> '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();
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user