Commit Graph

10 Commits (a475f58defac27f4dff1e0093ec2776b26c0d96c)

Author SHA1 Message Date
Dustin a475f58def js: Factor out getResponseError utility function
This function factors out the logic of extracting an error message from
a `Response` object into a reusable utility function.  This will allow
other pages to use the same logic without duplicating it.
2025-03-10 18:22:32 -05:00
Dustin e2b14ecf10 receipt-form: Omit empty uploaded file
If there is no file to be uploaded, then we must not send the value of
the `photo` file input.  Doing so causes two files to be uploaded, the
first one being a zero-byte file with no name, and the second one being
the one we captured from the camera.  The server only uses the first
uploaded file if there are multiple, so the correct file is never used.
2025-03-09 21:04:15 -05:00
Dustin da9d336817 Store receipts in the database
Rethinking the workflow again.  Requiring the transaction to be present
in Firefly already will be problematic for two very important cases:

* Gas station purchase never show up in Firefly automatically
* HSA purchase show up hours or days later and have no information
  besides the amount

These are arguably the most important cases, since they are the ones
that really need receipts in order to keep the transaction register
correct.  Thus, we need a different way to handle these types of
transactions.

Really, what I need is a way to associate transaction data with an image
I really liked the original idea of storing receipts in Paperless, but
that ended up not working out because the OCR failed miserably and thus
made it impossible to search, so finding a receipt meant looking at each
image individually.  I think, therefore, the best solution is to store
the images along with manually-entered data.

To implement this new functionality, I am using `sqlx`, a SQL toolkit
for Rust.  It's not exactly an ORM, nor does it have a dynamic query
builder like SQLAlchemy, but it does have compile-time checking of
query strings and can produce type-safe query results.  Rocket has
support for managing its connection pools as part of the server state,
so that simplifies usage quite a bit.

On the front-end, I factored out the camera image capture into an HTML
custom element, `camera-input`.  I did not update the original form to
use it, since I imagine that workflow will actually go away entirely.
2025-03-09 19:55:08 -05:00
Dustin 15227125b9 ui: Request higher video resolution from camera
Apparently, you get 640x480 unless you ask for more?  Need to constrain
the viewer size, though, otherwise it massively overflows the page and
makes it impossible to see what you're taking a picture of.
2025-03-08 20:41:40 -06:00
Dustin 3b586413ff ui: Remove photo cropper element
It turns out the cropper is _way_ too hard to use on mobile.  The
cropper handles are much too small for a touch screen.  Maybe I can
figure out how to improve the UX eventually, but for now, Tabitha
requested that we remove that functionality.
2025-03-08 20:20:29 -06:00
Dustin 9cef220f90 ui: Persist success notification, redirect to list
Tabitha requested that the application returns to the transaction list
after the form was submitted successfully.  Before the navigation
starts, we still want to see the info toast, though.  Since the
navigation may take a few seconds, we keep the toast open indefinitely.
2025-03-08 20:18:37 -06:00
Dustin 69b511613f ui: Disable submit button while cropping
It's not immediately clear that submitting the form while the cropper is
active will not include the image _at all_.  To make this evident, we
disable the submit button until the crop is complete.
2025-03-08 20:15:41 -06:00
Dustin 2050ebf088 ui: Move camera action buttons below image viewer
On mobile, the screen is too narrow to put the action buttons to the
right of the image viewer.
2025-03-08 20:12:20 -06:00
Dustin 2d7ff417fc ui: Select environment camera if possible
Apparently, Firefox/Android defaults to using the front camera, and
does not provide any native UI for selecting a different one.  We can
request the back camera by indiciting that the "environment" camera is
"ideal."
2025-03-08 20:10:50 -06:00
Dustin 837caecc3a js: Initial UI implementation
I've implemented the UI using TypeScript and Shoelace.  I originally
started with Pico CSS, but I didn't really like its visuals.  Since
capturing photos using the camera requires JavaScript, and that's
basically the entire point of this application, Shoelace's JavaScript
dependency (for WebComponents), is acceptable.

The photo capture uses the Media Capture Web API, which exposes the
camera directly as a video stream.  We capture a frame from this stream
and save it in a canvas, which we then pass to Cropper.js to let the
user select only the relevant portion of the picture containing the
receipt itself.
2025-03-08 11:11:42 -06:00