DanielOaks / codingtest-birdhouse-admin

Admin app for an innovative new Smart Birdhouse initiative.
https://birdhouse.danieloaks.net/
0 stars 0 forks source link

BirdHouses Admin Panel

This dashboard shows the status of all birdhouses using the innovative Smart BirdHouse technology.

We've got sections about the Project Design (nice descriptions of how this is organised) and the Project Log (stream-of-consciousness thoughts while developing this).

Environment variables

You can configure the dashboard through these environment variables:

Docker quick start

You can get the dashboard up and running quickly by using Docker!

Docker

You can run the prebuilt docker image with this command:

docker run -it -e NUXT_PUBLIC_API_BASE=https://example.com -p 3000:3000 ghcr.io/danieloaks/codingtest-birdhouse-admin:release

Just replace https://example.com with the real API you want to use, then access the dashboard on port 3000. If running the command locally, at http://localhost:3000

Docker compose

You can also use the default docker compose file with this command:

docker compose up

Change the environment variable NUXT_PUBLIC_API_BASE to point towards the real API, run the command, then access the dashboard on port 3000. If running the command locally, at http://localhost:3000

Development

If you want to get started developing the app, you can use the below commands.

Setup

Make sure to install the dependencies:

asdf install  # sets up the right version of nodejs
yarn install

Development Server

Start the development server on http://localhost:3000

yarn dev

Production

Build the application for production:

yarn build

Locally preview production build:

yarn preview

Start production build:

yarn start

Check out the Nuxt deployment documentation for more information.

Project design

This is a pretty standard Nuxt application, using Pinia and Tailwind!

The main interesting elements are the store which holds our state data, and how we communicate with the API. Below we'll describe how each works.

The API

Here's how API communication is handled in the app:


flowchart LR
    subgraph Site

    JsPackage(birdhouse-js<br>package)
    Plugin(birdhouseApi<br>Nuxt plugin)
    ActivePage(Active<br>Nuxt Page)
    Store(Store)

    JsPackage --imported by--> Plugin
    Plugin --provides<br>package to--> ActivePage
    ActivePage --passes<br>package to--> Store

    end

    API[Shockbyte API]

    Store --calls--> API

Basically, We interact with the BirdHouse API via the @danieloaks/codingtest-birdhouse-js package, and that library takes a lot of inspiration from Shockbyte's existing whmcs-node package.

This library is exposed via the birdhouseApi Nuxt plugin, available client-side only.

This finally gets passed to the birdhouse store by the caller, every time a method that calls the API is performed. It's done this way because Pinia stores can't access the config on setup very easily, and this seems like a usable solution for now.

The Store

The birdhouse store holds all state data that's used by the app. This state data includes the registrations and occupancy info, and is held for the life of the app. We intentionally don't persist any state data to localStorage, as we want to grab the most up-to-date data each time the page is reloaded.

The store will call the BirdHouse API as needed to backfill data, based on calls from the pages.

Here's how the elements in this store are logically setup:

classDiagram
    class Base_Info {
        setConfig()
    }
    class Registration_Info {
        currentRegistrationListPage: Integer
        totalRegistrationPages: Integer
        registrationPageItems: Map[page -> list of bhid's]
        registrationInfo: Map[bhid -> info returned by API]
        setRegistrationPage()
        getBaseRegistrationInfo()
    }
    class Occupancy_Info {
        occupancyPageInfo: Map[bhid -> pageInfo]
        occupancyHistory: Map[bhid -> Map[page -> list of states]]
        setOccupancyPage()
    }

Desired API changes

This app isn't as nice as it could be, and I'd request a few API changes to improve this.

Project log

This section outlines my thoughts while developing this project. It should give insight into my development progress and thoughts.


On first look this seems to be a pretty simple dashboard, with easy-to-follow designs and an API that suits this use case. My initial thoughts are to create a fairly standard Nuxt site with three pages – Landing, List of birdhouses, and Single birdhouse.

I've put together This MVP issue which outlines all the features this site must contain. And this Map mode issue describing a fancy 'map display' that I think would really suit this project once I've got the base functionality.


I've created the Nuxt base and landing page, and now I'm thinking about how and when to load the data from the API. I might make a separate JS module that only handles comms with the API, that makes sense.

There's also an issue around the navbar in the footer. It could paginate just the list of birdhouses, but could also paginate different elements on each page (e.g. on the Birdhouse Overview tab it paginates the dates, on the Graph tab it paginates the time). The footer stretches underneath the sidebar+wrapper, and it doesn't make a lot of sense for it to be there if it paginates different elements inside the content box – normally I'd want to clarify this with design. I'll tackle this element a bit later and see what makes the most sense.


Importing the icons has been a bit annoying. I've ended up with nuxt-svgo to handle loading svgs as inline elements, as it's by far the easiest way to get things up and running.


Having more of a think about the pagination in the footer, there's not any reason to include it on the birdhouse overview page unless it does paginate days and the like there as well. For now I'm going to have it be an element of the main content bit, and have the sidebar take up the entire left of the screen instead.


Alrighty, I've now got a very basic js lib ready, which is being made accessible via the birdhouseApi Nuxt plugin here. I'm a bit unsure of how to best integrate Pinia stores into the site, since with the pagination arbitrary pages can be grabbed... Perhaps I could store everything as a map/dict where the key is the page number and the value are the list of entries on that page, but that feels a bit naïve. I'm gonna do some sketches to see if I can work out how I want this to function.

After some sketching I've come to this as a rough layout:

I've asked for feedback on the footer pagination item, to see if we are paginating the occupancy details or not. If we are, the above would work!


So the footer on the single birdhouse page does paginate the occupancy numbers, hmm. I can make this work, but it may be a bit strange using it on the overview vs on the graph.

On the upside, we are now successfully grabbing occupancy data on the list page. However, it does add a lot of waiting (I'm guessing the API isn't hosted in Australia). Because of that I've had to add a loading modal, and I've also added a way to disable those extra API calls via an environment variable. Ideally we'd be returning the current occupancy figures in the registration API response. With this I think the list page is now feature complete, hooray!

It is time to do a decent cleanup though. Things are a bit messy, especially in the Birdhouses store. I'll try to do that, and allow paginating the occupancy details (right now it's a simple list and doesn't take paging into account), before diving into any other tasks.


The refactoring went well and the store is now a lot more understandable, usable, and requires a lot less functions than it previously did.

Now when you go between pages on the birdhouses list, it reflects this in the URL with a query variable. The final bit of site navigation work to do is to ensure that the birdhouse overview pagination does the same thing, and then I can call that section done.

I've also added a decent bit of documentation now, which is nice, and setup the whole docker + docker-compose part. Had to use multi-stage images because without doing so the final image was way larger than it should've been (I could potentially reduce the size further by only copying the .output folder to the final server, I'll try that if I have space to do so).

Now I'm onto describing the project design and adding docs for the typescript lib that calls the API, hooray!


The project design has been documented and I've got some basic docs added for the Typescript lib as well. Finally, we refactored the pagination helper functions out into a composable so it's easier to share that between the list and individual pages.

I'd still like to do the map mode, but I'll do so as a PR so that this repo can be reviewed in the meantime.

Overall I'm really happy with how this project has gone, Nuxt and Tailwind feel super natural. It also feels good to have a public repo that I can point to to illustrate my front-end knowledge.