Commit Graph

481 Commits

Author SHA1 Message Date
jordi fita mas 2ba3167d10 Fix off-by-one error in TimelineView’s implicit width
QDate("2025-01-01").daysTo(QDate("2025-01-01")) returns 1, but the width
should be for two days, because the view shows the last day too.
2025-01-15 14:29:28 +01:00
jordi fita mas ed56ba543d Remove *and* delete items once no longer in results
There was a memory leak: i was not deleting the elements from the QList
once they are no longer in the results of the SQL query.
2025-01-15 13:04:35 +01:00
jordi fita mas 0e371d71dd Check QSqlQuery errors when doing a query
This is more for me than the end user, because if there is an error with
a query, there is—almost aways—nothing a user can do, since it is
probably an error in the static SQL string or the database.  However, it
is better to show an error, than to do nothing at all when there is a
failure.

According to Qt’s documentation[0], QFuture relies on exceptions for the
error handling. At first i assumed that i had to attach an onFailed
handler to QFuture in order to receive that exception and skip the then
handler.

However, i can not attach the onFailure inside Database::query, before
returning the QFuture, because the subsequent then is only executed when
there _is_ an error, never in the normal, non-exceptional, case.  I
would have to add the onFailure after then.

Nonetheless, i found out that there is no need for onFailure: since i do
not call result(), i do not get the exception, and the actual work is
performed in the then handler when no exception is raised.

[0]: https://doc.qt.io/qt-6/qfuture.html
2025-01-15 13:01:40 +01:00
jordi fita mas c795effd3d Set reservation’s rectangle color based on status 2025-01-14 22:06:32 +01:00
jordi fita mas e404727c45 Show the reservation’s holder name in timelineview
TimelineView is not a general control that can be used with any model,
thus i do not need the complexity of Qt’s QQmlDelegateModel incubation
task to initialize required properties.  I _would_ have used it if it
were available in C++, but since it is not, what i mean is that i do not
need to _reimplement_ all of it for my case: i already know what
required properties there are.
2025-01-14 21:46:02 +01:00
jordi fita mas 66e12d5019 Add TimelineMonthRow and TimelineMonthModel
These are a lot like TimelineDayRow and TimelineDayModel, and are
supposed to go in the timeline’s header too, but instead of showing the
day number, they show the month name.

I still need to know the with of each day, because the month must be as
wide as all shown days, that may not be all the days in that month, in
the first and last months, at least.

Usually i will show two or three months, so i thing is is fair to keep a
list of all the months and their days, rather than compute them each
time the name or the day count, two times per month, are requested.
2025-01-13 18:32:02 +01:00
jordi fita mas 94b5faa9d8 Add TimelineModel and use it to feed each TimelineView
I have my doubts whether this model should be QAbstractListModel-derived
or not.  At first i thought i could not be, because i need to be able to
get a “view” (i.e., a [begin, end) pair of iterators) based on the
segment of TimelineView that becomes exposed due to scroll.  However,
QAbstractItemModel::match returns a QModelIndexList, and that could also
be used for the same purpose, except that there is no QDateRange….

Until i have more needs for this model, the current coupling between
TimelineModel and TimelineView is OK, as i do not expect TimelineView to
be used for anything else.

I also renamed CalendarModel to TimelineListModel, since this is the
important part—the timeline—, and there is no “calendar” thing anywhere
in the application—at least, not what traditionally is understood as a
calendar, that is.
2025-01-09 20:08:40 +01:00
jordi fita mas c49135247a Add TimelineDayRow and set it as header of timeline list
This is the equivalent control to Qt Quick Controls’ own DayOfWeekRow,
but for the number of each day in the visible range, instead of day of
the week.

Qt Quick Controls has this component written in C++, and also has a
separate, internal, model for the days in different formats, but i had
to implement the control in QML, because QtQuickControl is private.
However, as far as i understand, it is not much more than a container
for the delegate and the model that is used as a template.
2025-01-08 09:54:13 +01:00
jordi fita mas accc627ebd Rename struct TimelineViewItem to TimelineView::Item 2025-01-07 17:46:50 +01:00
jordi fita mas 00255f67a0 Begin TimelineView control
This is supposed to be like a kind of horizontal ListView, however the
elements need to be placed according to its starting date, and must be
as long as the number of days of the stay.  As far as i know, i can not
do that with a Qt-provided ListView because it has a strict one-to-one
mapping between QModelIndex’s row and visual element’s row (i.e., the
first item is always the first row, the second item the second row,
etc.), while i to have “holes” in the rows for item that are not
continous in time.

Unfortunately, Qt Quick does not provide a C++ API, meaning that i can
not derive from QQuickListView or, rather, QQuickItemView, without using
the private API.  And that API is private for a reason; i do not want to
see myself redoing the control because they have changed who knows what.
Thus, had to base this control on QQuickItem.

I am also pretty sure i will not be able to use QAbstractItemModel for
this control, as, again, that model assumes that everything is
continuous: lists have rows next to each other, tables columns next to
each other, and trees are “just” nested tables.  But i am not certain
yet.

Meanwhile, what i really need to do is to show a delegate for each
“filled in” day, and that’s is what this controls does for now.  Since i
can not inherit from QQuickFlickable—not that it would be a great idea
here, since i need a list of these views anyway—, i have to know the
viewport’s position and width in order to have only the minimum number
of items visible.  I do like QQmlDelgateModel (that i can not reuse
without private API), and reuse the delegates when possible.
2025-01-07 12:53:27 +01:00
jordi fita mas 87f51e733e Focus on the alert text when there is an error
This is to tell screen readers that have to read the text in the
notification, or the notification pops up and orca, at least, does not
know that it needs to read it.

I also had to change the role of SelectableLabel’s TextArea to
StaticText or orca would only read the first line. Not sure if that is
an error, because TextArea does indeed behave like a StaticText (i.e., a
label) in this case, but TextArea has Accessible.multiLine set to true.
2024-12-30 10:31:14 +01:00
jordi fita mas f77d01134b Include “alert” in the text of ErrorNotification’s close button
Otherwise screen readers just say “close”, and it is more difficult to
know what this button is going to close.
2024-12-30 10:26:50 +01:00
jordi fita mas 0416992a57 Move the Form role from LoginPage to its ColumnLayout
Is the ColumnLayout that functions as a form, not the whole page.
2024-12-30 10:09:14 +01:00
jordi fita mas 9f181047ac Bind MnemonicLabel’s “plain” label to buddy’s Accessible.name
This is to give a default name to form controls by default, like <label>
does to <input> and <textarea> in HTML.

Had to add a “plain” version of the label, with neither the <u> tag used
in rich text or the ‘&’ in the “regular” label, otherwise orca would
spell out these symbols.
2024-12-30 09:50:36 +01:00
jordi fita mas f4affd9241 Add automatic login feature
I am using .pg_service.conf to define all connection parameters, and
.pgpass for the password, thus i never have to input anything to the
login form, and want to skip it.

However, just in case someday i need to set the connection up
differently, i only try to log in automatically on startup, and can go
back to the login page by logging out, as usual.
2024-12-27 19:28:31 +01:00
jordi fita mas 5f1e2b3b2d Remove postgresql-iban from guix.scm
It is actually not used anywhere in the application, and i do not plan
to start using it any time soon.
2024-12-24 04:04:39 +01:00
jordi fita mas d0e2659c30 List the labels of all lodgings in QML
To query the database, i have to run the query inside the same thread
where the database was created, which means that Database should be a
singleton not only within QML, but also in C++, and has to be the _same_
singleton in both worlds.

Although i expose an object that i have created, i followed the same
section titled “Exposing an existing object as a singleton” from Qt’s
documentation[0].  The only difference is that i do not have to declare
the element as a foreign type, because it is a bona fide QObject.

[0]: https://doc.qt.io/qt-6/qml-singleton.html#exposing-an-existing-
object-as-a-singleton
2024-12-24 03:46:20 +01:00
jordi fita mas 7eeccbb033 Use database “advanced options” and persist them
I only want to store these options if the connection to the database is
established, to avoid saving incorrect values by error. I, then, did not
use property alias, as the documentation reccomends, and instead have a
function to save the parameters when the application sees the database
open.

I have to use the function because a Connections inside LoginPage gets
called _after_ the Connections in Main, meaning that i would get deleted
before it has the chance to save the settings.

Now i can’t assume connect options will include search_path, as it is
unreasonable to request people to know the internal schemas of the
application.
2024-12-23 20:54:04 +01:00
jordi fita mas dd2beba676 Change port’s Label to a CheckBox
For host, database name, and options i can leave them blank to use the
default value, but for the port i can’t with a SpinBox, since it _must_
have a value.

Qt’s driver uses -1 to mean “default port”, but i can not use the same
approach with the user interface, because there is no port -1, and makes
no sense to allow that value, specially since i then i have to allow 0
as well, which is a reserved port.

I also can not leave the SpinBox with PostgreSQL’s default port, 5432,
because that is slightly different than telling libpq to use the
“default”: if someone uses a ‘service=’ option, it would use the port in
the SpinBox rather than the one in ‘pg_service.conf’, if any.

My solution is to use a checkbox to tell the application “i wan to use a
port number”, and (will) only pass the port number if it is checked.  On
the other hand, now i do not have a keyboard shortcut to focus on the
port number field.
2024-12-23 00:09:44 +01:00
jordi fita mas 45c12ed2bb Stop ErrorNotification’s time on close
Otherwise, if there is another notification before the timer runs out,
it will close the new notification sooner than expected.
2024-12-22 04:20:04 +01:00
jordi fita mas fe7a9a78c6 Add Expander control to show “advanced controls” on login
I want to allow people to set all options of QSqlDatabase, that way i do
not force the ‘service=camper’ thing, and everyone can use whatever
parameters they see fit.

I plan to store these other options as Settings, because usualy this
should only be done once at setup, and then we only need to input the
username and login to enter.  For that same reason, i do not want to
show them all the time; only if the user wants to change anything.

As far as i know, neither QtWidgets nor Qt Quick Controls have anything
that works as Gtk’s Expander, so i had to create that component.  HTML
calls it <details>, Apple “disclosure control”[0], and both Microsoft
and Gtk “expander”[1, 2]. I’ve choosen Gtk name, but macOS looks.

[0]: https://developer.apple.com/design/human-interface-guidelines/
disclosure-controls
[1]: https://learn.microsoft.com/en-us/windows/apps/design/controls/
expander
[2]: https://docs.gtk.org/gtk4/class.Expander.html
2024-12-22 04:18:56 +01:00
jordi fita mas 392d993c8a Add MnemonicLabel and MnemonicAction components
Looks like i am going to do many Labels and Actions that require
mnemonics, and i do not want to setup the properties everytime.
2024-12-21 05:13:46 +01:00
jordi fita mas 268f4329c0 Add Mnemonic attached property
This is to accept Alt+(whatever has & in front in the label) for labels,
buttons, actions, and whatever requires a nmenonic.

I created an attached property because it is kind of similar to QML’s
Keys property.
2024-12-21 04:56:09 +01:00
jordi fita mas ca882f992b Do not keep a copy of database’s connection name
Since i only plan to use a single connection, i can use QtSql’s default
connection name.

According to the documentation, i have to make sure that no query or
database object is open when i remove the database.  I now use C++
scopes for that, but i need to have a QString declared outside of it to
get the default connection’s name.
2024-12-20 21:29:46 +01:00
jordi fita mas 5cc7192387 Move the logout button to the page’s toolbar 2024-12-19 22:29:48 +01:00
jordi fita mas 7343174056 Add login and reservations page in a StackView 2024-12-19 01:53:12 +01:00
jordi fita mas 4766d01ead Add ComponentBehavior QML pragma 2024-12-19 01:05:12 +01:00
jordi fita mas 3193e4469b Manually include moc_database.cpp
It consistently makes the build take a couple seconds(!) less than
leaving the compilation of these files to CMake; i have no idea why.
2024-12-16 19:22:39 +01:00
jordi fita mas 07705b012a Add a very ugly login page to test database connection
I want to perform all SQL queries in a thread, to avoid freezing the UI,
that sometimes might happen when there is a lot of data to fetch; should
not happen very often, though.

Neither libpq nor Qt SQL allow queries on the same connection from
differents threads, and, in Qt SQL, all queries must be performed from
the same thread where the connection was established.  In Qt5 i had to
either create a connection per thread, or use a QThread-derived object
to hold the connection and use signals and slots to pass query and
response data between the UI and database threads; it was usable but not
pretty.

With Qt6 and Concurrent’s QThreadPool now i can use QFutures instead,
that are not as cumbersome as with Qt5, because i no longer need
QFutureWatcher.  I still have the problem that all queries must be done
from within the same thread, and QThreadPool uses an arbitrary thread.
The solution is to create a “pool” with a single, non-expirable thread,
and call all Concurrent::run onto that pool.

I have to test it properly, and first need to open the database to test
whether that, at least, works. I added a simple “login page” for that,
and to make a first attempt to error messages; i use a control that is
like Kirigami’s InlineMessage for now, but i am not sure.

I also do not know how i will configure database’s connection details. I
usually make use of pg_service.conf, because then the application only
need to know its service name, but i am not sure whether other people
would find it as comfortable as i do.
2024-12-16 12:59:19 +01:00
jordi fita mas 49b2c035ad Add skeleton for a QML application 2024-12-14 01:19:20 +01:00
jordi fita mas 7c6bac1986 Add season dates for “next year”
This is to test the booking form’s behavior when there is a gap between
bookable dates, especially around New Year’s.
2024-11-20 19:43:59 +01:00
jordi fita mas 5b89c97b00 Add operating_dates to campsite type table
We forgot that different accommodation types are not always operating on
the whole season calendar, thus we need a specific date for each type.

Someday i will add the field in the administration panel, but for now i
will have to add them by hand, as people are starting to book plots on
dates that are not operating.
2024-07-15 23:41:47 +02:00
jordi fita mas d8524c347e Fix French typo 2024-07-15 23:12:12 +02:00
jordi fita mas c54e147173 Change “ACSI” to “ACSI / ANWB”
Apparently, ANWB is a camping card similar to ACSI from the Netherlands,
and both cards have the exact same discounts.
2024-05-13 10:40:21 +02:00
jordi fita mas 92c0cb4de0 Add filters and pagination to login attempts 2024-05-03 20:45:14 +02:00
jordi fita mas b4ccdeff2f Add filters and pagination to payments 2024-05-03 20:09:07 +02:00
jordi fita mas 48c1529e6c Add pagination to invoices
I’ve removed the total amount because it is very difficult to get it
with pagination, and customer never saw it (it was from Numerus), thus
they won’t miss it—i hope.
2024-05-03 19:13:49 +02:00
jordi fita mas 674cdff87b Add a new Cursor form type
To hold the common logic of detecting pagination, forming the key, and
splitting its values later on.

I can take advantage that a form with action="get" already adds its
fields to the query string to have a common template for pagination. The
only problem is that i have different column spans for different tables,
therefore had to add a colspan to the struct.
2024-05-03 19:00:02 +02:00
jordi fita mas 3a7d454826 Add filtering and pagination to customer section 2024-05-03 18:10:16 +02:00
jordi fita mas 50548c29ab Move booking filter form struct into a separate file 2024-05-03 18:09:55 +02:00
jordi fita mas e5253f9adb Allow to cancel bookings 2024-05-03 17:21:20 +02:00
oriol carbonell pujolàs e425b88477 Fix spacing of booking forms 2024-05-03 12:55:43 +02:00
jordi fita mas c84b58a0d5 Add the section of prebookings
It is just the index of bookings in the created state, but we thought it
would make easier to understand the difference between a booking from
a customer not yet confirmed, from a booking confirmed or created by the
staff.
2024-05-03 01:01:01 +02:00
jordi fita mas 5d4fe15e88 Add filtering and pagination for bookings 2024-05-03 00:28:48 +02:00
jordi fita mas b2ee4dfea3 Add marshal_payment and unmarshal_booking functions
The idea is that we will marshal the payment, send it to the campsite’s
instance by email, and then unmarshal it as a booking, that way we can
have a one way replication from the internal to the public instance with
a way back to send the payments.

For testing purposes, i just create the booking in the same instance.

Had to change the booking relation’s permissions to allow insert from
a guest, much like for payments, because the notification from Redsys
comes as a guest connection.  I need this even with all the
marshal/unmarshal shenanigans because not everyone will have an internal
instance, thus need to allow bookings from guest connections.
2024-04-29 20:59:22 +02:00
jordi fita mas 7edf3a3ed1 Pre-fill all guests with the holder’s address
Most will be families living at the same address.  And, if they are not,
it is far easier to replace the incorrect address with the actual,
rather than write the same address to all family members under the same
household.
2024-04-29 17:49:38 +02:00
jordi fita mas f71ad2cc65 Redirect to invoice after create, not booking 2024-04-28 22:50:56 +02:00
jordi fita mas 90d8247c0f Fix null problems 2024-04-28 22:49:04 +02:00
jordi fita mas 2299f2325e Allow to create the customer to invoice “in flight”
That way i can get the data from the booking or the actual customer.
2024-04-28 22:36:21 +02:00
jordi fita mas ff9f33dfba Add invoicing of bookings
It is a bit user-hostile because you have to create a new customer prior
to create the invoice, but that’s what it is for now.
2024-04-28 21:56:51 +02:00