Giter Club home page Giter Club logo

codingtest-birdhouse-admin's Introduction

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:

  • NUXT_PUBLIC_API_BASE: Base API URL, e.g. https://example.com. Note: This variable must be set for the app to work.
  • NUXT_PUBLIC_REGISTRATION_ITEMS_PER_PAGE: Number of items to display on the birdhouses list. Default: 4
  • NUXT_PUBLIC_LOAD_OCCUPANCY_DETAILS_ON_LIST: If set to false, don't load occupancy details on the birdhouses list. Since each birdhouse requires a separate API call to retrieve the current occupancy details, this vastly reduces the number of API calls and can improve loading times.
  • NUXT_PUBLIC_OCCUPANCY_STATES_PER_PAGE: Number of occupancy states to get from API on each specific birdhouse page. You should fine-tune this based on your production data, so the occupancy list and graph are readable. Defualt: 10

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.

  • GET /registration and GET /registration/{ubid}: Return the current occupancy details on these API responses. Since the dashboard must display these values, and they aren't provided currently, we need an extra API call for every single displayed registration entry that has a birdhouse.
  • GET /house/{ubid}/occupancy: Rather than paginating based on pages, it may make sense to update the design to use some kind of date selector and paginate based on entered dates? This could improve how we display the graph (see issue #5 for more details). It may also be worth checking the app/s that add data here, since we've seen a number of instances where the same timestamp and occupancy figures are recorded multiple times, with different occupancy state IDs.

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:

  • Birdhouses: Map, each page to a list of UBIDs.
  • BhRegistration: Map, each UBID to the reg info (birdhouse if it exists).
  • BhOccupancy: Map, each UBID to a map of paginated occupancy details.

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.

codingtest-birdhouse-admin's People

Contributors

danieloaks avatar

Watchers

 avatar  avatar

codingtest-birdhouse-admin's Issues

Improve graph display

There are a few issues with the way the graph is displayed currently.

For reference, here is the design:
image

And here is the graph today:
image

Legend

It'd hard to understand what's the birds and what's the eggs. We should probably display a legend somewhere nearby the graph to help make this easier to understand, since the blue/purple colours aren't used for these values anywhere else on the dashboard.

Timescale

The design shows times from Monday through Sunday. We have a tough time replicating this sort of static scale because of the way that history entries are returned from the API (we need to grab them as pages and can't ensure that each page of states falls within a pretty range). Because pages can be grabbed and displayed arbitrarily, we also can't take that paginated info and force it into a nicer time-delimited structure unless we grab all occupancy history before the graph is displayed.

This is an issue that would be best resolved on the API side.

Ticks

Currently, the ticks on the graph aren't aligned with the very left/right of the edges. This makes our graphs look less clean than they do on the design. This would also mean we don't need to try to add an extra dashed border around the outside, as the ticks will do that for us.

I'll see if we can have these align more nicely, but it may involve some filling gaps of non-existent data or starting/ending the graph before the very start and end points.

Map mode

Design still needs to check this off, but I'd love a mode where we display all birdhouses on a map and show their location on the birdhouse pages as a map too. Consider this extra credit.

  • Enable and disable with an env var, including on the Docker
  • Map overview page
    • Grab all data to display on map
  • Show small map with geocords plotted or the location as a name on birdhouse specifics page

MVP

All the basic features that this project needs to be functional.

  • Switching between prod API and localhost API
  • Sidebar (done in 422fe3e)
    • Changing icon colours on hover
  • Landing page
    • Link to birdhouse list page
  • Birdhouse list page
    • Fetching data from API
    • Pagination (done in 6c2bf04)
    • Show occupancy details (extra API calls) (done in 3571828)
  • Birdhouse data page
    • Displaying existing birdhouse data (name and geocoords)
    • Fetching data from API
    • Showing new data on Overview page (done in f0a6e7f)
    • Showing new data on Graph page (done in 2762e77)
    • Paginating new data on both pages (done in 87da535)
  • Dockerfile (done by 2a55649, multistage added by 8bb7217)
  • Docker compose file

Remember previous pages by pushing history updates

If you go to the third page of birdhouses, then click on a birdhouse, then click Back, you're sent back to page 1 of birdhouses. This doesn't feel as nice as it could.

We should push a page query param into our history, then read that when loading the page for the first time.

Grab brand new data after a few minutes

Since we intentionally don't store state to localStorage, the API data will be refreshed whenever the user refreshes the page. However, it will continue to be stored so long as the user keeps clicking buttons on the site.

Given that registrations and occupancy info will change over time, we should periodically grab new data every e.g. 1-5 minutes. Whenever we time out existing data and grab new info we'll need to ensure that any other entries in the same page map are also scrubbed, so we don't end up in weird cases where we're mixing old paginated info and new paginated info.

Here's how to implement this for each type of data:


Paginated Registration info

  1. When setting new pages from the API, store a 'first grabbed time' for the paginated registrations and store/update the 'grabbed time' for each item on the page.
  2. On future requests:
    a. Check the overall 'first grabbed time', if it's passed then dump the existing store and re-grab pages from the API as needed.
    b. Check the 'grabbed time' for each item on the page, if any are passed then dump the existing store and re-grab pages from the API as needed. This has the upside of forcing new grabs for any existing items (since the reg info for each item is included on new page grabs).

Individual Registrations

  1. Each time we grab a new registration from the API, we set a 'grabbed time' on it. This is likely already done above, but can also be done in getBaseRegInfo if visiting a single page directly.
  2. On future getBaseRegInfo requests, check if the grabbed time is elapsed. If it is, make a new API call to grab registration info and update the grabbed time.

Paginated Occupancy info

  1. When grabbing new pages from the API, store a 'first grabbed time' for the given bhid's occupancy info.
  2. On future requests, check the 'first grabbed time' for the given bhid. If it's passed then dump the existing occupancy store for that bhid and re-grab pages from the API as needed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.