Commit Graph

115 Commits

Author SHA1 Message Date
jordi fita mas 3161d54aba Add expense’s file input to new and edit forms
I had to change MethodOverrider to check whether the form is encoded as
multipart/form-data or i would not be able to get the method field from
forms with files.

For now i add the file manually, i.e., outside add_expense and
edit_expense PL/pgSQL functions, because it was faster for me, but i
will probably add an attach_to_expense function, or something like that,
to avoid having the whole ON CONFLICT logic inside Golang—this belongs
to the database.
2023-05-14 18:46:16 +02:00
jordi fita mas f639602170 Add contact’s inline form for tags 2023-05-12 11:32:39 +02:00
jordi fita mas df37583cc6 Add the actions menu to products and contacts 2023-05-11 23:32:21 +02:00
jordi fita mas 970340277d Add the contact filter form 2023-05-10 18:56:07 +02:00
jordi fita mas 856ddde00e Add the inline form for product tags 2023-05-09 12:18:31 +02:00
jordi fita mas f0f98e200c Add inline tag form for expenses 2023-05-08 12:58:54 +02:00
jordi fita mas 664088c748 Add filter form to expenses 2023-05-07 22:49:52 +02:00
jordi fita mas 1415c3ef10 Moved the link to edit expense from the invoicer’s name to a menu
This menu will also have options like delete, and whatever we like to do
to expenses, like invoices do.
2023-05-06 11:08:21 +02:00
jordi fita mas 55d650bd62 Add expenses’ index and add form 2023-05-03 12:46:25 +02:00
jordi fita mas 19bcfc29e8 Update HTMx version to 1.9.2
I was hit with a couple of bugs: hx-on not properly de-initializing,
with a workaround in 43fffb68 and properly fixed with version 1.9.2;
and elements with naked hx-trigger did not work with hx-boost, as i do
for the tag inline form, fixed in 1.9.1.

The other bug fixed in 1.9.1, play well with other libraries that also
use the window.onpopstate, did not affect me, i believe.
2023-04-29 16:20:13 +02:00
jordi fita mas 86ccbbe830 Add keyboard controls for product search
They are almost the same as for the multiselect, except that it “clicks”
the option to “select” it, as this will trigger the replacement of the
<fieldset> with the whole product.
2023-04-28 00:06:48 +02:00
jordi fita mas b10f0dcb3f Update HTMx to version 1.9.0
I mainly did it for the new hx-on attribute, to click the update
button on recompute, but it does not seem to work as i think it does.
Anyway, there are some fixed bugs.

From the release announcement[0]:

## New Features

  * Support for view transitions, based on the experimental View
    Transitions API currently available in Chrome 111+ and coming to
    other browsers soon.
  * Support for “naked” hx-trigger attributes, where an hx-trigger is
    present on an element that does not have an hx-get, etc. defined on
    it. Instead, it will trigger the new htmx:triggered event, which
    can be responded to via your preferred scripting solution.
  * Support for generalized inline event handling via the new hx-on
    attribute, which addresses the shortcoming of limited onevent
    properties attributes in HTML.

## Improvements & Bug fixes

  * A memory leak fix by @croxton

[0]: https://htmx.org/posts/2023-04-11-htmx-1-9-0-is-released/
2023-04-26 14:30:40 +02:00
jordi fita mas a06bc3df58 Use slugs too to select invoice products without JavaScript
The product search returns a list of products using its slug as the
“external key”, because i do not want people seeing the id in links,
and the search product list is just a different rendering of the product
index table.

However, now i had two almost identical select queries for product,
one using the product_id and the other its slug, the former intended for
the form to select products using checkboxes—the one non-JavaScript
users see—and the latter for the product search.

Using the slug in both forms i can now simplify the code and have a
single query.
2023-04-26 13:50:02 +02:00
jordi fita mas f2a0cd7d94 Move back most dialogs to regular (but still boosted) pages
With Oriol agreed that adding or editing invoices, products, and
contacts is not just a “user interruption” but the main flow of the
program, and, as such, it is not correct to use dialogs for these.

More importantly, it was harder to concentrate, specially with the more
involved form of invoices, because of all the “noise” behind the dialog.
2023-04-25 15:28:55 +02:00
jordi fita mas 7d895fe5f9 Use HTMx to add product rows “inline” in the invoice form
I actually find more comfortable to select the product from the list
presented up until now, but this is mostly because i have very few
products and the list is not too long, so the idea is that with
JavaScript we will dynamically add an empty product row to the invoice
and then use the name field to search the product by name.

I have the feeling that i am doing something wrong because i ended up
with a lot of HTMx attribute for what i feel is not that much work,
but for now it will work.

I have added the `Is` field to `InputField` in order to include the `id`
attribute to the HTML element, because the HTMLAttributes are attached
to the `input`, not the `div`, and i felt like this one should also be
a custom element based on div, like all the others.

These is not yet any keyboard control to select the search results.

I am not happy with having the search of products in a different URL
than the index, specially since they use the exact same SQL query and
ProductFilter struct, but i did not know how else ask for a different
representation without resorting to the more complicated MIME types.
2023-04-24 02:00:38 +02:00
jordi fita mas 2ced61d304 Add the product filter form
I need a way to search products by name in the invoice form, when the
user adds or changes a product.  Since this is something that i have to
add too to the product list, i added it now so the function will already
be ready.
2023-04-23 03:20:01 +02:00
jordi fita mas c2f6d299b4 Refactor the invoice product form template
I was using the exact same form from edit and new pages of invoice,
which is not too bad considering it won’t change very often, but i now
want to be able to add new empty product lines with the add product
button, and i will need to have a template for that form, which would
mean a third copy.
2023-04-20 15:37:22 +02:00
jordi fita mas 149557e42e “Integrate” the tags’ condition into the input field
We have reconsidered the toggle thing and instead moved the selection
into a little menu on top of the input, like the input’s label does à
la Material Design.

I just moved the checkboxes into a new details, that works as a menu,
but i had to add the type="search" to the existing input in the tags
field, or the CSS would style the checkboxes as well.

I do not do anything when the checkbox selection changes because that
already triggers a POST to the server that returns the new HTML with
the checkbox changed, and the JavaScript only has to retrieve that new
structure, exactly as it does in the initial rendering.

Since we want to add a little description to the options, i no longer
can use the same SelectOption in ToggleField, even though i could have
reused the Group element, but that felt wrong.
2023-04-16 19:01:11 +02:00
jordi fita mas b30aeb5d49 Reformat form.gohtml with IntelliJ 2023-04-15 20:43:20 +02:00
jordi fita mas 5e01965d7e Replace use of <select> for tags “and” and “or” with checkboxes
I realized that using a select for just two, short, options is overkill:
the select and its options use a lot more real state than the two
radios, which can have tooltips (not yet, though).

Since i am going to replace this field with a custom element that has
a toggle-like aspect, i already added the is="numerus-toggle" attribute
and use it for stying the non-JavaScript field.
2023-04-15 04:05:59 +02:00
jordi fita mas 8f7933ffe2 Allow to select AND or OR for tags filter
This is because Oriol thinks that there may be cases where you want to
search invoices and such that have any of the selected labels, not all
of them, so we agreed on adding an option to choose.

The idea is that it will be a toggle, but this requires JavaScript and
this commit adds it as a dropdown as a first non-JavaScript step.
2023-04-14 02:40:48 +02:00
jordi fita mas d20573aa99 Allow editing invoice tags inline from the index table
I use the same pattern as HTMx’s “Click to Edit” example[0], except that
my edit form is triggered by submit and by focus out of the tags input.

I could not, however, use the standard focus out event because it would
also trigger when removing a tag with the mouse, as for a moment the
remove button has the focus and the search input dispatches a bubbling
focusout.  I had to resort to a custom event for that, but i am not
happy with it.

The autofocus attribute seems to do nothing in this case, so i need to
manually change the focus to the new input with JavaScript.  However,
this means that i can not use the same input ID for all the forms
because getElementById would always return the first in document order,
changing the focus to that same element and automatically submit the
form due to focus out.  That’s why in this form i append the invoice’s
slug to the input’s ID.

Finally, this is the first time i am using an HTMx-only solution and i
needed a way to return back just the HTML for the <td>, without <title>,
breadcrumbs, or <dialog>.  In principle, the template would be the
“layout”, but then i would need to modify everything to check whether
the template file is empty, or something to that effect, so instead i
created a “standalone” template for these cases.

[0]: https://htmx.org/examples/click-to-edit/
2023-04-11 10:46:27 +02:00
jordi fita mas 233e7723c3 Use HX-Location instead of HX-Refresh when editing invoices
This makes reload only the <main> portion of the page, instead of the
whole thing, which to me looks faster; haven’t really measured it.

Like with duplicate, i had to add the location query argument when
inside the view page in order to return back to the same page, not the
index.
2023-04-05 10:29:03 +02:00
jordi fita mas dbfa58699c Show the duplicate invoice form in a dialog
Had to add a new hidden field to the form to know whether, when the
request is HTMx-triggered, to refresh the page, as i do when duplicating
from the index, or redirect the client to the new invoice’s view page,
but only if i was duplicating from that same page, not the index.

Since i now have to target main when redirecting to the view page, so
i had to add a location structure with the required json fields and all
that, when “refreshing” i actually tell HTMx to open the index page
again, which seems faster, now that i am used to boosted links.
2023-04-04 14:39:55 +02:00
jordi fita mas b6668e72ef Trigger filter form on change and search, as well as submit as before
Changed the invoice number field’s type to search to add the delete icon
on Chromium.  Firefox does not add that icon, but i do not care; it is
still better that type="text".

Had to emit the change event to the numerus-tag field, otherwise the
form would not detect the change.

I also can not use keyup as a trigger because the changed modifier can
not be used in the <form>, as nothing ever changes, i do not know how to
trigger the form from children (i.e., data-hx-trigger on the <input>
does nothing), and i can not trigger for just any keyup, or i would
make the request even if they only moved the cursor with the arrow keys,
which is very confusing as Firefox resets the position (this may be due
the fact that i reload the whole <main>, but still).
2023-04-03 12:45:15 +02:00
jordi fita mas ecfd840f14 Boost all breadcbrumb links 2023-04-02 16:10:13 +02:00
jordi fita mas 57f29fc883 Remove <fieldset> around hidden products when adding to invoice 2023-04-01 15:58:50 +02:00
jordi fita mas c453715ee1 Remove the number field from new invoice form
Initially, this field was meant to be left almost always blank, except
for when we deleted invoiced and had to “replace” its number with a new
invoice; using the automatic numbering in this cas would not “fill in”
the missing number in the sequence.

However, we decide to not allow removing invoicer not edit their
numbers, therefore, if everything goes as planned, there should not be
any gap in the sequence, and that field is rendered useless.

Oriol suggested making it a read-only field, both for new and edit
forms, but i do not think it makes sense to have a field if you can not
edit it at all, specially in the new invoice dialog, where it would
always be blank.  In the edit form we already show the number in the
title and breadcrumbs, thus no need for the read-only field as
reference.

I still keep a Number member to the form struct, but is now a string
(kind of “a read-only field”, in a way) and just to be written in the
title or breadcrumbs.  I did not like the idea of adding a new SQL
query just for that value.
2023-04-01 15:57:56 +02:00
jordi fita mas 5717a5b9ed Put new invoice and edit invoice forms into a dialog
In this case i have to use the same id for the dialog content in all
pages because, for now, there are a couple of forms that need to replace
it on submit—the new/edit form and the product selection form.

Unfortunately, HTMx does not have support for `formaction` attribute at
this point, so i had to use the workaround described in [0].

[0] https://github.com/bigskysoftware/htmx/issues/623
2023-03-31 13:01:26 +02:00
jordi fita mas b7881c505f Add filters form for invoices
Instead of using links in the invoice tags, that we will replace with a
“click-to-edit field”, with Oriol agreed to add a form with filters that
includes not only the tags but also dates, customer, status, and the
invoice number.

This means i now need dynamic SQL, and i do not think this belongs to
the database (i.e., no PL/pgSQL function for that).  I have looked at
query builder libraries for Golang, and did not find anything that
suited me: either they wanted to manage not only the SQL query but also
all structs, or they managed to confuse Goland’s SQL analyzer.

For now, at least, i am using a very simple approach with arrays, that
still confuses Goland’s analyzer, but just in a very specific part,
which i find tolerable—not that their analyzer is that great to begin
with, but that’s a story for another day.
2023-03-29 16:16:31 +02:00
jordi fita mas 3f092cd0d0 Use .String in hidden-field template, so that it works for TagsField too 2023-03-28 10:01:41 +02:00
jordi fita mas a5dc434aa2 Boost the links in the invoice table 2023-03-28 09:57:48 +02:00
jordi fita mas 2417b4ebd2 Remove the link to edit contact from the invoice table
We agreed with Oriol that this link would only serve to confuse people.
I initially added the link because i thought it was a shame to have to
navigate to the contact section to look or change the info of a customer
that you have an invoice for in front of you.  However, it makes little
sense to be able to edit the contact from both sections, and we do not
have a “view page” for contacts to link to, thus the removal.
2023-03-28 09:50:19 +02:00
jordi fita mas 47c23fc4cc Boost the products’ section links and forms
Had to add the editProductPage because now i need to know the slug in
order to build the form’s action link.  I also added the `ProductName`
field because it was less awkward than using `.Form.Name` everywhere.
2023-03-27 09:44:04 +02:00
jordi fita mas 2086f68bd8 Boost contact’s tag links to update <main> with the filtered list 2023-03-27 09:40:24 +02:00
jordi fita mas b0db8df732 Add the <title> element for the “main” boosted pages
Otherwise, the page title does not change and becomes useless.
2023-03-26 13:57:24 +02:00
jordi fita mas a1f70ff654 Add tags for products too
With Oriol we agreed that products should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices and
contacts.

Had to add the `company_id` attribute in the `using` clause for `tag` in
`MustFillFromDatabase`, even though it’s not strictly necessary, because
then PostgreSQL does not know which `company_id` attribute use for the
join with `company`—the one from `product` or the one from `tag`.
2023-03-26 13:51:57 +02:00
jordi fita mas 4131602fa3 Add tags for contacts too
With Oriol we agreed that contacts should have tags, too, and that the
“tag pool”, as it were, should be shared with the one for invoices (and
all future tags we might add).

I added the contact_tag relation and tag_contact function, just like
with invoices, and then realized that the SQL queries that Go had to
execute were becoming “complex” enough: i had to get not only the slug,
but the contact id to call tag_contact, and all inside a transaction.

Therefore, i opted to create the add_contact and edit_contact functions,
that mirror those for invoice and products, so now each “major” section
has these functions.  They also simplified a bit the handling of the
VATIN and phone numbers, because it is now encapsuled inside the
PL/pgSQL function and Go does not know how to assemble the parts.
2023-03-26 01:32:53 +01:00
jordi fita mas 7e8ec539ff Add a SnackBar to show HTMx errors
We do not have any design yet for errors and other notifications, so i
followed material design, for now, since we already kind of use their
input fields design.

This time i decided to use AlpineJS because there is not that much HTML
code, and the transitioning is way easier to do in AlpineJS than it
would be with plain JavaScript—not to mention the bugs i would
introduce.
2023-03-25 01:56:26 +01:00
jordi fita mas 41ce5af2ed Boost the main navigation links with HTMx
I am not sure if, at the end, all pages that now use
mustRenderAppTemplate will be replaced with mustRenderMainTemplate,
but for now i keep them separate to know which routes are already
“boosted”.
2023-03-23 10:55:02 +01:00
jordi fita mas 6e081a1846 Put the edit contact form into a dialog with HTMx
Had to change the data context for that template to include the Slug,
so that the <form> element can set the correct `action` instead of
using the current URI, as it is no longer “correct” (form-wise) when
using HTMx.
2023-03-23 10:46:14 +01:00
jordi fita mas b07fe6cfa2 Show the add contact form in a modal dialog 2023-03-22 14:59:54 +01:00
jordi fita mas b1e3afc48b Show the tax details form in a dialog using HTMx 2023-03-21 11:58:54 +01:00
jordi fita mas 9e757cb9f4 Show the profile form in a dialog using HTMx
Had to split the actual page content and the breadcrumbs because they
do not belong in a dialog.  However, i had to change all templates to
do that.
2023-03-20 13:09:52 +01:00
jordi fita mas 82eb8a2733 Start the tag input custom element
This is more or less the same as a multiselect, except that now it
adds a list of string element that you write into the search element.

It is supposed to fetch a list of tag suggestions from the server, but i
have not implemented it yet.
2023-03-19 23:11:40 +01:00
jordi fita mas 2dde25c862 Reimplement the multiselect as a custom element
What i really set off on was to refactor the multiselect’s x-data
context to a separate JavaScript file.

I did not see the need at first, thinking that it would not matter
because it was used only in a template and i was not duplicating the
code in my files.  However, i then realized that having the context
in the template means the visitor has to download it each and every time
it accesses a form with a multiselect, even if nothing changed, and,
worse, it would download it multiple times if there were many
multiselect controls.

It makes more sense to put all that into a file that the browser would
only download and parse once, if the proper caching is set.

Once i realized that, it was a shame that AlpineJS has no way to do
the same for the HTML structure[0], for the exact same reasons: not
wanting to download many times the same extra <template> and other
markup required to build the control for JavaScript users.  And then i
remembered that this is supposed to be custom element’s main selling
point.

At first i tried to create a shadow DOW to replace the <select> with
the same <div> and <ul> that i used with Alpine, but it turns out that
<select> is not one of the allowed elements that can have a shadow root
attached[0].

Therefore, i changed the custom element to extend the <div> for the
<select> and <label> instead—the same element that had the x-init
context—, but i would have to define or include all the styles inside
the shadow DOM, and bring the lang attribute, for it to look like it
did before.   Out with the shadow DOM, and modify the <div>’s contents
instead.

At this point the code was so far removed from the declarative way that
AlpineJS promotes that i did not see much value on using it, except for
its reactivity.   But, given that this is such a small component, at the
end decided to write it all in plain JavaScript.

It is more code, at least looking only at the code i had to write, but
i love how i only have to add an is="numerus-multiselect" attribute to
HTML for it to work.

[0]: https://github.com/alpinejs/alpine/discussions/1205
[1]: https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow
2023-03-17 14:55:12 +01:00
jordi fita mas 1c9fe14ab9 Improve multiselect’s usability with keyboard handling
Had to replace the tags <ul> with a div with an input, so that the
browser can focus the keywoard there.  For now i do not have a
focus-within CSS rule because we do no yet have a style for focus
highlight.

I have replaced the template for-loop to fill the options with the
JavaScript equivalent for two reasons.  The first is that GoLand is very
stupid and can not handle that templating code inside the JavaScript
function and complains of non-existing problemes all the time.

The second is that, taking advantage of the input, i now have filtering
of options and have to remove accents from the label and convert it to
lowercase into a separate property just for that.  I could do that with
a Go function, but it is something that i also have to do for the
input’s value when it changes, therefore i am forced to use JavaScript
and, if i am already using it for one string, it makes no sense to have
duplicate functionality in Go code.

The control still has missing aria attributes, and the list of options
is not yet navigable with the keyboard.
2023-03-16 12:52:44 +01:00
jordi fita mas f93d557aa9 Move the multiselect “component” to the select-field template
I had in the product edit page only because it was easier to test there
while i was developing it, but it is something that should be done for
all select[multiple], of course.

I removed the whole x-cloak thing because i am not sure what would
happen if i do something wrong and Alpine can not initialize the
multiselect; probably show nothing to the user.  Now it shows the
native select a fraction of a second, but if i fuck it up at least the
user can still use the app.
2023-03-15 11:44:18 +01:00
jordi fita mas 5702f0d198 Start “improving” the user interface with AlpineJS: tax selector
It is a shitty component, but i do not have more time today to do it
better.
2023-03-14 18:07:38 +01:00
jordi fita mas 8efae0485e Add the edit form for invoices
I had to change the way /invoices/new and /invoices/batch are handled,
because httprouter was not happy with the new POST /invoices/:slug/edit
route, claiming that /invoices/:slug conflicts with the previously
existing routes.

I also could not make it work with the PATCH method, even though i
correctly added the patchMethod override function, therefore editing
invoices is also weird because i have to take into account the “quick”
invoice status change.

I use the same form for both new and edit invoices, because the only
changes are that we can not edit the invoice date and number, by
Oriol’s design, but must be able to change the status; very similar
forms.
2023-03-13 15:00:35 +01:00