diff options
author | Matthew Lemon <y@yulqen.org> | 2024-04-23 11:16:38 +0100 |
---|---|---|
committer | Matthew Lemon <y@yulqen.org> | 2024-04-23 11:16:38 +0100 |
commit | 0f951dcf029d4af284467543a3afdf5bf6581a20 (patch) | |
tree | a48384210cdc168e3bd3ccff6d6d516eeed9e748 | |
parent | 8b084e9fe7a5f3a04c32daf9a24f7f2cf67300f9 (diff) |
switched to Django
164 files changed, 4049 insertions, 2211 deletions
diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 48b0266..0000000 --- a/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -.git -Dockerfile - -.dockerignore -**/.git
\ No newline at end of file @@ -1,44 +1,30 @@ -FROM docker.io/golang:alpine - -RUN addgroup -S app && adduser -S -g app app +# Pull base image +FROM python:3.11.4-slim-bullseye WORKDIR /app -RUN apk add --update npm -RUN npm i sass govuk-frontend --save - -# Switch to root user -USER root - -COPY go.mod ./ -RUN go mod download && go mod verify - -# instead of doing COPY . . here, I want to be specific about the files I want to copy -# this way if I add a new file to the project, it will not be copied over +RUN apt-get update \ + && apt-get install -y build-essential curl \ + && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \ + && apt-get clean \ + && useradd --create-home python \ + && chown python:python -R /app -# Can this be refactored? -COPY ./cmd ./cmd -COPY ./internal ./internal -COPY ./postgresql ./postgresql -COPY ./ui ./ui -COPY go.sum . -COPY go.mod . -COPY sonar-project.properties . +USER python -RUN chown -R app:app /app +COPY --chown=python:python requirements*.txt ./ -# Create a directory for the binary -RUN mkdir /app/bin -RUN chown app:app /app/bin +RUN pip install -r requirements.txt \ + && pip install -r requirements_dev.txt -# Switch back to app user -USER app +COPY --chown=python:python . . -# Build the Go binary in the /app/bin directory -RUN go build -v -o /app/bin/app ./cmd/web +ENV DEBUG="${DEBUG}" \ + PYTHONUNBUFFERED="true" \ + PATH="${PATH}:/home/python/.local/bin" \ + USER="python" -# Set the working directory to /app/bin -WORKDIR /app/bin +# Collect static files +RUN SECRET_KEY=nothing python manage.py collectstatic --no-input -CMD ["./app"] -EXPOSE 4000 +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] @@ -1,28 +1,10 @@ -create-network: - @docker network create ded - -docker-build-app: - @docker build -t ded-core:latest . - -docker-run-app: - @docker run -it --rm --network ded --name ded-app -p 4000:4000 -v ./:/app ded-core:latest - -docker-build-postres: - @docker build -t postgres -f postgresql/Dockerfile . - -docker-run-postgres: - @docker run --name ded-db --network ded -d --rm -e POSTGRES_PASSWORD=secret -v ded-data:/var/lib/postgres/data -p 5432:5432 postgres - -docker-stop-app: - @docker stop ded-core - -# run: -# @go run ./cmd/web -addr ":4000" - -# assume GRANT SELECT, INDEX, CREATE, INSERT, UPDATE, DROP, DELETE ON ded.* TO 'web'@'localhost'; -# assume CREATE DATABASE ded CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -populate: - mariadb -u web ded -p < populate.sql - -entr-run: - find . | entr -r go run ./cmd/web +runserver: + python manage.py runserver 0.0.0.0:8000 +runserver-docker: + docker run -it --name ded -d --rm -p 8000:8000 ded:latest +runserver-docker-with-vol: + docker run -it --name ded -d --rm -p 8000:8000 -v $(PWD):/app ded:latest +build: + docker build -t ded:latest . +test: + python manage.py test @@ -1,5 +1 @@ -# ded-web - This is a prototype application. - -Friday - 11:49 diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go deleted file mode 100644 index 0827ea1..0000000 --- a/cmd/web/handlers.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "net/http" - "strconv" - - "github.com/defencedigital/ded-web/internal/models" -) - -func (app *application) listPersons(w http.ResponseWriter, r *http.Request) { - ps, err := app.persons.ListAll() - if err != nil { - app.serverError(w, r, err) - return - } - - app.render(w, r, http.StatusOK, "persons_list.tmpl.html", templateData{ - Persons: ps, - }) -} - -func (app *application) listOperations(w http.ResponseWriter, r *http.Request) { - - ops, err := app.operations.ListAll() - if err != nil { - app.serverError(w, r, err) - return - } - - var newOps []models.Operation - - for _, op := range ops { - esses, err := app.engagementStrategies.GetForOperation(op.ID) - // TODO: Check what kind of error this is, don't just continue - if err != nil { - continue - } - if len(esses) > 0 { - op.EngagementStrategies = esses - newOps = append(newOps, op) - } else { - newOps = append(newOps, op) - } - } - - app.render(w, r, http.StatusOK, "operations_list.tmpl.html", templateData{ - Operations: newOps, - }) -} - -func (app *application) listOrganisations(w http.ResponseWriter, r *http.Request) { - - latest, err := app.organisations.Latest() - if err != nil { - app.serverError(w, r, err) - return - } - - // We are putting latest in the Organisations field of templateData and leaving - // templateData.Operations as the nil value because we don't need Operations in this - // page, of course. - app.render(w, r, http.StatusOK, "organisations_list.tmpl.html", templateData{ - Organisations: latest, - }) -} - -func (app *application) home(w http.ResponseWriter, r *http.Request) { - // Check if the current request URL path exactly matches "/". If it doesn't, use - // the http.NotFound() function to send a 404 response to the client. - // Importantly, we then return from the handler. If we don't return the handler - // would keep executing. - if r.URL.Path != "/" { - app.notFound(w) - return - } - - // organisations, err := app.organisations.Latest() - // if err != nil { - // app.serverError(w, r, err) - // return - // } - - // for _, organisation := range organisations { - // fmt.Fprintf(w, "%+v\n", organisation) - // } - - // files := []string{ - // "./ui/html/base.tmpl.html", - // "./ui/html/pages/home.tmpl.html", - // "./ui/html/partials/nav.tmpl.html", - // } - - // ts, err := template.ParseFiles(files...) - // if err != nil { - // app.serverError(w, r, err) // Use the serverError() helper - // return - // } - - // err = ts.ExecuteTemplate(w, "base", nil) - // if err != nil { - // app.serverError(w, r, err) // Use the serverError() helper - // } - app.render(w, r, http.StatusOK, "home.tmpl.html", templateData{}) -} - -func (app *application) organisationView(w http.ResponseWriter, r *http.Request) { - id, err := strconv.Atoi(r.URL.Query().Get("id")) - if err != nil || id < 1 { - app.notFound(w) - return - } - - o, err := app.organisations.Get(id) - if err != nil { - if errors.Is(err, models.ErrNoRecord) { - app.notFound(w) - } else { - app.serverError(w, r, err) - } - return - } - fmt.Fprintf(w, "%+v", o) -} - -func (app *application) organisationCreate(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - w.Header().Set("Allow", http.MethodPost) - app.clientError(w, http.StatusMethodNotAllowed) - return - } - - // Dummy data - name := "Test Organisation" - - id, err := app.organisations.Insert(name) - if err != nil { - app.serverError(w, r, err) - return - } - - http.Redirect(w, r, fmt.Sprintf("/organisation/view?id=%d", id), http.StatusSeeOther) -} diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go deleted file mode 100644 index 882f506..0000000 --- a/cmd/web/helpers.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "runtime/debug" -) - -func (app *application) serverError(w http.ResponseWriter, r *http.Request, err error) { - var ( - method = r.Method - uri = r.URL.RequestURI() - trace = string(debug.Stack()) - ) - - app.logger.Printf(err.Error(), method, uri, trace) - app.logger.Printf("Crash! %s %s %s", method, uri, trace) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) -} - -// The clientError helper sends a specific status code and corresponding description -// to the user. We'll use this later in the book to send responses like 400 "Bad -// Request" when there's a problem with the request that the user sent. -func (app *application) clientError(w http.ResponseWriter, status int) { - http.Error(w, http.StatusText(status), status) -} - -// notFound helper is a convenience wrapper around clientError which sends a 404 Not Found response -func (app *application) notFound(w http.ResponseWriter) { - app.clientError(w, http.StatusNotFound) -} - -func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) { - // retrieve the appropriate template set from the cache based on the page name. - // If no entry exists in the cache with the provided name, create a new error - // and call serverError() helper that we made earlier and return. - ts, ok := app.templateCache[page] - if !ok { - err := fmt.Errorf("the template %s does not exist", page) - app.serverError(w, r, err) - return - } - - // Write out the provided HTTP status code('200 OK', '400 Bad Request', etc). - w.WriteHeader(status) - - err := ts.ExecuteTemplate(w, "base", data) - if err != nil { - app.serverError(w, r, err) - } -} diff --git a/cmd/web/main.go b/cmd/web/main.go deleted file mode 100644 index bce1f1a..0000000 --- a/cmd/web/main.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "database/sql" - "flag" - "html/template" - "log" - "net/http" - "os" - - // _ "github.com/go-sql-driver/mysql" - "github.com/defencedigital/ded-web/internal/models" - // _ "github.com/lib/pq" -) - -type application struct { - logger *log.Logger - operations *models.OperationModel - organisations *models.OrganisationModel - persons *models.PersonModel - engagementStrategies *models.EngagementStrategyModel - templateCache map[string]*template.Template -} - -func main() { - addr := flag.String("addr", ":4000", "HTTP network port") - // This uses "ded-db" in the connection string; this should be the name of the container running postgres. - // If not running this app inside a container, use "localhost". - //dsn := flag.String("dsn", "postgresql://postgres:secret@ded-db?sslmode=disable", "PostgreSQL data source name") - flag.Parse() - - logger := log.New(os.Stdout, "ded-web: ", log.LstdFlags) - - // Database connection - //db, err := openDB(*dsn) - //if err != nil { - // logger.Error(err.Error()) - // os.Exit(1) - //} - // - //defer db.Close() - - // initialise the new template cache... - templateCache, err := newTemplateCache() - if err != nil { - logger.Println(err.Error()) - os.Exit(1) - } - - app := &application{ - logger: logger, - //operations: &models.OperationModel{DB: db}, - //organisations: &models.OrganisationModel{DB: db}, - //persons: &models.PersonModel{DB: db}, - //engagementStrategies: &models.EngagementStrategyModel{DB: db}, - templateCache: templateCache, - } - - // mux := http.NewServeMux() - // mux.HandleFunc("/", home) - // log.Print("starting server on :4000") - logger.Printf("starting server", addr) - err = http.ListenAndServe(*addr, app.routes()) - logger.Println(err.Error()) - os.Exit(1) -} - -// openDB wraps sql.Open() and returns a sql.DB connection pool -// for a given DSN. -func openDB(dsn string) (*sql.DB, error) { - db, err := sql.Open("postgres", dsn) - if err != nil { - return nil, err - } - - err = db.Ping() - if err != nil { - db.Close() - return nil, err - } - return db, nil -} diff --git a/cmd/web/routes.go b/cmd/web/routes.go deleted file mode 100644 index 58a351a..0000000 --- a/cmd/web/routes.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import "net/http" - -func (app *application) routes() *http.ServeMux { - mux := http.NewServeMux() - - fileServer := http.FileServer(http.Dir("./ui/static/")) - mux.Handle("/static/", http.StripPrefix("/static", fileServer)) - - mux.HandleFunc("/", app.home) - mux.HandleFunc("/organisation/list", app.listOrganisations) - mux.HandleFunc("/organisation/view", app.organisationView) - mux.HandleFunc("/organisation/create", app.organisationCreate) - mux.HandleFunc("/operation/list", app.listOperations) - mux.HandleFunc("/person/list", app.listPersons) - return mux -} diff --git a/cmd/web/templates.go b/cmd/web/templates.go deleted file mode 100644 index 845fe25..0000000 --- a/cmd/web/templates.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "html/template" - "path/filepath" - - "github.com/defencedigital/ded-web/internal/models" -) - -// TODO: At the moment we are using a struct will have fields for all the data -// we will need. Not sure whether this is good or bad at the moment. -type templateData struct { - Operations []models.Operation - Organisations []models.Organisation - Persons []models.Person -} - -func newTemplateCache() (map[string]*template.Template, error) { - cache := map[string]*template.Template{} - - // get a slice of filepaths match the pattern for our page templates - // e.g. [ui/html/pages/home.tmpl.html ui/html/pages/view.tmpl.html] - pages, err := filepath.Glob("./ui/html/pages/*.html") - if err != nil { - return nil, err - } - - // loop through the page filepaths one by one - for _, page := range pages { - // extract "home.tmp.html" from the full filepath. - name := filepath.Base(page) - - files := []string{ - "./ui/html/base.tmpl.html", - "./ui/html/partials/nav.tmpl.html", - page, - } - - ts, err := template.ParseFiles(files...) - if err != nil { - return nil, err - } - - cache[name] = ts - } - - return cache, nil -} diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/core/__init__.py diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 0000000..4185d36 --- /dev/null +++ b/core/admin.py @@ -0,0 +1,3 @@ +# from django.contrib import admin + +# Register your models here. diff --git a/core/apps.py b/core/apps.py new file mode 100644 index 0000000..c0ce093 --- /dev/null +++ b/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "core" diff --git a/core/migrations/__init__.py b/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/core/migrations/__init__.py diff --git a/core/models.py b/core/models.py new file mode 100644 index 0000000..0b4331b --- /dev/null +++ b/core/models.py @@ -0,0 +1,3 @@ +# from django.db import models + +# Create your models here. diff --git a/core/static/css/styles.css b/core/static/css/styles.css new file mode 100644 index 0000000..23064c6 --- /dev/null +++ b/core/static/css/styles.css @@ -0,0 +1,53 @@ +.format-container { + margin: 20px; + display: flex; + justify-content: space-evenly; + align-items: flex-start; + height: 300px; + gap: 1em; +} + +.form-thing { + width: 50%; + border-style: solid; + border-width: 5px 2px; + border-color: black; + padding: 20px; + background-color: #dbefff; + margin: auto; +} + +.ep-table { + font-size: small; + border: 1px solid; + border-collapse: collapse; + color: black; + border-spacing: 0; +} + +.summary-table { + font-size: small; + border: 1px solid; + border-collapse: collapse; + color: black; + border-spacing: 0; +} + +.summary-table > tbody > tr > td { + border: 1px solid; +} + + +.ep-table > thead > tr > th { + padding: 10px; + border: 1px solid; +} + +.ep-table > tbody > tr > td { + border: 1px solid; + padding: 3px; +} + +.ep-table > tbody > tr > td > ul > li > a { + color: green; +} diff --git a/core/templates/core/base.html b/core/templates/core/base.html new file mode 100644 index 0000000..1c2bfe0 --- /dev/null +++ b/core/templates/core/base.html @@ -0,0 +1,58 @@ +{% load static i18n %} + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css"> + <link rel="preconnect" href="https://fonts.gstatic.com"> + <link href='https://fonts.googleapis.com/css?family=Lato:400,700|Roboto+Slab:400,700' rel='stylesheet' type='text/css'/> + <link rel="stylesheet" href="{% static 'css/styles.css' %}"/> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> + <script src="https://unpkg.com/htmx.org@1.9.0" integrity="sha384-aOxz9UdWG0yBiyrTwPeMibmaoq07/d3a96GCbb9x60f3mOt5zwkjdbcHFnKH8qls" crossorigin="anonymous"></script> + <title>{% block title %}{% endblock title %}</title> + {% block extra_head_tags %}{% endblock extra_head_tags %} + <style type="text/css" media="screen"> + + * { + font-family: "Roboto Slab"; + } + + body, h1, h2, h3, h4, h5, h6 { + font-family: "Roboto Slab", sans-serif; +} + </style> +</head> +<body> +{% block navbar %} +<div class="w3-bar w3-border w3-blue w3-padding-16"> + <!-- <a href="/" class="w3-bar-item w3-button w3-dark-gray w3-xxlarge"><i class="fa fa-home"></i></a> --> + <a href="#" class="w3-bar-item w3-button w3-large">Home</a> + <a href="{% url 'engagements:home' %}" class="w3-bar-item w3-button w3-large">Engagement Plans</a> + <a href="{% url 'engagements:regulatedentities' %}" class="w3-bar-item w3-button w3-large">Regulated Entities</a> + <a href="#" class="w3-bar-item w3-button w3-large">Reporting</a> + <a href="#" class="w3-bar-item w3-button w3-large">Dashboards</a> + <div class="w3-dropdown-hover"> + <button class="w3-button w3-large">Teams</button> + <div class="w3-dropdown-content w3-bar-block w3-card-4"> + <a href="#" class="w3-bar-item w3-button">Submarines and Propulsion</a> + <a href="#" class="w3-bar-item w3-button">Transport</a> + </div> + </div> + {% if request.user.is_authenticated %} + <a class="w3-bar-item w3-right w3-button w3-large" href="#">{{ user }}</a> + <a class="w3-bar-item w3-right w3-button w3-large" href="{% url 'logout' %}">Log Out</a> + {% else %} + <a class="w3-bar-item w3-right w3-button w3-large" href="{% url 'login' %}">Log In</a> + {% endif %} + <a href="#" class="w3-right w3-bar-item w3-button w3-large">Help</a> +</div> +{% endblock navbar %} + + <div class="w3-container"> + {% block content %} + {% endblock content %} + </div> +</body> +</html> diff --git a/core/templates/core/index.html b/core/templates/core/index.html new file mode 100644 index 0000000..d279479 --- /dev/null +++ b/core/templates/core/index.html @@ -0,0 +1,100 @@ +{% extends "core/base.html" %} +{% load table_extras %} + +{% block title %}ComplyIQ - Information Management for DefNucSyR{% endblock title %} + +{% block content %} + +<div class="w3-container"> + <h2>ComplyIQ - Information Management for DefNucSyR</h2> + <div class="w3-panel w3-leftbar w3-sand w3-xlarge w3-serif w3-display-container"> + <span onclick="this.parentElement.style.display='none'" + class="w3-button w3-display-topright">×</span> + <h3>Quote of the day</h3> + <p>This is a placeholder for some interesting test. Use of lose.</p> + </div> + + <p>{% lorem %}</p> + + <div class="w3-card-2 w3-margin-top"> + <header class="w3-container w3-light-blue"> + <h3>Submarines and Propulsion</h3> + </header> + <div class="w3-container"> + + <h3>Assessments & Inspections:</h3> + <table class="w3-table w3-bordered w3-margin-bottom"> + <tr class="w3-light-blue"> + <th>Start Date</th> + <th>End Date</th> + <th>Event</th> + <th>Site</th> + <th>Inspectors</th> + </tr> + {% for s in subs_regulatory %} + <tr> + <td>{{ s.proposed_start_date|date:'j M y' }}</td> + <td>{{ s.proposed_end_date|date:'j M y' }}</td> + <td><a href="{% url 'engagements:engagement_detail' s.pk %}">{{ s }}</a></td> + <td>{{ s.external_party }}</td> + <td> + {% if s.officers.all %} + {{ s.officers.all|commalist }} + {% endif %} + </td> + </tr> + {% endfor %} + </table> + <div class="w3-cell-row"> + <div class="w3-cell w3-left"> + <a href="{% url 'engagements:create' 'reg' %}" class="w3-button w3-round w3-blue">Add New</a> + </div> + <div class="w3-cell w3-right"> + <small>See <a href="#">Enagements</a> for full list</small> + </div> + </div> + <hr> + <h4>Non-regulatory events:</h4> + <table class="w3-table w3-bordered w3-margin-bottom"> + <tr class="w3-light-blue"> + <th>Start Date</th> + <th>End Date</th> + <th>Summary</th> + </tr> + {% if not subs %} + <tr> + <td>None</td> + <td>None</td> + <td>None</td> + </tr> + {% else %} + {% for s in subs %} + <tr> + <td>{{ s.proposed_start_date|date:'j M y' }}</td> + <td>{{ s.proposed_end_date|date:'j M y' }}</td> + <td><a href="#">{{ s }}</a></td> + </tr> + {% endfor %} + {% endif %} + </table> + <div> + <button class="w3-button w3-round w3-blue">Add New</button> + </div> + + <p>{% lorem %}</p> + + </div> + </div> + + <div class="w3-card-2 w3-margin-top"> + <header class="w3-container w3-light-blue"> + <h3>Transport</h3> + </header> + <div class="w3-container"> + <p>{% lorem %}</p> + </div> + </div> + +</div> + +{% endblock content %} diff --git a/core/templates/core/profile.html b/core/templates/core/profile.html new file mode 100644 index 0000000..8171379 --- /dev/null +++ b/core/templates/core/profile.html @@ -0,0 +1,13 @@ +{% extends "core/base.html" %} + +{% block title %}Profile - {{ user }}{% endblock title %} + +{% block content %} + +<div class="container"> + {% block body %} + <h1>Profile for {{ user }} ({{ user.first_name }} {{ user.last_name }})</h1> + <p>Email: <a href="mailto:{{ user.email }}">{{ user.email }}</a></p> + {% endblock body %} +{% endblock content %} +</div> diff --git a/core/templates/registration/logged_out.html b/core/templates/registration/logged_out.html new file mode 100644 index 0000000..61e6831 --- /dev/null +++ b/core/templates/registration/logged_out.html @@ -0,0 +1,9 @@ +{% extends "core/base.html" %} + +{% block title %}Logged out{% endblock title %} + +{% block content %} + + <p>You've logged out. Bye.</p> + +{% endblock content %} diff --git a/core/templates/registration/login.html b/core/templates/registration/login.html new file mode 100644 index 0000000..9e00087 --- /dev/null +++ b/core/templates/registration/login.html @@ -0,0 +1,43 @@ +{% extends "core/base.html" %} + +{% block content %} + +<div class="w3-container"> + {% if form.errors %} + <p>Your username and password didn't match. Please try again.</p> + {% endif %} + + {% if next %} + {% if user.is_authenticated %} + <p>Your account doesn't have access to this page. To proceed, + please login with an account that has access.</p> + {% else %} + <p>Please login to see this page.</p> + {% endif %} + {% endif %} + + <form method="post" action="{% url 'login' %}"> + {% csrf_token %} + <div class="form-group"> + {{ form.username.label_tag }} + {{ form.username }} + </div> + + <div class="form-group"> + {{ form.password.label_tag }} + {{ form.password }} + </div> + + <div class="form-group"> + <button type="submit" class="btn btn-primary" value="login">Log In</button> + </div> + <input type="hidden" name="next" value="{{ next }}"> + </form> + + <div class="form-group"> + {# Assumes you set up the password_reset view in your URLconf #} + <a href="{% url 'password_reset' %}">Lost password?</a> + </div> + + {% endblock content %} +</div> diff --git a/core/templates/registration/password_change_done.html b/core/templates/registration/password_change_done.html new file mode 100644 index 0000000..20fac71 --- /dev/null +++ b/core/templates/registration/password_change_done.html @@ -0,0 +1,15 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} +{% block userlinks %}{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans "Documentation" %}</a> / {% endif %}{% trans "Change password" %} / <a href="{% url 'admin:logout' %}">{% trans "Log out" %}</a>{% endblock userlinks %} +{% block breadcrumbs %} +<div class="breadcrumbs"> +<a href="{% url 'admin:index' %}">{% trans "Home" %}</a> +› {% trans "Password change" %} +</div> +{% endblock breadcrumbs %} + +{% block title %}{{ title }}{% endblock title %} +{% block content_title %}<h1>{{ title }}</h1>{% endblock content_title %} +{% block content %} +<p>{% trans "Your password was changed." %}</p> +{% endblock content %} diff --git a/core/templates/registration/password_change_form.html b/core/templates/registration/password_change_form.html new file mode 100644 index 0000000..4ed12d4 --- /dev/null +++ b/core/templates/registration/password_change_form.html @@ -0,0 +1,59 @@ +{% extends "admin/base_site.html" %} +{% load i18n static %} +{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock extrastyle %} +{% block userlinks %}{% url "django-admindocs-docroot" as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans "Documentation" %}</a> / {% endif %} {% trans "Change password" %} / <a href="{% url 'admin:logout' %}">{% trans "Log out" %}</a>{% endblock userlinks %} +{% block breadcrumbs %} +<div class="breadcrumbs"> +<a href="{% url "admin:index" %}">{% trans "Home" %}</a> +› {% trans "Password change" %} +</div> +{% endblock breadcrumbs %} + +{% block title %}{{ title }}{% endblock title %} +{% block content_title %}<h1>{{ title }}</h1>{% endblock content_title %} + +{% block content %}<div id="content-main"> + +<form method="post">{% csrf_token %} +<div> +{% if form.errors %} + <p class="errornote"> + {% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} + </p> +{% endif %} + +<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p> + +<fieldset class="module aligned wide"> + +<div class="form-row"> + {{ form.old_password.errors }} + {{ form.old_password.label_tag }} {{ form.old_password }} +</div> + +<div class="form-row"> + {{ form.new_password1.errors }} + {{ form.new_password1.label_tag }} {{ form.new_password1 }} + {% if form.new_password1.help_text %} + <div class="help">{{ form.new_password1.help_text|safe }}</div> + {% endif %} +</div> + +<div class="form-row"> +{{ form.new_password2.errors }} + {{ form.new_password2.label_tag }} {{ form.new_password2 }} + {% if form.new_password2.help_text %} + <div class="help">{{ form.new_password2.help_text|safe }}</div> + {% endif %} +</div> + +</fieldset> + +<div class="submit-row"> + <input type="submit" value="{% trans "Change my password" %}" class="default" /> +</div> + +</div> +</form></div> + +{% endblock content %} diff --git a/core/templates/registration/password_reset_complete.html b/core/templates/registration/password_reset_complete.html new file mode 100644 index 0000000..0e60645 --- /dev/null +++ b/core/templates/registration/password_reset_complete.html @@ -0,0 +1,20 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %} +<div class="breadcrumbs"> +<a href="{% url 'admin:index' %}">{% trans "Home" %}</a> +› {% trans "Password reset" %} +</div> +{% endblock breadcrumbs %} + +{% block title %}{{ title }}{% endblock title %} +{% block content_title %}<h1>{{ title }}</h1>{% endblock content_title %} + +{% block content %} + +<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p> + +<p><a href="{{ login_url }}">{% trans "Log in" %}</a></p> + +{% endblock content %} diff --git a/core/templates/registration/password_reset_confirm.html b/core/templates/registration/password_reset_confirm.html new file mode 100644 index 0000000..e493f5a --- /dev/null +++ b/core/templates/registration/password_reset_confirm.html @@ -0,0 +1,42 @@ +{% extends "admin/base_site.html" %} +{% load i18n static %} + +{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock extrastyle %} +{% block breadcrumbs %} +<div class="breadcrumbs"> +<a href="{% url 'admin:index' %}">{% trans "Home" %}</a> +› {% trans "Password reset confirmation" %} +</div> +{% endblock breadcrumbs %} + +{% block title %}{{ title }}{% endblock title %} +{% block content_title %}<h1>{{ title }}</h1>{% endblock content_title %} +{% block content %} + +{% if validlink %} + +<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p> + +<form method="post">{% csrf_token %} +<fieldset class="module aligned"> + <div class="form-row field-password1"> + {{ form.new_password1.errors }} + <label for="id_new_password1">{% trans "New password:" %}</label> + {{ form.new_password1 }} + </div> + <div class="form-row field-password2"> + {{ form.new_password2.errors }} + <label for="id_new_password2">{% trans "Confirm password:" %}</label> + {{ form.new_password2 }} + </div> + <input type="submit" value="{% trans "Change my password" %}" /> +</fieldset> +</form> + +{% else %} + +<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p> + +{% endif %} + +{% endblock content %} diff --git a/core/templates/registration/password_reset_done.html b/core/templates/registration/password_reset_done.html new file mode 100644 index 0000000..a29461d --- /dev/null +++ b/core/templates/registration/password_reset_done.html @@ -0,0 +1,19 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block breadcrumbs %} +<div class="breadcrumbs"> +<a href="{% url 'admin:index' %}">{% trans "Home" %}</a> +› {% trans "Password reset" %} +</div> +{% endblock breadcrumbs %} + +{% block title %}{{ title }}{% endblock title %} +{% block content_title %}<h1>{{ title }}</h1>{% endblock content_title %} +{% block content %} + +<p>{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}</p> + +<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p> + +{% endblock content %} diff --git a/core/templates/registration/password_reset_email.html b/core/templates/registration/password_reset_email.html new file mode 100644 index 0000000..0cd2e51 --- /dev/null +++ b/core/templates/registration/password_reset_email.html @@ -0,0 +1,14 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %} + +{% trans "Please go to the following page and choose a new password:" %} +{% block reset_link %} +{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} +{% endblock reset_link %} +{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} diff --git a/core/templates/registration/password_reset_form.html b/core/templates/registration/password_reset_form.html new file mode 100644 index 0000000..921fb8e --- /dev/null +++ b/core/templates/registration/password_reset_form.html @@ -0,0 +1,29 @@ +{% extends "admin/base_site.html" %} +{% load i18n static %} + +{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock extrastyle %} +{% block breadcrumbs %} +<div class="breadcrumbs"> +<a href="{% url 'admin:index' %}">{% trans "Home" %}</a> +› {% trans "Password reset" %} +</div> +{% endblock breadcrumbs %} + +{% block title %}{{ title }}{% endblock title %} +{% block content_title %}<h1>{{ title }}</h1>{% endblock content_title %} +{% block content %} + +<p>{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}</p> + +<form method="post">{% csrf_token %} +<fieldset class="module aligned"> + <div class="form-row field-email"> + {{ form.email.errors }} + <label for="id_email">{% trans "Email address:" %}</label> + {{ form.email }} + </div> + <input type="submit" value="{% trans "Reset my password" %}" /> +</fieldset> +</form> + +{% endblock content %} diff --git a/core/templates/registration/register.html b/core/templates/registration/register.html new file mode 100644 index 0000000..98615fd --- /dev/null +++ b/core/templates/registration/register.html @@ -0,0 +1,22 @@ +{% extends "core/base.html" %} + +{% block title %}Register new user{% endblock title %} + +{% block content %} + <h1>Register a new user</h1> + <form action="{% url 'register' %}" method="post" accept-charset="utf-8"> + {% csrf_token %} + {% if messages %} + <ul> + {% for message in messages %} + <li>{{ message }}</li> + {% endfor %} + </ul> + {% endif %} + {{ form }} + + <input type="submit" name="submit" id="submit" value="Register" /> + + </form> + +{% endblock content %} diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/core/templatetags/__init__.py diff --git a/core/templatetags/table_extras.py b/core/templatetags/table_extras.py new file mode 100644 index 0000000..53185d2 --- /dev/null +++ b/core/templatetags/table_extras.py @@ -0,0 +1,31 @@ +from django import template + +from engagements import models + +register = template.Library() + + +def commalist(value): + "Value is a list of TeamUsers - insert commas!" + names = [p.fullname() for p in value] + out = ", ".join(names) + return out + + +def dsc_bar_list(value): + items = [v.title for v in value] + return " | ".join(items) + + +def effort_for_org(value, org): + eff = ( + models.EngagementEffort.objects.filter(engagement__external_party=org) + .filter(effort_type="REGULATION") + .filter(sub_instruments=value) + ) + return sum(e.effort_total_hours() for e in eff) + + +register.filter("commalist", commalist) +register.filter("dsc_bar_list", dsc_bar_list) +register.filter("effort_for_org", effort_for_org) diff --git a/core/tests.py b/core/tests.py new file mode 100644 index 0000000..a79ca8b --- /dev/null +++ b/core/tests.py @@ -0,0 +1,3 @@ +# from django.test import TestCase + +# Create your tests here. diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..b45430b --- /dev/null +++ b/core/urls.py @@ -0,0 +1,11 @@ +from django.contrib.auth.views import logout_then_login +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.index, name="index"), + path("dashboard/", views.dashboard, name="dashboard"), + path("logout-then-login/", logout_then_login, name="logout-the-login"), + path("profile/", views.profile, name="profile"), +] diff --git a/core/views.py b/core/views.py new file mode 100644 index 0000000..4e7cd41 --- /dev/null +++ b/core/views.py @@ -0,0 +1,24 @@ +from django.contrib.auth.decorators import login_required +from django.shortcuts import render + +from engagements.models import Engagement + + +@login_required +def index(request): + subs = Engagement.objects.sp().order_by("proposed_start_date")[:4] + subs_regulatory = Engagement.objects.sp_regulatory()[:4] + trans = Engagement.objects.tr()[:4] + context = dict(subs=subs, trans=trans, subs_regulatory=subs_regulatory) + return render(request, "core/index.html", context) + + +@login_required +def dashboard(request): + return render(request, "core/dashboard.html", {"section": "dashboard"}) + + +@login_required +def profile(request): + args = {"user": request.user} + return render(request, "core/profile.html", args) diff --git a/db.sqlite3 b/db.sqlite3 Binary files differnew file mode 100644 index 0000000..195c210 --- /dev/null +++ b/db.sqlite3 diff --git a/ded/__init__.py b/ded/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ded/__init__.py diff --git a/ded/asgi.py b/ded/asgi.py new file mode 100644 index 0000000..0c4955d --- /dev/null +++ b/ded/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for ded project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ded.settings") + +application = get_asgi_application() diff --git a/ded/settings.py b/ded/settings.py new file mode 100644 index 0000000..1b48377 --- /dev/null +++ b/ded/settings.py @@ -0,0 +1,147 @@ +""" +Django settings for ded project. + +Generated by 'django-admin startproject' using Django 4.0.5. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-z*tv24r*)-$q*l1=l64))qocs1x$*10c&6w_@ld^dw#=q#ndy2" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ["100.64.1.3", "100.64.2.3", "10.0.0.10", "localhost", "127.0.0.1"] + +STATIC_ROOT = BASE_DIR / "static" + +# Application definition + +INSTALLED_APPS = [ + "instruments.apps.InstrumentsConfig", + "engagements.apps.EngagementsConfig", + "myuser.apps.AuthuserConfig", + "core.apps.CoreConfig", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "crispy_forms", + "django_htmx", + "debug_toolbar", +] + + +# needed for debug_toolbar +INTERNAL_IPS = ["127.0.0.1", "localhost"] + +# DEBUG_TOOLBAR_PANELS = ["debug_toolbar.panels.request.RequestPanel"] + +SHOW_TOOLBAR_CALLBACK = "debug_toolbar.middleware.show_toolbar" + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django_htmx.middleware.HtmxMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "ded.urls" + +# new auth user +AUTH_USER_MODEL = "myuser.TeamUser" + + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +CRISPY_ALLOWED_TEMPLATE_PACKS = ("w3",) +CRISPY_TEMPLATE_PACK = "w3" + +WSGI_APPLICATION = "ded.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = False + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/ded/urls.py b/ded/urls.py new file mode 100644 index 0000000..9ee8110 --- /dev/null +++ b/ded/urls.py @@ -0,0 +1,25 @@ +"""ded URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import include, path + +urlpatterns = [ + path("__debug__", include("debug_toolbar.urls")), + path("accounts/", include("django.contrib.auth.urls")), + path("engagements/", include("engagements.urls")), + path("admin/", admin.site.urls), + path("", include("core.urls")), +] diff --git a/ded/wsgi.py b/ded/wsgi.py new file mode 100644 index 0000000..257eb17 --- /dev/null +++ b/ded/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for ded project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ded.settings") + +application = get_wsgi_application() diff --git a/engagements/__init__.py b/engagements/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/engagements/__init__.py diff --git a/engagements/admin.py b/engagements/admin.py new file mode 100644 index 0000000..9861867 --- /dev/null +++ b/engagements/admin.py @@ -0,0 +1,32 @@ +from django.contrib import admin + +from .models import ( + Engagement, + EngagementEffort, + EngagementType, + Organisation, + Person, + RegulatedEntityType, + RegulatoryRole, +) + +site = admin.site + +site.site_header = "DefNucSyR Engagement Database (DED)" + + +class PersonAdmin(admin.ModelAdmin): + list_display = ("__str__", "organisation") + + @admin.display(description="fullname") + def fullname(self, obj): + return f"{obj.first_name} {obj.last_name}" + + +site.register(Person, PersonAdmin) +site.register(Organisation) +site.register(RegulatedEntityType) +site.register(RegulatoryRole) +site.register(EngagementType) +site.register(Engagement) +site.register(EngagementEffort) diff --git a/engagements/apps.py b/engagements/apps.py new file mode 100644 index 0000000..e2f8f69 --- /dev/null +++ b/engagements/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EngagementsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "engagements" diff --git a/engagements/forms.py b/engagements/forms.py new file mode 100644 index 0000000..ae01916 --- /dev/null +++ b/engagements/forms.py @@ -0,0 +1,242 @@ +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Field, Fieldset, Layout, Submit +from django import forms +from django.forms.widgets import HiddenInput + +from .models import Engagement, EngagementEffort + +# TODO - need to handle errors correctly in this form and in the template + + +class EngagementEffortReportingCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + "This is a form", + Field("is_planned"), + Field("proposed_start_date", css_class="w3-input w3-border w3-round", type="date"), + Field("proposed_end_date", css_class="w3-input w3-border w3-round"), + "officers", + "notes", + ), + Submit("submit", "Submit", css_class="w3-button w3-green"), + ) + + class Meta: + model = EngagementEffort + fields = [ + "is_planned", + "proposed_start_date", + "proposed_end_date", + "officers", + "notes", + ] + help_texts = { + "is_planned": ("<br><small><em>To distinguish planned events from retrospective recording</em></small>") + } + widgets = { + "proposed_start_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + "proposed_end_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + } + + +class EngagementEffortRegulationCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + "This is a form", + Field("is_planned"), + Field("proposed_start_date", css_class="w3-input w3-border w3-round", type="date"), + Field("proposed_end_date", css_class="w3-input w3-border w3-round"), + "sub_instruments", + "officers", + ), + Submit("submit", "Submit", css_class="w3-button w3-green"), + ) + + class Meta: + model = EngagementEffort + fields = [ + "is_planned", + "proposed_start_date", + "proposed_end_date", + "officers", + "sub_instruments", + "notes", + ] + help_texts = { + "is_planned": ("<br><small><em>To distinguish planned events from retrospective recording</em></small>") + } + widgets = { + "proposed_start_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + "proposed_end_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + } + + +class EngagementEffortPlanningCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + "This is a form", + Field("is_planned"), + Field("proposed_start_date", css_class="w3-input w3-border w3-round", type="date"), + Field("proposed_end_date", css_class="w3-input w3-border w3-round"), + "officers", + "notes", + ), + Submit("submit", "Submit", css_class="w3-button w3-green"), + ) + + class Meta: + model = EngagementEffort + fields = [ + "is_planned", + "proposed_start_date", + "proposed_end_date", + "officers", + "notes", + ] + help_texts = { + "is_planned": ("<br><small><em>To distinguish planned events from retrospective recording</em></small>") + } + widgets = { + "proposed_start_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + "proposed_end_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + } + + +class EngagementEffortTravelCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + "This is a form", + Field("is_planned"), + Field("proposed_start_date", css_class="w3-input w3-border w3-round", type="date"), + Field("proposed_end_date", css_class="w3-input w3-border w3-round"), + "officers", + ), + Submit("submit", "Submit", css_class="w3-button w3-green"), + ) + + class Meta: + model = EngagementEffort + fields = ["is_planned", "proposed_start_date", "proposed_end_date", "officers"] + widgets = { + "proposed_start_date": forms.DateTimeInput(attrs={"type": "datetime-local"}), + "proposed_end_date": forms.DateTimeInput(attrs={"type": "datetime-local"}), + } + + +class EngagementEffortCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["engagement"].widget = HiddenInput() + self.fields["effort_type"].widget = HiddenInput() + if kwargs.get("initial"): + if not kwargs["initial"]["effort_type"] == "REGULATION": + self.fields["sub_instruments"].widget = HiddenInput() + + class Meta: + model = EngagementEffort + fields = "__all__" + widgets = { + "is_planned": forms.Select( + choices=( + (True, "YES"), + (False, "NO"), + ), + attrs={"class": "w3-border"}, + ), + "proposed_start_date": forms.DateTimeInput( + attrs={"class": "w3-input w3-border w3-round", "type": "date"}, + format="j M y H:i", + ), + "proposed_end_date": forms.DateTimeInput( + attrs={"class": "w3-input w3-border w3-round", "type": "date"}, + format="j M y H:i", + ), + "effort_type": forms.Select(attrs={"class": "w3-select w3-border w3-round"}), + "officers": forms.SelectMultiple(attrs={"class": "w3-select w3-border w3-round"}), + "sub_instruments": forms.SelectMultiple(attrs={"class": "w3-select w3-border w3-round"}), + } + help_texts = { + "proposed_start_date": "<small><em>YYYY-MM-DD HH:MM</em> " + "(<strong>Please include time here</strong>)</small>", + "proposed_end_date": "<small><em>YYYY-MM-DD HH:MM</em> " + "(<strong>Please include time here</strong>)</small>", + } + + +class EngagementCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + try: + self.fields["engagement_type"].queryset = kwargs["initial"]["engagement_type"] + except KeyError: + pass + + class Meta: + model = Engagement + fields = [ + "proposed_start_date", + "proposed_end_date", + "engagement_type", + "officers", + ] + labels = { + "officers": "Inspectors", + } + help_texts = { + "proposed_start_date": "<small><em>YYYY-MM-DD</em></small>", + "proposed_end_date": "<small><em>YYYY-MM-DD</em></small>", + } + widgets = { + "proposed_start_date": forms.DateInput(attrs={"class": "w3-input w3-border w3-round", "type": "date"}), + "proposed_end_date": forms.DateInput(attrs={"class": "w3-input w3-border w3-round", "type": "date"}), + "engagement_type": forms.Select(attrs={"class": "w3-select w3-input w3-border w3-round"}), + "officers": forms.SelectMultiple(attrs={"class": "w3-input w3-border w3-round"}), + } diff --git a/engagements/management/commands/create_engagement_data.py b/engagements/management/commands/create_engagement_data.py new file mode 100644 index 0000000..021d29b --- /dev/null +++ b/engagements/management/commands/create_engagement_data.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand + +from engagements.utils import populate_database + + +class Command(BaseCommand): + help = "python manage.py create_engagement_data" + + # def add_arguments(self, parser): + # parser.add_argument("year", nargs="+", type=int) + + def handle(self, *args, **options): + populate_database() + self.stdout.write(self.style.SUCCESS("Created engagement objects.")) diff --git a/engagements/migrations/0001_initial.py b/engagements/migrations/0001_initial.py new file mode 100644 index 0000000..2516517 --- /dev/null +++ b/engagements/migrations/0001_initial.py @@ -0,0 +1,134 @@ +# Generated by Django 4.0.8 on 2022-11-02 09:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Engagement', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('proposed_start_date', models.DateField()), + ('proposed_end_date', models.DateField(blank=True, null=True)), + ], + options={ + 'ordering': ('proposed_start_date',), + }, + ), + migrations.CreateModel( + name='EngagementType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=56)), + ('description', models.TextField(blank=True, null=True)), + ], + options={ + 'verbose_name_plural': 'Engagement Types', + }, + ), + migrations.CreateModel( + name='Organisation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=128)), + ('slug', models.SlugField(blank=True, max_length=128)), + ('is_regulated_entity', models.BooleanField(default=False)), + ], + options={ + 'verbose_name_plural': 'Organisations', + }, + ), + migrations.CreateModel( + name='RegulatedEntityType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=128)), + ], + options={ + 'verbose_name_plural': 'Regulated Entity Types', + }, + ), + migrations.CreateModel( + name='RegulatoryRole', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=64)), + ('description', models.TextField(max_length=1024)), + ], + options={ + 'verbose_name_plural': 'Regulatory Roles', + }, + ), + migrations.CreateModel( + name='Person', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('first_name', models.CharField(max_length=64)), + ('last_name', models.CharField(max_length=64)), + ('email', models.EmailField(blank=True, max_length=254, null=True)), + ('mobile', models.CharField(blank=True, max_length=64, null=True)), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engagements.organisation')), + ('regulatory_role', models.ManyToManyField(to='engagements.regulatoryrole')), + ], + options={ + 'verbose_name_plural': 'People', + }, + ), + migrations.AddField( + model_name='organisation', + name='ap', + field=models.ManyToManyField(blank=True, related_name='accountable_person', to='engagements.person', verbose_name='Accountable Person'), + ), + migrations.AddField( + model_name='organisation', + name='entitytype', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='engagements.regulatedentitytype'), + ), + migrations.AddField( + model_name='organisation', + name='ih', + field=models.ManyToManyField(blank=True, related_name='information_holder', to='engagements.person', verbose_name='Information Holder'), + ), + migrations.AddField( + model_name='organisation', + name='rp', + field=models.ManyToManyField(blank=True, related_name='responsible_person', to='engagements.person', verbose_name='Responsible Person'), + ), + migrations.CreateModel( + name='EngagementEffort', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('is_planned', models.BooleanField(blank=True, default=True, null=True, verbose_name='Planned')), + ('effort_type', models.CharField(choices=[('TRAVEL', 'Travel'), ('PLANNING', 'Planning'), ('REGULATION', 'Regulation (On-site or Remote)'), ('DISCUSSION', 'Discussion'), ('REPORT', 'Reporting')], max_length=32, verbose_name='Effort Type')), + ('proposed_start_date', models.DateTimeField()), + ('proposed_end_date', models.DateTimeField()), + ('engagement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='effort', to='engagements.engagement')), + ], + options={ + 'verbose_name_plural': 'Engagement Effort', + 'ordering': ('proposed_start_date',), + }, + ), + ] diff --git a/engagements/migrations/0002_initial.py b/engagements/migrations/0002_initial.py new file mode 100644 index 0000000..1a55d23 --- /dev/null +++ b/engagements/migrations/0002_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 4.0.8 on 2022-11-02 09:00 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('instruments', '0001_initial'), + ('engagements', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='engagementeffort', + name='officers', + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='engagementeffort', + name='sub_instruments', + field=models.ManyToManyField(blank=True, related_name='effort', to='instruments.subinstrument'), + ), + migrations.AddField( + model_name='engagement', + name='engagement_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engagements.engagementtype'), + ), + migrations.AddField( + model_name='engagement', + name='external_party', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engagements.organisation'), + ), + migrations.AddField( + model_name='engagement', + name='officers', + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/engagements/migrations/0003_engagementeffort_notes.py b/engagements/migrations/0003_engagementeffort_notes.py new file mode 100644 index 0000000..c3071c2 --- /dev/null +++ b/engagements/migrations/0003_engagementeffort_notes.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.8 on 2023-04-19 17:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("engagements", "0002_initial"), + ] + + operations = [ + migrations.AddField( + model_name="engagementeffort", + name="notes", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/engagements/migrations/__init__.py b/engagements/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/engagements/migrations/__init__.py diff --git a/engagements/models.py b/engagements/models.py new file mode 100644 index 0000000..20bed8d --- /dev/null +++ b/engagements/models.py @@ -0,0 +1,236 @@ +from django.conf import settings +from django.db import models +from django.db.models import Q +from django.utils.text import slugify + +from myuser.models import TeamUser + + +class Common(models.Model): + date_created = models.DateTimeField(auto_now_add=True) + last_modified = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + + +class RegulatoryRole(Common): + name = models.CharField(max_length=64, null=False, blank=False) + description = models.TextField(max_length=1024) + + def __str__(self): + return self.name + + class Meta: + verbose_name_plural = "Regulatory Roles" + + +class Person(Common): + "External person, rather than MOD at this point." + first_name = models.CharField(max_length=64, null=False, blank=False) + last_name = models.CharField(max_length=64, null=False, blank=False) + organisation = models.ForeignKey("Organisation", null=False, blank=False, on_delete=models.CASCADE) + email = models.EmailField(null=True, blank=True) + mobile = models.CharField(max_length=64, null=True, blank=True) + regulatory_role = models.ManyToManyField(RegulatoryRole) + + def __str__(self): + return f"{self.first_name} {self.last_name}" + + class Meta: + verbose_name_plural = "People" + + +class RegulatedEntityType(Common): + name = models.CharField(max_length=128, null=False, blank=False) + + def __str__(self): + return self.name + + class Meta: + verbose_name_plural = "Regulated Entity Types" + + +class Organisation(Common): + name = models.CharField(max_length=128, blank=False) + slug = models.SlugField(max_length=128, blank=True) + is_regulated_entity = models.BooleanField(default=False) + entitytype = models.ForeignKey(RegulatedEntityType, null=True, blank=True, on_delete=models.CASCADE) + # Responsible Person + rp = models.ManyToManyField( + Person, + blank=True, + related_name="responsible_person", + verbose_name="Responsible Person", + ) + # Accountable Person + ap = models.ManyToManyField( + Person, + blank=True, + related_name="accountable_person", + verbose_name="Accountable Person", + ) + # Information Holder + ih = models.ManyToManyField( + Person, + blank=True, + related_name="information_holder", + verbose_name="Information Holder", + ) + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + def lead_team(self): + teams = {ot.team for ot in self.lead_inspector.all()} + if not teams: + return "NA" + if len(teams) > 1: + if len(set([t.name for t in teams])) > 1: + return "Shared Lead" + else: + return teams.pop() + else: + return teams.pop() + + class Meta: + verbose_name_plural = "Organisations" + + +class EngagementType(Common): + name = models.CharField(max_length=56) + description = models.TextField(null=True, blank=True) + + def __str__(self): + return self.name + + class Meta: + verbose_name_plural = "Engagement Types" + + +class EngagementManager(models.Manager): + def sp(self): + # TODO fix this - returns duplicates + qs = self.filter(officers__team__name="Submarines and Propulsion") + return qs.exclude(Q(engagement_type__name="INSPECTION") | Q(engagement_type__name="ASSESSMENT")) + + def sp_regulatory(self): + "Assessments and Inspectons only" + qs = self.filter(officers__team__name="Submarines and Propulsion") + return qs.filter( + Q(engagement_type__name="INSPECTION") + | Q(engagement_type__name="ASSESSMENT") + | Q(engagement_type__name="L1RIF") + | Q(engagement_type__name="L2RIF") + | Q(engagement_type__name="L3RIF") + | Q(engagement_type__name="L4RIF") + | Q(engagement_type__name="SAMPLING") + ) + + def tr(self): + return self.filter(officers__team__name="Transport") + + +class Engagement(Common): + proposed_start_date = models.DateField(null=False, blank=False) + proposed_end_date = models.DateField(null=True, blank=True) + engagement_type = models.ForeignKey(EngagementType, on_delete=models.CASCADE) + external_party = models.ForeignKey(Organisation, on_delete=models.CASCADE) + officers = models.ManyToManyField(TeamUser) + + objects = EngagementManager() + + friendly_types = { + "INSPECTION": "Inspection", + "ASSESSMENT": "Assessment", + "SAMPLING": "Sampling", + "L1RIF": "L1 RIF", + "L2RIF": "L2 RIF", + "L3RIF": "L3 RIF", + "L4RIF": "L4 RIF", + } + + def total_planning_effort(self): + p_effort = self.engagementeffort_set.all().filter(is_planned=True) + return sum([x.effort_total_hours() for x in p_effort]) + + def total_planning_effort_per_officer(self): + # TODO - check this algorithm. It's not quite right is it? + p_effort = self.engagementeffort_set.all().filter(is_planned=True) + offs = sum([x.officers.count() for x in p_effort]) + return sum([x.effort_total_hours() for x in p_effort]) / offs + + def total_effort(self): + return 0 + + def friendly_type(self): + return self.friendly_types[self.engagement_type.name] + + def dscs(self): + "Return all declared DSCs as part of REGULATION effort" + dscs = set() + for ee in EngagementEffort.objects.filter(engagement=self, effort_type="REGULATION"): + for si in ee.sub_instruments.all(): + dscs.add(si) + return dscs + + def __str__(self): + return f"{self.engagement_type.name} at {self.external_party} ({self.proposed_start_date})" + + class Meta: + ordering = ("proposed_start_date",) + + +class EngagementEffort(Common): + choices = ( + ("TRAVEL", "Travel"), + ("PLANNING", "Planning"), + ("REGULATION", "Regulation (On-site or Remote)"), + ("DISCUSSION", "Discussion"), + ("REPORT", "Reporting"), + ) + is_planned = models.BooleanField(null=True, blank=True, verbose_name="Planned", default=True) + effort_type = models.CharField(max_length=32, choices=choices, verbose_name="Effort Type") + proposed_start_date = models.DateTimeField() + proposed_end_date = models.DateTimeField() + engagement = models.ForeignKey(Engagement, on_delete=models.CASCADE, related_name="effort") + officers = models.ManyToManyField(settings.AUTH_USER_MODEL) + sub_instruments = models.ManyToManyField("instruments.SubInstrument", blank=True, related_name="effort") + notes = models.TextField(null=True, blank=True) + + class Meta: + verbose_name_plural = "Engagement Effort" + ordering = ("proposed_start_date",) + + def effort_total_hours(self): + "Returns total effort for this engagement." + delta = self.proposed_end_date - self.proposed_start_date + return delta.seconds / 60 / 60 + + def effort_total_planned_hours(self): + "Returns total planned effort for this engagement." + if self.is_planned: + delta = self.proposed_end_date - self.proposed_start_date + return delta.seconds / 60 / 60 + else: + return 0 + + def effort_actual(self): + "Returns effort that where is_planned is false." + if not self.is_planned: + delta = self.proposed_end_date - self.proposed_start_date + return delta.seconds / 60 / 60 + else: + return 0 + + def effort_per_officer_hours(self): + "Returns effort in hours per officer" + delta = (self.proposed_end_date - self.proposed_start_date) / self.officers.count() + return delta.seconds / 60 / 60 + + def __str__(self): + return f"{self.effort_type} effort for {self.engagement}: {self.proposed_end_date - self.proposed_start_date}" diff --git a/engagements/static/js/yoap.js b/engagements/static/js/yoap.js new file mode 100644 index 0000000..84d5a2f --- /dev/null +++ b/engagements/static/js/yoap.js @@ -0,0 +1,12 @@ +// bllopcks +const heading = document.createElement("h3"); +const headingText = document.createTextNode("Snatch grab"); +heading.appendChild(headingText); + +const mydiv = document.body.querySelector("#test-container") +mydiv.appendChild(heading); + +var i; +for (i = 0; i < 15; i++) { + +} diff --git a/engagements/templates/engagements/engagement_create.html b/engagements/templates/engagements/engagement_create.html new file mode 100644 index 0000000..1e72ca0 --- /dev/null +++ b/engagements/templates/engagements/engagement_create.html @@ -0,0 +1,29 @@ +{% extends "core/base.html" %} + +{% block title %}Create new engagement{% endblock title %} + +{% block content %} + +<div class="w3-container w3-cell-row w3-margin-bottom"> + <h2>{{ title }}</h2> + <div class="w3-panel w3-light-blue w3-round w3-leftbar w3-border-blue w3-display-container"> + + <span onclick="this.parentElement.style.display='none'" + class="w3-button w3-display-topright">×</span> + <h4>Step 1</h4> + <p>To roughly plan out future events, you provide the minimal details here: <strong>start date</strong>, <strong>end date</strong> (optional),the <strong>type of Engagement</strong> (Assessment, Inspection or Sampling), the <strong>external site</strong> or operation and finally the <strong>inspectors</strong> who are carrying out the work.</p> + <h4>Step 2</h4> + <p>So that we can track the finer details involved with an Assessment or Inspection, each Engagement comprises additional <em>components</em>, such as <strong>Planning</strong>, <strong>On-site</strong> and <strong>Reporting</strong>. Inspector time can be allocated to these components. In addition, each compontent can be associated with <strong>Instruments</strong>, such as <strong>DSCs</strong>, etc. After you create the overarching Engagement using this form, you will have the opportunity to add components.</p> + </div> + + <hr> + <div class="w3-container w3-cell-middle"> + <h4>Enter main details:</h4> + <form method="post">{% csrf_token %} + {{ form.as_p }} + <input type="submit" class="w3-btn w3-green" value="Save"> + </form> + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/engagement_detail.html b/engagements/templates/engagements/engagement_detail.html new file mode 100644 index 0000000..85ef220 --- /dev/null +++ b/engagements/templates/engagements/engagement_detail.html @@ -0,0 +1,78 @@ +{% extends "core/base.html" %} +{% load table_extras %} + +{% block title %}{{ engagement }}{% endblock title %} + +{% block content %} + +<div class="w3-container"> + <div class="w3-container w3-center"> + <h2>{{ engagement.friendly_type }} at {{ engagement.external_party }}</h2> + </div> + <div class="w3-row"> + <div class="w3-container"> + <div> + <a href="{% url 'engagements:edit' engagement.pk %}">Edit Engagement</a> + </div> + <section class="w3-container w3-blue"> + <h3>Details</h3> + </section> + <table class="w3-table w3-border w3-light-gray w3-bordered"> + <tr> + <td><strong>Date</strong></td> + <td>{{ engagement.proposed_start_date|date:'l' }} - {{ engagement.proposed_start_date|date:'j M Y' }}</td> + </tr> + <tr> + <td><strong>Site/Operation:</strong></td> + <td>{{ engagement.external_party }} </td> + </tr> + <tr> + <td><strong>Subject of Activity</strong></td> + <td> + <p>Summmary text</p> + <ul class="w3-ul"> + {% for t in dscs %} + <li><a href='#'>{{ t }}</a></li> + {% endfor %} + </ul> + </td> + </tr> + <tr> + <td><strong>Inspectors:</strong></td> + <td>{{ engagement.officers.all|commalist }}</td> + </tr> + <tr> + <td><strong>Planned Effort:</strong></td> + <td>{{ effort_planned|floatformat }} hrs</td> + </tr> + <tr> + <td><strong>Actual Effort:</strong></td> + <td>{{ effort_actual|floatformat }} hrs</td> + </tr> + <tr> + <td><strong>Total Effort:</strong></td> + <td>{{ effort_total|floatformat }} hrs</td> + </tr> + </table> + <hr> + </div> + + <div class="w3-container w3-light-gray"> + <h3>Effort for this engagement</h3> + Add: + <a href="{% url 'engagements:effort_create' engagement.pk "TRAVEL" %}">Travel</a> | + <a href="{% url 'engagements:effort_create' engagement.pk "PLANNING" %}">Planning</a> | + <a href="{% url 'engagements:effort_create' engagement.pk "REGULATION" %}">Regulation</a> | + <a href="{% url 'engagements:effort_create' engagement.pk "REPORTING" %}">Reporting</a> + + {% if effort %} + {% for e in effort.all %} + <div id="planned_swap_{{ e.id }}"> + {% include "engagements/snippets/effort_summary_panel.html" with e=e %} + </div> + {% endfor %} + {% endif %} + </div> + </div> + + {% endblock content %} diff --git a/engagements/templates/engagements/engagement_effort_create.html b/engagements/templates/engagements/engagement_effort_create.html new file mode 100644 index 0000000..65e2e21 --- /dev/null +++ b/engagements/templates/engagements/engagement_effort_create.html @@ -0,0 +1,20 @@ +{% extends "core/base.html" %} + +{% load crispy_forms_tags %} + +{% block title %}Add {{ etype }} effort to Engagement{% endblock title %} + +{% block content %} + + <div class="w3-container w3-cell-row w3-margin-bottom"> + <h2>Register your {{ etype|lower }} effort for the {{ engagement.engagement_type.name|title }} event at {{ engagement.external_party }} on {{ engagement.proposed_start_date }}</h2> + + <div class="w3-container w3-cell-middle"> + <h4>Enter details:</h4> + <form method="post">{% csrf_token %} + {% crispy form form.helper %} + </form> + </div> + </div> + +{% endblock content %} diff --git a/engagements/templates/engagements/engagement_form.html b/engagements/templates/engagements/engagement_form.html new file mode 100644 index 0000000..e05c639 --- /dev/null +++ b/engagements/templates/engagements/engagement_form.html @@ -0,0 +1,32 @@ +{% extends "core/base.html" %} + +{% load static %} + +{% block title %}Create new engagement{% endblock title %} +{% block extra_head_tags %} +<link rel="stylesheet" href="{% static 'css/styles.css' %}"/> +{% endblock extra_head_tags %} + +{% block content %} + +<div class="format-container"> + <div class="form-thing"> + <h2>{{ title }}</h2> + <p>TAKE THIS OUT</p> + <span onclick="this.parentElement.style.display='none'" + class="w3-button w3-display-topright">×</span> + <h4>Step 1</h4> + <p>To roughly plan out future events, you provide the minimal details here: <strong>start date</strong>, <strong>end date</strong> (optional),the <strong>type of Engagement</strong> (Assessment, Inspection or Sampling), the <strong>external site</strong> or operation and finally the <strong>inspectors</strong> who are carrying out the work.</p> + <h4>Step 2</h4> + <p>So that we can track the finer details involved with an Assessment or Inspection, each Engagement comprises additional <em>components</em>, such as <strong>Planning</strong>, <strong>On-site</strong> and <strong>Reporting</strong>. Inspector time can be allocated to these components. In addition, each compontent can be associated with <strong>Instruments</strong>, such as <strong>DSCs</strong>, etc. After you create the overarching Engagement using this form, you will have the opportunity to add components.</p> + </div> + <div class="form-thing"> + <h4>Enter main details:</h4> + <form method="post">{% csrf_token %} + {{ form.as_p }} + <input type="submit" class="w3-btn w3-green" value="Save"> + </form> + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/engagement_plan_for_site.html b/engagements/templates/engagements/engagement_plan_for_site.html new file mode 100644 index 0000000..55ab54b --- /dev/null +++ b/engagements/templates/engagements/engagement_plan_for_site.html @@ -0,0 +1,35 @@ +{% extends "core/base.html" %} + +{% block title %}Create new engagement{% endblock title %} + +{% block content %} + +<div class="w3-container w3-cell-row w3-margin-bottom"> + <h2>{{ title }}</h2> + <div class="w3-panel w3-light-blue w3-round w3-leftbar w3-border-blue w3-display-container"> + + <span onclick="this.parentElement.style.display='none'" + class="w3-button w3-display-topright">×</span> + <h4>Step 1</h4> + <p>To roughly plan out future events, you provide the minimal details here: <strong>start date</strong>, <strong>end date</strong> (optional),the <strong>type of Engagement</strong> (Assessment, Inspection or Sampling), the <strong>external site</strong> or operation and finally the <strong>inspectors</strong> who are carrying out the work.</p> + <h4>Step 2</h4> + <p>So that we can track the finer details involved with an Assessment or Inspection, each Engagement comprises additional <em>components</em>, such as <strong>Planning</strong>, <strong>On-site</strong> and <strong>Reporting</strong>. Inspector time can be allocated to these components. In addition, each compontent can be associated with <strong>Instruments</strong>, such as <strong>DSCs</strong>, etc. After you create the overarching Engagement using this form, you will have the opportunity to add components.</p> + </div> + + <hr> + <div class="w3-cell-row"> + <div class="w3-container w3-cell"> + <h4>Enter main details:</h4> + <form method="post">{% csrf_token %} + {{ form.as_p }} + <input type="submit" class="w3-btn w3-green" value="Save"> + </form> + </div> + <div class="w3-container w3-cell"> + <h4>Current Engagement Plan for X</h4> + </div> + + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/ep_org.html b/engagements/templates/engagements/ep_org.html new file mode 100644 index 0000000..202100a --- /dev/null +++ b/engagements/templates/engagements/ep_org.html @@ -0,0 +1,427 @@ +{% extends "core/base.html" %} + +{% block title %}Create new engagement{% endblock title %} + +{% load table_extras %} + +{% load static %} + +{% block content %} + +<div class="w3-container"> + <h2>Engagement Plans for {{ entity.name }}</h2> + <a href="{% url 'engagements:create' entity.slug 'reg' %}">Add New Regulatory Engagement</a> | <a href="{% url 'engagements:create' entity.slug %}">Add New Engagement</a> +</div> + +<div class="w3-container"> + <h3>2023</h3> + <table class="ep-table"> + <thead> + <tr> + <th>Start Date</th> + <th>End Date</th> + <th>Engagement</th> + <th>Engagement Type</th> + <th>Area/Theme</th> + <th>Inspectors Involved</th> + <th>DSCs</th> + <th>Travel Effort</th> + <th>Planning and Regulation Effort</th> + <th>Completion Status</th> + </tr> + </thead> + <tbody> + {% for e in engagements %} + <tr> + <td>{{ e.proposed_start_date|date:'j M Y' }}</td> + <td>{{ e.proposed_end_date|date:'j M Y' }}</td> + <td><a href="{% url 'engagements:engagement_detail' e.pk %}">{{ e }}</a></td> + <td>{{ e.friendly_type }}</td> + <td>Area Theme</td> + <td> + <ul class="w3-ul"> + {% for inspector in e.officers.all %} + <li>{{ inspector }} </li> + {% endfor %} + </ul> + </td> + <td> + <ul class="w3-ul"> + {% for dsc in e.dscs %} + <li><a href="#">{{ dsc.short }}</a></li> + {% endfor %} + </ul> + </td> + <td>TBC</td> + <td>TBC</td> + <td class="ep-table">Incomplete</td> + </tr> + {% endfor %} + </tbody> + </table> + +</div> + +<hr> + +<div class="w3-container"> + <div class="w3-cell-row"> + <div class="w3-container w3-cell"> + <h3>Total DSC coverage</h3> + <table class="summary-table"> + <tr> + <th>DSC</th> + <th>Allocated time</th> + </tr> + {% for dsc in dscs %} + <tr> + <td>{{ dsc }} </td> + <td>{{ dsc | effort_for_org:entity }}</td> + </tr> + {% endfor %} + </table> + </div> + <div class="w3-container w3-cell"> + <h3>Another table of data</h3> + <table class="summary-table"> + <tr> + <th>DSC</th> + <th>Allocated time</th> + </tr> + {% for dsc in dscs %} + <tr> + <td>{{ dsc }} </td> + <td>{{ dsc | effort_for_org:entity }}</td> + </tr> + {% endfor %} + </table> + </div> + + </div> +</div> + +<style type="text/css" media="screen"> + + /* (A) CONTAINER */ + /* https://code-boxx.com/beginner-create-grid-html-css/ */ + #grid-col { + + /* (A1) GRID LAYOUT */ + display: grid; + + /* (A2) SPECIFY COLUMNS */ + grid-template-columns: 75px repeat(31, 40px); + + /* we can also specify exact pixels, percentage, repeat + grid-template-columns: 50px 100px 150px; + grid-template-columns: 25% 50% 25%; + grid-template-columns: 100px 20% auto; + grih-template-columns: repeat(3, auto); */ + } + + /* (B) GRID CELLS */ + div.cell { + background: white; + border: 1px solid lightgray; + padding: 10px; + } + + div.cellred{ + background: red; + border: 1px solid lightgray; + padding: 10px; + } + + div.cellyellow{ + background: yellow; + border: 1px solid lightgray; + padding: 10px; + } + div.cellorange{ + background: orange; + border: 1px solid lightgray; + padding: 10px; + } + /* (C) RESPONSIVE - 1 COLUMN ON SMALL SCREENS */ + /* @media screen and (max-width: 640px) { + #grid-col { grid-template-columns: 100%; } + } + */ +/* </style> +<!-- <div class="w3-container w3-padding-16"> --> +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell"></div><div class="cell">1</div> --> +<!-- <div class="cell">2</div><div class="cell">3</div> --> +<!-- <div class="cell">4</div><div class="cell">5</div> --> +<!-- <div class="cell">6</div><div class="cell">7</div> --> +<!-- <div class="cell">8</div><div class="cell">9</div> --> +<!-- <div class="cell">10</div><div class="cell">11</div> --> +<!-- <div class="cell">12</div><div class="cell">13</div> --> +<!-- <div class="cell">14</div><div class="cell">15</div> --> +<!-- <div class="cell">16</div><div class="cell">17</div> --> +<!-- <div class="cell">18</div><div class="cell">19</div> --> +<!-- <div class="cell">20</div><div class="cell">21</div> --> +<!-- <div class="cell">22</div><div class="cell">23</div> --> +<!-- <div class="cell">24</div><div class="cell">25</div> --> +<!-- <div class="cell">26</div><div class="cell">27</div> --> +<!-- <div class="cell">28</div><div class="cell">29</div> --> +<!-- <div class="cell">30</div><div class="cell">31</div> --> +<!-- </div> --> +<!-- </div> --> +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Jan</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cellred"><small></small></div><div class="cellred"><small></small></div> --> +<!-- <div class="cellred"><small></small></div><div class="cellred"><small></small></div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Feb</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cellyellow"><small></small></div><div class="cellyellow"><small></small></div> --> +<!-- <div class="cellyellow"><small></small></div><div class="cellyellow"><small></small></div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Mar</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cellorange"><small></small></div><div class="cellorange"><small></small></div> --> +<!-- <div class="cellorange"><small></small></div><div class="cellorange"><small></small></div> --> +<!-- <div class="cellorange"><small></small></div><div class="cellorange"><small></small></div> --> +<!-- <div class="cellorange"><small></small></div><div class="cellorange"><small></small></div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Apr</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">May</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Jun</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Jul</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Aug</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Sep</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Oct</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Nov</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Dec</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + + +<script src="{% static 'js/yoap.js' %}"></script> + +{% endblock content %} diff --git a/engagements/templates/engagements/index.html b/engagements/templates/engagements/index.html new file mode 100644 index 0000000..8d5df36 --- /dev/null +++ b/engagements/templates/engagements/index.html @@ -0,0 +1,30 @@ +{% extends "core/base.html" %} + +{% block title %}Engagements{% endblock title %} + +{% block content %} + +<div class="w3-container"> + <h2>Engagement Planning</h2> + <div class="w3-container"> + + <h3>Your entities</h3> + + <table class="w3-table w3-bordered"> + <tr> + <th>Engagement Plan</th> + <th>Scheduled Engagements</th> + <th>Total time</th> + </tr> + {% for e in entities %} + <tr> + <td><a href="{% url 'engagements:plan_for_org' e.slug %}">{{ e.name }}</a></td> + <td>{{ e.engagement_set.all.count }}</td> + <td>NA</td> + </tr> + {% endfor %} + </table> + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/organisations.html b/engagements/templates/engagements/organisations.html new file mode 100644 index 0000000..5bbd1b5 --- /dev/null +++ b/engagements/templates/engagements/organisations.html @@ -0,0 +1,50 @@ +{% extends "core/base.html" %} +{% load table_extras %} + +{% block title %}Regulated Entities{% endblock title %} + +{% block content %} + +<div class="w3-container"> + <h1>Regulated Entities</h1> + <div class="w3-container"> + <table class="w3-table w3-bordered w3-striped"> + <tr> + <th>Entity</th> + <th>Team</th> + <th>Lead Inspector[s]</th> + <th>Responsible Person[s]</th> + <th>Accountable Person[s]</th> + </tr> + {% for e in entities %} + <tr> + <td>{{ e.name }}</td> + <td>{{ e.lead_team }}</td> + <td> + {% if e.lead_inspector.all %} + {{ e.lead_inspector.all|commalist }} + {% endif %} + </td> + <td> + <!-- rp --> + {% if e.rp.all %} + {% for p in e.rp.all %} + {{ p }} + {% endfor %} + {% endif %} + </td> + <td> + <!-- ap --> + {% if e.ap.all %} + {% for p in e.ap.all %} + {{ p }} + {% endfor %} + {% endif %} + </td> + </tr> + {% endfor %} + </table> + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/snippets/effort_summary_panel.html b/engagements/templates/engagements/snippets/effort_summary_panel.html new file mode 100644 index 0000000..235a67a --- /dev/null +++ b/engagements/templates/engagements/snippets/effort_summary_panel.html @@ -0,0 +1,41 @@ +{% load table_extras %} + +{% if e.is_planned %} + <div class="w3-panel w3-pale-green w3-topbar w3-border-green"> +{% else %} + <div class="w3-panel w3-light-gray w3-topbar w3-border-green"> +{% endif %} + <div class="w3-cell-row"> + <div class="w3-cell w3-align-left"> + <h5>{{ e.proposed_start_date|date:'j M Y' }} - {{ e.effort_type }}</h5> + </div> + <div class="w3-cell w3-right-align"> + <p> {{ e.effort_total_hours|floatformat }} hrs</p> + Planned: {{ e.is_planned }} + <button hx-get="{% url 'engagements:htmx-effort-planned' e.id %}" hx-target="#planned_swap_{{ e.id }}"> + Flip + </button> + </div> + </div> + <div> + <strong>Inspectors:</strong> {{ e.officers.all|commalist }} + </div> + <div> + <strong>Start time</strong>: {{ e.proposed_start_date|date:'H:i' }} + {% if e.proposed_end_date %} + - {{ e.proposed_end_date|date:'H:i' }} + {% endif %} + </div> + + <div> + {% if e.sub_instruments.all %} + <strong>DSCs</strong>: + <ul> + {% for dsc in e.sub_instruments.all %} + <li><a href="#">{{ dsc.title }}</a></li> + {% endfor %} + </ul> + {% endif %} + + </div> +</div> diff --git a/engagements/tests/__init__.py b/engagements/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/engagements/tests/__init__.py diff --git a/engagements/tests/test_forms.py b/engagements/tests/test_forms.py new file mode 100644 index 0000000..b6aab9a --- /dev/null +++ b/engagements/tests/test_forms.py @@ -0,0 +1,45 @@ +from django.test import TestCase + +from engagements.forms import EngagementEffortCreateForm +from engagements.models import Engagement, EngagementType, Organisation +from myuser.models import TeamUser + + +class EngagementEffortCreate(TestCase): + def setUp(self): + data = { + "proposed_start_date": "2022-10-01", + "engagement_type": EngagementType.objects.create(name="ET1"), + "external_party": Organisation.objects.create(name="O1"), + } + self.e = Engagement.objects.create(**data) + self.user = TeamUser.objects.create_user(email="ming@ming.com") + + def test_basic_validation(self): + form = EngagementEffortCreateForm( + data={ + "is_planned": True, + "proposed_start_date": "2022-10-10 10:00", + "proposed_end_date": "2022-10-10 12:00", + "engagement": self.e, + "effort_type": "PLANNING", + "officers": [self.user], + } + ) + self.assertFalse(form.errors) + + def test_basic_validation_on_bad_entry(self): + form = EngagementEffortCreateForm( + data={ + "is_planned": True, + "proposed_start_date": "20240-10-10 10:00", + "proposed_end_date": "2022-10-10 12:00", + "engagement": self.e, + "effort_type": "bobbins", + "officers": [self.user], + "sub_instruments": [""], + } + ) + self.assertTrue(form.errors["effort_type"]) + self.assertTrue(form.errors["proposed_start_date"]) + self.assertTrue(form.errors["sub_instruments"]) diff --git a/engagements/tests/test_models.py b/engagements/tests/test_models.py new file mode 100644 index 0000000..08c5169 --- /dev/null +++ b/engagements/tests/test_models.py @@ -0,0 +1,30 @@ +import pytest +from django.test import TestCase + +from engagements.utils import populate_database + + +class TestModels(TestCase): + @classmethod + def setUpTestData(cls): + cls.data = populate_database() + + @pytest.mark.django_db + def test_check_all_dcs(self): + dscs = self.data.get("sub_instruments") + self.assertEqual(dscs[0].title, "DSC 1 - Title 1") + + @pytest.mark.django_db + def test_effort_by_type(self): + e = self.data["engagements"][0] + total_planning = sum([x.effort_total_planned_hours() for x in e.effort.filter(effort_type="PLANNING")]) + total_travel = sum([x.effort_total_planned_hours() for x in e.effort.filter(effort_type="TRAVEL")]) + total_regulation = sum([x.effort_total_planned_hours() for x in e.effort.filter(effort_type="REGULATION")]) + assert total_planning == 4.25 + assert total_regulation == 0 + assert total_travel == 1 + + # TODO finish this test! + def test_total_effort_for_engagement(self): + e = self.data["engagements"][0] + assert e.total_effort() == 5.25 diff --git a/engagements/tests/test_views.py b/engagements/tests/test_views.py new file mode 100644 index 0000000..f25eb3a --- /dev/null +++ b/engagements/tests/test_views.py @@ -0,0 +1,91 @@ +import datetime +from http import HTTPStatus + +from django.test import RequestFactory, TestCase +from django.urls import reverse + +from engagements import models, views +from engagements.utils import populate_database + + +class TestModels(TestCase): + @classmethod + def setUpTestData(cls): + cls.request = RequestFactory() # for use in _ep_request_factory test + cls.data = populate_database() + + def test_dscs_for_ep(self): + org = self.data["orgs"][0] + # we set up an engagement and effort for this org + et = models.EngagementType.objects.get(name="INSPECTION") + si = self.data["sub_instruments"][0] + si2 = self.data["sub_instruments"][2] + si3 = self.data["sub_instruments"][3] + # si_not = self.data["sub_instruments"][1] + engagement = models.Engagement.objects.create( + proposed_start_date=datetime.date(2022, 10, 10), + proposed_end_date=datetime.date(2022, 10, 10), + engagement_type=et, + external_party=org, + ) + ef1 = models.EngagementEffort.objects.create( + is_planned=True, + effort_type="REGULATION", + proposed_start_date=datetime.date(2022, 10, 10), + proposed_end_date=datetime.date(2022, 10, 10), + engagement=engagement, + ) + ef1.sub_instruments.add(si) # DSC 1 + ef1.sub_instruments.add(si2) # DSC 3 + ef1.sub_instruments.add(si3) # DSC 4 + ef1.save() + url = reverse("engagements:plan_for_org", kwargs={"orgslug": org.slug}) + self.client.force_login(self.data["superuser"]) + response = self.client.get(url) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertTrue(response.context["entity"]) + self.assertEqual(response.context["entity"].name, org.name) + self.assertIn(si, response.context["dscs"]) + self.assertIn(si2, response.context["dscs"]) + self.assertIn(si3, response.context["dscs"]) + self.assertEqual(response.context["dscs"].count(), 3) + # self.assertNotIn(si_not, response.context["dscs"]) + + def test_dscs_for_ep_request_factory(self): + """ + On the EP page, we expect to see a list of all DSCs related to effort + for this organisation. + + Included this here for reference + """ + org = self.data["orgs"][0] + url = reverse("engagements:plan_for_org", kwargs={"orgslug": org.slug}) + request = self.request.get(url) + request.user = self.data["superuser"] + response = views.engagement_plan_for(request, org.slug) + self.assertEqual(response.status_code, HTTPStatus.OK) + + +class TestEngagementEffortView(TestCase): + @classmethod + def setUpTestData(cls): + cls.request = RequestFactory() # for use in _ep_request_factory test + cls.data = populate_database() + + def test_get_blank_form(self): + url = reverse("engagements:effort_create", kwargs={"eid": 1, "etype": "PLANNING"}) + self.client.force_login(self.data["superuser"]) + request = self.request.get(url) + request.user = self.data["superuser"] + response = views.engagement_effort_create(request, eid=1, etype="PLANNING") + self.assertEqual(response.status_code, HTTPStatus.OK) + + # def test_post_data(self): + # url = reverse( + # "engagements:effort_create", kwargs={"eid": 1, "etype": "PLANNING"} + # ) + # self.client.force_login(self.data["superuser"]) + # request = self.request.post(url, {"proposed_start_date": "toss"}) + # request.user = self.data["superuser"] + # response = views.engagement_effort_create(request, eid=1, etype="PLANNING") + # self.assertEqual(response.status_code, HTTPStatus.OK) diff --git a/engagements/urls.py b/engagements/urls.py new file mode 100644 index 0000000..7b6ddd3 --- /dev/null +++ b/engagements/urls.py @@ -0,0 +1,29 @@ +from django.urls import path + +from . import views + +app_name = "engagements" +urlpatterns = [ + path("", views.engagement_planning, name="home"), + path("<int:pk>", views.engagement_detail, name="engagement_detail"), + path("plan/<slug:orgslug>/", views.engagement_plan_for, name="plan_for_org"), + path( + "regulatedentities/", + views.RegulatedEntitiesView.as_view(), + name="regulatedentities", + ), + path("edit/<int:eid>", views.engagement_edit, name="edit"), + path("create/<slug:slug>/", views.engagement_create, name="create"), + path("create/<slug:slug>/<str:reg>", views.engagement_create, name="create"), + path( + "effort/create/<int:eid>/<str:etype>", + views.engagement_effort_create, + name="effort_create", + ), +] + +htmx_urls = [ + path("htmx-effort-planned/<int:effid>", views.htmx_effort_planned, name="htmx-effort-planned") +] + +urlpatterns = urlpatterns + htmx_urls diff --git a/engagements/utils.py b/engagements/utils.py new file mode 100644 index 0000000..2c4ff72 --- /dev/null +++ b/engagements/utils.py @@ -0,0 +1,186 @@ +import random +from collections import defaultdict +from datetime import date, datetime + +from faker import Faker + +from engagements.models import ( + Engagement, + EngagementEffort, + EngagementType, + Organisation, + Person, + RegulatedEntityType, + RegulatoryRole, +) +from instruments.models import Instrument, SubInstrument +from myuser.models import Team, TeamUser + + +def populate_database(): + out = defaultdict(list) + fake = Faker(locale="en_GB") + + # Users and teams + TeamUser.objects.all().delete() + Team.objects.all().delete() + teams = ["Submarines and Propulsion", "Transport"] + Team.objects.all().delete() + tdefnuc = Team.objects.create(name=teams[0]) + u1 = TeamUser.objects.create_superuser( + email="lemon@lemon.com", + password="lemonlemon", + ) + u1.first_name = "Matthew" + u1.last_name = "Lemon" + u1.team = tdefnuc + u1.designation = "LI8" + u1.is_active = True + u1.save() + out["superuser"] = u1 + + desigs = ["LI1", "LI2", "LI3", "LI4", "LI5", "LI6"] + for p in range(6): + first_name = fake.first_name() + last_name = fake.last_name() + u = TeamUser.objects.create_superuser( + email=f"{first_name.lower()}@theregulator.com", + password="fakepassword", + ) + u.first_name = first_name + u.last_name = last_name + u.team = Team.objects.create(name=random.choice(teams)) + u.designation = desigs[p] + out["users"].append(u) + u.save() + + # RegulatoryRoles + RegulatoryRole.objects.all().delete() + RegulatoryRole.objects.create( + name="Responsible Person", + description="The Regulated Person charged with managing, etc", + ) + RegulatoryRole.objects.create(name="Accountable Person") + RegulatoryRole.objects.create( + name="Information Holder", + description="A regulated person who must ensure etc.", + ) + # RegulatedEntityTypes + RegulatedEntityType.objects.all().delete() + ret1 = RegulatedEntityType.objects.create(name="Site") + RegulatedEntityType.objects.create(name="Operation") + RegulatedEntityType.objects.create(name="Distrubuted Site") + # Organisations + Organisation.objects.all().delete() + for _ in range(10): + o = Organisation.objects.create(name=fake.company(), is_regulated_entity=True, entitytype=ret1) + if random.choice([1, 2, 3]) == 2: + u1.lead_for.add(o) + u1.save() + out["orgs"].append(o) + o1 = Organisation.objects.create(name="MOD", is_regulated_entity=False) + out["orgs"].append(o1) + # Instruments + Instrument.objects.all().delete() + j = Instrument.objects.create( + name="JSP 628", + long_title="Security Regulation of the DNE", + designator="JSP628", + owner=o1, + ) + # Create the DSCs + Faker.seed(0) + SubInstrument.objects.all().delete() + for n in range(1, 17): + si = SubInstrument.objects.create( + title=f"DSC {str(n)} - Title {n}", + itype="DSC", + parent=j, + short=f"DSC {n}", + description=fake.paragraph(nb_sentences=2), + rationale=fake.paragraph(nb_sentences=3), + ) + out["sub_instruments"].append(si) + for d in SubInstrument.objects.filter(itype="DSC"): + i = SubInstrument.objects.create( + title=f"DSTAIG {d.pk} - Title {d.pk}", + is_guidance=True, + itype="DSTAIG", + parent=j, + description=fake.paragraph(nb_sentences=2), + rationale=fake.paragraph(nb_sentences=3), + ) + out["sub_instruments"].append(i) + i.relative.add(d) + + # EngagementType + EngagementType.objects.all().delete() + EngagementType.objects.create(name="EMAIL", description=fake.paragraph(4)) + EngagementType.objects.create(name="MEETING", description=fake.paragraph(4)) + EngagementType.objects.create(name="BRIEFING", description=fake.paragraph(4)) + EngagementType.objects.create(name="TEAMSCALL", description=fake.paragraph(4)) + EngagementType.objects.create(name="PHONECALL", description=fake.paragraph(4)) + EngagementType.objects.create(name="L1RIF", description=fake.paragraph(4)) + EngagementType.objects.create(name="L2RIF", description=fake.paragraph(4)) + EngagementType.objects.create(name="L3RIF", description=fake.paragraph(4)) + EngagementType.objects.create(name="L4RIF", description=fake.paragraph(4)) + EngagementType.objects.create(name="SAMPLING", description=fake.paragraph(4)) + EngagementType.objects.create(name="INSPECTION", description=fake.paragraph(4)) + EngagementType.objects.create(name="ASSESSMENT", description=fake.paragraph(4)) + + # People + Person.objects.all().delete() + o_pks = [o.pk for o in Organisation.objects.all()] + for _ in range(5): + p = Person( + first_name=fake.first_name(), + last_name=fake.last_name(), + organisation=Organisation.objects.get(pk=random.choice(o_pks)), + mobile=fake.cellphone_number(), + ) + p.email = f"{p.first_name.lower()}@{p.organisation.slug}.com" + p.save() + out["people"].append(p) + + # Engagement + Engagement.objects.all().delete() + d1 = date(2022, 5, 10) + d2 = date(2022, 5, 12) + # users = get_user_model() + et = EngagementType.objects.get(name="INSPECTION") + ep = Organisation.objects.first() + e = Engagement.objects.create( + proposed_start_date=d1, + proposed_end_date=d2, + engagement_type=et, + external_party=ep, + ) + e.officers.add(u1) + e.save() + out["engagements"].append(e) + + # Effort + EngagementEffort.objects.all().delete() + d1 = datetime(2022, 4, 10, 10, 0, 0) + d2 = datetime(2022, 4, 10, 14, 15, 0) + d3 = datetime(2022, 4, 10, 12, 0, 0) + d4 = datetime(2022, 4, 10, 13, 0, 0) # 1 hour between d3 and d4 + ef = EngagementEffort.objects.create( + is_planned=True, + effort_type="PLANNING", + proposed_start_date=d1, + proposed_end_date=d2, + engagement=e, + ) + EngagementEffort.objects.create( + is_planned=True, + effort_type="TRAVEL", + proposed_start_date=d3, + proposed_end_date=d4, + engagement=e, + ) + ef.officers.add(u1) + ef.sub_instruments.add(out["sub_instruments"][0]) + ef.save() + out["engagement_effort"].append(ef) + return out diff --git a/engagements/views.py b/engagements/views.py new file mode 100644 index 0000000..fb804df --- /dev/null +++ b/engagements/views.py @@ -0,0 +1,182 @@ +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin +from django.db.models import Q +from django.shortcuts import get_object_or_404, redirect, render +from django.views.generic import ListView + +from instruments.models import SubInstrument + +from .forms import ( + EngagementCreateForm, + EngagementEffortPlanningCreateForm, + EngagementEffortRegulationCreateForm, + EngagementEffortReportingCreateForm, + EngagementEffortTravelCreateForm, +) +from .models import Engagement, EngagementEffort, EngagementType, Organisation + + +class RegulatedEntitiesView(ListView, LoginRequiredMixin): + context_object_name = "entities" + queryset = Organisation.objects.filter(is_regulated_entity=True).order_by("name") + template_name = "engagements/organisations.html" + + +@login_required +def engagement_planning(request): + user = request.user + reg_orgs = Organisation.objects.filter(is_regulated_entity=True) + my_orgs = reg_orgs.filter(lead_inspector=user) + return render(request, "engagements/index.html", {"entities": my_orgs}) + # display the list and ask which one? + + +@login_required +def htmx_effort_planned(request, effid): + if request.method == "GET": + effort = EngagementEffort.objects.get(id=effid) + if effort.is_planned is True: + effort.is_planned = False + else: + effort.is_planned = True + effort.save() + return render(request, "engagements/snippets/effort_summary_panel.html", {"e" : effort}) + + +@login_required +def engagement_detail(request, pk): + engagement = Engagement.objects.get(pk=pk) + subinstruments = SubInstrument.objects.filter(title__icontains="DSC") + effort = EngagementEffort.objects.filter(engagement=engagement).order_by("proposed_start_date") + dscs = [] + for e in effort: + subs = e.sub_instruments.all() + for s in subs: + dscs.append(s) + dscs = set(dscs) + effort_total = sum(e.effort_total_hours() for e in effort) + effort_planned = sum(e.effort_total_planned_hours() for e in effort) + effort_actual = sum(e.effort_actual() for e in effort) + context = { + "engagement": engagement, + "subinstruments": subinstruments, + "effort": effort, + "effort_total": effort_total, + "effort_planned": effort_planned, + "effort_actual": effort_actual, + "dscs": dscs, + } + return render(request, "engagements/engagement_detail.html", context) + + +@login_required +def engagement_plan_for(request, orgslug): + org = Organisation.objects.get(slug=orgslug) + engagements = Engagement.objects.filter(external_party=org) + dscs = SubInstrument.objects.filter(itype="DSC").filter(effort__engagement__external_party=org).distinct() + context = {"entity": org, "engagements": engagements, "dscs": dscs} + return render(request, "engagements/ep_org.html", context) + + +# class EngagementView(ListView, LoginRequiredMixin): +# context_object_name = "engagements" +# queryset = Engagement.objects.filter(engagement_type__name="INSPECTION") +# template_name = "engagements/index.html" + + +@login_required +def engagement_effort_create(request, eid, etype=None): + forms = { + "TRAVEL": EngagementEffortTravelCreateForm, + "PLANNING": EngagementEffortPlanningCreateForm, + "REGULATION": EngagementEffortRegulationCreateForm, + "REPORTING": EngagementEffortReportingCreateForm, + } + + if request.method == "POST": + engagement = Engagement.objects.get(pk=eid) + # use the specialised type of form on POST - it has the correct fields + form = forms[etype](request.POST) + if form.is_valid(): + eff = form.save(commit=False) + eff.engagement = engagement + eff.save() + eff.officers.add(request.user) + eff.effort_type = etype + eff.save() + form.save_m2m() + return redirect("engagements:engagement_detail", pk=eid) + else: + engagement = Engagement.objects.get(pk=eid) + form = forms.get(etype, EngagementCreateForm)( + initial={ + "proposed_start_date": engagement.proposed_start_date.isoformat(), + "engagement": engagement, + "effort_type": etype, + "officers": request.user, + }, + ) + return render( + request, + "engagements/engagement_effort_create.html", + {"form": form, "engagement": engagement, "etype": etype}, + ) + + +@login_required +def engagement_edit(request, eid): + e = get_object_or_404(Engagement, pk=eid) + form = EngagementCreateForm(request.POST or None, instance=e) + if request.method == "POST": + if form.is_valid(): + form.save() + return redirect("engagements:engagement_detail", pk=e.pk) + return render( + request, + "engagements/engagement_form.html", + {"form": form, "title": f"Edit Engagement {e}"}, + ) + + +@login_required +def engagement_create(request, slug, reg=None): + if request.method == "POST": + form = EngagementCreateForm(request.POST) + if form.is_valid(): + ef = form.save(commit=False) + ef.external_party = Organisation.objects.get(slug=slug) + ef.save() + return redirect("engagements:plan_for_org", orgslug=slug) + else: + if reg: + form = EngagementCreateForm( + initial={ + "engagement_type": EngagementType.objects.filter( + Q(name="ASSESSMENT") + | Q(name="INSPECTION") + | Q(name="SAMPLING") + | Q(name="L1RIF") + | Q(name="L2RIF") + | Q(name="L3RIF") + | Q(name="L1RIF") + ), + "external_party": Organisation.objects.get(slug=slug), + "officers": request.user, + } + ) + return render( + request, + "engagements/engagement_form.html", + {"form": form, "title": "Add New Regulatory Engagement"}, + ) + else: + form = EngagementCreateForm( + initial={ + "officers": request.user, + } + ) + return render( + request, + "engagements/engagement_form.html", + {"form": form, "title": "Add New Engagement"}, + ) @@ -1,5 +0,0 @@ -module github.com/defencedigital/ded-web - -go 1.20 - -require github.com/lib/pq v1.10.9 @@ -1,2 +0,0 @@ -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/instruments/__init__.py b/instruments/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/instruments/__init__.py diff --git a/instruments/admin.py b/instruments/admin.py new file mode 100644 index 0000000..2c55b2c --- /dev/null +++ b/instruments/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from .models import Instrument, SubInstrument + + +class SubInstrumentAdmin(admin.ModelAdmin): + list_display = ("__str__", "itype", "parent") + + +admin.site.register(Instrument) +admin.site.register(SubInstrument, SubInstrumentAdmin) diff --git a/instruments/apps.py b/instruments/apps.py new file mode 100644 index 0000000..9aefef2 --- /dev/null +++ b/instruments/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class InstrumentsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "instruments" diff --git a/instruments/migrations/0001_initial.py b/instruments/migrations/0001_initial.py new file mode 100644 index 0000000..ef73e3c --- /dev/null +++ b/instruments/migrations/0001_initial.py @@ -0,0 +1,50 @@ +# Generated by Django 4.0.8 on 2022-11-02 09:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('engagements', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Instrument', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=512)), + ('long_title', models.CharField(blank=True, max_length=1024, null=True)), + ('designator', models.CharField(blank=True, max_length=3, null=True)), + ('link', models.URLField(blank=True, max_length=1024, null=True)), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engagements.organisation')), + ], + options={ + 'verbose_name_plural': 'Instruments', + }, + ), + migrations.CreateModel( + name='SubInstrument', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('is_guidance', models.BooleanField(blank=True, default=False, null=True)), + ('itype', models.CharField(choices=[('DSC', 'Defence Security Condition (DSC)'), ('DSYAP', 'Defence Security Assessment Principle (DSyAP)'), ('DSTAIG', "Defence Security Testing And I Don't Know (DSTAIG)")], max_length=6, verbose_name='Instrument Type')), + ('title', models.CharField(max_length=512)), + ('description', models.TextField(blank=True, null=True)), + ('rationale', models.TextField(blank=True, null=True)), + ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='instruments.instrument', verbose_name='Parent Instrument')), + ('relative', models.ManyToManyField(to='instruments.subinstrument')), + ], + options={ + 'verbose_name_plural': 'Sub Instruments', + }, + ), + ] diff --git a/instruments/migrations/0002_subinstrument_short.py b/instruments/migrations/0002_subinstrument_short.py new file mode 100644 index 0000000..6492158 --- /dev/null +++ b/instruments/migrations/0002_subinstrument_short.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.8 on 2023-04-21 08:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("instruments", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="subinstrument", + name="short", + field=models.CharField(blank=True, max_length=10, null=True), + ), + ] diff --git a/instruments/migrations/__init__.py b/instruments/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/instruments/migrations/__init__.py diff --git a/instruments/models.py b/instruments/models.py new file mode 100644 index 0000000..e266fb2 --- /dev/null +++ b/instruments/models.py @@ -0,0 +1,56 @@ +from django.db import models + +from engagements.models import EngagementEffort, Organisation + + +class Common(models.Model): + date_created = models.DateTimeField(auto_now_add=True) + last_modified = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + + +class Instrument(Common): + "JSP 628, JSP 440, the ONR ones, etc" + name = models.CharField(max_length=512, null=False, blank=False) + long_title = models.CharField(max_length=1024, null=True, blank=True) + designator = models.CharField(max_length=3, null=True, blank=True) + owner = models.ForeignKey(Organisation, null=False, blank=False, on_delete=models.CASCADE) + link = models.URLField(max_length=1024, null=True, blank=True) + + def __str__(self): + return self.name + + class Meta: + verbose_name_plural = "Instruments" + + +class SubInstrument(Common): + "DSCs, Chapters, Supplements, Leaflets..." + choices = ( + ("DSC", "Defence Security Condition (DSC)"), + ("DSYAP", "Defence Security Assessment Principle (DSyAP)"), + ("DSTAIG", "Defence Security Testing And I Don't Know (DSTAIG)"), + ) + is_guidance = models.BooleanField(null=True, blank=True, default=False) + itype = models.CharField(choices=choices, max_length=6, verbose_name="Instrument Type") + title = models.CharField(max_length=512, null=False, blank=False) + parent = models.ForeignKey(Instrument, on_delete=models.CASCADE, verbose_name="Parent Instrument") + description = models.TextField(null=True, blank=True) + rationale = models.TextField(null=True, blank=True) + relative = models.ManyToManyField("self") + short = models.CharField(max_length=10, null=True, blank=True) + + def effort(self): + return EngagementEffort.objects.filter(sub_instruments__contains=self) + + def effort_for_org(self, org): + eff = EngagementEffort.objects.filter(engagement__external_party=org).filter(effort_type="REGULATION") + return sum(e.effort_total_hours() for e in eff) + + def __str__(self): + return self.title + + class Meta: + verbose_name_plural = "Sub Instruments" diff --git a/instruments/tests/__init__.py b/instruments/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/instruments/tests/__init__.py diff --git a/instruments/tests/test_models.py b/instruments/tests/test_models.py new file mode 100644 index 0000000..0d7eafa --- /dev/null +++ b/instruments/tests/test_models.py @@ -0,0 +1,46 @@ +import datetime + +import pytest +from django.test import TestCase + +from engagements import models +from engagements.utils import populate_database + + +class TestModels(TestCase): + @classmethod + def setUpTestData(cls): + cls.data = populate_database() + + def test_check_all_dcs(self): + dscs = self.data.get("sub_instruments") + self.assertEqual(dscs[0].title, "DSC 1 - Title 1") + + @pytest.mark.django_db + def test_get_hours_of_effort_for_dsc_for_org(self): + org = self.data["orgs"][0] + et = models.EngagementType.objects.get(name="INSPECTION") + si = self.data["sub_instruments"][0] + si2 = self.data["sub_instruments"][2] + si3 = self.data["sub_instruments"][3] + # si_not = self.data["sub_instruments"][1] + engagement = models.Engagement.objects.create( + proposed_start_date=datetime.date(2022, 10, 10), + proposed_end_date=datetime.date(2022, 10, 10), + engagement_type=et, + external_party=org, + ) + ef1 = models.EngagementEffort.objects.create( + is_planned=True, + effort_type="REGULATION", + proposed_start_date=datetime.datetime(2022, 10, 10, 10, 0), + proposed_end_date=datetime.datetime(2022, 10, 10, 12, 0), + engagement=engagement, + ) + ef1.sub_instruments.add(si) # DSC 1 + ef1.sub_instruments.add(si2) # DSC 3 + ef1.sub_instruments.add(si3) # DSC 4 + ef1.save() + self.assertEqual(si.effort_for_org(org), 2) + self.assertEqual(si2.effort_for_org(org), 2) + self.assertEqual(si3.effort_for_org(org), 2) diff --git a/instruments/views.py b/instruments/views.py new file mode 100644 index 0000000..fd0e044 --- /dev/null +++ b/instruments/views.py @@ -0,0 +1,3 @@ +# from django.shortcuts import render + +# Create your views here. diff --git a/internal/models/engagement.go b/internal/models/engagement.go deleted file mode 100644 index 87e0786..0000000 --- a/internal/models/engagement.go +++ /dev/null @@ -1,54 +0,0 @@ -package models - -import ( - "database/sql" - "fmt" - "strconv" - "time" -) - -type EngagementStrategy struct { - ID int - ValidFrom time.Time - ValidTo time.Time - Operation Operation -} - -func (es *EngagementStrategy) FormatForTable() string { - startYear := strconv.Itoa(es.ValidFrom.Year()) - endYear := strconv.Itoa(es.ValidTo.Year()) - return fmt.Sprintf("%s-%s", startYear, endYear) -} - -type EngagementStrategyModel struct { - DB *sql.DB -} - -func (m *EngagementStrategyModel) GetForOperation(id int) ([]EngagementStrategy, error) { - stmt := `SELECT id, valid_from, valid_to FROM engagement_strategies -WHERE operation_id = ?` - rows, err := m.DB.Query(stmt, id) - if err != nil { - return nil, err - } - - defer rows.Close() - - var esses []EngagementStrategy - - for rows.Next() { - var es EngagementStrategy - err = rows.Scan(&es.ID, &es.ValidFrom, &es.ValidTo) - if err != nil { - return nil, err - } - - esses = append(esses, es) - } - - if err = rows.Err(); err != nil { - return nil, err - } - - return esses, nil -} diff --git a/internal/models/errors.go b/internal/models/errors.go deleted file mode 100644 index a70c7dc..0000000 --- a/internal/models/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package models - -import ( - "errors" -) - -var ErrNoRecord = errors.New("models: no matching record found") diff --git a/internal/models/operation.go b/internal/models/operation.go deleted file mode 100644 index ecfd31b..0000000 --- a/internal/models/operation.go +++ /dev/null @@ -1,52 +0,0 @@ -package models - -import ( - "database/sql" - "time" -) - -type Operation struct { - ID int - Name string - Description string - Created time.Time - OrganisationName string - EngagementStrategies []EngagementStrategy -} - -type OperationModel struct { - DB *sql.DB -} - -func (m *OperationModel) ListAll() ([]Operation, error) { - - stmt := `SELECT op.id, op.name, op.description, op.created, org.name - FROM operations op - INNER JOIN organisations org ON op.organisation_id=org.id` - - // stmt := `SELECT * FROM operations` - - rows, err := m.DB.Query(stmt) - if err != nil { - return nil, err - } - - defer rows.Close() - - var ops []Operation - - for rows.Next() { - var o Operation - err = rows.Scan(&o.ID, &o.Name, &o.Description, &o.Created, &o.OrganisationName) - if err != nil { - return nil, err - } - - ops = append(ops, o) - } - - if err = rows.Err(); err != nil { - return nil, err - } - return ops, err -} diff --git a/internal/models/organisations.go b/internal/models/organisations.go deleted file mode 100644 index 88f7bc9..0000000 --- a/internal/models/organisations.go +++ /dev/null @@ -1,85 +0,0 @@ -package models - -import ( - "database/sql" - "errors" - "time" -) - -type Organisation struct { - ID int - Name string - Created time.Time -} - -type OrganisationModel struct { - DB *sql.DB -} - -func (m *OrganisationModel) Insert(name string) (int, error) { - stmt := `INSERT INTO organisations (name, created) - VALUEs (?, UTC_TIMESTAMP())` - - result, err := m.DB.Exec(stmt, name) - if err != nil { - return 0, err - } - - id, err := result.LastInsertId() - if err != nil { - return 0, err - } - return int(id), nil -} - -func (m *OrganisationModel) Get(id int) (Organisation, error) { - stmt := `SELECT id, name, created FROM organisations - WHERE id = ?` - - row := m.DB.QueryRow(stmt, id) - - var o Organisation - - err := row.Scan(&o.ID, &o.Name, &o.Created) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return Organisation{}, ErrNoRecord - } else { - return Organisation{}, err - } - } - return o, nil -} - -// Ten most recent... -func (m *OrganisationModel) Latest() ([]Organisation, error) { - // Pick out the last 10 - stmt := `SELECT id, name, created FROM organisations - ORDER BY id DESC LIMIT 10` - - rows, err := m.DB.Query(stmt) - if err != nil { - return nil, err - } - - defer rows.Close() - - var organisations []Organisation - - for rows.Next() { - var o Organisation - - err = rows.Scan(&o.ID, &o.Name, &o.Created) - if err != nil { - return nil, err - } - - organisations = append(organisations, o) - } - - if err = rows.Err(); err != nil { - return nil, err - } - - return organisations, err -} diff --git a/internal/models/person.go b/internal/models/person.go deleted file mode 100644 index d2b8585..0000000 --- a/internal/models/person.go +++ /dev/null @@ -1,51 +0,0 @@ -package models - -import ( - "database/sql" - "time" -) - -type Person struct { - ID string - FirstName string - LastName string - OrganisationName string - OrganisationID int - RoleName string - Created time.Time -} - -type PersonModel struct { - DB *sql.DB -} - -func (m *PersonModel) ListAll() ([]Person, error) { - stmt := `SELECT p.id, p.first_name, p.last_name, p.role_name, org.name, org.id - FROM persons p - INNER JOIN organisations org ON p.organisation_id=org.id` - - rows, err := m.DB.Query(stmt) - if err != nil { - return nil, err - } - - defer rows.Close() - - var ps []Person - - for rows.Next() { - var p Person - err = rows.Scan(&p.ID, &p.FirstName, &p.LastName, &p.RoleName, &p.OrganisationName, &p.OrganisationID) - if err != nil { - return nil, err - } - - ps = append(ps, p) - } - - if err = rows.Err(); err != nil { - return nil, err - } - - return ps, err -} diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..47da0a5 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ded.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/myuser/__init__.py b/myuser/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/myuser/__init__.py diff --git a/myuser/admin.py b/myuser/admin.py new file mode 100644 index 0000000..46dc494 --- /dev/null +++ b/myuser/admin.py @@ -0,0 +1,100 @@ +from django import forms +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.forms import ReadOnlyPasswordHashField +from django.contrib.auth.models import Group + +from .models import Team, TeamUser + + +class TeamUserCreationForm(forms.ModelForm): + password1 = forms.CharField(label="Password", widget=forms.PasswordInput) + password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput) + + class Meta: + model = TeamUser + fields = ("email",) + + def clean_password2(self): + "Check that the two password entries match" + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise ValueError("Passwords don't match") + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super().save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user + + +class TeamUserChangeForm(forms.ModelForm): + """A form for updating users. Includes all the fields on + the user, but replaces the password field with admin's + disabled password hash display field. + """ + + password = ReadOnlyPasswordHashField() + + class Meta: + model = TeamUser + fields = ( + "email", + "password", + "first_name", + "last_name", + "is_active", + "is_admin", + ) + + +class TeamUserAdmin(BaseUserAdmin): + form = TeamUserChangeForm + add_form = TeamUserCreationForm + + list_display = ("email", "first_name", "last_name", "designation", "is_admin") + list_filter = ("is_admin",) + fieldsets = ( + (None, {"fields": ("email", "password")}), + ( + "Personal info", + { + "fields": ( + "first_name", + "last_name", + "designation", + "dapsy", + "team", + "lead_for", + ) + }, + ), + ("Permissions", {"fields": ("is_admin",)}), + ) + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ( + "email", + "first_name", + "last_name", + "password1", + "password2", + ), + }, + ), + ) + search_fields = ("email",) + ordering = ("email",) + filer_horizontal = () + + +admin.site.register(TeamUser, TeamUserAdmin) +admin.site.register(Team) +admin.site.unregister(Group) diff --git a/myuser/apps.py b/myuser/apps.py new file mode 100644 index 0000000..73bb265 --- /dev/null +++ b/myuser/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthuserConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "myuser" diff --git a/myuser/migrations/0001_initial.py b/myuser/migrations/0001_initial.py new file mode 100644 index 0000000..3442670 --- /dev/null +++ b/myuser/migrations/0001_initial.py @@ -0,0 +1,52 @@ +# Generated by Django 4.0.8 on 2022-11-02 09:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('engagements', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=256)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='TeamUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('email', models.EmailField(max_length=255, unique=True, verbose_name='email address')), + ('first_name', models.CharField(blank=True, max_length=20, null=True)), + ('last_name', models.CharField(blank=True, max_length=20, null=True)), + ('dapsy', models.BooleanField(default=False)), + ('is_active', models.BooleanField(default=True)), + ('is_admin', models.BooleanField(default=False)), + ('designation', models.CharField(blank=True, max_length=3, null=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('lead_for', models.ManyToManyField(blank=True, related_name='lead_inspector', to='engagements.organisation')), + ('team', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='team_members', to='myuser.team')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/myuser/migrations/__init__.py b/myuser/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/myuser/migrations/__init__.py diff --git a/myuser/models.py b/myuser/models.py new file mode 100644 index 0000000..1236ba6 --- /dev/null +++ b/myuser/models.py @@ -0,0 +1,75 @@ +from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager +from django.contrib.auth.models import PermissionsMixin +from django.db import models +from django.db.models import BooleanField, CharField, EmailField + + +class Common(models.Model): + date_created = models.DateTimeField(auto_now_add=True) + last_modified = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + + +class TeamMgr(BaseUserManager): + def create_user(self, email, password=None): + if not email: + raise ValueError("Users must have an email address") + user = self.model( + email=self.normalize_email(email), + ) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None): + user = self.create_user( + email, + password=password, + ) + user.is_admin = True + user.save(using=self._db) + return user + + +class Team(Common): + name = models.CharField(max_length=256) + + def __str__(self): + return self.name + + +class TeamUser(AbstractBaseUser, PermissionsMixin): + email = EmailField(verbose_name="email address", max_length=255, unique=True) + first_name = CharField(max_length=20, null=True, blank=True) + last_name = CharField(max_length=20, null=True, blank=True) + dapsy = BooleanField(default=False) + is_active = BooleanField(default=True) + is_admin = BooleanField(default=False) + team = models.ForeignKey(Team, null=True, on_delete=models.CASCADE, related_name="team_members") + lead_for = models.ManyToManyField("engagements.Organisation", blank=True, related_name="lead_inspector") + designation = models.CharField(max_length=3, null=True, blank=True) + + USERNAME_FIELD = "email" + # EMAIL_FIELD = "email" + + objects = TeamMgr() + + def __str__(self): + return self.email + + def fullname(self): + return f"{self.first_name} {self.last_name}" + + def has_perm(self, perm, obj=None): + "Does this user have a specific permission? Set True for now." + return True + + def has_module_perms(self, app_label): + "Does user have permissions to view the app `app_label`? Yes for now." + return True + + @property + def is_staff(self): + return self.is_admin diff --git a/myuser/tests.py b/myuser/tests.py new file mode 100644 index 0000000..a79ca8b --- /dev/null +++ b/myuser/tests.py @@ -0,0 +1,3 @@ +# from django.test import TestCase + +# Create your tests here. diff --git a/myuser/views.py b/myuser/views.py new file mode 100644 index 0000000..fd0e044 --- /dev/null +++ b/myuser/views.py @@ -0,0 +1,3 @@ +# from django.shortcuts import render + +# Create your views here. diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..ab1033b --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/postgresql/Dockerfile b/postgresql/Dockerfile deleted file mode 100644 index 1196bce..0000000 --- a/postgresql/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM postgres:16-alpine - -RUN addgroup -S app && adduser -S -g app app - -USER app - -COPY postgresql/populate.sql /docker-entrypoint-initdb.d/ diff --git a/postgresql/populate.sql b/postgresql/populate.sql deleted file mode 100644 index 7fa09f4..0000000 --- a/postgresql/populate.sql +++ /dev/null @@ -1,82 +0,0 @@ -CREATE DATABASE ded; - --- Install uuid-ossp extension -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- Create the Organisations table. -CREATE TABLE organisations ( - id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - --- Add index to created column. -CREATE INDEX idx_organisations_created ON organisations(created); - --- Let's add some placeholder data to organisations table. -INSERT INTO organisations (name, created) VALUES ('Random Organisation 1', CURRENT_TIMESTAMP); -INSERT INTO organisations (name, created) VALUES ('Random Organisation 2', CURRENT_TIMESTAMP); -INSERT INTO organisations (name, created) VALUES ('Random Organisation 3', CURRENT_TIMESTAMP); - --- Persons! -CREATE TABLE persons ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - first_name VARCHAR(56) NOT NULL, - last_name VARCHAR(56) NOT NULL, - organisation_id INT NOT NULL, - role_name VARCHAR(56) NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT fk_person_organisation - FOREIGN KEY(organisation_id) - REFERENCES organisations(id) - ON DELETE CASCADE -); - --- Add index to created column. -CREATE INDEX idx_persons_created ON persons(created); - --- Add some people -INSERT INTO persons (first_name, last_name, organisation_id, role_name, created) VALUES ('Larry', 'Cluno', 1, 'Lead Inspector', CURRENT_TIMESTAMP); -INSERT INTO persons (first_name, last_name, organisation_id, role_name, created) VALUES ('Olivia', 'Capstan-Melville', 1, 'Lead Inspector', CURRENT_TIMESTAMP); - --- Operations! -CREATE TABLE operations ( - id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - description VARCHAR(255) NOT NULL, - organisation_id INT NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT fk_operation_organisation - FOREIGN KEY(organisation_id) - REFERENCES organisations(id) - ON DELETE CASCADE -); - --- Add index to created column. -CREATE INDEX idx_operations_created ON operations(created); - --- Engagement Strategies -CREATE TABLE engagement_strategies ( - id SERIAL PRIMARY KEY, - valid_from TIMESTAMP NOT NULL, - valid_to TIMESTAMP NOT NULL, - description VARCHAR(255), - operation_id INT NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT fk_es_operation - FOREIGN KEY(operation_id) - REFERENCES operations(id) - ON DELETE CASCADE -); - -CREATE INDEX idx_engagement_strategy_created ON engagement_strategies(created); - --- Create some operations -INSERT INTO operations (name, created, description, organisation_id) VALUES ('Operation 1', CURRENT_TIMESTAMP, 'Operation 1 Description', 1); -INSERT INTO operations (name, created, description, organisation_id) VALUES ('Operation 2', CURRENT_TIMESTAMP, 'Operation 2 Description', 1); -INSERT INTO operations (name, created, description, organisation_id) VALUES ('Operation 3', CURRENT_TIMESTAMP, 'Operation 3 Description', 2); - --- Create some ESs -INSERT INTO engagement_strategies (valid_from, valid_to, operation_id, created) VALUES ('2021-01-01', '2023-01-01', 1, CURRENT_TIMESTAMP); -INSERT INTO engagement_strategies (valid_from, valid_to, operation_id, created) VALUES ('2023-01-01', '2025-01-01', 1, CURRENT_TIMESTAMP); - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5743b1a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,64 @@ +# Some assistance and tips from:://www.tweag.io/blog/2023-04-04-python-monorepo-1/ + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.ruff] +line-length = 120 + +[tool.djlint] +profile="django" +ignore = "H023,H025,H030,H031" + +[tool.pyright] +reportMissingTypeArgument = true # Report generic classes used without type arguments +strictMissingInference = true # Use union types when inferring types of list elements, instead of Any + +[tool.black] +line-length = 120 +target-version = ['py310'] + +[tool.isort] +profile = "black" +combine_as_imports = true +include_trailing_comma = true +line_length = 120 + +[tool.poetry] +name = "ded" +version = "0.1.0" +description = "Open source regulatory data management and workflow application." +authors = ["Matthew Lemon <y@yulqen.org>"] +license = "AGPL v3" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +django-crispy-forms = "~2.0" +Django = "~5.0.4" +faker = "^18.4.0" +django-htmx = "^1.17.3" + +[tool.poetry.group.docs.dependencies] +sphinx = "~6.1.3" +myst-parser = "~1.0.0" + +[tool.poetry.group.dev.dependencies] +black = "~23.3.0" +pre-commit = "~3.2.2" +django-debug-toolbar = "~3.7" +flake8 = "~6.0.0" +pdbpp = "~0.10.3" +pyright = "~1.1.239" +mypy = "~1.1.1" +mypy-extensions = "~1.0.0" +litecli = "~1.9.0" +djlint = "~1.23.3" +isort = "~5.12.0" +ipython = "~8.12.0" +jedi = "^0.18.2" + +[tool.poetry.group.test.dependencies] +pytest = "~7.2.2" +pytest-django = "^4.5.2" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..3989267 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +DJANGO_SETTINGS_MODULE = ded.settings diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b8172c2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Django>=5.0.4,<5.1 +django-crispy-forms +django-extensions +faker>=13.14.0,<14.0.0 +django-htmx==1.17.3 diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..8f7b34c --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,7 @@ +pdbpp +pre-commit +isort +black +flake8 +requests +django-debug-toolbar==3.7 diff --git a/scripts/populate b/scripts/populate new file mode 100755 index 0000000..f3733ec --- /dev/null +++ b/scripts/populate @@ -0,0 +1,12 @@ +#!/bin/sh + + +find . -maxdepth 3 -iname "00*py" -exec rm {} + +if [ -e db.sqlite3 ]; then + rm db.sqlite3 +fi + +python manage.py makemigrations +python manage.py migrate +python manage.py create_engagement_data +# python manage.py createsuperuser diff --git a/templates/w3/accordion-group.html b/templates/w3/accordion-group.html new file mode 100644 index 0000000..3d4f5e8 --- /dev/null +++ b/templates/w3/accordion-group.html @@ -0,0 +1,17 @@ +<div class="card mb-2"> + <div class="card-header" role="tab"> + <h5 class="mb-0"> + <a data-toggle="collapse" href="#{{ div.css_id }}" aria-expanded="true" + aria-controls="{{ div.css_id }}"> + {{ div.name }} + </a> + </h5> + </div> + + <div id="{{ div.css_id }}" class="collapse{% if div.active %} show{% endif %}" role="tabpanel" + aria-labelledby="{{ div.css_id }}" data-parent="#{{ div.data_parent }}"> + <div class="card-body"> + {{ fields }} + </div> + </div> +</div> diff --git a/templates/w3/accordion.html b/templates/w3/accordion.html new file mode 100644 index 0000000..f3a9859 --- /dev/null +++ b/templates/w3/accordion.html @@ -0,0 +1,3 @@ +<div id="{{ accordion.css_id }}" role="tablist"> + {{ content }} +</div> diff --git a/templates/w3/betterform.html b/templates/w3/betterform.html new file mode 100644 index 0000000..4516291 --- /dev/null +++ b/templates/w3/betterform.html @@ -0,0 +1,21 @@ +{% for fieldset in form.fieldsets %} + <fieldset class="fieldset-{{ forloop.counter }} {{ fieldset.classes }}"> + {% if fieldset.legend %} + <legend>{{ fieldset.legend }}</legend> + {% endif %} + + {% if fieldset.description %} + <p class="description">{{ fieldset.description }}</p> + {% endif %} + + {% for field in fieldset %} + {% if field.is_hidden %} + {{ field }} + {% else %} + {% include "w3/field.html" %} + {% endif %} + {% endfor %} + {% if not forloop.last or not fieldset_open %} + </fieldset> + {% endif %} +{% endfor %} diff --git a/templates/w3/display_form.html b/templates/w3/display_form.html new file mode 100644 index 0000000..9c891dd --- /dev/null +++ b/templates/w3/display_form.html @@ -0,0 +1,9 @@ +{% if form.form_html %} + {% if include_media %}{{ form.media }}{% endif %} + {% if form_show_errors %} + {% include "w3/errors.html" %} + {% endif %} + {{ form.form_html }} +{% else %} + {% include "w3/uni_form.html" %} +{% endif %} diff --git a/templates/w3/errors.html b/templates/w3/errors.html new file mode 100644 index 0000000..db09490 --- /dev/null +++ b/templates/w3/errors.html @@ -0,0 +1,8 @@ +{% if form.non_field_errors %} + <div class="w3-red"> + {% if form_error_title %}<h4>{{ form_error_title }}</h4>{% endif %} + <ul> + {{ form.non_field_errors|unordered_list }} + </ul> + </div> +{% endif %} diff --git a/templates/w3/errors_formset.html b/templates/w3/errors_formset.html new file mode 100644 index 0000000..0382973 --- /dev/null +++ b/templates/w3/errors_formset.html @@ -0,0 +1,8 @@ +{% if formset.non_form_errors %} + <div class="alert alert-block alert-danger"> + {% if formset_error_title %}<h4 class="alert-heading">{{ formset_error_title }}</h4>{% endif %} + <ul class="m-0"> + {{ formset.non_form_errors|unordered_list }} + </ul> + </div> +{% endif %} diff --git a/templates/w3/field.html b/templates/w3/field.html new file mode 100644 index 0000000..f73441f --- /dev/null +++ b/templates/w3/field.html @@ -0,0 +1,81 @@ +{% load crispy_forms_field %} + +{% if field.is_hidden %} + {{ field }} +{% else %} + {% if field|is_checkbox %} + <div class="w3-input"> + {% if label_class %} + <div class="{% for offset in bootstrap_checkbox_offsets %}{{ offset }} {% endfor %}{{ field_class }}"> + {% endif %} + {% endif %} + <{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" class="{% if not field|is_checkbox %}w3-input{% if 'form-horizontal' in form_class %} row{% endif %}{% else %}{%if use_custom_control%}{% if tag != 'td' %}custom-control {%endif%} custom-checkbox{% else %}form-check{% endif %}{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}"> + {% if field.label and not field|is_checkbox and form_show_labels %} + {# not field|is_radioselect in row below can be removed once Django 3.2 is no longer supported #} + <label {% if field.id_for_label and not field|is_radioselect %}for="{{ field.id_for_label }}" {% endif %}class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}"> + {{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %} + </label> + {% endif %} + + {% if field|is_checkboxselectmultiple %} + {% include 'w3/layout/checkboxselectmultiple.html' %} + {% endif %} + + {% if field|is_radioselect %} + {% include 'w3/layout/radioselect.html' %} + {% endif %} + + {% if not field|is_checkboxselectmultiple and not field|is_radioselect %} + {% if field|is_checkbox and form_show_labels %} + {%if use_custom_control%} + {% if field.errors %} + {% crispy_field field 'class' 'custom-control-input is-invalid' %} + {% else %} + {% crispy_field field 'class' 'custom-control-input' %} + {% endif %} + {% else %} + {% if field.errors %} + {% crispy_field field 'class' 'form-check-input is-invalid' %} + {% else %} + {% crispy_field field 'class' 'form-check-input' %} + {% endif %} + {% endif %} + <label for="{{ field.id_for_label }}" class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}{% if field.field.required %} requiredField{% endif %}"> + {{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %} + </label> + {% include 'w3/layout/help_text_and_errors.html' %} + {% elif field|is_file and use_custom_control %} + {% include 'w3/layout/field_file.html' %} + {% else %} + <div{% if field_class %} class="{{ field_class }}"{% endif %}> + {% if field|is_select and use_custom_control %} + {% if field.errors %} + {% crispy_field field 'class' 'custom-select is-invalid' %} + {% else %} + {% crispy_field field 'class' 'custom-select' %} + {% endif %} + {% elif field|is_file %} + {% if field.errors %} + {% crispy_field field 'class' 'form-control-file is-invalid' %} + {% else %} + {% crispy_field field 'class' 'form-control-file' %} + {% endif %} + {% else %} + {% if field.errors %} + {% crispy_field field 'class' 'form-control is-invalid' %} + {% else %} + {% crispy_field field 'class' 'form-control' %} + {% endif %} + {% endif %} + {% include 'w3/layout/help_text_and_errors.html' %} + </div> + {% endif %} + {% endif %} + </{% if tag %}{{ tag }}{% else %}div{% endif %}> + {% if field|is_checkbox %} + {% if label_class %} + </div> + {% endif %} + </div> + {% endif %} +{% endif %} diff --git a/templates/w3/inputs.html b/templates/w3/inputs.html new file mode 100644 index 0000000..250031c --- /dev/null +++ b/templates/w3/inputs.html @@ -0,0 +1,13 @@ +{% if inputs %} + <div class="w3-input"> + {% if label_class %} + <div class="aab {{ label_class }}"></div> + {% endif %} + + <div class="{{ field_class }}"> + {% for input in inputs %} + {% include "w3/layout/baseinput.html" %} + {% endfor %} + </div> + </div> +{% endif %} diff --git a/templates/w3/layout/alert.html b/templates/w3/layout/alert.html new file mode 100644 index 0000000..ba20ae9 --- /dev/null +++ b/templates/w3/layout/alert.html @@ -0,0 +1,4 @@ +<div{% if alert.css_id %} id="{{ alert.css_id }}"{% endif %}{% if alert.css_class %} class="{{ alert.css_class }}"{% endif %}> + {% if dismiss %}<button type="button" class="close" data-dismiss="alert">×</button>{% endif %} + {{ content }} +</div> diff --git a/templates/w3/layout/attrs.html b/templates/w3/layout/attrs.html new file mode 100644 index 0000000..c52de9e --- /dev/null +++ b/templates/w3/layout/attrs.html @@ -0,0 +1 @@ +{% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %} diff --git a/templates/w3/layout/baseinput.html b/templates/w3/layout/baseinput.html new file mode 100644 index 0000000..ffde644 --- /dev/null +++ b/templates/w3/layout/baseinput.html @@ -0,0 +1,9 @@ +<input type="{{ input.input_type }}" + name="{% if input.name|wordcount > 1 %}{{ input.name|slugify }}{% else %}{{ input.name }}{% endif %}" + value="{{ input.value }}" + {% if input.input_type != "hidden" %} + class="{{ input.field_classes }}" + id="{{ input.id }}" + {% endif %} + {{ input.flat_attrs }} + /> diff --git a/templates/w3/layout/button.html b/templates/w3/layout/button.html new file mode 100644 index 0000000..f41f9cf --- /dev/null +++ b/templates/w3/layout/button.html @@ -0,0 +1 @@ +<button {{ button.flat_attrs }}>{{ button.content }}</button> diff --git a/templates/w3/layout/buttonholder.html b/templates/w3/layout/buttonholder.html new file mode 100644 index 0000000..bad9405 --- /dev/null +++ b/templates/w3/layout/buttonholder.html @@ -0,0 +1,4 @@ +<div {% if buttonholder.css_id %}id="{{ buttonholder.css_id }}"{% endif %} + class="buttonHolder{% if buttonholder.css_class %} {{ buttonholder.css_class }}{% endif %}"> + {{ fields_output }} +</div> diff --git a/templates/w3/layout/checkboxselectmultiple.html b/templates/w3/layout/checkboxselectmultiple.html new file mode 100644 index 0000000..c943fc4 --- /dev/null +++ b/templates/w3/layout/checkboxselectmultiple.html @@ -0,0 +1,29 @@ +{% load crispy_forms_filters %} +{% load l10n %} + +<div {% if field_class %}class="{{ field_class }}"{% endif %}{% if flat_attrs %} {{ flat_attrs }}{% endif %}> + + {% for group, options, index in field|optgroups %} + {% if group %}<strong>{{ group }}</strong>{% endif %} + {% for option in options %} + <div class="{%if use_custom_control%}custom-control custom-checkbox{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}"> + <input type="checkbox" class="{%if use_custom_control%}custom-control-input{% else %}form-check-input{% endif %}{% if field.errors %} is-invalid{% endif %}" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "w3/layout/attrs.html" with widget=option %}> + <label class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}" for="{{ option.attrs.id }}"> + {{ option.label|unlocalize }} + </label> + {% if field.errors and forloop.last and not inline_class and forloop.parentloop.last %} + {% include 'w3/layout/field_errors_block.html' %} + {% endif %} + </div> + {% endfor %} + {% endfor %} + {% if field.errors and inline_class %} + <div class="w-100 {%if use_custom_control%}custom-control custom-checkbox{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}"> + {# the following input is only meant to allow boostrap to render the error message as it has to be after an invalid input. As the input has no name, no data will be sent. #} + <input type="checkbox" class="custom-control-input {% if field.errors %}is-invalid{%endif%}"> + {% include 'w3/layout/field_errors_block.html' %} + </div> + {% endif %} + + {% include 'w3/layout/help_text.html' %} +</div> diff --git a/templates/w3/layout/checkboxselectmultiple_inline.html b/templates/w3/layout/checkboxselectmultiple_inline.html new file mode 100644 index 0000000..10f0e9e --- /dev/null +++ b/templates/w3/layout/checkboxselectmultiple_inline.html @@ -0,0 +1,14 @@ +{% if field.is_hidden %} + {{ field }} +{% else %} + <div id="div_{{ field.auto_id }}" class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}"> + + {% if field.label %} + <label {% if field.id_for_label %}for="{{ field.id_for_label }}" {% endif %} class="{{ label_class }}{% if not inline_class %} col-form-label{% endif %}{% if field.field.required %} requiredField{% endif %}"> + {{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %} + </label> + {% endif %} + + {% include 'w3/layout/checkboxselectmultiple.html' %} + </div> +{% endif %} diff --git a/templates/w3/layout/column.html b/templates/w3/layout/column.html new file mode 100644 index 0000000..e372c38 --- /dev/null +++ b/templates/w3/layout/column.html @@ -0,0 +1,4 @@ +<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} + class="{% if 'col' in div.css_class %}{{ div.css_class|default:'' }}{% else %}col-md {{ div.css_class|default:'' }}{% endif %}" {{ div.flat_attrs }}> + {{ fields }} +</div> diff --git a/templates/w3/layout/div.html b/templates/w3/layout/div.html new file mode 100644 index 0000000..1651ddc --- /dev/null +++ b/templates/w3/layout/div.html @@ -0,0 +1,4 @@ +<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} + {% if div.css_class %}class="{{ div.css_class }}"{% endif %} {{ div.flat_attrs }}> + {{ fields }} +</div> diff --git a/templates/w3/layout/field_errors.html b/templates/w3/layout/field_errors.html new file mode 100644 index 0000000..f649872 --- /dev/null +++ b/templates/w3/layout/field_errors.html @@ -0,0 +1,5 @@ +{% if form_show_errors and field.errors %} + {% for error in field.errors %} + <span id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="invalid-feedback"><strong>{{ error }}</strong></span> + {% endfor %} +{% endif %} diff --git a/templates/w3/layout/field_errors_block.html b/templates/w3/layout/field_errors_block.html new file mode 100644 index 0000000..e788b79 --- /dev/null +++ b/templates/w3/layout/field_errors_block.html @@ -0,0 +1,5 @@ +{% if form_show_errors and field.errors %} + {% for error in field.errors %} + <p id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="invalid-feedback"><strong>{{ error }}</strong></p> + {% endfor %} +{% endif %} diff --git a/templates/w3/layout/field_file.html b/templates/w3/layout/field_file.html new file mode 100644 index 0000000..f58268a --- /dev/null +++ b/templates/w3/layout/field_file.html @@ -0,0 +1,52 @@ +{% load crispy_forms_field %} + +<div class="{{ field_class }} mb-2"> +{% for widget in field.subwidgets %} +{% if widget.data.is_initial %} +<div class="input-group mb-2"> + <div class="input-group-prepend"> + <span class="input-group-text">{{ widget.data.initial_text }}</span> + </div> + <div class="form-control d-flex h-auto"> + <span class="text-break" style="flex-grow:1;min-width:0"> + <a href="{{ field.value.url }}">{{ field.value }}</a> + </span> + {% if not widget.data.required %} + <span class="align-self-center ml-2"> + <span class="custom-control custom-checkbox"> + <input type="checkbox" name="{{ widget.data.checkbox_name }}" id="{{ widget.data.checkbox_id }}" class="custom-control-input"{% if field.field.disabled %} disabled{% endif %} > + <label class="custom-control-label mb-0" for="{{ widget.data.checkbox_id }}">{{ widget.data.clear_checkbox_label }}</label> + </span> + </span> + {% endif %} + </div> +</div> +<div class="input-group mb-0"> + <div class="input-group-prepend"> + <span class="input-group-text">{{ widget.data.input_text }}</span> + </div> +{% endif %} + <div class="form-control custom-file{% if field.errors %} is-invalid{%endif%}" style="border:0"> + <input type="{{ widget.data.type }}" name="{{ widget.data.name }}" class="custom-file-input{% if widget.data.attrs.class %} {{ widget.data.attrs.class }}{% endif %}{% if field.errors %} is-invalid{%endif%}"{% if field.field.disabled %} disabled{% endif %}{% for name, value in widget.data.attrs.items %}{% if value is not False and name != 'class' %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}> + <label class="custom-file-label text-truncate" for="{{ field.id_for_label }}">---</label> + <script type="text/javascript" id="script-{{ field.id_for_label }}"> + document.getElementById("script-{{ field.id_for_label }}").parentNode.querySelector('.custom-file-input').onchange = function (e){ + var filenames = ""; + for (let i=0;i<e.target.files.length;i++){ + filenames+=(i>0?", ":"")+e.target.files[i].name; + } + e.target.parentNode.querySelector('.custom-file-label').textContent=filenames; + } + </script> + </div> + {% if not widget.data.is_initial %} + {% include 'w3/layout/help_text_and_errors.html' %} + {% endif %} +{% if widget.data.is_initial %} +</div> +<div class="input-group mb-0"> +{% include 'w3/layout/help_text_and_errors.html' %} +</div> +{% endif %} +{% endfor %} +</div> diff --git a/templates/w3/layout/field_with_buttons.html b/templates/w3/layout/field_with_buttons.html new file mode 100644 index 0000000..234b310 --- /dev/null +++ b/templates/w3/layout/field_with_buttons.html @@ -0,0 +1,17 @@ +{% load crispy_forms_field %} + +<div{% if div.css_id %} id="{{ div.css_id }}"{% endif %} class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}{% if div.css_class %} {{ div.css_class }}{% endif %}" {{ div.flat_attrs }}> + {% if field.label and form_show_labels %} + <label for="{{ field.id_for_label }}" class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}"> + {{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %} + </label> + {% endif %} + + <div class="{{ field_class }}"> + <div class="input-group {% if div.input_size %} {{ div.input_size }}{% endif %}"> + {% crispy_field field 'class' 'form-control' %} + <span class="input-group-append{% if active %} active{% endif %}">{{ buttons }}</span> + </div> + {% include 'w3/layout/help_text_and_errors.html' %} + </div> +</div> diff --git a/templates/w3/layout/fieldset.html b/templates/w3/layout/fieldset.html new file mode 100644 index 0000000..85ae30b --- /dev/null +++ b/templates/w3/layout/fieldset.html @@ -0,0 +1,6 @@ +<fieldset {% if fieldset.css_id %}id="{{ fieldset.css_id }}"{% endif %} + {% if fieldset.css_class%}class="{{ fieldset.css_class }}"{% endif %} + {{ fieldset.flat_attrs }}> + {% if legend %}<legend>{{ legend }}</legend>{% endif %} + {{ fields }} +</fieldset> diff --git a/templates/w3/layout/formactions.html b/templates/w3/layout/formactions.html new file mode 100644 index 0000000..04fed0b --- /dev/null +++ b/templates/w3/layout/formactions.html @@ -0,0 +1,9 @@ +<div{% if formactions.flat_attrs %} {{ formactions.flat_attrs }}{% endif %} class="form-group{% if 'form-horizontal' in form_class %} row{% endif %} {{ formactions.css_class }}" {% if formactions.id %} id="{{ formactions.id }}"{% endif %}> + {% if label_class %} + <div class="aab {{ label_class }}"></div> + {% endif %} + + <div class="{{ field_class }}"> + {{ fields_output }} + </div> +</div> diff --git a/templates/w3/layout/help_text.html b/templates/w3/layout/help_text.html new file mode 100644 index 0000000..3b5695c --- /dev/null +++ b/templates/w3/layout/help_text.html @@ -0,0 +1,7 @@ +{% if field.help_text %} + {% if help_text_inline %} + <span id="hint_{{ field.auto_id }}" class="text-muted">{{ field.help_text|safe }}</span> + {% else %} + <small id="hint_{{ field.auto_id }}" class="form-text text-muted">{{ field.help_text|safe }}</small> + {% endif %} +{% endif %} diff --git a/templates/w3/layout/help_text_and_errors.html b/templates/w3/layout/help_text_and_errors.html new file mode 100644 index 0000000..7313246 --- /dev/null +++ b/templates/w3/layout/help_text_and_errors.html @@ -0,0 +1,13 @@ +{% if help_text_inline and not error_text_inline %} + {% include 'w3/layout/help_text.html' %} +{% endif %} + +{% if error_text_inline %} + {% include 'w3/layout/field_errors.html' %} +{% else %} + {% include 'w3/layout/field_errors_block.html' %} +{% endif %} + +{% if not help_text_inline %} + {% include 'w3/layout/help_text.html' %} +{% endif %} diff --git a/templates/w3/layout/inline_field.html b/templates/w3/layout/inline_field.html new file mode 100644 index 0000000..aff23bb --- /dev/null +++ b/templates/w3/layout/inline_field.html @@ -0,0 +1,29 @@ +{% load crispy_forms_field %} + +{% if field.is_hidden %} + {{ field }} +{% else %} + {% if field|is_checkbox %} + <div id="div_{{ field.auto_id }}" class="form-check form-check-inline{% if wrapper_class %} {{ wrapper_class }}{% endif %}"> + <label for="{{ field.id_for_label }}" class="form-check-label{% if field.field.required %} requiredField{% endif %}"> + {% if field.errors %} + {% crispy_field field 'class' 'form-check-input is-invalid' %} + {% else %} + {% crispy_field field 'class' 'form-check-input' %} + {% endif %} + {{ field.label }} + </label> + </div> + {% else %} + <div id="div_{{ field.auto_id }}" class="input-group{% if wrapper_class %} {{ wrapper_class }}{% endif %}"> + <label for="{{ field.id_for_label }}" class="sr-only{% if field.field.required %} requiredField{% endif %}"> + {{ field.label }} + </label> + {% if field.errors %} + {% crispy_field field 'placeholder' field.label 'class' 'form-control is-invalid' %} + {% else %} + {% crispy_field field 'placeholder' field.label 'class' 'form-control' %} + {% endif %} + </div> + {% endif %} +{% endif %} diff --git a/templates/w3/layout/modal.html b/templates/w3/layout/modal.html new file mode 100644 index 0000000..ab18198 --- /dev/null +++ b/templates/w3/layout/modal.html @@ -0,0 +1,15 @@ +<div id="{{ modal.css_id }}" class="modal fade {{ modal.css_class }}" {{ modal.flat_attrs }}> + <div class="modal-dialog modal-lg" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title {{ modal.title_class }}" id="{{ modal.title_id }}">{{ modal.title }}</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true" style="float: left">×</span> + </button> + </div> + <div class="modal-body"> + {{ fields }} + </div> + </div> + </div> +</div> diff --git a/templates/w3/layout/multifield.html b/templates/w3/layout/multifield.html new file mode 100644 index 0000000..0a2c050 --- /dev/null +++ b/templates/w3/layout/multifield.html @@ -0,0 +1,27 @@ +{% load crispy_forms_field %} + +{% if field.is_hidden %} + {{ field }} +{% else %} + + {% if field.label %} + <label for="{{ field.id_for_label }}"{% if labelclass %} class="{{ labelclass }}"{% endif %}> + {% endif %} + + {% if field|is_checkbox %} + {% crispy_field field %} + {% endif %} + + {% if field.label %} + {{ field.label }} + {% endif %} + + {% if not field|is_checkbox %} + {% crispy_field field %} + {% endif %} + + {% if field.label %} + </label> + {% endif %} + +{% endif %} diff --git a/templates/w3/layout/prepended_appended_text.html b/templates/w3/layout/prepended_appended_text.html new file mode 100644 index 0000000..1cccf79 --- /dev/null +++ b/templates/w3/layout/prepended_appended_text.html @@ -0,0 +1,51 @@ +{% load crispy_forms_field %} + +{% if field.is_hidden %} + {{ field }} +{% else %} + <div id="div_{{ field.auto_id }}" class="form-group{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if 'form-horizontal' in form_class %} row{% endif %}{% if form_group_wrapper_class %} {{ form_group_wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}"> + + {% if field.label and form_show_labels %} + <label for="{{ field.id_for_label }}" class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}"> + {{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %} + </label> + {% endif %} + + <div class="{{ field_class }}"> + <div class="input-group{% if input_size %} {{ input_size }}{% endif %}"> + {% if crispy_prepended_text %} + <div class="input-group-prepend{% if active %} active{% endif %}"> + <span class="input-group-text">{{ crispy_prepended_text }}</span> + </div> + {% endif %} + {% if field|is_select and use_custom_control %} + {% if field.errors %} + {% crispy_field field 'class' 'custom-select is-invalid' %} + {% else %} + {% crispy_field field 'class' 'custom-select' %} + {% endif %} + {% else %} + {% if field.errors %} + {% crispy_field field 'class' 'form-control is-invalid' %} + {% else %} + {% crispy_field field 'class' 'form-control' %} + {% endif %} + {% endif %} + {% if crispy_appended_text %} + <div class="input-group-append{% if active %} active{% endif %}"> + <span class="input-group-text">{{ crispy_appended_text }}</span> + </div> + {% endif %} + {% if error_text_inline %} + {% include 'w3/layout/field_errors.html' %} + {% else %} + {% include 'w3/layout/field_errors_block.html' %} + {% endif %} + </div> + {% if not help_text_inline %} + {% include 'w3/layout/help_text.html' %} + {% endif %} + </div> + + </div> +{% endif %} diff --git a/templates/w3/layout/radioselect.html b/templates/w3/layout/radioselect.html new file mode 100644 index 0000000..d9c9e5a --- /dev/null +++ b/templates/w3/layout/radioselect.html @@ -0,0 +1,29 @@ +{% load crispy_forms_filters %} +{% load l10n %} + +<div {% if field_class %}class="{{ field_class }}"{% endif %}{% if flat_attrs %} {{ flat_attrs }}{% endif %}> + + {% for group, options, index in field|optgroups %} + {% if group %}<strong>{{ group }}</strong>{% endif %} + {% for option in options %} + <div class="{%if use_custom_control%}custom-control custom-radio{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}"> + <input type="radio" class="{%if use_custom_control%}custom-control-input{% else %}form-check-input{% endif %}{% if field.errors %} is-invalid{% endif %}" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "w3/layout/attrs.html" with widget=option %}> + <label class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}" for="{{ option.attrs.id }}"> + {{ option.label|unlocalize }} + </label> + {% if field.errors and forloop.last and not inline_class and forloop.parentloop.last %} + {% include 'w3/layout/field_errors_block.html' %} + {% endif %} + </div> + {% endfor %} + {% endfor %} + {% if field.errors and inline_class %} + <div class="w-100 {%if use_custom_control%}custom-control custom-radio{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}"> + {# the following input is only meant to allow boostrap to render the error message as it has to be after an invalid input. As the input has no name, no data will be sent. #} + <input type="checkbox" class="custom-control-input {% if field.errors %}is-invalid{%endif%}"> + {% include 'w3/layout/field_errors_block.html' %} + </div> + {% endif %} + + {% include 'w3/layout/help_text.html' %} +</div> diff --git a/templates/w3/layout/radioselect_inline.html b/templates/w3/layout/radioselect_inline.html new file mode 100644 index 0000000..22ba321 --- /dev/null +++ b/templates/w3/layout/radioselect_inline.html @@ -0,0 +1,14 @@ +{% if field.is_hidden %} + {{ field }} +{% else %} + <div id="div_{{ field.auto_id }}" class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}"> + + {% if field.label %} + <label {% if field.id_for_label %}for="{{ field.id_for_label }}" {% endif %}class="{{ label_class }}{% if not inline_class %} col-form-label{% endif %}{% if field.field.required %} requiredField{% endif %}"> + {{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %} + </label> + {% endif %} + + {% include 'w3/layout/radioselect.html' %} + </div> +{% endif %} diff --git a/templates/w3/layout/row.html b/templates/w3/layout/row.html new file mode 100644 index 0000000..6922605 --- /dev/null +++ b/templates/w3/layout/row.html @@ -0,0 +1,3 @@ +<div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} class="form-row {{ div.css_class|default:'' }}" {{ div.flat_attrs }}> + {{ fields }} +</div> diff --git a/templates/w3/layout/tab-link.html b/templates/w3/layout/tab-link.html new file mode 100644 index 0000000..8f68f7c --- /dev/null +++ b/templates/w3/layout/tab-link.html @@ -0,0 +1 @@ +<li class="nav-item"><a class="nav-link{% if 'active' in link.css_class %} active{% endif %}" href="#{{ link.css_id }}" data-toggle="tab">{{ link.name|capfirst }}{% if tab.errors %}!{% endif %}</a></li> diff --git a/templates/w3/layout/tab.html b/templates/w3/layout/tab.html new file mode 100644 index 0000000..59a03bd --- /dev/null +++ b/templates/w3/layout/tab.html @@ -0,0 +1,6 @@ +<ul{% if tabs.css_id %} id="{{ tabs.css_id }}"{% endif %} class="nav nav-tabs"> + {{ links }} +</ul> +<div class="tab-content card-body"> + {{ content }} +</div> diff --git a/templates/w3/layout/uneditable_input.html b/templates/w3/layout/uneditable_input.html new file mode 100644 index 0000000..0af45e4 --- /dev/null +++ b/templates/w3/layout/uneditable_input.html @@ -0,0 +1,14 @@ +{% load crispy_forms_field %} +<div id="div_{{ field.auto_id }}" class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% if form_show_errors and field.errors %} error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}"> + <label class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}">{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}</label> + <div class="{{ field_class }}"> + {% if field|is_select and use_custom_control %} + {% crispy_field field 'class' 'custom-select' 'disabled' 'disabled' %} + {% elif field|is_file %} + {% crispy_field field 'class' 'form-control-file' 'disabled' 'disabled' %} + {% else %} + {% crispy_field field 'class' 'form-control' 'disabled' 'disabled' %} + {% endif %} + {% include 'w3/layout/help_text.html' %} + </div> +</div> diff --git a/templates/w3/table_inline_formset.html b/templates/w3/table_inline_formset.html new file mode 100644 index 0000000..a4b951d --- /dev/null +++ b/templates/w3/table_inline_formset.html @@ -0,0 +1,57 @@ +{% load crispy_forms_tags %} +{% load crispy_forms_utils %} +{% load crispy_forms_field %} + +{% specialspaceless %} +{% if formset_tag %} +<form {{ flat_attrs }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}> +{% endif %} + {% if formset_method|lower == 'post' and not disable_csrf %} + {% csrf_token %} + {% endif %} + + <div> + {{ formset.management_form|crispy }} + </div> + + <table{% if form_id %} id="{{ form_id }}_table"{% endif%} class="table table-striped table-sm"> + <thead> + {% if formset.readonly and not formset.queryset.exists %} + {% else %} + <tr> + {% for field in formset.forms.0 %} + {% if field.label and not field.is_hidden %} + <th for="{{ field.auto_id }}" class="col-form-label {% if field.field.required %}requiredField{% endif %}"> + {{ field.label }}{% if field.field.required and not field|is_checkbox %}<span class="asteriskField">*</span>{% endif %} + </th> + {% endif %} + {% endfor %} + </tr> + {% endif %} + </thead> + + <tbody> + <tr class="d-none empty-form"> + {% for field in formset.empty_form %} + {% include 'w3/field.html' with tag="td" form_show_labels=False %} + {% endfor %} + </tr> + + {% for form in formset %} + {% if form_show_errors and not form.is_extra %} + {% include "w3/errors.html" %} + {% endif %} + + <tr> + {% for field in form %} + {% include 'w3/field.html' with tag="td" form_show_labels=False %} + {% endfor %} + </tr> + {% endfor %} + </tbody> + </table> + + {% include "w3/inputs.html" %} + +{% if formset_tag %}</form>{% endif %} +{% endspecialspaceless %} diff --git a/templates/w3/uni_form.html b/templates/w3/uni_form.html new file mode 100644 index 0000000..6b63a3f --- /dev/null +++ b/templates/w3/uni_form.html @@ -0,0 +1,11 @@ +{% load crispy_forms_utils %} + +{% specialspaceless %} + {% if include_media %}{{ form.media }}{% endif %} + {% if form_show_errors %} + {% include "w3/errors.html" %} + {% endif %} + {% for field in form %} + {% include field_template %} + {% endfor %} +{% endspecialspaceless %} diff --git a/templates/w3/uni_formset.html b/templates/w3/uni_formset.html new file mode 100644 index 0000000..5c6cd36 --- /dev/null +++ b/templates/w3/uni_formset.html @@ -0,0 +1,8 @@ +{% with formset.management_form as form %} + {% include 'w3/uni_form.html' %} +{% endwith %} +{% for form in formset %} + <div class="multiField"> + {% include 'w3/uni_form.html' %} + </div> +{% endfor %} diff --git a/templates/w3/whole_uni_form.html b/templates/w3/whole_uni_form.html new file mode 100644 index 0000000..df71b0b --- /dev/null +++ b/templates/w3/whole_uni_form.html @@ -0,0 +1,14 @@ +{% load crispy_forms_utils %} + +{% specialspaceless %} +{% if form_tag %}<form {{ flat_attrs }} method="{{ form_method }}" {% if form.is_multipart %} enctype="multipart/form-data"{% endif %}>{% endif %} + {% if form_method|lower == 'post' and not disable_csrf %} + {% csrf_token %} + {% endif %} + + {% include "w3/display_form.html" %} + + {% include "w3/inputs.html" %} + +{% if form_tag %}</form>{% endif %} +{% endspecialspaceless %} diff --git a/templates/w3/whole_uni_formset.html b/templates/w3/whole_uni_formset.html new file mode 100644 index 0000000..ba40759 --- /dev/null +++ b/templates/w3/whole_uni_formset.html @@ -0,0 +1,30 @@ +{% load crispy_forms_tags %} +{% load crispy_forms_utils %} + +{% specialspaceless %} +{% if formset_tag %} +<form {{ flat_attrs }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}> +{% endif %} + {% if formset_method|lower == 'post' and not disable_csrf %} + {% csrf_token %} + {% endif %} + + <div> + {{ formset.management_form|crispy }} + </div> + + {% include "w3/errors_formset.html" %} + + {% for form in formset %} + {% include "w3/display_form.html" %} + {% endfor %} + + {% if inputs %} + <div class="form-actions"> + {% for input in inputs %} + {% include "w3/layout/baseinput.html" %} + {% endfor %} + </div> + {% endif %} +{% if formset_tag %}</form>{% endif %} +{% endspecialspaceless %} diff --git a/ui/html/base.tmpl.html b/ui/html/base.tmpl.html deleted file mode 100644 index cc94537..0000000 --- a/ui/html/base.tmpl.html +++ /dev/null @@ -1,198 +0,0 @@ -{{ define "base" }} -<!DOCTYPE html> -<html class="govuk-template app-html-class"> - <head> - <meta charset="utf-8"> - <title>DefNucSyR Engagement Database (DED)</title> - <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> - <meta name="theme-color" content="blue"> - <link rel="icon" sizes="48x48" href="/static/assets/images/favicon.ico"> - <link rel="icon" sizes="any" href="/static/assets/images/favicon.svg" type="image/svg+xml"> - <link rel="mask-icon" href="/static/assets/images/govuk-icon-mask.svg" color="blue"> - <link rel="apple-touch-icon" href="/static/assets/images/govuk-icon-180.png"> - <link rel="manifest" href="/static/assets/manifest.json"> - <link rel="stylesheet" href="/static/css/govuk-frontend.min.css"> - <link rel="stylesheet" href="/static/css/main.css"> - <meta property="og:image" content="/static/assets/images/govuk-opengraph-image.png"> - </head> - - <body class="govuk-template__body app-body-class" data-test="My value" data-other="report:details"> - <script> - document.body.className += ' js-enabled' + ('noModule' in HTMLScriptElement.prototype ? ' govuk-frontend-supported' : ''); - </script> - <form action="/form-handler" method="post" novalidate> - <div class="govuk-cookie-banner" data-nosnippet role="region" aria-label="Cookies on [name of service]"> - <div class="govuk-cookie-banner__message govuk-width-container"> - <div class="govuk-grid-row"> - <div class="govuk-grid-column-two-thirds"> - <h2 class="govuk-cookie-banner__heading govuk-heading-m"> - Cookies on [name of service] - </h2> - <div class="govuk-cookie-banner__content"> - <p class="govuk-body">We use some essential cookies to make this service work.</p> - <p class="govuk-body">We’d also like to use analytics cookies so we can understand how you use the service and make improvements.</p> - </div> - </div> - </div> - <div class="govuk-button-group"> - <button value="yes" type="submit" name="cookies[analytics]" class="govuk-button" data-module="govuk-button"> - Accept analytics cookies - </button> - <button value="no" type="submit" name="cookies[analytics]" class="govuk-button" data-module="govuk-button"> - Reject analytics cookies - </button> - <a class="govuk-link" href="#">View cookies</a> - </div> - </div> - </div> - </form> - <a href="#main-content" class="govuk-skip-link" data-module="govuk-skip-link">Skip to main content</a> - <header class="govuk-header" role="banner" data-module="govuk-header"> - <div class="govuk-header__container app-width-container"> - <div class="govuk-header__logo"> - <a href="#" class="govuk-header__link govuk-header__link--homepage"> - <svg - focusable="false" - role="img" - class="govuk-header__logotype" - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 148 30" - height="30" - width="148" - aria-label="GOV.UK"> - <title>GOV.UK</title> - <path d="M22.6 10.4c-1 .4-2-.1-2.4-1-.4-.9.1-2 1-2.4.9-.4 2 .1 2.4 1s-.1 2-1 2.4m-5.9 6.7c-.9.4-2-.1-2.4-1-.4-.9.1-2 1-2.4.9-.4 2 .1 2.4 1s-.1 2-1 2.4m10.8-3.7c-1 .4-2-.1-2.4-1-.4-.9.1-2 1-2.4.9-.4 2 .1 2.4 1s0 2-1 2.4m3.3 4.8c-1 .4-2-.1-2.4-1-.4-.9.1-2 1-2.4.9-.4 2 .1 2.4 1s-.1 2-1 2.4M17 4.7l2.3 1.2V2.5l-2.3.7-.2-.2.9-3h-3.4l.9 3-.2.2c-.1.1-2.3-.7-2.3-.7v3.4L15 4.7c.1.1.1.2.2.2l-1.3 4c-.1.2-.1.4-.1.6 0 1.1.8 2 1.9 2.2h.7c1-.2 1.9-1.1 1.9-2.1 0-.2 0-.4-.1-.6l-1.3-4c-.1-.2 0-.2.1-.3m-7.6 5.7c.9.4 2-.1 2.4-1 .4-.9-.1-2-1-2.4-.9-.4-2 .1-2.4 1s0 2 1 2.4m-5 3c.9.4 2-.1 2.4-1 .4-.9-.1-2-1-2.4-.9-.4-2 .1-2.4 1s.1 2 1 2.4m-3.2 4.8c.9.4 2-.1 2.4-1 .4-.9-.1-2-1-2.4-.9-.4-2 .1-2.4 1s0 2 1 2.4m14.8 11c4.4 0 8.6.3 12.3.8 1.1-4.5 2.4-7 3.7-8.8l-2.5-.9c.2 1.3.3 1.9 0 2.7-.4-.4-.8-1.1-1.1-2.3l-1.2 4c.7-.5 1.3-.8 2-.9-1.1 2.5-2.6 3.1-3.5 3-1.1-.2-1.7-1.2-1.5-2.1.3-1.2 1.5-1.5 2.1-.1 1.1-2.3-.8-3-2-2.3 1.9-1.9 2.1-3.5.6-5.6-2.1 1.6-2.1 3.2-1.2 5.5-1.2-1.4-3.2-.6-2.5 1.6.9-1.4 2.1-.5 1.9.8-.2 1.1-1.7 2.1-3.5 1.9-2.7-.2-2.9-2.1-2.9-3.6.7-.1 1.9.5 2.9 1.9l.4-4.3c-1.1 1.1-2.1 1.4-3.2 1.4.4-1.2 2.1-3 2.1-3h-5.4s1.7 1.9 2.1 3c-1.1 0-2.1-.2-3.2-1.4l.4 4.3c1-1.4 2.2-2 2.9-1.9-.1 1.5-.2 3.4-2.9 3.6-1.9.2-3.4-.8-3.5-1.9-.2-1.3 1-2.2 1.9-.8.7-2.3-1.2-3-2.5-1.6.9-2.2.9-3.9-1.2-5.5-1.5 2-1.3 3.7.6 5.6-1.2-.7-3.1 0-2 2.3.6-1.4 1.8-1.1 2.1.1.2.9-.3 1.9-1.5 2.1-.9.2-2.4-.5-3.5-3 .6 0 1.2.3 2 .9l-1.2-4c-.3 1.1-.7 1.9-1.1 2.3-.3-.8-.2-1.4 0-2.7l-2.9.9C1.3 23 2.6 25.5 3.7 30c3.7-.5 7.9-.8 12.3-.8m28.3-11.6c0 .9.1 1.7.3 2.5.2.8.6 1.5 1 2.2.5.6 1 1.1 1.7 1.5.7.4 1.5.6 2.5.6.9 0 1.7-.1 2.3-.4s1.1-.7 1.5-1.1c.4-.4.6-.9.8-1.5.1-.5.2-1 .2-1.5v-.2h-5.3v-3.2h9.4V28H55v-2.5c-.3.4-.6.8-1 1.1-.4.3-.8.6-1.3.9-.5.2-1 .4-1.6.6s-1.2.2-1.8.2c-1.5 0-2.9-.3-4-.8-1.2-.6-2.2-1.3-3-2.3-.8-1-1.4-2.1-1.8-3.4-.3-1.4-.5-2.8-.5-4.3s.2-2.9.7-4.2c.5-1.3 1.1-2.4 2-3.4.9-1 1.9-1.7 3.1-2.3 1.2-.6 2.6-.8 4.1-.8 1 0 1.9.1 2.8.3.9.2 1.7.6 2.4 1s1.4.9 1.9 1.5c.6.6 1 1.3 1.4 2l-3.7 2.1c-.2-.4-.5-.9-.8-1.2-.3-.4-.6-.7-1-1-.4-.3-.8-.5-1.3-.7-.5-.2-1.1-.2-1.7-.2-1 0-1.8.2-2.5.6-.7.4-1.3.9-1.7 1.5-.5.6-.8 1.4-1 2.2-.3.8-.4 1.9-.4 2.7zM71.5 6.8c1.5 0 2.9.3 4.2.8 1.2.6 2.3 1.3 3.1 2.3.9 1 1.5 2.1 2 3.4s.7 2.7.7 4.2-.2 2.9-.7 4.2c-.4 1.3-1.1 2.4-2 3.4-.9 1-1.9 1.7-3.1 2.3-1.2.6-2.6.8-4.2.8s-2.9-.3-4.2-.8c-1.2-.6-2.3-1.3-3.1-2.3-.9-1-1.5-2.1-2-3.4-.4-1.3-.7-2.7-.7-4.2s.2-2.9.7-4.2c.4-1.3 1.1-2.4 2-3.4.9-1 1.9-1.7 3.1-2.3 1.2-.5 2.6-.8 4.2-.8zm0 17.6c.9 0 1.7-.2 2.4-.5s1.3-.8 1.7-1.4c.5-.6.8-1.3 1.1-2.2.2-.8.4-1.7.4-2.7v-.1c0-1-.1-1.9-.4-2.7-.2-.8-.6-1.6-1.1-2.2-.5-.6-1.1-1.1-1.7-1.4-.7-.3-1.5-.5-2.4-.5s-1.7.2-2.4.5-1.3.8-1.7 1.4c-.5.6-.8 1.3-1.1 2.2-.2.8-.4 1.7-.4 2.7v.1c0 1 .1 1.9.4 2.7.2.8.6 1.6 1.1 2.2.5.6 1.1 1.1 1.7 1.4.6.3 1.4.5 2.4.5zM88.9 28 83 7h4.7l4 15.7h.1l4-15.7h4.7l-5.9 21h-5.7zm28.8-3.6c.6 0 1.2-.1 1.7-.3.5-.2 1-.4 1.4-.8.4-.4.7-.8.9-1.4.2-.6.3-1.2.3-2v-13h4.1v13.6c0 1.2-.2 2.2-.6 3.1s-1 1.7-1.8 2.4c-.7.7-1.6 1.2-2.7 1.5-1 .4-2.2.5-3.4.5-1.2 0-2.4-.2-3.4-.5-1-.4-1.9-.9-2.7-1.5-.8-.7-1.3-1.5-1.8-2.4-.4-.9-.6-2-.6-3.1V6.9h4.2v13c0 .8.1 1.4.3 2 .2.6.5 1 .9 1.4.4.4.8.6 1.4.8.6.2 1.1.3 1.8.3zm13-17.4h4.2v9.1l7.4-9.1h5.2l-7.2 8.4L148 28h-4.9l-5.5-9.4-2.7 3V28h-4.2V7zm-27.6 16.1c-1.5 0-2.7 1.2-2.7 2.7s1.2 2.7 2.7 2.7 2.7-1.2 2.7-2.7-1.2-2.7-2.7-2.7z"></path> - </svg> - </a> - </div> - <div class="govuk-header__content"> - <a href="#" class="govuk-header__link govuk-header__service-name"> - Defence Nuclear Security Regulator - </a> - <nav aria-label="Menu" class="govuk-header__navigation"> - <button type="button" class="govuk-header__menu-button govuk-js-header-toggle" aria-controls="navigation" hidden> - Menu - </button> - <ul id="navigation" class="govuk-header__navigation-list"> - <li class="govuk-header__navigation-item govuk-header__navigation-item--active"> - <a class="govuk-header__link" href="#"> - Operations - </a> - </li> - <li class="govuk-header__navigation-item"> - <a class="govuk-header__link" href="#"> - Engagement Plans - </a> - </li> - <li class="govuk-header__navigation-item"> - <a class="govuk-header__link" href="#"> - Regulation - </a> - </li> - </ul> - </nav> - </div> - </div> - </header> - - <div class="govuk-width-container app-width-container"> - <div class="govuk-phase-banner"> - <p class="govuk-phase-banner__content"> - <strong class="govuk-tag govuk-phase-banner__content__tag"> - Alpha - </strong> - <span class="govuk-phase-banner__text"> - This is a new service – your <a class="govuk-link" href="#">feedback</a> will help us to improve it. - </span> - </p> - </div> - <a href="#" class="govuk-back-link">Back</a> - <main class="govuk-main-wrapper app-main-class" id="main-content" role="main"> - {{ template "main" . }} <!-- DONT FORGET THE DOT!!! --> - </main> - </div> - <footer class="govuk-footer" role="contentinfo"> - <div class="govuk-width-container"> - <div class="govuk-footer__meta"> - <div class="govuk-footer__meta-item govuk-footer__meta-item--grow"> - <h2 class="govuk-visually-hidden">Support links</h2> - <ul class="govuk-footer__inline-list"> - <li class="govuk-footer__inline-list-item"> - <a class="govuk-footer__link" href="#"> - Help - </a> - </li> - <li class="govuk-footer__inline-list-item"> - <a class="govuk-footer__link" href="#"> - Cookies - </a> - </li> - <li class="govuk-footer__inline-list-item"> - <a class="govuk-footer__link" href="#"> - Contact - </a> - </li> - <li class="govuk-footer__inline-list-item"> - <a class="govuk-footer__link" href="#"> - Terms and conditions - </a> - </li> - </ul> - <svg - aria-hidden="true" - focusable="false" - class="govuk-footer__licence-logo" - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 483.2 195.7" - height="17" - width="41"> - <path - fill="currentColor" - d="M421.5 142.8V.1l-50.7 32.3v161.1h112.4v-50.7zm-122.3-9.6A47.12 47.12 0 0 1 221 97.8c0-26 21.1-47.1 47.1-47.1 16.7 0 31.4 8.7 39.7 21.8l42.7-27.2A97.63 97.63 0 0 0 268.1 0c-36.5 0-68.3 20.1-85.1 49.7A98 98 0 0 0 97.8 0C43.9 0 0 43.9 0 97.8s43.9 97.8 97.8 97.8c36.5 0 68.3-20.1 85.1-49.7a97.76 97.76 0 0 0 149.6 25.4l19.4 22.2h3v-87.8h-80l24.3 27.5zM97.8 145c-26 0-47.1-21.1-47.1-47.1s21.1-47.1 47.1-47.1 47.2 21 47.2 47S123.8 145 97.8 145" /> - </svg> - <span class="govuk-footer__licence-description"> - All content is available under the - <a - class="govuk-footer__link" - href="https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/" - rel="license">Open Government Licence v3.0</a>, except where otherwise stated - </span> - </div> - <div class="govuk-footer__meta-item"> - <a - class="govuk-footer__link govuk-footer__copyright-logo" - href="https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/"> - © Crown copyright - </a> - </div> - </div> - </div> - </footer> - <script type="module" src="/static/js/govuk-frontend.min.js"></script> - <script type="module"> - import { - initAll - } from '/static/js/govuk-frontend.min.js' - initAll() - </script> - </body> - - - <!-- <body class="govuk-template__body"> --> - <!-- <script>document.body.className += ' js-enabled' + ('noModule' in HTMLScriptElement.prototype ? ' govuk-frontend-supported' : '');</script> --> - <!-- <script type="module" src="/static/js/govuk-frontend.min.js"></script> --> - <!-- <script type="module"> --> - <!-- import { initAll } from '/static/js/govuk-frontend.min.js' --> - <!-- initAll() --> - <!-- </script> --> - <!-- <div class="content"> --> - <!-- <header> --> - <!-- {{ template "nav" . }} --> - <!-- </header> --> - <!-- <main> --> - <!-- {{ template "main" . }} <!-1- DONT FORGET THE DOT!!! -1-> --> - <!-- </main> --> - <!-- </div> --> - <!-- </body> --> -</html> -{{ end }} diff --git a/ui/html/pages/home.tmpl.html b/ui/html/pages/home.tmpl.html deleted file mode 100644 index 1164108..0000000 --- a/ui/html/pages/home.tmpl.html +++ /dev/null @@ -1,56 +0,0 @@ -{{ define "title"}}Home{{ end }} - -{{ define "main" }} -<h2 class="govuk-heading-xl">DefNucSyR Engagement Database (DED)</h2> - -<div class="govuk-accordion" data-module="govuk-accordion" id="accordion-default"> - <div class="govuk-accordion__section"> - <div class="govuk-accordion__section-header"> - <h2 class="govuk-accordion__section-heading"> - <span class="govuk-accordion__section-button" id="accordion-default-heading-1"> - Regulation - </span> - </h2> - </div> - <div id="accordion-default-content-1" class="govuk-accordion__section-content"> - <p class="govuk-body">Stuff here</p> - </div> - </div> - <div class="govuk-accordion__section"> - <div class="govuk-accordion__section-header"> - <h2 class="govuk-accordion__section-heading"> - <span class="govuk-accordion__section-button" id="accordion-default-heading-2"> - Submarines & Propulsion Team - </span> - </h2> - </div> - <div id="accordion-default-content-2" class="govuk-accordion__section-content"> - <p class="govuk-body">Stuff here</p> - </div> - </div> - <div class="govuk-accordion__section"> - <div class="govuk-accordion__section-header"> - <h2 class="govuk-accordion__section-heading"> - <span class="govuk-accordion__section-button" id="accordion-default-heading-3"> - Warhead & Transport Team - </span> - </h2> - </div> - <div id="accordion-default-content-3" class="govuk-accordion__section-content"> - <p class="govuk-body">Stuff here</p> - </div> - </div> - <div class="govuk-accordion__section"> - <div class="govuk-accordion__section-header"> - <h2 class="govuk-accordion__section-heading"> - <span class="govuk-accordion__section-button" id="accordion-default-heading-4"> - How people read - </span> - </h2> - </div> - <div id="accordion-default-content-4" class="govuk-accordion__section-content"> - <p class="govuk-body">This is the content for How people read.</p> - </div> - </div> -</div> -{{ end }} diff --git a/ui/html/pages/operations_list.tmpl.html b/ui/html/pages/operations_list.tmpl.html deleted file mode 100644 index 2863d19..0000000 --- a/ui/html/pages/operations_list.tmpl.html +++ /dev/null @@ -1,59 +0,0 @@ -{{ define "title" }}Operations{{end}} - - -{{ define "main" }} -<h3>Operations <span><a href="#" class="admin-link">[Admin]</a></span></h3> - -<p> -Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. -</p> - - -{{if .Operations}} -<table id="home-summary-table"> - <thead> - <tr id="header-row"> - <th>Operation</th> - <th>RP</th> - <th>Lead Inspector[s]</th> - <th>Description</th> - <th>Organisation</th> - <th>EPs</th> - <th>ESs</th> - <th>SharePoint</th> - </tr> - </thead> - <tbody> - {{range .Operations }} - <tr> - <td><a href="#">{{.Name}}</a></td> - <td>Tim Gudgeon</td> - <td>Tina McKinchey / Barbara Snelland</td> - <td>{{.Description}}</td> - <td>{{.OrganisationName}}</td> - <td><a href="#">EP 2024</a></td> - <td> - {{if .EngagementStrategies}} - <ul> - {{range .EngagementStrategies}} - <li class="table-list"> - <a href="#">{{.FormatForTable }}</a> - </li> - {{end}} - - </ul> - {{else}} - NA - {{end}} - </td> - <td><a href="#">Link</a></td> - </tr> - {{end}} - </tbody> -</table> -{{else}} -<p>There are no operations.</p> -{{end}} - -{{ end }} - diff --git a/ui/html/pages/organisations_list.tmpl.html b/ui/html/pages/organisations_list.tmpl.html deleted file mode 100644 index 5fedb09..0000000 --- a/ui/html/pages/organisations_list.tmpl.html +++ /dev/null @@ -1,33 +0,0 @@ -{{ define "title" }}Organisations{{end}} - -{{ define "main" }} -<h3>Organisations <span><a href="#" class="admin-link">[Admin]</a></span></h3> -<table id="home-summary-table"> - <thead> - <tr id="header-row"> - <th>Organisation</th> - <th>RP</th> - <th>Lead Inspector[s]</th> - <th>Another attribute</th> - <th>Another attribute</th> - <th>SharePoint</th> - </tr> - </thead> - <tbody> - {{ if .Organisations }} - {{range .Organisations}} - <tr> - <td><a href="#">{{.Name}}</a></td> - <td>Tim Gudgeon</td> - <td>Tina McKinchey / Barbara Snelland</td> - <td>Bobbins</td> - <td>Bobbins</td> - <td><a href="#">Link</a></td> - </tr> - {{end}} - {{else}} - <p>There are no organisations yet.</p> - {{end}} - </tbody> -</table> -{{ end }} diff --git a/ui/html/pages/persons_list.tmpl.html b/ui/html/pages/persons_list.tmpl.html deleted file mode 100644 index 87689df..0000000 --- a/ui/html/pages/persons_list.tmpl.html +++ /dev/null @@ -1,33 +0,0 @@ -{{ define "title" }}Operations{{end}} - - -{{ define "main" }} -<h3>People <span><a href="#" class="admin-link">[Admin]</a></span></h3> - -{{if .Persons}} - <table id="home-summary-table"> - <thead> - <tr id="header-row"> - <th>First Name</th> - <th>Last Name</th> - <th>Organisation</th> - <th>Role</th> - </tr> - </thead> - <tbody> - {{range .Persons }} - <tr> - <td>{{.FirstName}}</td> - <td>{{.LastName}}</td> - <td>{{.OrganisationName}}</td> - <td>{{.RoleName}}</td> - </tr> - {{end}} - </tbody> - </table> -{{else}} -<p>There are no operations.</p> -{{end}} - -{{ end }} - diff --git a/ui/html/partials/nav.tmpl.html b/ui/html/partials/nav.tmpl.html deleted file mode 100644 index ac79192..0000000 --- a/ui/html/partials/nav.tmpl.html +++ /dev/null @@ -1,10 +0,0 @@ -{{ define "nav" }} -<nav> - <h1>DefNucSyR Engagement Database</h1> - <a href="/">Home</a> - <a href="/organisation/list">Organisations</a> - <a href="/operation/list">Operations</a> - <a href="/person/list">People</a> - <a href="#">Engagement Planning</a> -</nav> -{{ end }} diff --git a/ui/static/assets/fonts/bold-affa96571d-v2.woff b/ui/static/assets/fonts/bold-affa96571d-v2.woff Binary files differdeleted file mode 100755 index 48fbcf5..0000000 --- a/ui/static/assets/fonts/bold-affa96571d-v2.woff +++ /dev/null diff --git a/ui/static/assets/fonts/bold-b542beb274-v2.woff2 b/ui/static/assets/fonts/bold-b542beb274-v2.woff2 Binary files differdeleted file mode 100755 index 81fd149..0000000 --- a/ui/static/assets/fonts/bold-b542beb274-v2.woff2 +++ /dev/null diff --git a/ui/static/assets/fonts/light-94a07e06a1-v2.woff2 b/ui/static/assets/fonts/light-94a07e06a1-v2.woff2 Binary files differdeleted file mode 100755 index 1eb1015..0000000 --- a/ui/static/assets/fonts/light-94a07e06a1-v2.woff2 +++ /dev/null diff --git a/ui/static/assets/fonts/light-f591b13f7d-v2.woff b/ui/static/assets/fonts/light-f591b13f7d-v2.woff Binary files differdeleted file mode 100755 index 3b26d5f..0000000 --- a/ui/static/assets/fonts/light-f591b13f7d-v2.woff +++ /dev/null diff --git a/ui/static/assets/images/favicon.ico b/ui/static/assets/images/favicon.ico Binary files differdeleted file mode 100644 index 20129a0..0000000 --- a/ui/static/assets/images/favicon.ico +++ /dev/null diff --git a/ui/static/assets/images/favicon.svg b/ui/static/assets/images/favicon.svg deleted file mode 100644 index 67d7ef9..0000000 --- a/ui/static/assets/images/favicon.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#0b0c0c" d="M0 0h32v32H0z"/><path fill="#fff" d="m16.88 7.87 2.11 1.1V5.86l-2.11.67c-.06-.08-.13-.15-.2-.21s.85-2.68.85-2.68h-3.04l.85 2.67c-.08.07-.15.14-.2.21s-2.11-.66-2.11-.66v3.11l2.11-1.11c.06.09.14.16.22.22l-1.21 3.66v.01c0 .01 0 0 0 0-.06.18-.09.38-.09.58 0 .99.73 1.81 1.68 1.95h.04c.08.01.16.02.25.02.08 0 .17 0 .25-.02h.04c.95-.14 1.68-.96 1.68-1.95 0-.2-.03-.39-.09-.58s0 0 0-.01L16.7 8.08c.08-.06.16-.13.22-.22M9.16 12.3a1.75 1.75 0 1 0 1.52-3.15c-.86-.41-1.91-.04-2.33.83s-.05 1.91.81 2.32M4.82 15.87c.9.37 1.92-.05 2.29-.94s-.05-1.92-.95-2.29c-.88-.36-1.91.07-2.28.96s.06 1.91.94 2.27M22.86 12.3a1.75 1.75 0 1 1-1.52-3.15c.86-.41 1.91-.04 2.33.83s.05 1.91-.81 2.32M27.18 15.84c-.9.37-1.92-.05-2.29-.94s.05-1.92.95-2.29c.88-.36 1.91.07 2.28.96s-.06 1.91-.94 2.27M16 25.68c3.59 0 6.99.24 10.02.68.86-3.61 1.91-5.68 2.99-7.16l-2.03-.72c.2 1.03.23 1.51 0 2.18-.34-.33-.65-.93-.9-1.85l-.99 3.28c.6-.41 1.06-.68 1.59-.69-.94 2.02-2.11 2.54-2.87 2.4-.93-.17-1.35-1-1.21-1.7.21-.99 1.23-1.25 1.71-.1.91-1.86-.63-2.44-1.63-1.89 1.53-1.52 1.7-2.88.47-4.52-1.72 1.31-1.74 2.61-.97 4.44-1-1.15-2.56-.53-2 1.32.72-1.12 1.68-.42 1.53.65-.13.93-1.35 1.68-2.87 1.54-2.18-.2-2.31-1.71-2.37-2.95.54-.1 1.5.4 2.33 1.56l.3-3.48c-.9.93-1.71 1.11-2.62 1.14.3-.94 1.69-2.48 1.69-2.48h-4.34s1.38 1.54 1.69 2.48c-.91-.03-1.72-.21-2.62-1.14l.3 3.48c.82-1.16 1.79-1.66 2.33-1.56-.05 1.25-.18 2.75-2.37 2.95-1.52.13-2.75-.62-2.87-1.54-.15-1.06.81-1.77 1.53-.65.56-1.85-1-2.47-2-1.32.77-1.83.75-3.13-.97-4.44-1.23 1.64-1.06 2.99.47 4.52-.99-.55-2.54.03-1.63 1.89.48-1.16 1.5-.9 1.71.1.14.7-.28 1.53-1.21 1.7-.76.14-1.93-.38-2.87-2.4.53.01.99.28 1.59.69l-.99-3.28c-.25.92-.57 1.52-.9 1.85-.23-.66-.19-1.14 0-2.18l-2.03.72c1.08 1.47 2.13 3.54 2.99 7.16 3.03-.43 6.42-.68 10.01-.68"/></svg> diff --git a/ui/static/assets/images/govuk-crest-2x.png b/ui/static/assets/images/govuk-crest-2x.png Binary files differdeleted file mode 100644 index 78e751c..0000000 --- a/ui/static/assets/images/govuk-crest-2x.png +++ /dev/null diff --git a/ui/static/assets/images/govuk-crest.png b/ui/static/assets/images/govuk-crest.png Binary files differdeleted file mode 100644 index bed4efe..0000000 --- a/ui/static/assets/images/govuk-crest.png +++ /dev/null diff --git a/ui/static/assets/images/govuk-icon-180.png b/ui/static/assets/images/govuk-icon-180.png Binary files differdeleted file mode 100644 index 7c33beb..0000000 --- a/ui/static/assets/images/govuk-icon-180.png +++ /dev/null diff --git a/ui/static/assets/images/govuk-icon-192.png b/ui/static/assets/images/govuk-icon-192.png Binary files differdeleted file mode 100644 index 35e51d7..0000000 --- a/ui/static/assets/images/govuk-icon-192.png +++ /dev/null diff --git a/ui/static/assets/images/govuk-icon-512.png b/ui/static/assets/images/govuk-icon-512.png Binary files differdeleted file mode 100644 index f5eb6f4..0000000 --- a/ui/static/assets/images/govuk-icon-512.png +++ /dev/null diff --git a/ui/static/assets/images/govuk-icon-mask.svg b/ui/static/assets/images/govuk-icon-mask.svg deleted file mode 100644 index e10ff6c..0000000 --- a/ui/static/assets/images/govuk-icon-mask.svg +++ /dev/null @@ -1 +0,0 @@ -<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m0 0v512h512v-512zm415.76 238.55c11.12-4.56 24.05.88 28.67 12.11 4.63 11.17-.72 24.05-11.85 28.61-11.34 4.68-24.21-.67-28.83-11.88-4.65-11.18.67-24.18 12.01-28.84zm-39.05-58.11c11.12-4.56 24.05.89 28.68 12.12 4.62 11.17-.73 24.04-11.85 28.6-11.34 4.68-24.21-.67-28.83-11.88-4.65-11.18.67-24.18 12-28.84zm-58.98-35.19c11.12-4.57 24.05.88 28.67 12.11 4.62 11.17-.72 24.04-11.85 28.6-11.34 4.68-24.21-.67-28.84-11.88-4.64-11.18.67-24.18 12.01-28.83zm-101.34-55.92 27.83 8.81.09-.08c.77-1.03 1.68-1.94 2.7-2.73l.08-.08-11.2-35.25h20.12s20.12 0 20.12 0l-11.2 35.25.08.08c1.02.79 1.93 1.7 2.7 2.73l.08.08 27.84-8.81v41.08l-27.87-14.63-.06.06c-.83 1.08-1.8 2.04-2.88 2.86l16 48.28c.02.06.05.12.06.18l.02.05c.74 2.42 1.14 4.98 1.14 7.63 0 13.06-9.65 23.85-22.19 25.7-.18.03-.37.06-.55.08-1.07.14-2.16.23-3.26.23-1.11 0-2.19-.09-3.26-.23-.19-.02-.38-.06-.56-.08-12.55-1.85-22.19-12.63-22.19-25.7 0-2.65.4-5.21 1.15-7.62s.01-.06.01-.06c.02-.06.04-.12.06-.18l16.01-48.28c-1.09-.81-2.06-1.77-2.88-2.86l-.06-.06-27.87 14.63v-41.08zm48.11 174.62c-11.34 4.68-24.21-.67-28.84-11.88-4.64-11.18.67-24.18 12.01-28.83 11.12-4.57 24.05.88 28.67 12.11 4.62 11.17-.72 24.04-11.85 28.6zm-99.28-106.59c4.62-11.23 17.55-16.68 28.67-12.11 11.34 4.65 16.65 17.65 12.01 28.83-4.62 11.21-17.49 16.56-28.84 11.88-11.12-4.56-16.47-17.44-11.85-28.6zm-58.99 35.2c4.63-11.22 17.55-16.68 28.68-12.12 11.34 4.65 16.65 17.65 12 28.84-4.62 11.21-17.49 16.56-28.83 11.88-11.12-4.56-16.48-17.44-11.85-28.6zm-39.04 58.11c4.62-11.23 17.55-16.68 28.67-12.11 11.34 4.65 16.66 17.65 12.01 28.84-4.62 11.21-17.49 16.56-28.83 11.88-11.13-4.55-16.47-17.43-11.85-28.61zm335.98 169.34c-44.5-6.37-94.44-9.94-147.21-9.94s-102.66 3.57-147.14 9.93c-12.63-53.1-28-83.53-43.87-105.19l29.89-10.64c-2.97 15.18-3.45 22.24-.12 32 4.93-4.82 9.6-13.68 13.25-27.23l14.49 48.23c-8.82-6.07-15.61-9.99-23.39-10.17 13.79 29.7 31.01 37.34 42.17 35.28 13.62-2.5 19.89-14.65 17.78-24.95-3.13-14.6-18.12-18.41-25.12-1.42-13.39-27.31 9.32-35.81 23.93-27.73-22.44-22.39-25.01-42.29-6.89-66.38 25.24 19.3 25.54 38.41 14.18 65.3 14.74-16.87 37.68-7.81 29.4 19.45-10.64-16.46-24.71-6.1-22.5 9.53 1.87 13.62 19.85 24.62 42.25 22.68 32.1-2.91 34.01-25.06 34.81-43.39-7.89-1.46-22.1 5.86-34.22 22.87l-4.46-51.16c13.18 13.74 25.16 16.35 38.46 16.77-4.43-13.82-24.78-36.46-24.78-36.46h63.85s-20.35 22.64-24.78 36.46c13.3-.42 25.28-3.03 38.46-16.77l-4.46 51.16c-12.11-17.01-26.32-24.33-34.21-22.87.8 18.34 2.71 40.48 34.81 43.39 22.39 1.95 40.38-9.05 42.25-22.68 2.21-15.63-11.86-25.99-22.49-9.53-8.29-27.26 14.65-36.32 29.39-19.45-11.36-26.89-11.07-46 14.18-65.3 18.12 24.08 15.55 43.99-6.9 66.38 14.62-8.08 37.32.43 23.94 27.73-7-16.99-21.98-13.18-25.12 1.42-2.11 10.3 4.15 22.46 17.77 24.95 11.16 2.06 28.38-5.58 42.17-35.28-7.77.18-14.57 4.11-23.38 10.17l14.48-48.23c3.65 13.55 8.32 22.41 13.25 27.23 3.33-9.76 2.85-16.82-.12-32l29.89 10.64c-15.88 21.66-31.25 52.09-43.88 105.2z"/></svg>
\ No newline at end of file diff --git a/ui/static/assets/images/govuk-opengraph-image.png b/ui/static/assets/images/govuk-opengraph-image.png Binary files differdeleted file mode 100644 index 4d0e312..0000000 --- a/ui/static/assets/images/govuk-opengraph-image.png +++ /dev/null diff --git a/ui/static/assets/manifest.json b/ui/static/assets/manifest.json deleted file mode 100644 index 0d183a2..0000000 --- a/ui/static/assets/manifest.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "icons": [ - { - "src": "images/favicon.ico", - "type": "image/x-icon", - "sizes": "48x48" - }, - { - "src": "images/favicon.svg", - "type": "image/svg+xml", - "sizes": "150x150", - "purpose": "any" - }, - { - "src": "images/govuk-icon-180.png", - "type": "image/png", - "sizes": "180x180", - "purpose": "maskable" - }, - { - "src": "images/govuk-icon-192.png", - "type": "image/png", - "sizes": "192x192", - "purpose": "maskable" - }, - { - "src": "images/govuk-icon-512.png", - "type": "image/png", - "sizes": "512x512", - "purpose": "maskable" - }, - { - "src": "images/govuk-icon-mask.svg", - "type": "image/svg+xml", - "sizes": "150x150", - "purpose": "monochrome" - } - ] -} diff --git a/ui/static/css/cgit.css b/ui/static/css/cgit.css deleted file mode 100644 index 6be986e..0000000 --- a/ui/static/css/cgit.css +++ /dev/null @@ -1,912 +0,0 @@ -div#cgit { - padding: 0em; - margin: 0em; - font-family: sans-serif; - font-size: 10pt; - color: #333; - background: white; - padding: 4px; -} - -div#cgit a { - color: blue; - text-decoration: none; -} - -div#cgit a:hover { - text-decoration: underline; -} - -div#cgit table { - border-collapse: collapse; -} - -div#cgit table#header { - width: 100%; - margin-bottom: 1em; -} - -div#cgit table#header td.logo { - width: 96px; - vertical-align: top; -} - -div#cgit table#header td.main { - font-size: 250%; - padding-left: 10px; - white-space: nowrap; -} - -div#cgit table#header td.main a { - color: #000; -} - -div#cgit table#header td.form { - text-align: right; - vertical-align: bottom; - padding-right: 1em; - padding-bottom: 2px; - white-space: nowrap; -} - -div#cgit table#header td.form form, -div#cgit table#header td.form input, -div#cgit table#header td.form select { - font-size: 90%; -} - -div#cgit table#header td.sub { - color: #777; - border-top: solid 1px #ccc; - padding-left: 10px; -} - -div#cgit table.tabs { - border-bottom: solid 3px #ccc; - border-collapse: collapse; - margin-top: 2em; - margin-bottom: 0px; - width: 100%; -} - -div#cgit table.tabs td { - padding: 0px 1em; - vertical-align: bottom; -} - -div#cgit table.tabs td a { - padding: 2px 0.75em; - color: #777; - font-size: 110%; -} - -div#cgit table.tabs td a.active { - color: #000; - background-color: #ccc; -} - -div#cgit table.tabs a[href^="http://"]:after, div#cgit table.tabs a[href^="https://"]:after { - content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhcJDQY+gm2TAAAAHWlUWHRDb21tZW50AAAAAABDcmVhdGVkIHdpdGggR0lNUGQuZQcAAABbSURBVAhbY2BABs4MU4CwhYHBh2Erww4wrGFQZHjI8B8IgUIscJWyDHcggltQhI4zGDCcRwhChPggHIggP1QoAVmQkSETrGoHsiAEsACtBYN0oDAMbgU6EBcAAL2eHUt4XUU4AAAAAElFTkSuQmCC); - opacity: 0.5; - margin: 0 0 0 5px; -} - -div#cgit table.tabs td.form { - text-align: right; -} - -div#cgit table.tabs td.form form { - padding-bottom: 2px; - font-size: 90%; - white-space: nowrap; -} - -div#cgit table.tabs td.form input, -div#cgit table.tabs td.form select { - font-size: 90%; -} - -div#cgit div.path { - margin: 0px; - padding: 5px 2em 2px 2em; - color: #000; - background-color: #eee; -} - -div#cgit div.content { - margin: 0px; - padding: 2em; - border-bottom: solid 3px #ccc; -} - - -div#cgit table.list { - width: 100%; - border: none; - border-collapse: collapse; -} - -div#cgit table.list tr { - background: white; -} - -div#cgit table.list tr.logheader { - background: #eee; -} - -div#cgit table.list tr:nth-child(even) { - background: #f7f7f7; -} - -div#cgit table.list tr:nth-child(odd) { - background: white; -} - -div#cgit table.list tr:hover { - background: #eee; -} - -div#cgit table.list tr.nohover { - background: white; -} - -div#cgit table.list tr.nohover:hover { - background: white; -} - -div#cgit table.list tr.nohover-highlight:hover:nth-child(even) { - background: #f7f7f7; -} - -div#cgit table.list tr.nohover-highlight:hover:nth-child(odd) { - background: white; -} - -div#cgit table.list th { - font-weight: bold; - /* color: #888; - border-top: dashed 1px #888; - border-bottom: dashed 1px #888; - */ - padding: 0.1em 0.5em 0.05em 0.5em; - vertical-align: baseline; -} - -div#cgit table.list td { - border: none; - padding: 0.1em 0.5em 0.1em 0.5em; -} - -div#cgit table.list td.commitgraph { - font-family: monospace; - white-space: pre; -} - -div#cgit table.list td.commitgraph .column1 { - color: #a00; -} - -div#cgit table.list td.commitgraph .column2 { - color: #0a0; -} - -div#cgit table.list td.commitgraph .column3 { - color: #aa0; -} - -div#cgit table.list td.commitgraph .column4 { - color: #00a; -} - -div#cgit table.list td.commitgraph .column5 { - color: #a0a; -} - -div#cgit table.list td.commitgraph .column6 { - color: #0aa; -} - -div#cgit table.list td.logsubject { - font-family: monospace; - font-weight: bold; -} - -div#cgit table.list td.logmsg { - font-family: monospace; - white-space: pre; - padding: 0 0.5em; -} - -div#cgit table.list td a { - color: black; -} - -div#cgit table.list td a.ls-dir { - font-weight: bold; - color: #00f; -} - -div#cgit table.list td a:hover { - color: #00f; -} - -div#cgit img { - border: none; -} - -div#cgit input#switch-btn { - margin: 2px 0px 0px 0px; -} - -div#cgit td#sidebar input.txt { - width: 100%; - margin: 2px 0px 0px 0px; -} - -div#cgit table#grid { - margin: 0px; -} - -div#cgit td#content { - vertical-align: top; - padding: 1em 2em 1em 1em; - border: none; -} - -div#cgit div#summary { - vertical-align: top; - margin-bottom: 1em; -} - -div#cgit table#downloads { - float: right; - border-collapse: collapse; - border: solid 1px #777; - margin-left: 0.5em; - margin-bottom: 0.5em; -} - -div#cgit table#downloads th { - background-color: #ccc; -} - -div#cgit div#blob { - border: solid 1px black; -} - -div#cgit div.error { - color: red; - font-weight: bold; - margin: 1em 2em; -} - -div#cgit a.ls-blob, div#cgit a.ls-dir, div#cgit .ls-mod { - font-family: monospace; -} - -div#cgit td.ls-size { - text-align: right; - font-family: monospace; - width: 10em; -} - -div#cgit td.ls-mode { - font-family: monospace; - width: 10em; -} - -div#cgit table.blob { - margin-top: 0.5em; - border-top: solid 1px black; -} - -div#cgit table.blob td.hashes, -div#cgit table.blob td.lines { - margin: 0; padding: 0 0 0 0.5em; - vertical-align: top; - color: black; -} - -div#cgit table.blob td.linenumbers { - margin: 0; padding: 0 0.5em 0 0.5em; - vertical-align: top; - text-align: right; - border-right: 1px solid gray; -} - -div#cgit table.blob pre { - padding: 0; margin: 0; -} - -div#cgit table.blob td.linenumbers a, -div#cgit table.ssdiff td.lineno a { - color: gray; - text-align: right; - text-decoration: none; -} - -div#cgit table.blob td.linenumbers a:hover, -div#cgit table.ssdiff td.lineno a:hover { - color: black; -} - -div#cgit table.blame td.hashes, -div#cgit table.blame td.lines, -div#cgit table.blame td.linenumbers { - padding: 0; -} - -div#cgit table.blame td.hashes div.alt, -div#cgit table.blame td.lines div.alt { - padding: 0 0.5em 0 0.5em; -} - -div#cgit table.blame td.linenumbers div.alt { - padding: 0 0.5em 0 0; -} - -div#cgit table.blame div.alt:nth-child(even) { - background: #eee; -} - -div#cgit table.blame div.alt:nth-child(odd) { - background: white; -} - -div#cgit table.blame td.lines > div { - position: relative; -} - -div#cgit table.blame td.lines > div > pre { - padding: 0 0 0 0.5em; - position: absolute; - top: 0; -} - -div#cgit table.bin-blob { - margin-top: 0.5em; - border: solid 1px black; -} - -div#cgit table.bin-blob th { - font-family: monospace; - white-space: pre; - border: solid 1px #777; - padding: 0.5em 1em; -} - -div#cgit table.bin-blob td { - font-family: monospace; - white-space: pre; - border-left: solid 1px #777; - padding: 0em 1em; -} - -div#cgit table.nowrap td { - white-space: nowrap; -} - -div#cgit table.commit-info { - border-collapse: collapse; - margin-top: 1.5em; -} - -div#cgit div.cgit-panel { - float: right; - margin-top: 1.5em; -} - -div#cgit div.cgit-panel table { - border-collapse: collapse; - border: solid 1px #aaa; - background-color: #eee; -} - -div#cgit div.cgit-panel th { - text-align: center; -} - -div#cgit div.cgit-panel td { - padding: 0.25em 0.5em; -} - -div#cgit div.cgit-panel td.label { - padding-right: 0.5em; -} - -div#cgit div.cgit-panel td.ctrl { - padding-left: 0.5em; -} - -div#cgit table.commit-info th { - text-align: left; - font-weight: normal; - padding: 0.1em 1em 0.1em 0.1em; - vertical-align: top; -} - -div#cgit table.commit-info td { - font-weight: normal; - padding: 0.1em 1em 0.1em 0.1em; -} - -div#cgit div.commit-subject { - font-weight: bold; - font-size: 125%; - margin: 1.5em 0em 0.5em 0em; - padding: 0em; -} - -div#cgit div.commit-msg { - white-space: pre; - font-family: monospace; -} - -div#cgit div.notes-header { - font-weight: bold; - padding-top: 1.5em; -} - -div#cgit div.notes { - white-space: pre; - font-family: monospace; - border: solid 1px #ee9; - background-color: #ffd; - padding: 0.3em 2em 0.3em 1em; - float: left; -} - -div#cgit div.notes-footer { - clear: left; -} - -div#cgit div.diffstat-header { - font-weight: bold; - padding-top: 1.5em; -} - -div#cgit table.diffstat { - border-collapse: collapse; - border: solid 1px #aaa; - background-color: #eee; -} - -div#cgit table.diffstat th { - font-weight: normal; - text-align: left; - text-decoration: underline; - padding: 0.1em 1em 0.1em 0.1em; - font-size: 100%; -} - -div#cgit table.diffstat td { - padding: 0.2em 0.2em 0.1em 0.1em; - font-size: 100%; - border: none; -} - -div#cgit table.diffstat td.mode { - white-space: nowrap; -} - -div#cgit table.diffstat td span.modechange { - padding-left: 1em; - color: red; -} - -div#cgit table.diffstat td.add a { - color: green; -} - -div#cgit table.diffstat td.del a { - color: red; -} - -div#cgit table.diffstat td.upd a { - color: blue; -} - -div#cgit table.diffstat td.graph { - width: 500px; - vertical-align: middle; -} - -div#cgit table.diffstat td.graph table { - border: none; -} - -div#cgit table.diffstat td.graph td { - padding: 0px; - border: 0px; - height: 7pt; -} - -div#cgit table.diffstat td.graph td.add { - background-color: #5c5; -} - -div#cgit table.diffstat td.graph td.rem { - background-color: #c55; -} - -div#cgit div.diffstat-summary { - color: #888; - padding-top: 0.5em; -} - -div#cgit table.diff { - width: 100%; -} - -div#cgit table.diff td { - font-family: monospace; - white-space: pre; -} - -div#cgit table.diff td div.head { - font-weight: bold; - margin-top: 1em; - color: black; -} - -div#cgit table.diff td div.hunk { - color: #009; -} - -div#cgit table.diff td div.add { - color: green; -} - -div#cgit table.diff td div.del { - color: red; -} - -div#cgit .sha1 { - font-family: monospace; - font-size: 90%; -} - -div#cgit .left { - text-align: left; -} - -div#cgit .right { - text-align: right; -} - -div#cgit table.list td.reposection { - font-style: italic; - color: #888; -} - -div#cgit a.button { - font-size: 80%; - padding: 0em 0.5em; -} - -div#cgit a.primary { - font-size: 100%; -} - -div#cgit a.secondary { - font-size: 90%; -} - -div#cgit td.toplevel-repo { - -} - -div#cgit table.list td.sublevel-repo { - padding-left: 1.5em; -} - -div#cgit ul.pager { - list-style-type: none; - text-align: center; - margin: 1em 0em 0em 0em; - padding: 0; -} - -div#cgit ul.pager li { - display: inline-block; - margin: 0.25em 0.5em; -} - -div#cgit ul.pager a { - color: #777; -} - -div#cgit ul.pager .current { - font-weight: bold; -} - -div#cgit span.age-mins { - font-weight: bold; - color: #080; -} - -div#cgit span.age-hours { - color: #080; -} - -div#cgit span.age-days { - color: #040; -} - -div#cgit span.age-weeks { - color: #444; -} - -div#cgit span.age-months { - color: #888; -} - -div#cgit span.age-years { - color: #bbb; -} - -div#cgit span.insertions { - color: #080; -} - -div#cgit span.deletions { - color: #800; -} - -div#cgit div.footer { - margin-top: 0.5em; - text-align: center; - font-size: 80%; - color: #ccc; -} - -div#cgit div.footer a { - color: #ccc; - text-decoration: none; -} - -div#cgit div.footer a:hover { - text-decoration: underline; -} - -div#cgit a.branch-deco { - color: #000; - margin: 0px 0.5em; - padding: 0px 0.25em; - background-color: #88ff88; - border: solid 1px #007700; -} - -div#cgit a.tag-deco { - color: #000; - margin: 0px 0.5em; - padding: 0px 0.25em; - background-color: #ffff88; - border: solid 1px #777700; -} - -div#cgit a.tag-annotated-deco { - color: #000; - margin: 0px 0.5em; - padding: 0px 0.25em; - background-color: #ffcc88; - border: solid 1px #777700; -} - -div#cgit a.remote-deco { - color: #000; - margin: 0px 0.5em; - padding: 0px 0.25em; - background-color: #ccccff; - border: solid 1px #000077; -} - -div#cgit a.deco { - color: #000; - margin: 0px 0.5em; - padding: 0px 0.25em; - background-color: #ff8888; - border: solid 1px #770000; -} - -div#cgit div.commit-subject a.branch-deco, -div#cgit div.commit-subject a.tag-deco, -div#cgit div.commit-subject a.tag-annotated-deco, -div#cgit div.commit-subject a.remote-deco, -div#cgit div.commit-subject a.deco { - margin-left: 1em; - font-size: 75%; -} - -div#cgit table.stats { - border: solid 1px black; - border-collapse: collapse; -} - -div#cgit table.stats th { - text-align: left; - padding: 1px 0.5em; - background-color: #eee; - border: solid 1px black; -} - -div#cgit table.stats td { - text-align: right; - padding: 1px 0.5em; - border: solid 1px black; -} - -div#cgit table.stats td.total { - font-weight: bold; - text-align: left; -} - -div#cgit table.stats td.sum { - color: #c00; - font-weight: bold; -/* background-color: #eee; */ -} - -div#cgit table.stats td.left { - text-align: left; -} - -div#cgit table.vgraph { - border-collapse: separate; - border: solid 1px black; - height: 200px; -} - -div#cgit table.vgraph th { - background-color: #eee; - font-weight: bold; - border: solid 1px white; - padding: 1px 0.5em; -} - -div#cgit table.vgraph td { - vertical-align: bottom; - padding: 0px 10px; -} - -div#cgit table.vgraph div.bar { - background-color: #eee; -} - -div#cgit table.hgraph { - border: solid 1px black; - width: 800px; -} - -div#cgit table.hgraph th { - background-color: #eee; - font-weight: bold; - border: solid 1px black; - padding: 1px 0.5em; -} - -div#cgit table.hgraph td { - vertical-align: middle; - padding: 2px 2px; -} - -div#cgit table.hgraph div.bar { - background-color: #eee; - height: 1em; -} - -div#cgit table.ssdiff { - width: 100%; -} - -div#cgit table.ssdiff td { - font-size: 75%; - font-family: monospace; - white-space: pre; - padding: 1px 4px 1px 4px; - border-left: solid 1px #aaa; - border-right: solid 1px #aaa; -} - -div#cgit table.ssdiff td.add { - color: black; - background: #cfc; - min-width: 50%; -} - -div#cgit table.ssdiff td.add_dark { - color: black; - background: #aca; - min-width: 50%; -} - -div#cgit table.ssdiff span.add { - background: #cfc; - font-weight: bold; -} - -div#cgit table.ssdiff td.del { - color: black; - background: #fcc; - min-width: 50%; -} - -div#cgit table.ssdiff td.del_dark { - color: black; - background: #caa; - min-width: 50%; -} - -div#cgit table.ssdiff span.del { - background: #fcc; - font-weight: bold; -} - -div#cgit table.ssdiff td.changed { - color: black; - background: #ffc; - min-width: 50%; -} - -div#cgit table.ssdiff td.changed_dark { - color: black; - background: #cca; - min-width: 50%; -} - -div#cgit table.ssdiff td.lineno { - color: black; - background: #eee; - text-align: right; - width: 3em; - min-width: 3em; -} - -div#cgit table.ssdiff td.hunk { - color: black; - background: #ccf; - border-top: solid 1px #aaa; - border-bottom: solid 1px #aaa; -} - -div#cgit table.ssdiff td.head { - border-top: solid 1px #aaa; - border-bottom: solid 1px #aaa; -} - -div#cgit table.ssdiff td.head div.head { - font-weight: bold; - color: black; -} - -div#cgit table.ssdiff td.foot { - border-top: solid 1px #aaa; - border-left: none; - border-right: none; - border-bottom: none; -} - -div#cgit table.ssdiff td.space { - border: none; -} - -div#cgit table.ssdiff td.space div { - min-height: 3em; -} - -/* Style definition file generated by highlight 3.9, http://www.andre-simon.de/ */ -/* Highlighting theme: Kwrite Editor */ -/* adapted for cgit */ -div#cgit table.blob .num { color:#b07e00; } -div#cgit table.blob .esc { color:#ff00ff; } -div#cgit table.blob .str { color:#bf0303; } -div#cgit table.blob .pps { color:#818100; } -div#cgit table.blob .slc { color:#838183; font-style:italic; } -div#cgit table.blob .com { color:#838183; font-style:italic; } -div#cgit table.blob .ppc { color:#008200; } -div#cgit table.blob .opt { color:#000000; } -div#cgit table.blob .lin { color:#555555; } -div#cgit table.blob .kwa { color:#000000; font-weight:bold; } -div#cgit table.blob .kwb { color:#0057ae; } -div#cgit table.blob .kwc { color:#000000; font-weight:bold; } -div#cgit table.blob .kwd { color:#010181; } diff --git a/ui/static/css/govuk-frontend.min.css b/ui/static/css/govuk-frontend.min.css deleted file mode 100644 index bd36c41..0000000 --- a/ui/static/css/govuk-frontend.min.css +++ /dev/null @@ -1,3 +0,0 @@ -@charset "UTF-8";:root{--govuk-frontend-version:"5.2.0";--govuk-frontend-breakpoint-mobile:20rem;--govuk-frontend-breakpoint-tablet:40.0625rem;--govuk-frontend-breakpoint-desktop:48.0625rem}.govuk-link{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em} -/*! Copyright (c) 2011 by Margaret Calvert & Henrik Kubel. All rights reserved. The font has been customised for exclusive use on gov.uk. This cut is not commercially available. */@font-face{font-family:GDS Transport;font-style:normal;font-weight:400;src:url(../../static/assets/fonts/light-94a07e06a1-v2.woff2) format("woff2"),url(../../static/assets/fonts/light-f591b13f7d-v2.woff) format("woff");font-display:fallback}@font-face{font-family:GDS Transport;font-style:normal;font-weight:700;src:url(../../static/assets/fonts/bold-b542beb274-v2.woff2) format("woff2"),url(../../static/assets/fonts/bold-affa96571d-v2.woff) format("woff");font-display:fallback}@media print{.govuk-link{font-family:sans-serif}}.govuk-link:hover{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-link:focus{outline:3px solid #0000;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-link:link{color:#1d70b8}.govuk-link:visited{color:#4c2c92}.govuk-link:hover{color:#003078}.govuk-link:active,.govuk-link:focus{color:#0b0c0c}@media print{[href^="/"].govuk-link:after,[href^="http://"].govuk-link:after,[href^="https://"].govuk-link:after{content:" (" attr(href) ")";font-size:90%;word-wrap:break-word}}.govuk-link--muted:link,.govuk-link--muted:visited{color:#505a5f}.govuk-link--muted:active,.govuk-link--muted:focus,.govuk-link--muted:hover,.govuk-link--text-colour:link,.govuk-link--text-colour:visited{color:#0b0c0c}@media print{.govuk-link--text-colour:link,.govuk-link--text-colour:visited{color:#000}}.govuk-link--text-colour:hover{color:#0b0c0cfc}.govuk-link--text-colour:active,.govuk-link--text-colour:focus{color:#0b0c0c}@media print{.govuk-link--text-colour:active,.govuk-link--text-colour:focus{color:#000}}.govuk-link--inverse:link,.govuk-link--inverse:visited{color:#fff}.govuk-link--inverse:active,.govuk-link--inverse:hover{color:#fffffffc}.govuk-link--inverse:focus{color:#0b0c0c}.govuk-link--no-underline:not(:hover):not(:active){text-decoration:none}.govuk-link--no-visited-state:link,.govuk-link--no-visited-state:visited{color:#1d70b8}.govuk-link--no-visited-state:hover{color:#003078}.govuk-link--no-visited-state:active,.govuk-link--no-visited-state:focus{color:#0b0c0c}.govuk-link-image{display:inline-block;line-height:0;text-decoration:none}.govuk-link-image:focus{outline:3px solid #0000;box-shadow:0 0 0 4px #fd0,0 0 0 8px #0b0c0c}.govuk-list{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;margin-top:0;margin-bottom:15px;padding-left:0;list-style-type:none}@media print{.govuk-list{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-list{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-list{font-size:14pt;line-height:1.15;color:#000}}@media (min-width:40.0625em){.govuk-list{margin-bottom:20px}}.govuk-list .govuk-list{margin-top:10px}.govuk-list>li{margin-bottom:5px}.govuk-list--bullet{padding-left:20px;list-style-type:disc}.govuk-list--number{padding-left:20px;list-style-type:decimal}.govuk-list--bullet>li,.govuk-list--number>li{margin-bottom:0}@media (min-width:40.0625em){.govuk-list--bullet>li,.govuk-list--number>li{margin-bottom:5px}}.govuk-list--spaced>li{margin-bottom:10px}@media (min-width:40.0625em){.govuk-list--spaced>li{margin-bottom:15px}}.govuk-heading-xl{color:#0b0c0c;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:700;font-size:2rem;line-height:1.09375;display:block;margin-top:0;margin-bottom:30px}@media print{.govuk-heading-xl{color:#000;font-family:sans-serif}}@media (min-width:40.0625em){.govuk-heading-xl{font-size:3rem;line-height:1.0416666667}}@media print{.govuk-heading-xl{font-size:32pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-heading-xl{margin-bottom:50px}}.govuk-heading-l{color:#0b0c0c;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:700;font-size:1.5rem;line-height:1.0416666667;display:block;margin-top:0;margin-bottom:20px}@media print{.govuk-heading-l{color:#000;font-family:sans-serif}}@media (min-width:40.0625em){.govuk-heading-l{font-size:2.25rem;line-height:1.1111111111}}@media print{.govuk-heading-l{font-size:24pt;line-height:1.05}}@media (min-width:40.0625em){.govuk-heading-l{margin-bottom:30px}}.govuk-heading-m{color:#0b0c0c;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:700;font-size:1.125rem;line-height:1.1111111111;display:block;margin-top:0;margin-bottom:15px}@media print{.govuk-heading-m{color:#000;font-family:sans-serif}}@media (min-width:40.0625em){.govuk-heading-m{font-size:1.5rem;line-height:1.25}}@media print{.govuk-heading-m{font-size:18pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-heading-m{margin-bottom:20px}}.govuk-heading-s{color:#0b0c0c;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:700;font-size:1rem;line-height:1.25;display:block;margin-top:0;margin-bottom:15px}@media print{.govuk-heading-s{color:#000;font-family:sans-serif}}@media (min-width:40.0625em){.govuk-heading-s{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-heading-s{font-size:14pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-heading-s{margin-bottom:20px}}.govuk-caption-xl{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1.125rem;line-height:1.1111111111;display:block;margin-bottom:5px;color:#505a5f}@media print{.govuk-caption-xl{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-caption-xl{font-size:1.6875rem;line-height:1.1111111111}}@media print{.govuk-caption-xl{font-size:18pt;line-height:1.15}}.govuk-caption-l{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1.125rem;line-height:1.1111111111;display:block;margin-bottom:5px;color:#505a5f}@media print{.govuk-caption-l{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-caption-l{font-size:1.5rem;line-height:1.25}}@media print{.govuk-caption-l{font-size:18pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-caption-l{margin-bottom:0}}.govuk-caption-m{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;display:block;color:#505a5f}@media print{.govuk-caption-m{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-caption-m{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-caption-m{font-size:14pt;line-height:1.15}}.govuk-body-l,.govuk-body-lead{color:#0b0c0c;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1.125rem;line-height:1.1111111111;margin-top:0;margin-bottom:20px}@media print{.govuk-body-l,.govuk-body-lead{color:#000;font-family:sans-serif}}@media (min-width:40.0625em){.govuk-body-l,.govuk-body-lead{font-size:1.5rem;line-height:1.25}}@media print{.govuk-body-l,.govuk-body-lead{font-size:18pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-body-l,.govuk-body-lead{margin-bottom:30px}}.govuk-body,.govuk-body-m{color:#0b0c0c;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;margin-top:0;margin-bottom:15px}@media print{.govuk-body,.govuk-body-m{color:#000;font-family:sans-serif}}@media (min-width:40.0625em){.govuk-body,.govuk-body-m{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-body,.govuk-body-m{font-size:14pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-body,.govuk-body-m{margin-bottom:20px}}.govuk-body-s{color:#0b0c0c;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:.875rem;line-height:1.1428571429;margin-top:0;margin-bottom:15px}@media print{.govuk-body-s{color:#000;font-family:sans-serif}}@media (min-width:40.0625em){.govuk-body-s{font-size:1rem;line-height:1.25}}@media print{.govuk-body-s{font-size:14pt;line-height:1.2}}@media (min-width:40.0625em){.govuk-body-s{margin-bottom:20px}}.govuk-body-xs{color:#0b0c0c;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:.75rem;line-height:1.25;margin-top:0;margin-bottom:15px}@media print{.govuk-body-xs{color:#000;font-family:sans-serif}}@media (min-width:40.0625em){.govuk-body-xs{font-size:.875rem;line-height:1.4285714286}}@media print{.govuk-body-xs{font-size:12pt;line-height:1.2}}@media (min-width:40.0625em){.govuk-body-xs{margin-bottom:20px}}.govuk-body-l+.govuk-heading-l,.govuk-body-lead+.govuk-heading-l{padding-top:5px}@media (min-width:40.0625em){.govuk-body-l+.govuk-heading-l,.govuk-body-lead+.govuk-heading-l{padding-top:10px}}.govuk-body+.govuk-heading-l,.govuk-body-m+.govuk-heading-l,.govuk-body-s+.govuk-heading-l,.govuk-list+.govuk-heading-l{padding-top:15px}@media (min-width:40.0625em){.govuk-body+.govuk-heading-l,.govuk-body-m+.govuk-heading-l,.govuk-body-s+.govuk-heading-l,.govuk-list+.govuk-heading-l{padding-top:20px}}.govuk-body+.govuk-heading-m,.govuk-body+.govuk-heading-s,.govuk-body-m+.govuk-heading-m,.govuk-body-m+.govuk-heading-s,.govuk-body-s+.govuk-heading-m,.govuk-body-s+.govuk-heading-s,.govuk-list+.govuk-heading-m,.govuk-list+.govuk-heading-s{padding-top:5px}@media (min-width:40.0625em){.govuk-body+.govuk-heading-m,.govuk-body+.govuk-heading-s,.govuk-body-m+.govuk-heading-m,.govuk-body-m+.govuk-heading-s,.govuk-body-s+.govuk-heading-m,.govuk-body-s+.govuk-heading-s,.govuk-list+.govuk-heading-m,.govuk-list+.govuk-heading-s{padding-top:10px}}.govuk-section-break{margin:0;border:0}.govuk-section-break--xl{margin-top:30px;margin-bottom:30px}@media (min-width:40.0625em){.govuk-section-break--xl{margin-top:50px;margin-bottom:50px}}.govuk-section-break--l{margin-top:20px;margin-bottom:20px}@media (min-width:40.0625em){.govuk-section-break--l{margin-top:30px;margin-bottom:30px}}.govuk-section-break--m{margin-top:15px;margin-bottom:15px}@media (min-width:40.0625em){.govuk-section-break--m{margin-top:20px;margin-bottom:20px}}.govuk-section-break--visible{border-bottom:1px solid #b1b4b6}.govuk-button-group{margin-bottom:5px;display:flex;flex-direction:column;align-items:center}@media (min-width:40.0625em){.govuk-button-group{margin-bottom:15px}}.govuk-button-group .govuk-link{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.1875;display:inline-block;max-width:100%;margin-top:5px;margin-bottom:20px;text-align:center}@media print{.govuk-button-group .govuk-link{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-button-group .govuk-link{font-size:1.1875rem;line-height:1}}@media print{.govuk-button-group .govuk-link{font-size:14pt;line-height:19px}}.govuk-button-group .govuk-button{margin-bottom:17px}@media (min-width:40.0625em){.govuk-button-group{margin-right:-15px;flex-direction:row;flex-wrap:wrap;align-items:baseline}.govuk-button-group .govuk-button,.govuk-button-group .govuk-link{margin-right:15px}.govuk-button-group .govuk-link{text-align:left}}.govuk-form-group{margin-bottom:20px}.govuk-form-group:after{content:"";display:block;clear:both}@media (min-width:40.0625em){.govuk-form-group{margin-bottom:30px}}.govuk-form-group .govuk-form-group:last-of-type{margin-bottom:0}.govuk-form-group--error{padding-left:15px;border-left:5px solid #d4351c}.govuk-form-group--error .govuk-form-group{padding:0;border:0}.govuk-grid-row{margin-right:-15px;margin-left:-15px}.govuk-grid-row:after{content:"";display:block;clear:both}.govuk-grid-column-one-quarter{box-sizing:border-box;width:100%;padding:0 15px}@media (min-width:40.0625em){.govuk-grid-column-one-quarter{width:25%;float:left}}.govuk-grid-column-one-third{box-sizing:border-box;width:100%;padding:0 15px}@media (min-width:40.0625em){.govuk-grid-column-one-third{width:33.3333333333%;float:left}}.govuk-grid-column-one-half{box-sizing:border-box;width:100%;padding:0 15px}@media (min-width:40.0625em){.govuk-grid-column-one-half{width:50%;float:left}}.govuk-grid-column-two-thirds{box-sizing:border-box;width:100%;padding:0 15px}@media (min-width:40.0625em){.govuk-grid-column-two-thirds{width:66.6666666667%;float:left}}.govuk-grid-column-three-quarters{box-sizing:border-box;width:100%;padding:0 15px}@media (min-width:40.0625em){.govuk-grid-column-three-quarters{width:75%;float:left}}.govuk-grid-column-full{box-sizing:border-box;width:100%;padding:0 15px}@media (min-width:40.0625em){.govuk-grid-column-full{width:100%;float:left}}.govuk-grid-column-one-quarter-from-desktop{box-sizing:border-box;padding:0 15px}@media (min-width:48.0625em){.govuk-grid-column-one-quarter-from-desktop{width:25%;float:left}}.govuk-grid-column-one-third-from-desktop{box-sizing:border-box;padding:0 15px}@media (min-width:48.0625em){.govuk-grid-column-one-third-from-desktop{width:33.3333333333%;float:left}}.govuk-grid-column-one-half-from-desktop{box-sizing:border-box;padding:0 15px}@media (min-width:48.0625em){.govuk-grid-column-one-half-from-desktop{width:50%;float:left}}.govuk-grid-column-two-thirds-from-desktop{box-sizing:border-box;padding:0 15px}@media (min-width:48.0625em){.govuk-grid-column-two-thirds-from-desktop{width:66.6666666667%;float:left}}.govuk-grid-column-three-quarters-from-desktop{box-sizing:border-box;padding:0 15px}@media (min-width:48.0625em){.govuk-grid-column-three-quarters-from-desktop{width:75%;float:left}}.govuk-grid-column-full-from-desktop{box-sizing:border-box;padding:0 15px}@media (min-width:48.0625em){.govuk-grid-column-full-from-desktop{width:100%;float:left}}.govuk-main-wrapper{display:block;padding-top:20px;padding-bottom:20px}@media (min-width:40.0625em){.govuk-main-wrapper{padding-top:40px;padding-bottom:40px}}.govuk-main-wrapper--auto-spacing:first-child,.govuk-main-wrapper--l{padding-top:30px}@media (min-width:40.0625em){.govuk-main-wrapper--auto-spacing:first-child,.govuk-main-wrapper--l{padding-top:50px}}.govuk-template{background-color:#f3f2f1;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}@supports ((position:-webkit-sticky) or (position:sticky)){.govuk-template{scroll-padding-top:60px}.govuk-template:not(:has(.govuk-exit-this-page)){scroll-padding-top:0}}@media screen{.govuk-template{overflow-y:scroll}}.govuk-template__body{margin:0;background-color:#fff}.govuk-width-container{max-width:960px;margin-right:15px;margin-left:15px}@supports (margin:max(calc(0px))){.govuk-width-container{margin-right:max(15px,calc(15px + env(safe-area-inset-right)));margin-left:max(15px,calc(15px + env(safe-area-inset-left)))}}@media (min-width:40.0625em){.govuk-width-container{margin-right:30px;margin-left:30px}@supports (margin:max(calc(0px))){.govuk-width-container{margin-right:max(30px,calc(15px + env(safe-area-inset-right)));margin-left:max(30px,calc(15px + env(safe-area-inset-left)))}}}@media (min-width:1020px){.govuk-width-container{margin-right:auto;margin-left:auto}@supports (margin:max(calc(0px))){.govuk-width-container{margin-right:auto;margin-left:auto}}}.govuk-accordion{margin-bottom:20px}@media (min-width:40.0625em){.govuk-accordion{margin-bottom:30px}}.govuk-accordion__section{padding-top:15px}.govuk-accordion__section-heading{margin-top:0;margin-bottom:0;padding-top:15px;padding-bottom:15px}.govuk-accordion__section-button{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:700;font-size:1.125rem;line-height:1.1111111111;color:#0b0c0c;display:block;margin-bottom:0;padding-top:15px}@media print{.govuk-accordion__section-button{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-accordion__section-button{font-size:1.5rem;line-height:1.25}}@media print{.govuk-accordion__section-button{font-size:18pt;line-height:1.15;color:#000}}.govuk-accordion__section-content>:last-child{margin-bottom:0}.govuk-frontend-supported .govuk-accordion{border-bottom:1px solid #b1b4b6}.govuk-frontend-supported .govuk-accordion__section{padding-top:0}.govuk-frontend-supported .govuk-accordion__section-content{display:none;padding-top:15px;padding-bottom:30px}@media (min-width:40.0625em){.govuk-frontend-supported .govuk-accordion__section-content{padding-bottom:50px}}.govuk-frontend-supported .govuk-accordion__section-content[hidden]{padding-top:0;padding-bottom:0}@supports (content-visibility:hidden){.govuk-frontend-supported .govuk-accordion__section-content[hidden]{content-visibility:hidden;display:inherit}}.govuk-frontend-supported .govuk-accordion__section--expanded .govuk-accordion__section-content{display:block}.govuk-frontend-supported .govuk-accordion__show-all{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;position:relative;z-index:1;margin-bottom:9px;padding:5px 2px 5px 0;border-width:0;color:#1d70b8;background:none;cursor:pointer;-webkit-appearance:none}@media print{.govuk-frontend-supported .govuk-accordion__show-all{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-frontend-supported .govuk-accordion__show-all{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-frontend-supported .govuk-accordion__show-all{font-size:14pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-frontend-supported .govuk-accordion__show-all{margin-bottom:14px}}.govuk-frontend-supported .govuk-accordion__show-all::-moz-focus-inner{padding:0;border:0}.govuk-frontend-supported .govuk-accordion__show-all:hover{color:#0b0c0c;background:#f3f2f1;box-shadow:0 -2px #f3f2f1,0 4px #f3f2f1}.govuk-frontend-supported .govuk-accordion__show-all:hover .govuk-accordion__section-toggle-text{color:#0b0c0c}.govuk-frontend-supported .govuk-accordion__show-all:hover .govuk-accordion-nav__chevron{color:#0b0c0c;background:#0b0c0c}.govuk-frontend-supported .govuk-accordion__show-all:hover .govuk-accordion-nav__chevron:after{color:#f3f2f1}.govuk-frontend-supported .govuk-accordion__show-all:focus{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-frontend-supported .govuk-accordion__show-all:focus .govuk-accordion-nav__chevron{background:#0b0c0c}.govuk-frontend-supported .govuk-accordion__show-all:focus .govuk-accordion-nav__chevron:after{color:#fd0}.govuk-frontend-supported .govuk-accordion__section-heading{padding:0}.govuk-frontend-supported .govuk-accordion-nav__chevron{box-sizing:border-box;display:inline-block;position:relative;width:1.25rem;height:1.25rem;border:.0625rem solid;border-radius:50%;vertical-align:middle}.govuk-frontend-supported .govuk-accordion-nav__chevron:after{content:"";box-sizing:border-box;display:block;position:absolute;bottom:.3125rem;left:.375rem;width:.375rem;height:.375rem;transform:rotate(-45deg);border-top:.125rem solid;border-right:.125rem solid}.govuk-frontend-supported .govuk-accordion-nav__chevron--down{transform:rotate(180deg)}.govuk-frontend-supported .govuk-accordion__section-button{width:100%;padding:10px 0 0;border:0;border-top:1px solid #b1b4b6;border-bottom:10px solid #0000;color:#0b0c0c;background:none;text-align:left;cursor:pointer;-webkit-appearance:none}@media (min-width:40.0625em){.govuk-frontend-supported .govuk-accordion__section-button{padding-bottom:10px}}.govuk-frontend-supported .govuk-accordion__section-button:active{color:#0b0c0c;background:none}.govuk-frontend-supported .govuk-accordion__section-button:hover{color:#0b0c0c;background:#f3f2f1}.govuk-frontend-supported .govuk-accordion__section-button:hover .govuk-accordion__section-toggle-text{color:#0b0c0c}.govuk-frontend-supported .govuk-accordion__section-button:hover .govuk-accordion-nav__chevron{color:#0b0c0c;background:#0b0c0c}.govuk-frontend-supported .govuk-accordion__section-button:hover .govuk-accordion-nav__chevron:after{color:#f3f2f1}.govuk-frontend-supported .govuk-accordion__section-button:focus{outline:0}.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion__section-heading-text-focus,.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion__section-summary-focus,.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion__section-toggle-focus{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion-nav__chevron{color:#0b0c0c;background:#0b0c0c}.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion-nav__chevron:after{color:#fd0}.govuk-frontend-supported .govuk-accordion__section-button::-moz-focus-inner{padding:0;border:0}.govuk-frontend-supported .govuk-accordion__section--expanded .govuk-accordion__section-button{padding-bottom:15px;border-bottom:0}@media (min-width:40.0625em){.govuk-frontend-supported .govuk-accordion__section--expanded .govuk-accordion__section-button{padding-bottom:20px}}.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion__section-toggle-focus{padding-bottom:3px}@media (min-width:48.0625em){.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion__section-toggle-focus{padding-bottom:2px}}.govuk-frontend-supported .govuk-accordion__section-heading-text,.govuk-frontend-supported .govuk-accordion__section-summary,.govuk-frontend-supported .govuk-accordion__section-toggle{display:block;margin-bottom:13px}.govuk-frontend-supported .govuk-accordion__section-heading-text .govuk-accordion__section-heading-text-focus,.govuk-frontend-supported .govuk-accordion__section-heading-text .govuk-accordion__section-summary-focus,.govuk-frontend-supported .govuk-accordion__section-heading-text .govuk-accordion__section-toggle-focus,.govuk-frontend-supported .govuk-accordion__section-summary .govuk-accordion__section-heading-text-focus,.govuk-frontend-supported .govuk-accordion__section-summary .govuk-accordion__section-summary-focus,.govuk-frontend-supported .govuk-accordion__section-summary .govuk-accordion__section-toggle-focus,.govuk-frontend-supported .govuk-accordion__section-toggle .govuk-accordion__section-heading-text-focus,.govuk-frontend-supported .govuk-accordion__section-toggle .govuk-accordion__section-summary-focus,.govuk-frontend-supported .govuk-accordion__section-toggle .govuk-accordion__section-toggle-focus{display:inline}.govuk-frontend-supported .govuk-accordion__section-toggle{font-size:1rem;line-height:1.25;font-weight:400;color:#1d70b8}@media (min-width:40.0625em){.govuk-frontend-supported .govuk-accordion__section-toggle{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-frontend-supported .govuk-accordion__section-toggle{font-size:14pt;line-height:1.15}}.govuk-frontend-supported .govuk-accordion__section-toggle-text,.govuk-frontend-supported .govuk-accordion__show-all-text{margin-left:5px;vertical-align:middle}@media screen and (forced-colors:active){.govuk-frontend-supported .govuk-accordion__section-button:hover .govuk-accordion-nav__chevron,.govuk-frontend-supported .govuk-accordion__show-all:hover .govuk-accordion-nav__chevron{background-color:initial}.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion-nav__chevron,.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion__section-heading-text-focus,.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion__section-summary-focus,.govuk-frontend-supported .govuk-accordion__section-button:focus .govuk-accordion__section-toggle-focus,.govuk-frontend-supported .govuk-accordion__show-all:focus .govuk-accordion-nav__chevron,.govuk-frontend-supported .govuk-accordion__show-all:focus .govuk-accordion__section-heading-text-focus,.govuk-frontend-supported .govuk-accordion__show-all:focus .govuk-accordion__section-summary-focus,.govuk-frontend-supported .govuk-accordion__show-all:focus .govuk-accordion__section-toggle-focus{background:#0000;background-color:initial}}@media (hover:none){.govuk-frontend-supported .govuk-accordion__section-header:hover{border-top-color:#b1b4b6;box-shadow:inset 0 3px 0 0 #1d70b8}.govuk-frontend-supported .govuk-accordion__section-header:hover .govuk-accordion__section-button{border-top-color:#b1b4b6}}.govuk-back-link{font-size:.875rem;line-height:1.1428571429;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em;display:inline-block;position:relative;margin-top:15px;margin-bottom:15px;padding-left:.875em}@media (min-width:40.0625em){.govuk-back-link{font-size:1rem;line-height:1.25}}@media print{.govuk-back-link{font-size:14pt;line-height:1.2;font-family:sans-serif}}.govuk-back-link:hover{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-back-link:focus{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-back-link:link,.govuk-back-link:visited{color:#0b0c0c}@media print{.govuk-back-link:link,.govuk-back-link:visited{color:#000}}.govuk-back-link:hover{color:#0b0c0cfc}.govuk-back-link:active,.govuk-back-link:focus{color:#0b0c0c}@media print{.govuk-back-link:active,.govuk-back-link:focus{color:#000}}.govuk-back-link:before{content:"";display:block;position:absolute;top:0;bottom:0;left:.1875em;width:.4375em;height:.4375em;margin:auto 0;transform:rotate(225deg);border:solid;border-width:1px 1px 0 0;border-color:#505a5f}@supports (border-width:max(0px)){.govuk-back-link:before{border-width:max(1px,.0625em) max(1px,.0625em) 0 0;font-size:max(16px,1em)}}.govuk-back-link:focus:before{border-color:#0b0c0c}.govuk-back-link:after{content:"";position:absolute;top:-14px;right:0;bottom:-14px;left:0}.govuk-back-link--inverse:link,.govuk-back-link--inverse:visited{color:#fff}.govuk-back-link--inverse:active,.govuk-back-link--inverse:hover{color:#fffffffc}.govuk-back-link--inverse:focus{color:#0b0c0c}.govuk-back-link--inverse:before{border-color:currentcolor}.govuk-breadcrumbs{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:.875rem;line-height:1.1428571429;color:#0b0c0c;margin-top:15px;margin-bottom:10px}@media print{.govuk-breadcrumbs{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-breadcrumbs{font-size:1rem;line-height:1.25}}@media print{.govuk-breadcrumbs{font-size:14pt;line-height:1.2;color:#000}}.govuk-breadcrumbs__list{margin:0;padding:0;list-style-type:none}.govuk-breadcrumbs__list:after{content:"";display:block;clear:both}.govuk-breadcrumbs__list-item{display:inline-block;position:relative;margin-bottom:5px;margin-left:.625em;padding-left:.9784375em;float:left}.govuk-breadcrumbs__list-item:before{content:"";display:block;position:absolute;top:0;bottom:0;left:-.206875em;width:.4375em;height:.4375em;margin:auto 0;transform:rotate(45deg);border:solid;border-width:1px 1px 0 0;border-color:#505a5f}@supports (border-width:max(0px)){.govuk-breadcrumbs__list-item:before{border-width:max(1px,.0625em) max(1px,.0625em) 0 0;font-size:max(16px,1em)}}.govuk-breadcrumbs__list-item:first-child{margin-left:0;padding-left:0}.govuk-breadcrumbs__list-item:first-child:before{content:none;display:none}.govuk-breadcrumbs__link{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em}@media print{.govuk-breadcrumbs__link{font-family:sans-serif}}.govuk-breadcrumbs__link:hover{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-breadcrumbs__link:focus{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-breadcrumbs__link:link,.govuk-breadcrumbs__link:visited{color:#0b0c0c}@media print{.govuk-breadcrumbs__link:link,.govuk-breadcrumbs__link:visited{color:#000}}.govuk-breadcrumbs__link:hover{color:#0b0c0cfc}.govuk-breadcrumbs__link:active,.govuk-breadcrumbs__link:focus{color:#0b0c0c}@media print{.govuk-breadcrumbs__link:active,.govuk-breadcrumbs__link:focus{color:#000}}@media (max-width:40.0525em){.govuk-breadcrumbs--collapse-on-mobile .govuk-breadcrumbs__list-item{display:none}.govuk-breadcrumbs--collapse-on-mobile .govuk-breadcrumbs__list-item:first-child,.govuk-breadcrumbs--collapse-on-mobile .govuk-breadcrumbs__list-item:last-child{display:inline-block}.govuk-breadcrumbs--collapse-on-mobile .govuk-breadcrumbs__list-item:before{top:.375em;margin:0}.govuk-breadcrumbs--collapse-on-mobile .govuk-breadcrumbs__list{display:flex}}.govuk-breadcrumbs--inverse,.govuk-breadcrumbs--inverse .govuk-breadcrumbs__link:link,.govuk-breadcrumbs--inverse .govuk-breadcrumbs__link:visited{color:#fff}.govuk-breadcrumbs--inverse .govuk-breadcrumbs__link:active,.govuk-breadcrumbs--inverse .govuk-breadcrumbs__link:hover{color:#fffffffc}.govuk-breadcrumbs--inverse .govuk-breadcrumbs__link:focus{color:#0b0c0c}.govuk-breadcrumbs--inverse .govuk-breadcrumbs__list-item:before{border-color:currentcolor}.govuk-button{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.1875;box-sizing:border-box;display:inline-block;position:relative;width:100%;margin:0 0 22px;padding:8px 10px 7px;border:2px solid #0000;border-radius:0;color:#fff;background-color:#00703c;box-shadow:0 2px 0 #002d18;text-align:center;vertical-align:top;cursor:pointer;-webkit-appearance:none}@media print{.govuk-button{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-button{font-size:1.1875rem;line-height:1}}@media print{.govuk-button{font-size:14pt;line-height:19px}}@media (min-width:40.0625em){.govuk-button{margin-bottom:32px;width:auto}}.govuk-button:active,.govuk-button:hover,.govuk-button:link,.govuk-button:visited{color:#fff;text-decoration:none}.govuk-button::-moz-focus-inner{padding:0;border:0}.govuk-button:hover{background-color:#005a30}.govuk-button:active{top:2px}.govuk-button:focus{border-color:#fd0;outline:3px solid #0000;box-shadow:inset 0 0 0 1px #fd0}.govuk-button:focus:not(:active):not(:hover){border-color:#fd0;color:#0b0c0c;background-color:#fd0;box-shadow:0 2px 0 #0b0c0c}.govuk-button:before{content:"";display:block;position:absolute;top:-2px;right:-2px;bottom:-4px;left:-2px;background:#0000}.govuk-button:active:before{top:-4px}.govuk-button[disabled]{opacity:.5}.govuk-button[disabled]:hover{background-color:#00703c;cursor:not-allowed}.govuk-button[disabled]:active{top:0;box-shadow:0 2px 0 #002d18}.govuk-button--secondary{background-color:#f3f2f1;box-shadow:0 2px 0 #929191}.govuk-button--secondary,.govuk-button--secondary:active,.govuk-button--secondary:hover,.govuk-button--secondary:link,.govuk-button--secondary:visited{color:#0b0c0c}.govuk-button--secondary:hover{background-color:#dbdad9}.govuk-button--secondary:hover[disabled]{background-color:#f3f2f1}.govuk-button--warning{background-color:#d4351c;box-shadow:0 2px 0 #55150b}.govuk-button--warning,.govuk-button--warning:active,.govuk-button--warning:hover,.govuk-button--warning:link,.govuk-button--warning:visited{color:#fff}.govuk-button--warning:hover{background-color:#aa2a16}.govuk-button--warning:hover[disabled]{background-color:#d4351c}.govuk-button--inverse{background-color:#fff;box-shadow:0 2px 0 #144e81}.govuk-button--inverse,.govuk-button--inverse:active,.govuk-button--inverse:hover,.govuk-button--inverse:link,.govuk-button--inverse:visited{color:#1d70b8}.govuk-button--inverse:hover{background-color:#e8f1f8}.govuk-button--inverse:hover[disabled]{background-color:#fff}.govuk-button--start{font-weight:700;font-size:1.125rem;line-height:1;display:inline-flex;min-height:auto;justify-content:center}@media (min-width:40.0625em){.govuk-button--start{font-size:1.5rem;line-height:1}}@media print{.govuk-button--start{font-size:18pt;line-height:1}}.govuk-button__start-icon{margin-left:5px;vertical-align:middle;flex-shrink:0;align-self:center;forced-color-adjust:auto}@media (min-width:48.0625em){.govuk-button__start-icon{margin-left:10px}}.govuk-error-message{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:700;font-size:1rem;line-height:1.25;display:block;margin-top:0;margin-bottom:15px;clear:both;color:#d4351c}@media print{.govuk-error-message{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-error-message{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-error-message{font-size:14pt;line-height:1.15}}.govuk-hint{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;margin-bottom:15px;color:#505a5f}@media print{.govuk-hint{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-hint{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-hint{font-size:14pt;line-height:1.15}}.govuk-fieldset__legend:not(.govuk-fieldset__legend--m):not(.govuk-fieldset__legend--l):not(.govuk-fieldset__legend--xl)+.govuk-hint,.govuk-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl)+.govuk-hint{margin-bottom:10px}.govuk-fieldset__legend+.govuk-hint{margin-top:-5px}.govuk-label{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;display:block;margin-bottom:5px}@media print{.govuk-label{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-label{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-label{font-size:14pt;line-height:1.15;color:#000}}.govuk-label--l,.govuk-label--m,.govuk-label--xl{font-weight:700;margin-bottom:15px}.govuk-label--xl{font-size:2rem;line-height:1.09375}@media (min-width:40.0625em){.govuk-label--xl{font-size:3rem;line-height:1.0416666667}}@media print{.govuk-label--xl{font-size:32pt;line-height:1.15}}.govuk-label--l{font-size:1.5rem;line-height:1.0416666667}@media (min-width:40.0625em){.govuk-label--l{font-size:2.25rem;line-height:1.1111111111}}@media print{.govuk-label--l{font-size:24pt;line-height:1.05}}.govuk-label--m{font-size:1.125rem;line-height:1.1111111111}@media (min-width:40.0625em){.govuk-label--m{font-size:1.5rem;line-height:1.25}}@media print{.govuk-label--m{font-size:18pt;line-height:1.15}}.govuk-label--s{font-weight:700}.govuk-label-wrapper{margin:0}.govuk-textarea{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;box-sizing:border-box;display:block;width:100%;min-height:40px;margin-bottom:20px;padding:5px;resize:vertical;border:2px solid #0b0c0c;border-radius:0;-webkit-appearance:none}@media print{.govuk-textarea{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-textarea{font-size:1.1875rem;line-height:1.25}}@media print{.govuk-textarea{font-size:14pt;line-height:1.25}}@media (min-width:40.0625em){.govuk-textarea{margin-bottom:30px}}.govuk-textarea:focus{outline:3px solid #fd0;outline-offset:0;box-shadow:inset 0 0 0 2px}.govuk-textarea:disabled{opacity:.5;color:inherit;background-color:initial;cursor:not-allowed}.govuk-textarea--error{border-color:#d4351c}.govuk-textarea--error:focus{border-color:#0b0c0c}.govuk-character-count{margin-bottom:20px}@media (min-width:40.0625em){.govuk-character-count{margin-bottom:30px}}.govuk-character-count .govuk-form-group,.govuk-character-count .govuk-textarea{margin-bottom:5px}.govuk-character-count__message{font-variant-numeric:tabular-nums;margin-top:0;margin-bottom:0}.govuk-character-count__message:after{content:"​"}.govuk-character-count__message--disabled{visibility:hidden}.govuk-fieldset{min-width:0;margin:0;padding:0;border:0}.govuk-fieldset:after{content:"";display:block;clear:both}@supports not (caret-color:auto){.govuk-fieldset,x:-moz-any-link{display:table-cell}}.govuk-fieldset__legend{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;box-sizing:border-box;display:table;max-width:100%;margin-bottom:10px;padding:0;white-space:normal}@media print{.govuk-fieldset__legend{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-fieldset__legend{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-fieldset__legend{font-size:14pt;line-height:1.15;color:#000}}.govuk-fieldset__legend--l,.govuk-fieldset__legend--m,.govuk-fieldset__legend--xl{font-weight:700;margin-bottom:15px}.govuk-fieldset__legend--xl{font-size:2rem;line-height:1.09375}@media (min-width:40.0625em){.govuk-fieldset__legend--xl{font-size:3rem;line-height:1.0416666667}}@media print{.govuk-fieldset__legend--xl{font-size:32pt;line-height:1.15}}.govuk-fieldset__legend--l{font-size:1.5rem;line-height:1.0416666667}@media (min-width:40.0625em){.govuk-fieldset__legend--l{font-size:2.25rem;line-height:1.1111111111}}@media print{.govuk-fieldset__legend--l{font-size:24pt;line-height:1.05}}.govuk-fieldset__legend--m{font-size:1.125rem;line-height:1.1111111111}@media (min-width:40.0625em){.govuk-fieldset__legend--m{font-size:1.5rem;line-height:1.25}}@media print{.govuk-fieldset__legend--m{font-size:18pt;line-height:1.15}}.govuk-fieldset__legend--s{font-weight:700}.govuk-fieldset__heading{margin:0;font-size:inherit;font-weight:inherit}.govuk-checkboxes__item{display:flex;flex-wrap:wrap;position:relative;margin-bottom:10px}.govuk-checkboxes__item:last-child,.govuk-checkboxes__item:last-of-type{margin-bottom:0}.govuk-checkboxes__input{z-index:1;width:44px;height:44px;margin:0;opacity:0;cursor:pointer}.govuk-checkboxes__label{align-self:center;max-width:calc(100% - 74px);margin-bottom:0;padding:7px 15px;cursor:pointer;touch-action:manipulation}.govuk-checkboxes__label:before{top:2px;left:2px;width:40px;height:40px;border:2px solid}.govuk-checkboxes__label:after,.govuk-checkboxes__label:before{content:"";box-sizing:border-box;position:absolute;background:#0000}.govuk-checkboxes__label:after{top:13px;left:10px;width:23px;height:12px;transform:rotate(-45deg);border:solid;border-width:0 0 5px 5px;border-top-color:#0000;opacity:0}.govuk-checkboxes__hint{display:block;width:100%;margin-top:-5px;padding-right:15px;padding-left:59px}.govuk-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl)+.govuk-checkboxes__hint{margin-bottom:0}.govuk-checkboxes__input:focus+.govuk-checkboxes__label:before{border-width:4px;outline:3px solid #0000;outline-offset:1px;box-shadow:0 0 0 3px #fd0}@media (-ms-high-contrast:active),screen and (forced-colors:active){.govuk-checkboxes__input:focus+.govuk-checkboxes__label:before{outline-color:Highlight}}.govuk-checkboxes__input:checked+.govuk-checkboxes__label:after{opacity:1}.govuk-checkboxes__input:disabled,.govuk-checkboxes__input:disabled+.govuk-checkboxes__label{cursor:not-allowed}.govuk-checkboxes__input:disabled+.govuk-checkboxes__label,.govuk-checkboxes__input:disabled~.govuk-hint{opacity:.5}.govuk-checkboxes__divider{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;width:40px;margin-bottom:10px;text-align:center}@media print{.govuk-checkboxes__divider{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-checkboxes__divider{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-checkboxes__divider{font-size:14pt;line-height:1.15;color:#000}}.govuk-checkboxes__conditional{margin-bottom:15px;margin-left:18px;padding-left:33px;border-left:4px solid #b1b4b6}@media (min-width:40.0625em){.govuk-checkboxes__conditional{margin-bottom:20px}}.govuk-frontend-supported .govuk-checkboxes__conditional--hidden{display:none}.govuk-checkboxes--small .govuk-checkboxes__item,.govuk-checkboxes__conditional>:last-child{margin-bottom:0}.govuk-checkboxes--small .govuk-checkboxes__input{margin-left:-10px}.govuk-checkboxes--small .govuk-checkboxes__label{padding-left:1px}.govuk-checkboxes--small .govuk-checkboxes__label:before{top:10px;left:0;width:24px;height:24px}.govuk-checkboxes--small .govuk-checkboxes__label:after{top:17px;left:6px;width:12px;height:6.5px;border-width:0 0 3px 3px}.govuk-checkboxes--small .govuk-checkboxes__hint{padding-left:34px}.govuk-checkboxes--small .govuk-checkboxes__conditional{margin-left:10px;padding-left:20px}.govuk-checkboxes--small .govuk-checkboxes__item:hover .govuk-checkboxes__input:not(:disabled)+.govuk-checkboxes__label:before{outline:3px dashed #0000;outline-offset:1px;box-shadow:0 0 0 10px #b1b4b6}.govuk-checkboxes--small .govuk-checkboxes__item:hover .govuk-checkboxes__input:focus+.govuk-checkboxes__label:before{box-shadow:0 0 0 3px #fd0,0 0 0 10px #b1b4b6}@media (-ms-high-contrast:active),screen and (forced-colors:active){.govuk-checkboxes--small .govuk-checkboxes__item:hover .govuk-checkboxes__input:focus+.govuk-checkboxes__label:before{outline-color:Highlight}}@media (hover:none),(pointer:coarse){.govuk-checkboxes--small .govuk-checkboxes__item:hover .govuk-checkboxes__input:not(:disabled)+.govuk-checkboxes__label:before{box-shadow:none}.govuk-checkboxes--small .govuk-checkboxes__item:hover .govuk-checkboxes__input:focus+.govuk-checkboxes__label:before{box-shadow:0 0 0 3px #fd0}}.govuk-cookie-banner{padding-top:20px;border-bottom:10px solid #0000;background-color:#f3f2f1}.govuk-cookie-banner[hidden]{display:none}.govuk-cookie-banner__message{margin-bottom:-10px}.govuk-cookie-banner__message[hidden]{display:none}.govuk-cookie-banner__message:focus{outline:none}.govuk-input{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;box-sizing:border-box;width:100%;height:2.5rem;margin-top:0;padding:5px;border:2px solid #0b0c0c;border-radius:0;-webkit-appearance:none;appearance:none}@media print{.govuk-input{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-input{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-input{font-size:14pt;line-height:1.15}}.govuk-input:focus{outline:3px solid #fd0;outline-offset:0;box-shadow:inset 0 0 0 2px}.govuk-input:disabled{opacity:.5;color:inherit;background-color:initial;cursor:not-allowed}.govuk-input::-webkit-inner-spin-button,.govuk-input::-webkit-outer-spin-button{margin:0;-webkit-appearance:none}.govuk-input[type=number]{-moz-appearance:textfield}.govuk-input--error{border-color:#d4351c}.govuk-input--error:focus{border-color:#0b0c0c}.govuk-input--extra-letter-spacing{font-variant-numeric:tabular-nums;letter-spacing:.05em}.govuk-input--width-30{max-width:29.5em}.govuk-input--width-20{max-width:20.5em}.govuk-input--width-10{max-width:11.5em}.govuk-input--width-5{max-width:5.5em}.govuk-input--width-4{max-width:4.5em}.govuk-input--width-3{max-width:3.75em}.govuk-input--width-2{max-width:2.75em}.govuk-input__wrapper{display:flex}.govuk-input__wrapper .govuk-input{flex:0 1 auto}.govuk-input__wrapper .govuk-input:focus{z-index:1}@media (max-width:19.99em){.govuk-input__wrapper{display:block}.govuk-input__wrapper .govuk-input{max-width:100%}}.govuk-input__prefix,.govuk-input__suffix{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;box-sizing:border-box;display:flex;align-items:center;justify-content:center;min-width:2.5rem;height:2.5rem;padding:5px;border:2px solid #0b0c0c;background-color:#f3f2f1;text-align:center;white-space:nowrap;cursor:default;flex:0 0 auto}@media print{.govuk-input__prefix,.govuk-input__suffix{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-input__prefix,.govuk-input__suffix{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-input__prefix,.govuk-input__suffix{font-size:14pt;line-height:1.15}}@media (max-width:19.99em){.govuk-input__prefix,.govuk-input__suffix{display:block;height:100%;white-space:normal}.govuk-input__prefix{border-bottom:0}}@media (min-width:20em){.govuk-input__prefix{border-right:0}}@media (max-width:19.99em){.govuk-input__suffix{border-top:0}}@media (min-width:20em){.govuk-input__suffix{border-left:0}}.govuk-date-input{font-size:0}.govuk-date-input:after{content:"";display:block;clear:both}.govuk-date-input__item{display:inline-block;margin-right:20px;margin-bottom:0}.govuk-date-input__label{display:block}.govuk-date-input__input{margin-bottom:0}.govuk-details{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;margin-bottom:20px;display:block}@media print{.govuk-details{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-details{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-details{font-size:14pt;line-height:1.15;color:#000}}@media (min-width:40.0625em){.govuk-details{margin-bottom:30px}}.govuk-details__summary{display:inline-block;margin-bottom:5px}.govuk-details__summary-text>:first-child{margin-top:0}.govuk-details__summary-text>:last-child,.govuk-details__summary-text>:only-child{margin-bottom:0}.govuk-details__text{padding-top:15px;padding-bottom:15px;padding-left:20px}.govuk-details__text p{margin-top:0;margin-bottom:20px}.govuk-details__text>:last-child{margin-bottom:0}@media screen\0 {.govuk-details{border-left:10px solid #b1b4b6}.govuk-details__summary{margin-top:15px}.govuk-details__summary-text{font-weight:700;margin-bottom:15px;padding-left:20px}}@media screen\0 and (min-width:40.0625em){.govuk-details__summary-text{margin-bottom:20px}}@supports not (-ms-ime-align:auto){.govuk-details__summary{position:relative;padding-left:25px;color:#1d70b8;cursor:pointer}.govuk-details__summary:hover{color:#003078}.govuk-details__summary:focus{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-details__summary-text{text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em}.govuk-details__summary:hover .govuk-details__summary-text{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-details__summary:focus .govuk-details__summary-text{text-decoration:none}.govuk-details__summary::-webkit-details-marker{display:none}.govuk-details__summary:before{content:"";position:absolute;top:-1px;bottom:0;left:0;margin:auto;display:block;width:0;height:0;-webkit-clip-path:polygon(0 0,100% 50%,0 100%);clip-path:polygon(0 0,100% 50%,0 100%);border-color:#0000;border-style:solid;border-width:7px 0 7px 12.124px;border-left-color:inherit}.govuk-details[open]>.govuk-details__summary:before{display:block;width:0;height:0;-webkit-clip-path:polygon(0 0,50% 100%,100% 0);clip-path:polygon(0 0,50% 100%,100% 0);border-color:#0000;border-style:solid;border-width:12.124px 7px 0;border-top-color:inherit}.govuk-details__text{border-left:5px solid #b1b4b6}}.govuk-error-summary{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;padding:15px;margin-bottom:30px;border:5px solid #d4351c}@media print{.govuk-error-summary{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-error-summary{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-error-summary{font-size:14pt;line-height:1.15;color:#000}}@media (min-width:40.0625em){.govuk-error-summary{padding:20px;margin-bottom:50px}}.govuk-error-summary:focus{outline:3px solid #fd0}.govuk-error-summary__title{font-size:1.125rem;line-height:1.1111111111;font-weight:700;margin-top:0;margin-bottom:15px}@media (min-width:40.0625em){.govuk-error-summary__title{font-size:1.5rem;line-height:1.25}}@media print{.govuk-error-summary__title{font-size:18pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-error-summary__title{margin-bottom:20px}}.govuk-error-summary__body p{margin-top:0;margin-bottom:15px}@media (min-width:40.0625em){.govuk-error-summary__body p{margin-bottom:20px}}.govuk-error-summary__list{margin-top:0;margin-bottom:0}.govuk-error-summary__list a{font-weight:700;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em}@media print{.govuk-error-summary__list a{font-family:sans-serif}}.govuk-error-summary__list a:hover{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-error-summary__list a:focus{outline:3px solid #0000;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-error-summary__list a:link,.govuk-error-summary__list a:visited{color:#d4351c}.govuk-error-summary__list a:hover{color:#942514}.govuk-error-summary__list a:active{color:#d4351c}.govuk-error-summary__list a:focus{color:#0b0c0c}.govuk-exit-this-page{margin-bottom:30px;position:-webkit-sticky;position:sticky;z-index:1000;top:0;left:0;width:100%}@media (min-width:40.0625em){.govuk-exit-this-page{margin-bottom:50px;display:inline-block;right:0;left:auto;width:auto;float:right}}.govuk-exit-this-page__button{margin-bottom:0}.govuk-exit-this-page__indicator{display:none;padding:10px 10px 0;color:inherit;line-height:0;text-align:center;pointer-events:none}.govuk-exit-this-page__indicator--visible{display:block}.govuk-exit-this-page__indicator-light{box-sizing:border-box;display:inline-block;width:.75em;height:.75em;margin:0 .125em;border-radius:50%;border:2px solid}.govuk-exit-this-page__indicator-light--on{border-width:.375em}@media only print{.govuk-exit-this-page{display:none}}.govuk-exit-this-page-overlay{position:fixed;z-index:9999;top:0;right:0;bottom:0;left:0;background-color:#fff}.govuk-exit-this-page-hide-content *{display:none!important}.govuk-exit-this-page-hide-content .govuk-exit-this-page-overlay{display:block!important}.govuk-file-upload{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;max-width:100%;margin-left:-5px;padding:5px}@media print{.govuk-file-upload{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-file-upload{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-file-upload{font-size:14pt;line-height:1.15;color:#000}}.govuk-file-upload::-webkit-file-upload-button{-webkit-appearance:button;color:inherit;font:inherit}.govuk-file-upload:focus,.govuk-file-upload:focus-within{outline:3px solid #fd0;box-shadow:inset 0 0 0 4px #0b0c0c}.govuk-file-upload:disabled{opacity:.5;cursor:not-allowed}.govuk-footer{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:.875rem;line-height:1.1428571429;padding-top:25px;padding-bottom:15px;border-top:1px solid #b1b4b6;color:#0b0c0c;background:#f3f2f1}@media print{.govuk-footer{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-footer{font-size:1rem;line-height:1.25}}@media print{.govuk-footer{font-size:14pt;line-height:1.2}}@media (min-width:40.0625em){.govuk-footer{padding-top:40px;padding-bottom:25px}}.govuk-footer__link{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em}@media print{.govuk-footer__link{font-family:sans-serif}}.govuk-footer__link:hover{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-footer__link:focus{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-footer__link:link,.govuk-footer__link:visited{color:#0b0c0c}@media print{.govuk-footer__link:link,.govuk-footer__link:visited{color:#000}}.govuk-footer__link:hover{color:#0b0c0cfc}.govuk-footer__link:active,.govuk-footer__link:focus{color:#0b0c0c}@media print{.govuk-footer__link:active,.govuk-footer__link:focus{color:#000}}.govuk-footer__section-break{margin:0 0 30px;border:0;border-bottom:1px solid #b1b4b6}@media (min-width:40.0625em){.govuk-footer__section-break{margin-bottom:50px}}.govuk-footer__meta{display:flex;margin-right:-15px;margin-left:-15px;flex-wrap:wrap;align-items:flex-end;justify-content:center}.govuk-footer__meta-item{margin-right:15px;margin-bottom:25px;margin-left:15px}.govuk-footer__meta-item--grow{flex:1}@media (max-width:40.0525em){.govuk-footer__meta-item--grow{flex-basis:320px}}.govuk-footer__licence-logo{display:inline-block;margin-right:10px;vertical-align:top;forced-color-adjust:auto}@media (max-width:48.0525em){.govuk-footer__licence-logo{margin-bottom:15px}}.govuk-footer__licence-description{display:inline-block}.govuk-footer__copyright-logo{display:inline-block;min-width:125px;padding-top:112px;background-image:url(/assets/images/govuk-crest.png);background-repeat:no-repeat;background-position:50% 0;background-size:125px 102px;text-align:center;white-space:nowrap}@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-resolution:192dpi),only screen and (min-resolution:2dppx){.govuk-footer__copyright-logo{background-image:url(/assets/images/govuk-crest-2x.png)}}.govuk-footer__inline-list{margin-top:0;margin-bottom:15px;padding:0}.govuk-footer__meta-custom{margin-bottom:20px}.govuk-footer__inline-list-item{display:inline-block;margin-right:15px;margin-bottom:5px}.govuk-footer__heading{margin-bottom:30px;padding-bottom:20px;border-bottom:1px solid #b1b4b6}@media (max-width:40.0525em){.govuk-footer__heading{padding-bottom:10px}}.govuk-footer__navigation{margin-right:-15px;margin-left:-15px}.govuk-footer__navigation:after{content:"";display:block;clear:both}.govuk-footer__section{display:inline-block;margin-bottom:30px;vertical-align:top}.govuk-footer__list{margin:0;padding:0;list-style:none;column-gap:30px}@media (min-width:48.0625em){.govuk-footer__list--columns-2{column-count:2}.govuk-footer__list--columns-3{column-count:3}}.govuk-footer__list-item{margin-bottom:15px}@media (min-width:40.0625em){.govuk-footer__list-item{margin-bottom:20px}}.govuk-footer__list-item:last-child{margin-bottom:0}.govuk-header{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:.875rem;line-height:1;border-bottom:10px solid #fff;color:#fff;background:#0b0c0c}@media print{.govuk-header{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-header{font-size:1rem;line-height:1}}@media print{.govuk-header{font-size:14pt;line-height:1}}.govuk-header__container--full-width{padding:0 15px;border-color:#1d70b8}.govuk-header__container--full-width .govuk-header__menu-button{right:15px}.govuk-header__container{position:relative;margin-bottom:-10px;padding-top:10px;border-bottom:10px solid #1d70b8}.govuk-header__container:after{content:"";display:block;clear:both}.govuk-header__logotype{display:inline-block;position:relative;top:-3px;margin-right:5px;fill:currentcolor;vertical-align:top}@media (forced-colors:active){.govuk-header__logotype{forced-color-adjust:none;color:linktext}}.govuk-header__logotype:last-child{margin-right:0}.govuk-header__product-name{font-size:1.125rem;line-height:1;font-weight:400;display:inline-table;margin-top:10px;vertical-align:top}@media (min-width:40.0625em){.govuk-header__product-name{font-size:1.5rem;line-height:1}}@media print{.govuk-header__product-name{font-size:18pt;line-height:1}}@-moz-document url-prefix(){.govuk-header__product-name{margin-top:9.5px}}@media (min-width:40.0625em){.govuk-header__product-name{margin-top:5px}@-moz-document url-prefix(){.govuk-header__product-name{margin-top:4.5px}}}.govuk-header__link{text-decoration:none}.govuk-header__link:link,.govuk-header__link:visited{color:#fff}.govuk-header__link:active,.govuk-header__link:hover{color:#fffffffc}.govuk-header__link:hover{text-decoration:underline;text-decoration-thickness:3px;text-underline-offset:.1578em}.govuk-header__link:focus{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-header__link--homepage{display:inline-block;margin-right:10px;font-size:30px}@media (min-width:48.0625em){.govuk-header__link--homepage{display:inline}.govuk-header__link--homepage:focus{box-shadow:0 0 #fd0}}.govuk-header__link--homepage:link,.govuk-header__link--homepage:visited{text-decoration:none}.govuk-header__link--homepage:active,.govuk-header__link--homepage:hover{margin-bottom:-3px;border-bottom:3px solid}.govuk-header__link--homepage:focus{margin-bottom:0;border-bottom:0}.govuk-header__service-name{display:inline-block;margin-bottom:10px;font-size:1.125rem;line-height:1.1111111111;font-weight:700}@media (min-width:40.0625em){.govuk-header__service-name{font-size:1.5rem;line-height:1.25}}@media print{.govuk-header__service-name{font-size:18pt;line-height:1.15}}.govuk-header__content,.govuk-header__logo{box-sizing:border-box}.govuk-header__logo{margin-bottom:10px;padding-right:80px}@media (min-width:48.0625em){.govuk-header__logo{width:33.33%;padding-right:15px;float:left;vertical-align:top}.govuk-header__logo:last-child{width:auto;padding-right:0;float:none}.govuk-header__content{width:66.66%;padding-left:15px;float:left}}.govuk-header__menu-button{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:.875rem;line-height:1.1428571429;position:absolute;top:13px;right:0;max-width:80px;min-height:24px;margin:0;padding:0;border:0;color:#fff;background:none;word-break:break-all;cursor:pointer}@media print{.govuk-header__menu-button{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-header__menu-button{font-size:1rem;line-height:1.25}}@media print{.govuk-header__menu-button{font-size:14pt;line-height:1.2}}.govuk-header__menu-button:hover{-webkit-text-decoration:solid underline 3px;text-decoration:solid underline 3px;text-underline-offset:.1578em}.govuk-header__menu-button:focus{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-header__menu-button:after{display:inline-block;width:0;height:0;-webkit-clip-path:polygon(0 0,50% 100%,100% 0);clip-path:polygon(0 0,50% 100%,100% 0);border-color:#0000;border-style:solid;border-width:8.66px 5px 0;border-top-color:inherit;content:"";margin-left:5px}.govuk-header__menu-button[aria-expanded=true]:after{display:inline-block;width:0;height:0;-webkit-clip-path:polygon(50% 0,0 100%,100% 100%);clip-path:polygon(50% 0,0 100%,100% 100%);border-color:#0000;border-style:solid;border-width:0 5px 8.66px;border-bottom-color:inherit}@media (min-width:40.0625em){.govuk-header__menu-button{top:15px}}.govuk-frontend-supported .govuk-header__menu-button{display:block}.govuk-frontend-supported .govuk-header__menu-button[hidden],.govuk-header__menu-button[hidden]{display:none}@media (min-width:48.0625em){.govuk-header__navigation{margin-bottom:10px}}.govuk-header__navigation-list{margin:0;padding:0;list-style:none}.govuk-header__navigation-list[hidden]{display:none}@media (min-width:48.0625em){.govuk-header__navigation--end{margin:0;padding:5px 0;text-align:right}}.govuk-header__navigation-item{padding:10px 0;border-bottom:1px solid #2e3133}@media (min-width:48.0625em){.govuk-header__navigation-item{display:inline-block;margin-right:15px;padding:5px 0;border:0}}.govuk-header__navigation-item a{font-size:.875rem;line-height:1.1428571429;font-weight:700;white-space:nowrap}@media (min-width:40.0625em){.govuk-header__navigation-item a{font-size:1rem;line-height:1.25}}@media print{.govuk-header__navigation-item a{font-size:14pt;line-height:1.2}}.govuk-header__navigation-item--active a:hover,.govuk-header__navigation-item--active a:link,.govuk-header__navigation-item--active a:visited{color:#1d8feb}@media print{.govuk-header__navigation-item--active a{color:#1d70b8}}.govuk-header__navigation-item--active a:focus{color:#0b0c0c}.govuk-header__navigation-item:last-child{margin-right:0;border-bottom:0}@media print{.govuk-header{border-bottom-width:0;color:#0b0c0c;background:#0000}.govuk-header__link:link,.govuk-header__link:visited{color:#0b0c0c}.govuk-header__link:after{display:none}}.govuk-inset-text{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;padding:15px;margin-top:20px;margin-bottom:20px;clear:both;border-left:10px solid #b1b4b6}@media print{.govuk-inset-text{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-inset-text{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-inset-text{font-size:14pt;line-height:1.15;color:#000}}@media (min-width:40.0625em){.govuk-inset-text{margin-top:30px;margin-bottom:30px}}.govuk-inset-text>:first-child{margin-top:0}.govuk-inset-text>:last-child,.govuk-inset-text>:only-child{margin-bottom:0}.govuk-notification-banner{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;margin-bottom:30px;border:5px solid #1d70b8;background-color:#1d70b8}@media print{.govuk-notification-banner{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-notification-banner{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-notification-banner{font-size:14pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-notification-banner{margin-bottom:50px}}.govuk-notification-banner:focus{outline:3px solid #fd0}.govuk-notification-banner__header{padding:2px 15px 5px;border-bottom:1px solid #0000}@media (min-width:40.0625em){.govuk-notification-banner__header{padding:2px 20px 5px}}.govuk-notification-banner__title{font-size:1rem;line-height:1.25;font-weight:700;margin:0;padding:0;color:#fff}@media (min-width:40.0625em){.govuk-notification-banner__title{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-notification-banner__title{font-size:14pt;line-height:1.15}}.govuk-notification-banner__content{color:#0b0c0c;padding:15px;background-color:#fff}@media print{.govuk-notification-banner__content{color:#000}}@media (min-width:40.0625em){.govuk-notification-banner__content{padding:20px}}.govuk-notification-banner__content>*{box-sizing:border-box;max-width:605px}.govuk-notification-banner__content>:last-child{margin-bottom:0}.govuk-notification-banner__heading{font-size:1.125rem;line-height:1.1111111111;font-weight:700;margin:0 0 15px;padding:0}@media (min-width:40.0625em){.govuk-notification-banner__heading{font-size:1.5rem;line-height:1.25}}@media print{.govuk-notification-banner__heading{font-size:18pt;line-height:1.15}}.govuk-notification-banner__link{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em}@media print{.govuk-notification-banner__link{font-family:sans-serif}}.govuk-notification-banner__link:hover{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-notification-banner__link:focus{outline:3px solid #0000;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-notification-banner__link:link,.govuk-notification-banner__link:visited{color:#1d70b8}.govuk-notification-banner__link:hover{color:#003078}.govuk-notification-banner__link:active,.govuk-notification-banner__link:focus{color:#0b0c0c}.govuk-notification-banner--success{border-color:#00703c;background-color:#00703c}.govuk-notification-banner--success .govuk-notification-banner__link:link,.govuk-notification-banner--success .govuk-notification-banner__link:visited{color:#00703c}.govuk-notification-banner--success .govuk-notification-banner__link:hover{color:#004e2a}.govuk-notification-banner--success .govuk-notification-banner__link:active{color:#00703c}.govuk-notification-banner--success .govuk-notification-banner__link:focus{color:#0b0c0c}.govuk-pagination{margin-bottom:20px;display:flex;flex-direction:column;align-items:center;flex-wrap:wrap}@media (min-width:40.0625em){.govuk-pagination{margin-bottom:30px;flex-direction:row;align-items:flex-start}}.govuk-pagination__list{margin:0;padding:0;list-style:none}.govuk-pagination__item,.govuk-pagination__next,.govuk-pagination__prev{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;box-sizing:border-box;position:relative;min-width:45px;min-height:45px;padding:10px 15px;float:left}@media print{.govuk-pagination__item,.govuk-pagination__next,.govuk-pagination__prev{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-pagination__item,.govuk-pagination__next,.govuk-pagination__prev{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-pagination__item,.govuk-pagination__next,.govuk-pagination__prev{font-size:14pt;line-height:1.15}}.govuk-pagination__item:hover,.govuk-pagination__next:hover,.govuk-pagination__prev:hover{background-color:#f3f2f1}.govuk-pagination__item{display:none;text-align:center}@media (min-width:40.0625em){.govuk-pagination__item{display:block}}.govuk-pagination__next,.govuk-pagination__prev{font-weight:700}.govuk-pagination__next .govuk-pagination__link,.govuk-pagination__prev .govuk-pagination__link{display:flex;align-items:center}.govuk-pagination__prev{padding-left:0}.govuk-pagination__next{padding-right:0}.govuk-pagination__item--current,.govuk-pagination__item--ellipses,.govuk-pagination__item:first-child,.govuk-pagination__item:last-child{display:block}.govuk-pagination__item--current{font-weight:700;outline:1px solid #0000;background-color:#1d70b8}.govuk-pagination__item--current:hover{background-color:#1d70b8}.govuk-pagination__item--current .govuk-pagination__link:link,.govuk-pagination__item--current .govuk-pagination__link:visited{color:#fff}.govuk-pagination__item--current .govuk-pagination__link:active,.govuk-pagination__item--current .govuk-pagination__link:hover{color:#fffffffc}.govuk-pagination__item--current .govuk-pagination__link:focus{color:#0b0c0c}.govuk-pagination__item--ellipses{font-weight:700;color:#505a5f}.govuk-pagination__item--ellipses:hover{background-color:initial}.govuk-pagination__link{display:block;min-width:15px}@media screen{.govuk-pagination__link:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0}}.govuk-pagination__link:active .govuk-pagination__link-title--decorated,.govuk-pagination__link:hover .govuk-pagination__link-title--decorated{text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em}.govuk-pagination__link:active .govuk-pagination__link-label,.govuk-pagination__link:active .govuk-pagination__link-title--decorated,.govuk-pagination__link:hover .govuk-pagination__link-label,.govuk-pagination__link:hover .govuk-pagination__link-title--decorated{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-pagination__link:focus .govuk-pagination__icon{color:#0b0c0c}.govuk-pagination__link:focus .govuk-pagination__link-label,.govuk-pagination__link:focus .govuk-pagination__link-title--decorated{text-decoration:none}.govuk-pagination__link-label{font-weight:400;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em;display:inline-block;padding-left:30px}.govuk-pagination__icon{width:.9375rem;height:.8125rem;color:#505a5f;fill:currentcolor;forced-color-adjust:auto}.govuk-pagination__icon--prev{margin-right:15px}.govuk-pagination__icon--next{margin-left:15px}.govuk-pagination--block{display:block}.govuk-pagination--block .govuk-pagination__item{padding:15px;float:none}.govuk-pagination--block .govuk-pagination__next,.govuk-pagination--block .govuk-pagination__prev{padding-left:0;float:none}.govuk-pagination--block .govuk-pagination__next{padding-right:15px}.govuk-pagination--block .govuk-pagination__next .govuk-pagination__icon{margin-left:0}.govuk-pagination--block .govuk-pagination__prev+.govuk-pagination__next{border-top:1px solid #b1b4b6}.govuk-pagination--block .govuk-pagination__link,.govuk-pagination--block .govuk-pagination__link-title{display:inline}.govuk-pagination--block .govuk-pagination__link-title:after{content:"";display:block}.govuk-pagination--block .govuk-pagination__link{text-align:left}.govuk-pagination--block .govuk-pagination__link:focus .govuk-pagination__link-label{outline:3px solid #0000;color:#0b0c0c;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-pagination--block .govuk-pagination__link:not(:focus){text-decoration:none}.govuk-pagination--block .govuk-pagination__icon{margin-right:10px}.govuk-panel{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1.5rem;line-height:1.0416666667;box-sizing:border-box;margin-bottom:15px;padding:35px;border:5px solid #0000;text-align:center}@media print{.govuk-panel{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-panel{font-size:2.25rem;line-height:1.1111111111}}@media print{.govuk-panel{font-size:24pt;line-height:1.05}}@media (max-width:40.0525em){.govuk-panel{padding:10px;overflow-wrap:break-word;word-wrap:break-word}}.govuk-panel--confirmation{color:#fff;background:#00703c}@media print{.govuk-panel--confirmation{border-color:currentcolor;color:#000;background:none}}.govuk-panel__title{font-size:2rem;line-height:1.09375;font-weight:700;margin-top:0;margin-bottom:30px}@media (min-width:40.0625em){.govuk-panel__title{font-size:3rem;line-height:1.0416666667}}@media print{.govuk-panel__title{font-size:32pt;line-height:1.15}}.govuk-panel__title:last-child{margin-bottom:0}.govuk-tag{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;display:inline-block;max-width:160px;margin-top:-2px;margin-bottom:-3px;padding:2px 8px 3px;color:#0c2d4a;background-color:#bbd4ea;text-decoration:none;overflow-wrap:break-word}@media print{.govuk-tag{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-tag{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-tag{font-size:14pt;line-height:1.15}}@media screen and (forced-colors:active){.govuk-tag{font-weight:700}}.govuk-tag--grey{color:#282d30;background-color:#e5e6e7}.govuk-tag--purple{color:#491644;background-color:#efdfed}.govuk-tag--turquoise{color:#10403c;background-color:#d4ecea}.govuk-tag--blue{color:#0c2d4a;background-color:#bbd4ea}.govuk-tag--light-blue{color:#0c2d4a;background-color:#e8f1f8}.govuk-tag--yellow{color:#594d00;background-color:#fff7bf}.govuk-tag--orange{color:#6e3619;background-color:#fcd6c3}.govuk-tag--red{color:#2a0b06;background-color:#f4cdc6}.govuk-tag--pink{color:#6b1c40;background-color:#f9e1ec}.govuk-tag--green{color:#005a30;background-color:#cce2d8}.govuk-phase-banner{padding-top:10px;padding-bottom:10px;border-bottom:1px solid #b1b4b6}.govuk-phase-banner__content{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:.875rem;line-height:1.1428571429;color:#0b0c0c;display:table;margin:0}@media print{.govuk-phase-banner__content{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-phase-banner__content{font-size:1rem;line-height:1.25}}@media print{.govuk-phase-banner__content{font-size:14pt;line-height:1.2;color:#000}}.govuk-phase-banner__content__tag{font-size:.875rem;line-height:1.1428571429;margin-right:10px}@media (min-width:40.0625em){.govuk-phase-banner__content__tag{font-size:1rem;line-height:1.25}}@media print{.govuk-phase-banner__content__tag{font-size:14pt;line-height:1.2}}@media screen and (forced-colors:active){.govuk-phase-banner__content__tag{font-weight:700}}.govuk-phase-banner__text{display:table-cell;vertical-align:middle}.govuk-radios__item{display:flex;flex-wrap:wrap;position:relative;margin-bottom:10px}.govuk-radios__item:last-child,.govuk-radios__item:last-of-type{margin-bottom:0}.govuk-radios__input{z-index:1;width:44px;height:44px;margin:0;opacity:0;cursor:pointer}.govuk-radios__label{align-self:center;max-width:calc(100% - 74px);margin-bottom:0;padding:7px 15px;cursor:pointer;touch-action:manipulation}.govuk-radios__label:before{content:"";box-sizing:border-box;position:absolute;top:2px;left:2px;width:40px;height:40px;border:2px solid;border-radius:50%;background:#0000}.govuk-radios__label:after{content:"";position:absolute;top:12px;left:12px;width:0;height:0;border:10px solid;border-radius:50%;opacity:0;background:currentcolor}.govuk-radios__hint{display:block;width:100%;margin-top:-5px;padding-right:15px;padding-left:59px}.govuk-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl)+.govuk-radios__hint{margin-bottom:0}.govuk-radios__input:focus+.govuk-radios__label:before{border-width:4px;outline:3px solid #0000;outline-offset:1px;box-shadow:0 0 0 4px #fd0}@media (-ms-high-contrast:active),screen and (forced-colors:active){.govuk-radios__input:focus+.govuk-radios__label:before{outline-color:Highlight}}.govuk-radios__input:checked+.govuk-radios__label:after{opacity:1}.govuk-radios__input:disabled,.govuk-radios__input:disabled+.govuk-radios__label{cursor:not-allowed}.govuk-radios__input:disabled+.govuk-radios__label,.govuk-radios__input:disabled~.govuk-hint{opacity:.5}@media (min-width:40.0625em){.govuk-radios--inline{display:flex;flex-wrap:wrap;align-items:flex-start}.govuk-radios--inline .govuk-radios__item{margin-right:20px}}.govuk-radios__divider{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;width:40px;margin-bottom:10px;text-align:center}@media print{.govuk-radios__divider{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-radios__divider{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-radios__divider{font-size:14pt;line-height:1.15;color:#000}}.govuk-radios__conditional{margin-bottom:15px;margin-left:18px;padding-left:33px;border-left:4px solid #b1b4b6}@media (min-width:40.0625em){.govuk-radios__conditional{margin-bottom:20px}}.govuk-frontend-supported .govuk-radios__conditional--hidden{display:none}.govuk-radios--small .govuk-radios__item,.govuk-radios__conditional>:last-child{margin-bottom:0}.govuk-radios--small .govuk-radios__input{margin-left:-10px}.govuk-radios--small .govuk-radios__label{padding-left:1px}.govuk-radios--small .govuk-radios__label:before{top:10px;left:0;width:24px;height:24px}.govuk-radios--small .govuk-radios__label:after{top:17px;left:7px;border-width:5px}.govuk-radios--small .govuk-radios__hint{padding-left:34px}.govuk-radios--small .govuk-radios__conditional{margin-left:10px;padding-left:20px}.govuk-radios--small .govuk-radios__divider{width:24px;margin-bottom:5px}.govuk-radios--small .govuk-radios__item:hover .govuk-radios__input:not(:disabled)+.govuk-radios__label:before{outline:4px dashed #0000;outline-offset:1px;box-shadow:0 0 0 10px #b1b4b6}.govuk-radios--small .govuk-radios__item:hover .govuk-radios__input:focus+.govuk-radios__label:before{box-shadow:0 0 0 4px 0 0 0 10px #fd0 #b1b4b6}@media (-ms-high-contrast:active),screen and (forced-colors:active){.govuk-radios--small .govuk-radios__item:hover .govuk-radios__input:focus+.govuk-radios__label:before{outline-color:Highlight}}@media (hover:none),(pointer:coarse){.govuk-radios--small .govuk-radios__item:hover .govuk-radios__input:not(:disabled)+.govuk-radios__label:before{box-shadow:none}.govuk-radios--small .govuk-radios__item:hover .govuk-radios__input:focus+.govuk-radios__label:before{box-shadow:0 0 0 4px #fd0}}.govuk-select{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;box-sizing:border-box;min-width:11.5em;max-width:100%;height:2.5rem;padding:5px;border:2px solid #0b0c0c;color:#0b0c0c;background-color:#fff}@media print{.govuk-select{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-select{font-size:1.1875rem;line-height:1.25}}@media print{.govuk-select{font-size:14pt;line-height:1.25}}.govuk-select:focus{outline:3px solid #fd0;outline-offset:0;box-shadow:inset 0 0 0 2px}.govuk-select:disabled{opacity:.5;color:inherit;cursor:not-allowed}.govuk-select option:active,.govuk-select option:checked,.govuk-select:focus::-ms-value{color:#fff;background-color:#1d70b8}.govuk-select--error{border-color:#d4351c}.govuk-select--error:focus{border-color:#0b0c0c}.govuk-skip-link{position:absolute!important;width:1px!important;height:1px!important;margin:0!important;overflow:hidden!important;clip:rect(0 0 0 0)!important;-webkit-clip-path:inset(50%)!important;clip-path:inset(50%)!important;white-space:nowrap!important;-webkit-user-select:none;-ms-user-select:none;user-select:none;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em;font-size:.875rem;line-height:1.1428571429;display:block;padding:10px 15px}.govuk-skip-link:active,.govuk-skip-link:focus{position:static!important;width:auto!important;height:auto!important;margin:inherit!important;overflow:visible!important;clip:auto!important;-webkit-clip-path:none!important;clip-path:none!important;white-space:inherit!important;-webkit-user-select:text;-ms-user-select:text;user-select:text}@media print{.govuk-skip-link{font-family:sans-serif}}.govuk-skip-link:link,.govuk-skip-link:visited{color:#0b0c0c}@media print{.govuk-skip-link:link,.govuk-skip-link:visited{color:#000}}.govuk-skip-link:hover{color:#0b0c0cfc}.govuk-skip-link:active,.govuk-skip-link:focus{color:#0b0c0c}@media print{.govuk-skip-link:active,.govuk-skip-link:focus{color:#000}}@media (min-width:40.0625em){.govuk-skip-link{font-size:1rem;line-height:1.25}}@media print{.govuk-skip-link{font-size:14pt;line-height:1.2}}@supports (padding:max(calc(0px))){.govuk-skip-link{padding-right:max(15px,calc(15px + env(safe-area-inset-right)));padding-left:max(15px,calc(15px + env(safe-area-inset-left)))}}.govuk-skip-link:focus{outline:3px solid #fd0;outline-offset:0;background-color:#fd0}.govuk-skip-link-focused-element:focus{outline:none}.govuk-summary-list{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;margin:0 0 20px}@media print{.govuk-summary-list{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-summary-list{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-summary-list{font-size:14pt;line-height:1.15;color:#000}}@media (min-width:40.0625em){.govuk-summary-list{display:table;width:100%;table-layout:fixed;border-collapse:collapse;margin-bottom:30px}}.govuk-summary-list__row{border-bottom:1px solid #b1b4b6}@media (max-width:40.0525em){.govuk-summary-list__row{margin-bottom:15px}}@media (min-width:40.0625em){.govuk-summary-list__row{display:table-row}}.govuk-summary-list__row:not(.govuk-summary-list__row--no-actions)>:last-child{padding-right:0}@media (min-width:40.0625em){.govuk-summary-list__row--no-actions:after{content:"";display:table-cell;width:20%}}.govuk-summary-list__actions,.govuk-summary-list__key,.govuk-summary-list__value{margin:0}@media (min-width:40.0625em){.govuk-summary-list__actions,.govuk-summary-list__key,.govuk-summary-list__value{display:table-cell;padding-top:10px;padding-right:20px;padding-bottom:10px}}.govuk-summary-list__actions{margin-bottom:15px}@media (min-width:40.0625em){.govuk-summary-list__actions{width:20%;text-align:right}}.govuk-summary-list__key,.govuk-summary-list__value{word-wrap:break-word;overflow-wrap:break-word}.govuk-summary-list__key{margin-bottom:5px;font-weight:700}@media (min-width:40.0625em){.govuk-summary-list__key{width:30%}}@media (max-width:40.0525em){.govuk-summary-list__value{margin-bottom:15px}}.govuk-summary-list__value>p{margin-bottom:10px}.govuk-summary-list__value>:last-child{margin-bottom:0}.govuk-summary-list__actions-list{width:100%;margin:0;padding:0}.govuk-summary-list__actions-list-item{display:inline-block}@media (max-width:40.0525em){.govuk-summary-list__actions-list-item{margin-right:10px;padding-right:10px;border-right:1px solid #b1b4b6}.govuk-summary-list__actions-list-item:last-child{margin-right:0;padding-right:0;border:0}}@media (min-width:40.0625em){.govuk-summary-list__actions-list-item{margin-left:10px;padding-left:10px}.govuk-summary-list__actions-list-item:not(:first-child){border-left:1px solid #b1b4b6}.govuk-summary-list__actions-list-item:first-child{margin-left:0;padding-left:0;border:0}}.govuk-summary-list__actions-list-item .govuk-link:focus{isolation:isolate}.govuk-summary-list--no-border .govuk-summary-list__row{border:0}@media (min-width:40.0625em){.govuk-summary-list--no-border .govuk-summary-list__actions,.govuk-summary-list--no-border .govuk-summary-list__key,.govuk-summary-list--no-border .govuk-summary-list__value{padding-bottom:11px}}.govuk-summary-list__row--no-border{border:0}@media (min-width:40.0625em){.govuk-summary-list__row--no-border .govuk-summary-list__actions,.govuk-summary-list__row--no-border .govuk-summary-list__key,.govuk-summary-list__row--no-border .govuk-summary-list__value{padding-bottom:11px}}.govuk-summary-card{margin-bottom:20px;border:1px solid #b1b4b6}@media (min-width:40.0625em){.govuk-summary-card{margin-bottom:30px}}.govuk-summary-card__title-wrapper{padding:15px;border-bottom:1px solid #0000;background-color:#f3f2f1}@media (min-width:40.0625em){.govuk-summary-card__title-wrapper{display:flex;justify-content:space-between;flex-wrap:nowrap;padding:15px 20px}}.govuk-summary-card__title{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:700;font-size:1rem;line-height:1.25;color:#0b0c0c;margin:5px 20px 10px 0}@media print{.govuk-summary-card__title{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-summary-card__title{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-summary-card__title{font-size:14pt;line-height:1.15;color:#000}}@media (min-width:40.0625em){.govuk-summary-card__title{margin-bottom:5px}}.govuk-summary-card__actions{font-size:1rem;line-height:1.25;font-weight:700;display:flex;flex-wrap:wrap;row-gap:10px;margin:5px 0;padding:0;list-style:none}@media (min-width:40.0625em){.govuk-summary-card__actions{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-summary-card__actions{font-size:14pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-summary-card__actions{justify-content:right;text-align:right}}.govuk-summary-card__action{display:inline;margin:0 10px 0 0;padding-right:10px;border-right:1px solid #b1b4b6}@media (min-width:40.0625em){.govuk-summary-card__action{margin-right:0}}@media (-ms-high-contrast:none),screen and (-ms-high-contrast:active){.govuk-summary-card__action{margin-bottom:5px}}.govuk-summary-card__action:last-child{margin:0;padding-right:0;border-right:none}@media (min-width:40.0625em){.govuk-summary-card__action:last-child{padding-left:10px}}@media (-ms-high-contrast:none),screen and (-ms-high-contrast:active){.govuk-summary-card__action:last-child{margin-bottom:0}}.govuk-summary-card__content{padding:15px 15px 0}@media (min-width:40.0625em){.govuk-summary-card__content{padding:15px 20px}}.govuk-summary-card__content .govuk-summary-list{margin-bottom:0}.govuk-summary-card__content .govuk-summary-list__row:last-of-type{margin-bottom:0;border-bottom:none}.govuk-table{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;color:#0b0c0c;width:100%;margin-bottom:20px;border-spacing:0;border-collapse:collapse}@media print{.govuk-table{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-table{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-table{font-size:14pt;line-height:1.15;color:#000}}@media (min-width:40.0625em){.govuk-table{margin-bottom:30px}}.govuk-table__header{font-weight:700}.govuk-table__cell,.govuk-table__header{padding:10px 20px 10px 0;border-bottom:1px solid #b1b4b6;text-align:left;vertical-align:top}.govuk-table__cell--numeric{font-variant-numeric:tabular-nums}.govuk-table__cell--numeric,.govuk-table__header--numeric{text-align:right}.govuk-table__cell:last-child,.govuk-table__header:last-child{padding-right:0}.govuk-table__caption{font-weight:700;display:table-caption;text-align:left}.govuk-table__caption--l,.govuk-table__caption--m,.govuk-table__caption--xl{margin-bottom:15px}.govuk-table__caption--xl{font-size:2rem;line-height:1.09375}@media (min-width:40.0625em){.govuk-table__caption--xl{font-size:3rem;line-height:1.0416666667}}@media print{.govuk-table__caption--xl{font-size:32pt;line-height:1.15}}.govuk-table__caption--l{font-size:1.5rem;line-height:1.0416666667}@media (min-width:40.0625em){.govuk-table__caption--l{font-size:2.25rem;line-height:1.1111111111}}@media print{.govuk-table__caption--l{font-size:24pt;line-height:1.05}}.govuk-table__caption--m{font-size:1.125rem;line-height:1.1111111111}@media (min-width:40.0625em){.govuk-table__caption--m{font-size:1.5rem;line-height:1.25}}@media print{.govuk-table__caption--m{font-size:18pt;line-height:1.15}}.govuk-tabs{margin-top:5px;margin-bottom:20px;font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25}@media (min-width:40.0625em){.govuk-tabs{margin-bottom:30px}}@media print{.govuk-tabs{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-tabs{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-tabs{font-size:14pt;line-height:1.15}}.govuk-tabs__title{font-size:1rem;line-height:1.25;font-weight:400;color:#0b0c0c;margin-bottom:10px}@media (min-width:40.0625em){.govuk-tabs__title{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-tabs__title{font-size:14pt;line-height:1.15;color:#000}}.govuk-tabs__list{padding:0;list-style:none;margin:0 0 20px}@media (min-width:40.0625em){.govuk-tabs__list{margin-bottom:30px}}.govuk-tabs__list-item{margin-left:25px}.govuk-tabs__list-item:before{color:#0b0c0c;content:"—";margin-left:-25px;padding-right:5px}@media print{.govuk-tabs__list-item:before{color:#000}}.govuk-tabs__tab{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-decoration:underline;text-decoration-thickness:max(1px,.0625rem);text-underline-offset:.1578em;display:inline-block;margin-bottom:10px}@media print{.govuk-tabs__tab{font-family:sans-serif}}.govuk-tabs__tab:hover{text-decoration-thickness:max(3px,.1875rem,.12em);-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration-skip:none;text-decoration-skip:none}.govuk-tabs__tab:focus{outline:3px solid #0000;background-color:#fd0;box-shadow:0 -2px #fd0,0 4px #0b0c0c;text-decoration:none;-webkit-box-decoration-break:clone;box-decoration-break:clone}.govuk-tabs__tab:link{color:#1d70b8}.govuk-tabs__tab:visited{color:#4c2c92}.govuk-tabs__tab:hover{color:#003078}.govuk-tabs__tab:active,.govuk-tabs__tab:focus{color:#0b0c0c}.govuk-tabs__panel{margin-bottom:30px}@media (min-width:40.0625em){.govuk-tabs__panel{margin-bottom:50px}.govuk-frontend-supported .govuk-tabs__list{margin-bottom:0;border-bottom:1px solid #b1b4b6}.govuk-frontend-supported .govuk-tabs__list:after{content:"";display:block;clear:both}.govuk-frontend-supported .govuk-tabs__title{display:none}.govuk-frontend-supported .govuk-tabs__list-item{position:relative;margin-right:5px;margin-bottom:0;margin-left:0;padding:10px 20px;float:left;background-color:#f3f2f1;text-align:center}.govuk-frontend-supported .govuk-tabs__list-item:before{content:none}.govuk-frontend-supported .govuk-tabs__list-item--selected{position:relative;margin-top:-5px;margin-bottom:-1px;padding:14px 19px 16px;border:1px solid #b1b4b6;border-bottom:0;background-color:#fff}.govuk-frontend-supported .govuk-tabs__list-item--selected .govuk-tabs__tab{text-decoration:none}.govuk-frontend-supported .govuk-tabs__tab{margin-bottom:0}.govuk-frontend-supported .govuk-tabs__tab:link,.govuk-frontend-supported .govuk-tabs__tab:visited{color:#0b0c0c}}@media print and (min-width:40.0625em){.govuk-frontend-supported .govuk-tabs__tab:link,.govuk-frontend-supported .govuk-tabs__tab:visited{color:#000}}@media (min-width:40.0625em){.govuk-frontend-supported .govuk-tabs__tab:hover{color:#0b0c0cfc}.govuk-frontend-supported .govuk-tabs__tab:active,.govuk-frontend-supported .govuk-tabs__tab:focus{color:#0b0c0c}}@media print and (min-width:40.0625em){.govuk-frontend-supported .govuk-tabs__tab:active,.govuk-frontend-supported .govuk-tabs__tab:focus{color:#000}}@media (min-width:40.0625em){.govuk-frontend-supported .govuk-tabs__tab:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0}.govuk-frontend-supported .govuk-tabs__panel{margin-bottom:0;padding:30px 20px;border:1px solid #b1b4b6;border-top:0}.govuk-frontend-supported .govuk-tabs__panel>:last-child{margin-bottom:0}.govuk-frontend-supported .govuk-tabs__panel--hidden{display:none}}.govuk-task-list{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;margin-top:0;margin-bottom:20px;padding:0;list-style-type:none}@media print{.govuk-task-list{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-task-list{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-task-list{font-size:14pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-task-list{margin-bottom:30px}}.govuk-task-list__item{display:table;position:relative;width:100%;margin-bottom:0;padding-top:10px;padding-bottom:10px;border-bottom:1px solid #b1b4b6}.govuk-task-list__item:first-child{border-top:1px solid #b1b4b6}.govuk-task-list__item--with-link:hover{background:#f3f2f1}.govuk-task-list__name-and-hint{display:table-cell;vertical-align:top;color:#0b0c0c}@media print{.govuk-task-list__name-and-hint{color:#000}}.govuk-task-list__status{display:table-cell;padding-left:10px;text-align:right;vertical-align:top;color:#0b0c0c}@media print{.govuk-task-list__status{color:#000}}.govuk-task-list__status--cannot-start-yet{color:#505a5f}.govuk-task-list__link:after{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0}.govuk-task-list__hint{margin-top:5px;color:#505a5f}.govuk-warning-text{font-family:GDS Transport,arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:400;font-size:1rem;line-height:1.25;margin-bottom:20px;position:relative;padding:10px 0}@media print{.govuk-warning-text{font-family:sans-serif}}@media (min-width:40.0625em){.govuk-warning-text{font-size:1.1875rem;line-height:1.3157894737}}@media print{.govuk-warning-text{font-size:14pt;line-height:1.15}}@media (min-width:40.0625em){.govuk-warning-text{margin-bottom:30px}}.govuk-warning-text__icon{font-weight:700;box-sizing:border-box;display:inline-block;position:absolute;left:0;min-width:35px;min-height:35px;margin-top:-7px;border:3px solid #0b0c0c;border-radius:50%;color:#fff;background:#0b0c0c;font-size:30px;line-height:29px;text-align:center;-webkit-user-select:none;-ms-user-select:none;user-select:none;forced-color-adjust:none}@media (min-width:40.0625em){.govuk-warning-text__icon{margin-top:-5px}}@media screen and (forced-colors:active){.govuk-warning-text__icon{border-color:windowText;color:windowText;background:#0000}}.govuk-warning-text__text{color:#0b0c0c;display:block;padding-left:45px}@media print{.govuk-warning-text__text{color:#000}}.govuk-clearfix:after{content:"";display:block;clear:both}.govuk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;margin:0!important;padding:0!important;overflow:hidden!important;clip:rect(0 0 0 0)!important;-webkit-clip-path:inset(50%)!important;clip-path:inset(50%)!important;border:0!important;white-space:nowrap!important;-webkit-user-select:none;-ms-user-select:none;user-select:none}.govuk-visually-hidden:after,.govuk-visually-hidden:before{content:" "}.govuk-visually-hidden-focusable{position:absolute!important;width:1px!important;height:1px!important;margin:0!important;overflow:hidden!important;clip:rect(0 0 0 0)!important;-webkit-clip-path:inset(50%)!important;clip-path:inset(50%)!important;white-space:nowrap!important;-webkit-user-select:none;-ms-user-select:none;user-select:none}.govuk-visually-hidden-focusable:active,.govuk-visually-hidden-focusable:focus{position:static!important;width:auto!important;height:auto!important;margin:inherit!important;overflow:visible!important;clip:auto!important;-webkit-clip-path:none!important;clip-path:none!important;white-space:inherit!important;-webkit-user-select:text;-ms-user-select:text;user-select:text}.govuk-\!-display-inline{display:inline!important}.govuk-\!-display-inline-block{display:inline-block!important}.govuk-\!-display-block{display:block!important}.govuk-\!-display-none{display:none!important}@media print{.govuk-\!-display-none-print{display:none!important}}.govuk-\!-margin-0{margin:0!important}.govuk-\!-margin-top-0{margin-top:0!important}.govuk-\!-margin-right-0{margin-right:0!important}.govuk-\!-margin-bottom-0{margin-bottom:0!important}.govuk-\!-margin-left-0{margin-left:0!important}.govuk-\!-margin-1{margin:5px!important}.govuk-\!-margin-top-1{margin-top:5px!important}.govuk-\!-margin-right-1{margin-right:5px!important}.govuk-\!-margin-bottom-1{margin-bottom:5px!important}.govuk-\!-margin-left-1{margin-left:5px!important}.govuk-\!-margin-2{margin:10px!important}.govuk-\!-margin-top-2{margin-top:10px!important}.govuk-\!-margin-right-2{margin-right:10px!important}.govuk-\!-margin-bottom-2{margin-bottom:10px!important}.govuk-\!-margin-left-2{margin-left:10px!important}.govuk-\!-margin-3{margin:15px!important}.govuk-\!-margin-top-3{margin-top:15px!important}.govuk-\!-margin-right-3{margin-right:15px!important}.govuk-\!-margin-bottom-3{margin-bottom:15px!important}.govuk-\!-margin-left-3{margin-left:15px!important}.govuk-\!-margin-4{margin:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-4{margin:20px!important}}.govuk-\!-margin-top-4{margin-top:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-top-4{margin-top:20px!important}}.govuk-\!-margin-right-4{margin-right:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-right-4{margin-right:20px!important}}.govuk-\!-margin-bottom-4{margin-bottom:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-bottom-4{margin-bottom:20px!important}}.govuk-\!-margin-left-4{margin-left:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-left-4{margin-left:20px!important}}.govuk-\!-margin-5{margin:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-5{margin:25px!important}}.govuk-\!-margin-top-5{margin-top:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-top-5{margin-top:25px!important}}.govuk-\!-margin-right-5{margin-right:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-right-5{margin-right:25px!important}}.govuk-\!-margin-bottom-5{margin-bottom:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-bottom-5{margin-bottom:25px!important}}.govuk-\!-margin-left-5{margin-left:15px!important}@media (min-width:40.0625em){.govuk-\!-margin-left-5{margin-left:25px!important}}.govuk-\!-margin-6{margin:20px!important}@media (min-width:40.0625em){.govuk-\!-margin-6{margin:30px!important}}.govuk-\!-margin-top-6{margin-top:20px!important}@media (min-width:40.0625em){.govuk-\!-margin-top-6{margin-top:30px!important}}.govuk-\!-margin-right-6{margin-right:20px!important}@media (min-width:40.0625em){.govuk-\!-margin-right-6{margin-right:30px!important}}.govuk-\!-margin-bottom-6{margin-bottom:20px!important}@media (min-width:40.0625em){.govuk-\!-margin-bottom-6{margin-bottom:30px!important}}.govuk-\!-margin-left-6{margin-left:20px!important}@media (min-width:40.0625em){.govuk-\!-margin-left-6{margin-left:30px!important}}.govuk-\!-margin-7{margin:25px!important}@media (min-width:40.0625em){.govuk-\!-margin-7{margin:40px!important}}.govuk-\!-margin-top-7{margin-top:25px!important}@media (min-width:40.0625em){.govuk-\!-margin-top-7{margin-top:40px!important}}.govuk-\!-margin-right-7{margin-right:25px!important}@media (min-width:40.0625em){.govuk-\!-margin-right-7{margin-right:40px!important}}.govuk-\!-margin-bottom-7{margin-bottom:25px!important}@media (min-width:40.0625em){.govuk-\!-margin-bottom-7{margin-bottom:40px!important}}.govuk-\!-margin-left-7{margin-left:25px!important}@media (min-width:40.0625em){.govuk-\!-margin-left-7{margin-left:40px!important}}.govuk-\!-margin-8{margin:30px!important}@media (min-width:40.0625em){.govuk-\!-margin-8{margin:50px!important}}.govuk-\!-margin-top-8{margin-top:30px!important}@media (min-width:40.0625em){.govuk-\!-margin-top-8{margin-top:50px!important}}.govuk-\!-margin-right-8{margin-right:30px!important}@media (min-width:40.0625em){.govuk-\!-margin-right-8{margin-right:50px!important}}.govuk-\!-margin-bottom-8{margin-bottom:30px!important}@media (min-width:40.0625em){.govuk-\!-margin-bottom-8{margin-bottom:50px!important}}.govuk-\!-margin-left-8{margin-left:30px!important}@media (min-width:40.0625em){.govuk-\!-margin-left-8{margin-left:50px!important}}.govuk-\!-margin-9{margin:40px!important}@media (min-width:40.0625em){.govuk-\!-margin-9{margin:60px!important}}.govuk-\!-margin-top-9{margin-top:40px!important}@media (min-width:40.0625em){.govuk-\!-margin-top-9{margin-top:60px!important}}.govuk-\!-margin-right-9{margin-right:40px!important}@media (min-width:40.0625em){.govuk-\!-margin-right-9{margin-right:60px!important}}.govuk-\!-margin-bottom-9{margin-bottom:40px!important}@media (min-width:40.0625em){.govuk-\!-margin-bottom-9{margin-bottom:60px!important}}.govuk-\!-margin-left-9{margin-left:40px!important}@media (min-width:40.0625em){.govuk-\!-margin-left-9{margin-left:60px!important}}.govuk-\!-padding-0{padding:0!important}.govuk-\!-padding-top-0{padding-top:0!important}.govuk-\!-padding-right-0{padding-right:0!important}.govuk-\!-padding-bottom-0{padding-bottom:0!important}.govuk-\!-padding-left-0{padding-left:0!important}.govuk-\!-padding-1{padding:5px!important}.govuk-\!-padding-top-1{padding-top:5px!important}.govuk-\!-padding-right-1{padding-right:5px!important}.govuk-\!-padding-bottom-1{padding-bottom:5px!important}.govuk-\!-padding-left-1{padding-left:5px!important}.govuk-\!-padding-2{padding:10px!important}.govuk-\!-padding-top-2{padding-top:10px!important}.govuk-\!-padding-right-2{padding-right:10px!important}.govuk-\!-padding-bottom-2{padding-bottom:10px!important}.govuk-\!-padding-left-2{padding-left:10px!important}.govuk-\!-padding-3{padding:15px!important}.govuk-\!-padding-top-3{padding-top:15px!important}.govuk-\!-padding-right-3{padding-right:15px!important}.govuk-\!-padding-bottom-3{padding-bottom:15px!important}.govuk-\!-padding-left-3{padding-left:15px!important}.govuk-\!-padding-4{padding:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-4{padding:20px!important}}.govuk-\!-padding-top-4{padding-top:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-top-4{padding-top:20px!important}}.govuk-\!-padding-right-4{padding-right:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-right-4{padding-right:20px!important}}.govuk-\!-padding-bottom-4{padding-bottom:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-bottom-4{padding-bottom:20px!important}}.govuk-\!-padding-left-4{padding-left:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-left-4{padding-left:20px!important}}.govuk-\!-padding-5{padding:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-5{padding:25px!important}}.govuk-\!-padding-top-5{padding-top:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-top-5{padding-top:25px!important}}.govuk-\!-padding-right-5{padding-right:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-right-5{padding-right:25px!important}}.govuk-\!-padding-bottom-5{padding-bottom:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-bottom-5{padding-bottom:25px!important}}.govuk-\!-padding-left-5{padding-left:15px!important}@media (min-width:40.0625em){.govuk-\!-padding-left-5{padding-left:25px!important}}.govuk-\!-padding-6{padding:20px!important}@media (min-width:40.0625em){.govuk-\!-padding-6{padding:30px!important}}.govuk-\!-padding-top-6{padding-top:20px!important}@media (min-width:40.0625em){.govuk-\!-padding-top-6{padding-top:30px!important}}.govuk-\!-padding-right-6{padding-right:20px!important}@media (min-width:40.0625em){.govuk-\!-padding-right-6{padding-right:30px!important}}.govuk-\!-padding-bottom-6{padding-bottom:20px!important}@media (min-width:40.0625em){.govuk-\!-padding-bottom-6{padding-bottom:30px!important}}.govuk-\!-padding-left-6{padding-left:20px!important}@media (min-width:40.0625em){.govuk-\!-padding-left-6{padding-left:30px!important}}.govuk-\!-padding-7{padding:25px!important}@media (min-width:40.0625em){.govuk-\!-padding-7{padding:40px!important}}.govuk-\!-padding-top-7{padding-top:25px!important}@media (min-width:40.0625em){.govuk-\!-padding-top-7{padding-top:40px!important}}.govuk-\!-padding-right-7{padding-right:25px!important}@media (min-width:40.0625em){.govuk-\!-padding-right-7{padding-right:40px!important}}.govuk-\!-padding-bottom-7{padding-bottom:25px!important}@media (min-width:40.0625em){.govuk-\!-padding-bottom-7{padding-bottom:40px!important}}.govuk-\!-padding-left-7{padding-left:25px!important}@media (min-width:40.0625em){.govuk-\!-padding-left-7{padding-left:40px!important}}.govuk-\!-padding-8{padding:30px!important}@media (min-width:40.0625em){.govuk-\!-padding-8{padding:50px!important}}.govuk-\!-padding-top-8{padding-top:30px!important}@media (min-width:40.0625em){.govuk-\!-padding-top-8{padding-top:50px!important}}.govuk-\!-padding-right-8{padding-right:30px!important}@media (min-width:40.0625em){.govuk-\!-padding-right-8{padding-right:50px!important}}.govuk-\!-padding-bottom-8{padding-bottom:30px!important}@media (min-width:40.0625em){.govuk-\!-padding-bottom-8{padding-bottom:50px!important}}.govuk-\!-padding-left-8{padding-left:30px!important}@media (min-width:40.0625em){.govuk-\!-padding-left-8{padding-left:50px!important}}.govuk-\!-padding-9{padding:40px!important}@media (min-width:40.0625em){.govuk-\!-padding-9{padding:60px!important}}.govuk-\!-padding-top-9{padding-top:40px!important}@media (min-width:40.0625em){.govuk-\!-padding-top-9{padding-top:60px!important}}.govuk-\!-padding-right-9{padding-right:40px!important}@media (min-width:40.0625em){.govuk-\!-padding-right-9{padding-right:60px!important}}.govuk-\!-padding-bottom-9{padding-bottom:40px!important}@media (min-width:40.0625em){.govuk-\!-padding-bottom-9{padding-bottom:60px!important}}.govuk-\!-padding-left-9{padding-left:40px!important}@media (min-width:40.0625em){.govuk-\!-padding-left-9{padding-left:60px!important}}.govuk-\!-static-margin-0{margin:0!important}.govuk-\!-static-margin-top-0{margin-top:0!important}.govuk-\!-static-margin-right-0{margin-right:0!important}.govuk-\!-static-margin-bottom-0{margin-bottom:0!important}.govuk-\!-static-margin-left-0{margin-left:0!important}.govuk-\!-static-margin-1{margin:5px!important}.govuk-\!-static-margin-top-1{margin-top:5px!important}.govuk-\!-static-margin-right-1{margin-right:5px!important}.govuk-\!-static-margin-bottom-1{margin-bottom:5px!important}.govuk-\!-static-margin-left-1{margin-left:5px!important}.govuk-\!-static-margin-2{margin:10px!important}.govuk-\!-static-margin-top-2{margin-top:10px!important}.govuk-\!-static-margin-right-2{margin-right:10px!important}.govuk-\!-static-margin-bottom-2{margin-bottom:10px!important}.govuk-\!-static-margin-left-2{margin-left:10px!important}.govuk-\!-static-margin-3{margin:15px!important}.govuk-\!-static-margin-top-3{margin-top:15px!important}.govuk-\!-static-margin-right-3{margin-right:15px!important}.govuk-\!-static-margin-bottom-3{margin-bottom:15px!important}.govuk-\!-static-margin-left-3{margin-left:15px!important}.govuk-\!-static-margin-4{margin:20px!important}.govuk-\!-static-margin-top-4{margin-top:20px!important}.govuk-\!-static-margin-right-4{margin-right:20px!important}.govuk-\!-static-margin-bottom-4{margin-bottom:20px!important}.govuk-\!-static-margin-left-4{margin-left:20px!important}.govuk-\!-static-margin-5{margin:25px!important}.govuk-\!-static-margin-top-5{margin-top:25px!important}.govuk-\!-static-margin-right-5{margin-right:25px!important}.govuk-\!-static-margin-bottom-5{margin-bottom:25px!important}.govuk-\!-static-margin-left-5{margin-left:25px!important}.govuk-\!-static-margin-6{margin:30px!important}.govuk-\!-static-margin-top-6{margin-top:30px!important}.govuk-\!-static-margin-right-6{margin-right:30px!important}.govuk-\!-static-margin-bottom-6{margin-bottom:30px!important}.govuk-\!-static-margin-left-6{margin-left:30px!important}.govuk-\!-static-margin-7{margin:40px!important}.govuk-\!-static-margin-top-7{margin-top:40px!important}.govuk-\!-static-margin-right-7{margin-right:40px!important}.govuk-\!-static-margin-bottom-7{margin-bottom:40px!important}.govuk-\!-static-margin-left-7{margin-left:40px!important}.govuk-\!-static-margin-8{margin:50px!important}.govuk-\!-static-margin-top-8{margin-top:50px!important}.govuk-\!-static-margin-right-8{margin-right:50px!important}.govuk-\!-static-margin-bottom-8{margin-bottom:50px!important}.govuk-\!-static-margin-left-8{margin-left:50px!important}.govuk-\!-static-margin-9{margin:60px!important}.govuk-\!-static-margin-top-9{margin-top:60px!important}.govuk-\!-static-margin-right-9{margin-right:60px!important}.govuk-\!-static-margin-bottom-9{margin-bottom:60px!important}.govuk-\!-static-margin-left-9{margin-left:60px!important}.govuk-\!-static-padding-0{padding:0!important}.govuk-\!-static-padding-top-0{padding-top:0!important}.govuk-\!-static-padding-right-0{padding-right:0!important}.govuk-\!-static-padding-bottom-0{padding-bottom:0!important}.govuk-\!-static-padding-left-0{padding-left:0!important}.govuk-\!-static-padding-1{padding:5px!important}.govuk-\!-static-padding-top-1{padding-top:5px!important}.govuk-\!-static-padding-right-1{padding-right:5px!important}.govuk-\!-static-padding-bottom-1{padding-bottom:5px!important}.govuk-\!-static-padding-left-1{padding-left:5px!important}.govuk-\!-static-padding-2{padding:10px!important}.govuk-\!-static-padding-top-2{padding-top:10px!important}.govuk-\!-static-padding-right-2{padding-right:10px!important}.govuk-\!-static-padding-bottom-2{padding-bottom:10px!important}.govuk-\!-static-padding-left-2{padding-left:10px!important}.govuk-\!-static-padding-3{padding:15px!important}.govuk-\!-static-padding-top-3{padding-top:15px!important}.govuk-\!-static-padding-right-3{padding-right:15px!important}.govuk-\!-static-padding-bottom-3{padding-bottom:15px!important}.govuk-\!-static-padding-left-3{padding-left:15px!important}.govuk-\!-static-padding-4{padding:20px!important}.govuk-\!-static-padding-top-4{padding-top:20px!important}.govuk-\!-static-padding-right-4{padding-right:20px!important}.govuk-\!-static-padding-bottom-4{padding-bottom:20px!important}.govuk-\!-static-padding-left-4{padding-left:20px!important}.govuk-\!-static-padding-5{padding:25px!important}.govuk-\!-static-padding-top-5{padding-top:25px!important}.govuk-\!-static-padding-right-5{padding-right:25px!important}.govuk-\!-static-padding-bottom-5{padding-bottom:25px!important}.govuk-\!-static-padding-left-5{padding-left:25px!important}.govuk-\!-static-padding-6{padding:30px!important}.govuk-\!-static-padding-top-6{padding-top:30px!important}.govuk-\!-static-padding-right-6{padding-right:30px!important}.govuk-\!-static-padding-bottom-6{padding-bottom:30px!important}.govuk-\!-static-padding-left-6{padding-left:30px!important}.govuk-\!-static-padding-7{padding:40px!important}.govuk-\!-static-padding-top-7{padding-top:40px!important}.govuk-\!-static-padding-right-7{padding-right:40px!important}.govuk-\!-static-padding-bottom-7{padding-bottom:40px!important}.govuk-\!-static-padding-left-7{padding-left:40px!important}.govuk-\!-static-padding-8{padding:50px!important}.govuk-\!-static-padding-top-8{padding-top:50px!important}.govuk-\!-static-padding-right-8{padding-right:50px!important}.govuk-\!-static-padding-bottom-8{padding-bottom:50px!important}.govuk-\!-static-padding-left-8{padding-left:50px!important}.govuk-\!-static-padding-9{padding:60px!important}.govuk-\!-static-padding-top-9{padding-top:60px!important}.govuk-\!-static-padding-right-9{padding-right:60px!important}.govuk-\!-static-padding-bottom-9{padding-bottom:60px!important}.govuk-\!-static-padding-left-9{padding-left:60px!important}.govuk-\!-text-align-left{text-align:left!important}.govuk-\!-text-align-centre{text-align:center!important}.govuk-\!-text-align-right{text-align:right!important}.govuk-\!-font-size-80{font-size:3.3125rem!important;line-height:1.0377358491!important}@media (min-width:40.0625em){.govuk-\!-font-size-80{font-size:5rem!important;line-height:1!important}}@media print{.govuk-\!-font-size-80{font-size:53pt!important;line-height:1.1!important}}.govuk-\!-font-size-48{font-size:2rem!important;line-height:1.09375!important}@media (min-width:40.0625em){.govuk-\!-font-size-48{font-size:3rem!important;line-height:1.0416666667!important}}@media print{.govuk-\!-font-size-48{font-size:32pt!important;line-height:1.15!important}}.govuk-\!-font-size-36{font-size:1.5rem!important;line-height:1.0416666667!important}@media (min-width:40.0625em){.govuk-\!-font-size-36{font-size:2.25rem!important;line-height:1.1111111111!important}}@media print{.govuk-\!-font-size-36{font-size:24pt!important;line-height:1.05!important}}.govuk-\!-font-size-27{font-size:1.125rem!important;line-height:1.1111111111!important}@media (min-width:40.0625em){.govuk-\!-font-size-27{font-size:1.6875rem!important;line-height:1.1111111111!important}}@media print{.govuk-\!-font-size-27{font-size:18pt!important;line-height:1.15!important}}.govuk-\!-font-size-24{font-size:1.125rem!important;line-height:1.1111111111!important}@media (min-width:40.0625em){.govuk-\!-font-size-24{font-size:1.5rem!important;line-height:1.25!important}}@media print{.govuk-\!-font-size-24{font-size:18pt!important;line-height:1.15!important}}.govuk-\!-font-size-19{font-size:1rem!important;line-height:1.25!important}@media (min-width:40.0625em){.govuk-\!-font-size-19{font-size:1.1875rem!important;line-height:1.3157894737!important}}@media print{.govuk-\!-font-size-19{font-size:14pt!important;line-height:1.15!important}}.govuk-\!-font-size-16{font-size:.875rem!important;line-height:1.1428571429!important}@media (min-width:40.0625em){.govuk-\!-font-size-16{font-size:1rem!important;line-height:1.25!important}}@media print{.govuk-\!-font-size-16{font-size:14pt!important;line-height:1.2!important}}.govuk-\!-font-size-14{font-size:.75rem!important;line-height:1.25!important}@media (min-width:40.0625em){.govuk-\!-font-size-14{font-size:.875rem!important;line-height:1.4285714286!important}}@media print{.govuk-\!-font-size-14{font-size:12pt!important;line-height:1.2!important}}.govuk-\!-font-weight-regular{font-weight:400!important}.govuk-\!-font-weight-bold{font-weight:700!important}.govuk-\!-width-full,.govuk-\!-width-three-quarters{width:100%!important}@media (min-width:40.0625em){.govuk-\!-width-three-quarters{width:75%!important}}.govuk-\!-width-two-thirds{width:100%!important}@media (min-width:40.0625em){.govuk-\!-width-two-thirds{width:66.66%!important}}.govuk-\!-width-one-half{width:100%!important}@media (min-width:40.0625em){.govuk-\!-width-one-half{width:50%!important}}.govuk-\!-width-one-third{width:100%!important}@media (min-width:40.0625em){.govuk-\!-width-one-third{width:33.33%!important}}.govuk-\!-width-one-quarter{width:100%!important}@media (min-width:40.0625em){.govuk-\!-width-one-quarter{width:25%!important}} -/*# sourceMappingURL=govuk-frontend.min.css.map */ diff --git a/ui/static/css/main.css b/ui/static/css/main.css deleted file mode 100644 index 96307a2..0000000 --- a/ui/static/css/main.css +++ /dev/null @@ -1,106 +0,0 @@ -/* * { */ -/* box-sizing: border-box; */ -/* margin: 0em; */ -/* padding: 4px; */ -/* font-size: 10pt; */ -/* /1* font-family: sans-serif; *1/ */ -/* color: #333; */ -/* /1* background: lightgray; *1/ */ -/* } */ -.govuk-header__logo { - width: 22%; -} - -/* .content { */ -/* margin: 8px 8px; */ -/* } */ - -/* .table-list { */ -/* list-style: none; */ -/* } */ - -/* /1* h1 { *1/ */ -/* /1* font-size: 1.5em; *1/ */ -/* /1* } *1/ */ - -/* nav { */ -/* border-bottom: solid 3px #ccc; */ -/* } */ - -/* a { */ -/* color: green; */ -/* text-decoration: none; */ -/* } */ - -/* a:hover { */ -/* text-decoration: underline; */ -/* } */ - -/* a.admin-link { */ -/* font-size: 0.65rem; */ -/* } */ - -/* table#home-summary-table { */ -/* margin-top: 1.2em; */ -/* /1* background: white; *1/ */ -/* table-layout: fixed; */ -/* width: 100%; */ -/* border-collapse: collapse; */ -/* border: 2px solid gray; */ -/* } */ - -/* table#home-summary-table td { */ -/* /1* background: white; *1/ */ -/* border: 1px solid gray; */ -/* } */ - -/* /1* table#home-summary-table a, *1/ */ -/* /1* table#home-summary-table ul *1/ */ -/* /1* { *1/ */ -/* /1* background: white; *1/ */ -/* /1* } *1/ */ - -/* table#home-summary-table thead th:nth-child(1) { */ -/* width: 10%; */ -/* } */ - -/* /1* If we want stripes... *1/ */ -/* /1* tbody tr:nth-child(odd) { *1/ */ -/* /1* background-color: rgb(0 0 0 / 5%); *1/ */ -/* /1* } *1/ */ - -/* /1* table#home-summary-table thead th:nth-child(2) { *1/ */ -/* /1* width: 6%; *1/ */ -/* /1* } *1/ */ - -/* /1* table#home-summary-table thead th:nth-child(4) { *1/ */ -/* /1* width: 10%; *1/ */ -/* /1* } *1/ */ - -/* /1* table#home-summary-table thead th:nth-child(5) { *1/ */ -/* /1* width: 10%; *1/ */ -/* /1* } *1/ */ - -/* /1* table#home-summary-table thead th:nth-child(6) { *1/ */ -/* /1* width: 5%; *1/ */ -/* /1* } *1/ */ - -/* /1* table#home-summary-table thead th:nth-child(7) { *1/ */ -/* /1* width: 5%; *1/ */ -/* /1* } *1/ */ - -/* table#home-summary-table th */ -/* { */ -/* /1* border: solid 2px #ccc; *1/ */ -/* /1* background: lightgray; *1/ */ -/* /1* background: linear-gradient(to bottom, rgb(0 0 0 / 10%), rgb(0 0 0 / 30%)); *1/ */ -/* /1* font-weight: unset; *1/ */ -/* letter-spacing: 1px; */ -/* text-align: left; */ -/* } */ - -/* /1* table#home-summary-table li *1/ */ -/* /1* { *1/ */ -/* /1* background: white; *1/ */ -/* /1* } *1/ */ - diff --git a/ui/static/js/govuk-frontend.min.js b/ui/static/js/govuk-frontend.min.js deleted file mode 100644 index 6f14aa6..0000000 --- a/ui/static/js/govuk-frontend.min.js +++ /dev/null @@ -1 +0,0 @@ -const version="5.2.0";function mergeConfigs(...t){function flattenObject(t){const e={};return function flattenLoop(t,n){for(const[i,s]of Object.entries(t)){const t=n?`${n}.${i}`:i;s&&"object"==typeof s?flattenLoop(s,t):e[t]=s}}(t),e}const e={};for(const n of t){const t=flattenObject(n);for(const[n,i]of Object.entries(t))e[n]=i}return e}function extractConfigByNamespace(t,e){const n={};for(const[i,s]of Object.entries(t)){const t=i.split(".");if(t[0]===e){t.length>1&&t.shift();n[t.join(".")]=s}}return n}function getFragmentFromUrl(t){if(t.includes("#"))return t.split("#").pop()}function getBreakpoint(t){const e=`--govuk-frontend-breakpoint-${t}`;return{property:e,value:window.getComputedStyle(document.documentElement).getPropertyValue(e)||void 0}}function setFocus(t,e={}){var n;const i=t.getAttribute("tabindex");function onBlur(){var n;null==(n=e.onBlur)||n.call(t),i||t.removeAttribute("tabindex")}i||t.setAttribute("tabindex","-1"),t.addEventListener("focus",(function(){t.addEventListener("blur",onBlur,{once:!0})}),{once:!0}),null==(n=e.onBeforeFocus)||n.call(t),t.focus()}function isSupported(t=document.body){return!!t&&t.classList.contains("govuk-frontend-supported")}function normaliseString(t){if("string"!=typeof t)return t;const e=t.trim();return"true"===e||"false"!==e&&(e.length>0&&isFinite(Number(e))?Number(e):t)}function normaliseDataset(t){const e={};for(const[n,i]of Object.entries(t))e[n]=normaliseString(i);return e}class GOVUKFrontendError extends Error{constructor(...t){super(...t),this.name="GOVUKFrontendError"}}class SupportError extends GOVUKFrontendError{constructor(t=document.body){const e="noModule"in HTMLScriptElement.prototype?'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet':"GOV.UK Frontend is not supported in this browser";super(t?e:'GOV.UK Frontend initialised without `<script type="module">`'),this.name="SupportError"}}class ConfigError extends GOVUKFrontendError{constructor(...t){super(...t),this.name="ConfigError"}}class ElementError extends GOVUKFrontendError{constructor(t){let e="string"==typeof t?t:"";if("object"==typeof t){const{componentName:n,identifier:i,element:s,expectedType:o}=t;e=`${n}: ${i}`,e+=s?` is not of type ${null!=o?o:"HTMLElement"}`:" not found"}super(e),this.name="ElementError"}}class GOVUKFrontendComponent{constructor(){this.checkSupport()}checkSupport(){if(!isSupported())throw new SupportError}}class I18n{constructor(t={},e={}){var n;this.translations=void 0,this.locale=void 0,this.translations=t,this.locale=null!=(n=e.locale)?n:document.documentElement.lang||"en"}t(t,e){if(!t)throw new Error("i18n: lookup key missing");"number"==typeof(null==e?void 0:e.count)&&(t=`${t}.${this.getPluralSuffix(t,e.count)}`);const n=this.translations[t];if("string"==typeof n){if(n.match(/%{(.\S+)}/)){if(!e)throw new Error("i18n: cannot replace placeholders in string if no option data provided");return this.replacePlaceholders(n,e)}return n}return t}replacePlaceholders(t,e){const n=Intl.NumberFormat.supportedLocalesOf(this.locale).length?new Intl.NumberFormat(this.locale):void 0;return t.replace(/%{(.\S+)}/g,(function(t,i){if(Object.prototype.hasOwnProperty.call(e,i)){const t=e[i];return!1===t||"number"!=typeof t&&"string"!=typeof t?"":"number"==typeof t?n?n.format(t):`${t}`:t}throw new Error(`i18n: no data found to replace ${t} placeholder in string`)}))}hasIntlPluralRulesSupport(){return Boolean("PluralRules"in window.Intl&&Intl.PluralRules.supportedLocalesOf(this.locale).length)}getPluralSuffix(t,e){if(e=Number(e),!isFinite(e))return"other";const n=this.hasIntlPluralRulesSupport()?new Intl.PluralRules(this.locale).select(e):this.selectPluralFormUsingFallbackRules(e);if(`${t}.${n}`in this.translations)return n;if(`${t}.other`in this.translations)return console.warn(`i18n: Missing plural form ".${n}" for "${this.locale}" locale. Falling back to ".other".`),"other";throw new Error(`i18n: Plural form ".other" is required for "${this.locale}" locale`)}selectPluralFormUsingFallbackRules(t){t=Math.abs(Math.floor(t));const e=this.getPluralRulesForLocale();return e?I18n.pluralRules[e](t):"other"}getPluralRulesForLocale(){const t=this.locale.split("-")[0];for(const e in I18n.pluralRulesMap){const n=I18n.pluralRulesMap[e];if(n.includes(this.locale)||n.includes(t))return e}}}I18n.pluralRulesMap={arabic:["ar"],chinese:["my","zh","id","ja","jv","ko","ms","th","vi"],french:["hy","bn","fr","gu","hi","fa","pa","zu"],german:["af","sq","az","eu","bg","ca","da","nl","en","et","fi","ka","de","el","hu","lb","no","so","sw","sv","ta","te","tr","ur"],irish:["ga"],russian:["ru","uk"],scottish:["gd"],spanish:["pt-PT","it","es"],welsh:["cy"]},I18n.pluralRules={arabic:t=>0===t?"zero":1===t?"one":2===t?"two":t%100>=3&&t%100<=10?"few":t%100>=11&&t%100<=99?"many":"other",chinese:()=>"other",french:t=>0===t||1===t?"one":"other",german:t=>1===t?"one":"other",irish:t=>1===t?"one":2===t?"two":t>=3&&t<=6?"few":t>=7&&t<=10?"many":"other",russian(t){const e=t%100,n=e%10;return 1===n&&11!==e?"one":n>=2&&n<=4&&!(e>=12&&e<=14)?"few":0===n||n>=5&&n<=9||e>=11&&e<=14?"many":"other"},scottish:t=>1===t||11===t?"one":2===t||12===t?"two":t>=3&&t<=10||t>=13&&t<=19?"few":"other",spanish:t=>1===t?"one":t%1e6==0&&0!==t?"many":"other",welsh:t=>0===t?"zero":1===t?"one":2===t?"two":3===t?"few":6===t?"many":"other"};class Accordion extends GOVUKFrontendComponent{constructor(e,n={}){if(super(),this.$module=void 0,this.config=void 0,this.i18n=void 0,this.controlsClass="govuk-accordion__controls",this.showAllClass="govuk-accordion__show-all",this.showAllTextClass="govuk-accordion__show-all-text",this.sectionClass="govuk-accordion__section",this.sectionExpandedClass="govuk-accordion__section--expanded",this.sectionButtonClass="govuk-accordion__section-button",this.sectionHeaderClass="govuk-accordion__section-header",this.sectionHeadingClass="govuk-accordion__section-heading",this.sectionHeadingDividerClass="govuk-accordion__section-heading-divider",this.sectionHeadingTextClass="govuk-accordion__section-heading-text",this.sectionHeadingTextFocusClass="govuk-accordion__section-heading-text-focus",this.sectionShowHideToggleClass="govuk-accordion__section-toggle",this.sectionShowHideToggleFocusClass="govuk-accordion__section-toggle-focus",this.sectionShowHideTextClass="govuk-accordion__section-toggle-text",this.upChevronIconClass="govuk-accordion-nav__chevron",this.downChevronIconClass="govuk-accordion-nav__chevron--down",this.sectionSummaryClass="govuk-accordion__section-summary",this.sectionSummaryFocusClass="govuk-accordion__section-summary-focus",this.sectionContentClass="govuk-accordion__section-content",this.$sections=void 0,this.browserSupportsSessionStorage=!1,this.$showAllButton=null,this.$showAllIcon=null,this.$showAllText=null,!(e instanceof HTMLElement))throw new ElementError({componentName:"Accordion",element:e,identifier:"Root element (`$module`)"});this.$module=e,this.config=mergeConfigs(Accordion.defaults,n,normaliseDataset(e.dataset)),this.i18n=new I18n(extractConfigByNamespace(this.config,"i18n"));const i=this.$module.querySelectorAll(`.${this.sectionClass}`);if(!i.length)throw new ElementError({componentName:"Accordion",identifier:`Sections (\`<div class="${this.sectionClass}">\`)`});this.$sections=i,this.browserSupportsSessionStorage=t.checkForSessionStorage(),this.initControls(),this.initSectionHeaders();const s=this.checkIfAllSectionsOpen();this.updateShowAllButton(s)}initControls(){this.$showAllButton=document.createElement("button"),this.$showAllButton.setAttribute("type","button"),this.$showAllButton.setAttribute("class",this.showAllClass),this.$showAllButton.setAttribute("aria-expanded","false"),this.$showAllIcon=document.createElement("span"),this.$showAllIcon.classList.add(this.upChevronIconClass),this.$showAllButton.appendChild(this.$showAllIcon);const t=document.createElement("div");t.setAttribute("class",this.controlsClass),t.appendChild(this.$showAllButton),this.$module.insertBefore(t,this.$module.firstChild),this.$showAllText=document.createElement("span"),this.$showAllText.classList.add(this.showAllTextClass),this.$showAllButton.appendChild(this.$showAllText),this.$showAllButton.addEventListener("click",(()=>this.onShowOrHideAllToggle())),"onbeforematch"in document&&document.addEventListener("beforematch",(t=>this.onBeforeMatch(t)))}initSectionHeaders(){this.$sections.forEach(((t,e)=>{const n=t.querySelector(`.${this.sectionHeaderClass}`);if(!n)throw new ElementError({componentName:"Accordion",identifier:`Section headers (\`<div class="${this.sectionHeaderClass}">\`)`});this.constructHeaderMarkup(n,e),this.setExpanded(this.isExpanded(t),t),n.addEventListener("click",(()=>this.onSectionToggle(t))),this.setInitialState(t)}))}constructHeaderMarkup(t,e){const n=t.querySelector(`.${this.sectionButtonClass}`),i=t.querySelector(`.${this.sectionHeadingClass}`),s=t.querySelector(`.${this.sectionSummaryClass}`);if(!i)throw new ElementError({componentName:"Accordion",identifier:`Section heading (\`.${this.sectionHeadingClass}\`)`});if(!n)throw new ElementError({componentName:"Accordion",identifier:`Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`});const o=document.createElement("button");o.setAttribute("type","button"),o.setAttribute("aria-controls",`${this.$module.id}-content-${e+1}`);for(const d of Array.from(n.attributes))"id"!==d.nodeName&&o.setAttribute(d.nodeName,`${d.nodeValue}`);const r=document.createElement("span");r.classList.add(this.sectionHeadingTextClass),r.id=n.id;const a=document.createElement("span");a.classList.add(this.sectionHeadingTextFocusClass),r.appendChild(a),a.innerHTML=n.innerHTML;const l=document.createElement("span");l.classList.add(this.sectionShowHideToggleClass),l.setAttribute("data-nosnippet","");const c=document.createElement("span");c.classList.add(this.sectionShowHideToggleFocusClass),l.appendChild(c);const h=document.createElement("span"),u=document.createElement("span");if(u.classList.add(this.upChevronIconClass),c.appendChild(u),h.classList.add(this.sectionShowHideTextClass),c.appendChild(h),o.appendChild(r),o.appendChild(this.getButtonPunctuationEl()),null!=s&&s.parentNode){const t=document.createElement("span"),e=document.createElement("span");e.classList.add(this.sectionSummaryFocusClass),t.appendChild(e);for(const n of Array.from(s.attributes))t.setAttribute(n.nodeName,`${n.nodeValue}`);e.innerHTML=s.innerHTML,s.parentNode.replaceChild(t,s),o.appendChild(t),o.appendChild(this.getButtonPunctuationEl())}o.appendChild(l),i.removeChild(n),i.appendChild(o)}onBeforeMatch(t){const e=t.target;if(!(e instanceof Element))return;const n=e.closest(`.${this.sectionClass}`);n&&this.setExpanded(!0,n)}onSectionToggle(t){const e=this.isExpanded(t);this.setExpanded(!e,t),this.storeState(t)}onShowOrHideAllToggle(){const t=!this.checkIfAllSectionsOpen();this.$sections.forEach((e=>{this.setExpanded(t,e),this.storeState(e)})),this.updateShowAllButton(t)}setExpanded(t,e){const n=e.querySelector(`.${this.upChevronIconClass}`),i=e.querySelector(`.${this.sectionShowHideTextClass}`),s=e.querySelector(`.${this.sectionButtonClass}`),o=e.querySelector(`.${this.sectionContentClass}`);if(!o)throw new ElementError({componentName:"Accordion",identifier:`Section content (\`<div class="${this.sectionContentClass}">\`)`});if(!n||!i||!s)return;const r=t?this.i18n.t("hideSection"):this.i18n.t("showSection");i.textContent=r,s.setAttribute("aria-expanded",`${t}`);const a=[],l=e.querySelector(`.${this.sectionHeadingTextClass}`);l&&a.push(`${l.textContent}`.trim());const c=e.querySelector(`.${this.sectionSummaryClass}`);c&&a.push(`${c.textContent}`.trim());const h=t?this.i18n.t("hideSectionAriaLabel"):this.i18n.t("showSectionAriaLabel");a.push(h),s.setAttribute("aria-label",a.join(" , ")),t?(o.removeAttribute("hidden"),e.classList.add(this.sectionExpandedClass),n.classList.remove(this.downChevronIconClass)):(o.setAttribute("hidden","until-found"),e.classList.remove(this.sectionExpandedClass),n.classList.add(this.downChevronIconClass));const u=this.checkIfAllSectionsOpen();this.updateShowAllButton(u)}isExpanded(t){return t.classList.contains(this.sectionExpandedClass)}checkIfAllSectionsOpen(){return this.$sections.length===this.$module.querySelectorAll(`.${this.sectionExpandedClass}`).length}updateShowAllButton(t){this.$showAllButton&&this.$showAllText&&this.$showAllIcon&&(this.$showAllButton.setAttribute("aria-expanded",t.toString()),this.$showAllText.textContent=t?this.i18n.t("hideAllSections"):this.i18n.t("showAllSections"),this.$showAllIcon.classList.toggle(this.downChevronIconClass,!t))}storeState(t){if(this.browserSupportsSessionStorage&&this.config.rememberExpanded){const e=t.querySelector(`.${this.sectionButtonClass}`);if(e){const t=e.getAttribute("aria-controls"),n=e.getAttribute("aria-expanded");t&&n&&window.sessionStorage.setItem(t,n)}}}setInitialState(t){if(this.browserSupportsSessionStorage&&this.config.rememberExpanded){const e=t.querySelector(`.${this.sectionButtonClass}`);if(e){const n=e.getAttribute("aria-controls"),i=n?window.sessionStorage.getItem(n):null;null!==i&&this.setExpanded("true"===i,t)}}}getButtonPunctuationEl(){const t=document.createElement("span");return t.classList.add("govuk-visually-hidden",this.sectionHeadingDividerClass),t.innerHTML=", ",t}}Accordion.moduleName="govuk-accordion",Accordion.defaults=Object.freeze({i18n:{hideAllSections:"Hide all sections",hideSection:"Hide",hideSectionAriaLabel:"Hide this section",showAllSections:"Show all sections",showSection:"Show",showSectionAriaLabel:"Show this section"},rememberExpanded:!0});const t={checkForSessionStorage:function(){const t="this is the test string";let e;try{return window.sessionStorage.setItem(t,t),e=window.sessionStorage.getItem(t)===t.toString(),window.sessionStorage.removeItem(t),e}catch(n){return!1}}};class Button extends GOVUKFrontendComponent{constructor(t,e={}){if(super(),this.$module=void 0,this.config=void 0,this.debounceFormSubmitTimer=null,!(t instanceof HTMLElement))throw new ElementError({componentName:"Button",element:t,identifier:"Root element (`$module`)"});this.$module=t,this.config=mergeConfigs(Button.defaults,e,normaliseDataset(t.dataset)),this.$module.addEventListener("keydown",(t=>this.handleKeyDown(t))),this.$module.addEventListener("click",(t=>this.debounce(t)))}handleKeyDown(t){const e=t.target;32===t.keyCode&&e instanceof HTMLElement&&"button"===e.getAttribute("role")&&(t.preventDefault(),e.click())}debounce(t){if(this.config.preventDoubleClick)return this.debounceFormSubmitTimer?(t.preventDefault(),!1):void(this.debounceFormSubmitTimer=window.setTimeout((()=>{this.debounceFormSubmitTimer=null}),1e3))}}function closestAttributeValue(t,e){const n=t.closest(`[${e}]`);return n?n.getAttribute(e):null}Button.moduleName="govuk-button",Button.defaults=Object.freeze({preventDoubleClick:!1});class CharacterCount extends GOVUKFrontendComponent{constructor(t,e={}){var n,i;if(super(),this.$module=void 0,this.$textarea=void 0,this.$visibleCountMessage=void 0,this.$screenReaderCountMessage=void 0,this.lastInputTimestamp=null,this.lastInputValue="",this.valueChecker=null,this.config=void 0,this.i18n=void 0,this.maxLength=void 0,!(t instanceof HTMLElement))throw new ElementError({componentName:"Character count",element:t,identifier:"Root element (`$module`)"});const s=t.querySelector(".govuk-js-character-count");if(!(s instanceof HTMLTextAreaElement||s instanceof HTMLInputElement))throw new ElementError({componentName:"Character count",element:s,expectedType:"HTMLTextareaElement or HTMLInputElement",identifier:"Form field (`.govuk-js-character-count`)"});const o=normaliseDataset(t.dataset);let r={};("maxwords"in o||"maxlength"in o)&&(r={maxlength:void 0,maxwords:void 0}),this.config=mergeConfigs(CharacterCount.defaults,e,r,o);const a=function(t,e){const n=[];for(const[i,s]of Object.entries(t)){const t=[];for(const{required:n,errorMessage:i}of s)n.every((t=>!!e[t]))||t.push(i);"anyOf"!==i||s.length-t.length>=1||n.push(...t)}return n}(CharacterCount.schema,this.config);if(a[0])throw new ConfigError(`Character count: ${a[0]}`);this.i18n=new I18n(extractConfigByNamespace(this.config,"i18n"),{locale:closestAttributeValue(t,"lang")}),this.maxLength=null!=(n=null!=(i=this.config.maxwords)?i:this.config.maxlength)?n:1/0,this.$module=t,this.$textarea=s;const l=`${this.$textarea.id}-info`,c=document.getElementById(l);if(!c)throw new ElementError({componentName:"Character count",element:c,identifier:`Count message (\`id="${l}"\`)`});`${c.textContent}`.match(/^\s*$/)&&(c.textContent=this.i18n.t("textareaDescription",{count:this.maxLength})),this.$textarea.insertAdjacentElement("afterend",c);const h=document.createElement("div");h.className="govuk-character-count__sr-status govuk-visually-hidden",h.setAttribute("aria-live","polite"),this.$screenReaderCountMessage=h,c.insertAdjacentElement("afterend",h);const u=document.createElement("div");u.className=c.className,u.classList.add("govuk-character-count__status"),u.setAttribute("aria-hidden","true"),this.$visibleCountMessage=u,c.insertAdjacentElement("afterend",u),c.classList.add("govuk-visually-hidden"),this.$textarea.removeAttribute("maxlength"),this.bindChangeEvents(),window.addEventListener("pageshow",(()=>this.updateCountMessage())),this.updateCountMessage()}bindChangeEvents(){this.$textarea.addEventListener("keyup",(()=>this.handleKeyUp())),this.$textarea.addEventListener("focus",(()=>this.handleFocus())),this.$textarea.addEventListener("blur",(()=>this.handleBlur()))}handleKeyUp(){this.updateVisibleCountMessage(),this.lastInputTimestamp=Date.now()}handleFocus(){this.valueChecker=window.setInterval((()=>{(!this.lastInputTimestamp||Date.now()-500>=this.lastInputTimestamp)&&this.updateIfValueChanged()}),1e3)}handleBlur(){this.valueChecker&&window.clearInterval(this.valueChecker)}updateIfValueChanged(){this.$textarea.value!==this.lastInputValue&&(this.lastInputValue=this.$textarea.value,this.updateCountMessage())}updateCountMessage(){this.updateVisibleCountMessage(),this.updateScreenReaderCountMessage()}updateVisibleCountMessage(){const t=this.maxLength-this.count(this.$textarea.value)<0;this.$visibleCountMessage.classList.toggle("govuk-character-count__message--disabled",!this.isOverThreshold()),this.$textarea.classList.toggle("govuk-textarea--error",t),this.$visibleCountMessage.classList.toggle("govuk-error-message",t),this.$visibleCountMessage.classList.toggle("govuk-hint",!t),this.$visibleCountMessage.textContent=this.getCountMessage()}updateScreenReaderCountMessage(){this.isOverThreshold()?this.$screenReaderCountMessage.removeAttribute("aria-hidden"):this.$screenReaderCountMessage.setAttribute("aria-hidden","true"),this.$screenReaderCountMessage.textContent=this.getCountMessage()}count(t){if(this.config.maxwords){var e;return(null!=(e=t.match(/\S+/g))?e:[]).length}return t.length}getCountMessage(){const t=this.maxLength-this.count(this.$textarea.value),e=this.config.maxwords?"words":"characters";return this.formatCountMessage(t,e)}formatCountMessage(t,e){if(0===t)return this.i18n.t(`${e}AtLimit`);const n=t<0?"OverLimit":"UnderLimit";return this.i18n.t(`${e}${n}`,{count:Math.abs(t)})}isOverThreshold(){if(!this.config.threshold)return!0;const t=this.count(this.$textarea.value);return this.maxLength*this.config.threshold/100<=t}}CharacterCount.moduleName="govuk-character-count",CharacterCount.defaults=Object.freeze({threshold:0,i18n:{charactersUnderLimit:{one:"You have %{count} character remaining",other:"You have %{count} characters remaining"},charactersAtLimit:"You have 0 characters remaining",charactersOverLimit:{one:"You have %{count} character too many",other:"You have %{count} characters too many"},wordsUnderLimit:{one:"You have %{count} word remaining",other:"You have %{count} words remaining"},wordsAtLimit:"You have 0 words remaining",wordsOverLimit:{one:"You have %{count} word too many",other:"You have %{count} words too many"},textareaDescription:{other:""}}}),CharacterCount.schema=Object.freeze({anyOf:[{required:["maxwords"],errorMessage:'Either "maxlength" or "maxwords" must be provided'},{required:["maxlength"],errorMessage:'Either "maxlength" or "maxwords" must be provided'}]});class Checkboxes extends GOVUKFrontendComponent{constructor(t){if(super(),this.$module=void 0,this.$inputs=void 0,!(t instanceof HTMLElement))throw new ElementError({componentName:"Checkboxes",element:t,identifier:"Root element (`$module`)"});const e=t.querySelectorAll('input[type="checkbox"]');if(!e.length)throw new ElementError({componentName:"Checkboxes",identifier:'Form inputs (`<input type="checkbox">`)'});this.$module=t,this.$inputs=e,this.$inputs.forEach((t=>{const e=t.getAttribute("data-aria-controls");if(e){if(!document.getElementById(e))throw new ElementError({componentName:"Checkboxes",identifier:`Conditional reveal (\`id="${e}"\`)`});t.setAttribute("aria-controls",e),t.removeAttribute("data-aria-controls")}})),window.addEventListener("pageshow",(()=>this.syncAllConditionalReveals())),this.syncAllConditionalReveals(),this.$module.addEventListener("click",(t=>this.handleClick(t)))}syncAllConditionalReveals(){this.$inputs.forEach((t=>this.syncConditionalRevealWithInputState(t)))}syncConditionalRevealWithInputState(t){const e=t.getAttribute("aria-controls");if(!e)return;const n=document.getElementById(e);if(n&&n.classList.contains("govuk-checkboxes__conditional")){const e=t.checked;t.setAttribute("aria-expanded",e.toString()),n.classList.toggle("govuk-checkboxes__conditional--hidden",!e)}}unCheckAllInputsExcept(t){document.querySelectorAll(`input[type="checkbox"][name="${t.name}"]`).forEach((e=>{t.form===e.form&&e!==t&&(e.checked=!1,this.syncConditionalRevealWithInputState(e))}))}unCheckExclusiveInputs(t){document.querySelectorAll(`input[data-behaviour="exclusive"][type="checkbox"][name="${t.name}"]`).forEach((e=>{t.form===e.form&&(e.checked=!1,this.syncConditionalRevealWithInputState(e))}))}handleClick(t){const e=t.target;if(!(e instanceof HTMLInputElement)||"checkbox"!==e.type)return;if(e.getAttribute("aria-controls")&&this.syncConditionalRevealWithInputState(e),!e.checked)return;"exclusive"===e.getAttribute("data-behaviour")?this.unCheckAllInputsExcept(e):this.unCheckExclusiveInputs(e)}}Checkboxes.moduleName="govuk-checkboxes";class ErrorSummary extends GOVUKFrontendComponent{constructor(t,e={}){if(super(),this.$module=void 0,this.config=void 0,!(t instanceof HTMLElement))throw new ElementError({componentName:"Error summary",element:t,identifier:"Root element (`$module`)"});this.$module=t,this.config=mergeConfigs(ErrorSummary.defaults,e,normaliseDataset(t.dataset)),this.config.disableAutoFocus||setFocus(this.$module),this.$module.addEventListener("click",(t=>this.handleClick(t)))}handleClick(t){const e=t.target;e&&this.focusTarget(e)&&t.preventDefault()}focusTarget(t){if(!(t instanceof HTMLAnchorElement))return!1;const e=getFragmentFromUrl(t.href);if(!e)return!1;const n=document.getElementById(e);if(!n)return!1;const i=this.getAssociatedLegendOrLabel(n);return!!i&&(i.scrollIntoView(),n.focus({preventScroll:!0}),!0)}getAssociatedLegendOrLabel(t){var e;const n=t.closest("fieldset");if(n){const e=n.getElementsByTagName("legend");if(e.length){const n=e[0];if(t instanceof HTMLInputElement&&("checkbox"===t.type||"radio"===t.type))return n;const i=n.getBoundingClientRect().top,s=t.getBoundingClientRect();if(s.height&&window.innerHeight){if(s.top+s.height-i<window.innerHeight/2)return n}}}return null!=(e=document.querySelector(`label[for='${t.getAttribute("id")}']`))?e:t.closest("label")}}ErrorSummary.moduleName="govuk-error-summary",ErrorSummary.defaults=Object.freeze({disableAutoFocus:!1});class ExitThisPage extends GOVUKFrontendComponent{constructor(t,e={}){if(super(),this.$module=void 0,this.config=void 0,this.i18n=void 0,this.$button=void 0,this.$skiplinkButton=null,this.$updateSpan=null,this.$indicatorContainer=null,this.$overlay=null,this.keypressCounter=0,this.lastKeyWasModified=!1,this.timeoutTime=5e3,this.keypressTimeoutId=null,this.timeoutMessageId=null,!(t instanceof HTMLElement))throw new ElementError({componentName:"Exit this page",element:t,identifier:"Root element (`$module`)"});const n=t.querySelector(".govuk-exit-this-page__button");if(!(n instanceof HTMLAnchorElement))throw new ElementError({componentName:"Exit this page",element:n,expectedType:"HTMLAnchorElement",identifier:"Button (`.govuk-exit-this-page__button`)"});this.config=mergeConfigs(ExitThisPage.defaults,e,normaliseDataset(t.dataset)),this.i18n=new I18n(extractConfigByNamespace(this.config,"i18n")),this.$module=t,this.$button=n;const i=document.querySelector(".govuk-js-exit-this-page-skiplink");i instanceof HTMLAnchorElement&&(this.$skiplinkButton=i),this.buildIndicator(),this.initUpdateSpan(),this.initButtonClickHandler(),"govukFrontendExitThisPageKeypress"in document.body.dataset||(document.addEventListener("keyup",this.handleKeypress.bind(this),!0),document.body.dataset.govukFrontendExitThisPageKeypress="true"),window.addEventListener("pageshow",this.resetPage.bind(this))}initUpdateSpan(){this.$updateSpan=document.createElement("span"),this.$updateSpan.setAttribute("role","status"),this.$updateSpan.className="govuk-visually-hidden",this.$module.appendChild(this.$updateSpan)}initButtonClickHandler(){this.$button.addEventListener("click",this.handleClick.bind(this)),this.$skiplinkButton&&this.$skiplinkButton.addEventListener("click",this.handleClick.bind(this))}buildIndicator(){this.$indicatorContainer=document.createElement("div"),this.$indicatorContainer.className="govuk-exit-this-page__indicator",this.$indicatorContainer.setAttribute("aria-hidden","true");for(let t=0;t<3;t++){const t=document.createElement("div");t.className="govuk-exit-this-page__indicator-light",this.$indicatorContainer.appendChild(t)}this.$button.appendChild(this.$indicatorContainer)}updateIndicator(){if(!this.$indicatorContainer)return;this.$indicatorContainer.classList.toggle("govuk-exit-this-page__indicator--visible",this.keypressCounter>0);this.$indicatorContainer.querySelectorAll(".govuk-exit-this-page__indicator-light").forEach(((t,e)=>{t.classList.toggle("govuk-exit-this-page__indicator-light--on",e<this.keypressCounter)}))}exitPage(){this.$updateSpan&&(this.$updateSpan.textContent="",document.body.classList.add("govuk-exit-this-page-hide-content"),this.$overlay=document.createElement("div"),this.$overlay.className="govuk-exit-this-page-overlay",this.$overlay.setAttribute("role","alert"),document.body.appendChild(this.$overlay),this.$overlay.textContent=this.i18n.t("activated"),window.location.href=this.$button.href)}handleClick(t){t.preventDefault(),this.exitPage()}handleKeypress(t){this.$updateSpan&&("Shift"!==t.key&&16!==t.keyCode&&16!==t.which||this.lastKeyWasModified?this.keypressTimeoutId&&this.resetKeypressTimer():(this.keypressCounter+=1,this.updateIndicator(),this.timeoutMessageId&&(window.clearTimeout(this.timeoutMessageId),this.timeoutMessageId=null),this.keypressCounter>=3?(this.keypressCounter=0,this.keypressTimeoutId&&(window.clearTimeout(this.keypressTimeoutId),this.keypressTimeoutId=null),this.exitPage()):1===this.keypressCounter?this.$updateSpan.textContent=this.i18n.t("pressTwoMoreTimes"):this.$updateSpan.textContent=this.i18n.t("pressOneMoreTime"),this.setKeypressTimer()),this.lastKeyWasModified=t.shiftKey)}setKeypressTimer(){this.keypressTimeoutId&&window.clearTimeout(this.keypressTimeoutId),this.keypressTimeoutId=window.setTimeout(this.resetKeypressTimer.bind(this),this.timeoutTime)}resetKeypressTimer(){if(!this.$updateSpan)return;this.keypressTimeoutId&&(window.clearTimeout(this.keypressTimeoutId),this.keypressTimeoutId=null);const t=this.$updateSpan;this.keypressCounter=0,t.textContent=this.i18n.t("timedOut"),this.timeoutMessageId=window.setTimeout((()=>{t.textContent=""}),this.timeoutTime),this.updateIndicator()}resetPage(){document.body.classList.remove("govuk-exit-this-page-hide-content"),this.$overlay&&(this.$overlay.remove(),this.$overlay=null),this.$updateSpan&&(this.$updateSpan.setAttribute("role","status"),this.$updateSpan.textContent=""),this.updateIndicator(),this.keypressTimeoutId&&window.clearTimeout(this.keypressTimeoutId),this.timeoutMessageId&&window.clearTimeout(this.timeoutMessageId)}}ExitThisPage.moduleName="govuk-exit-this-page",ExitThisPage.defaults=Object.freeze({i18n:{activated:"Loading.",timedOut:"Exit this page expired.",pressTwoMoreTimes:"Shift, press 2 more times to exit.",pressOneMoreTime:"Shift, press 1 more time to exit."}});class Header extends GOVUKFrontendComponent{constructor(t){if(super(),this.$module=void 0,this.$menuButton=void 0,this.$menu=void 0,this.menuIsOpen=!1,this.mql=null,!t)throw new ElementError({componentName:"Header",element:t,identifier:"Root element (`$module`)"});this.$module=t;const e=t.querySelector(".govuk-js-header-toggle");if(!e)return this;const n=e.getAttribute("aria-controls");if(!n)throw new ElementError({componentName:"Header",identifier:'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'});const i=document.getElementById(n);if(!i)throw new ElementError({componentName:"Header",element:i,identifier:`Navigation (\`<ul id="${n}">\`)`});this.$menu=i,this.$menuButton=e,this.setupResponsiveChecks(),this.$menuButton.addEventListener("click",(()=>this.handleMenuButtonClick()))}setupResponsiveChecks(){const t=getBreakpoint("desktop");if(!t.value)throw new ElementError({componentName:"Header",identifier:`CSS custom property (\`${t.property}\`) on pseudo-class \`:root\``});this.mql=window.matchMedia(`(min-width: ${t.value})`),"addEventListener"in this.mql?this.mql.addEventListener("change",(()=>this.checkMode())):this.mql.addListener((()=>this.checkMode())),this.checkMode()}checkMode(){this.mql&&this.$menu&&this.$menuButton&&(this.mql.matches?(this.$menu.removeAttribute("hidden"),this.$menuButton.setAttribute("hidden","")):(this.$menuButton.removeAttribute("hidden"),this.$menuButton.setAttribute("aria-expanded",this.menuIsOpen.toString()),this.menuIsOpen?this.$menu.removeAttribute("hidden"):this.$menu.setAttribute("hidden","")))}handleMenuButtonClick(){this.menuIsOpen=!this.menuIsOpen,this.checkMode()}}Header.moduleName="govuk-header";class NotificationBanner extends GOVUKFrontendComponent{constructor(t,e={}){if(super(),this.$module=void 0,this.config=void 0,!(t instanceof HTMLElement))throw new ElementError({componentName:"Notification banner",element:t,identifier:"Root element (`$module`)"});this.$module=t,this.config=mergeConfigs(NotificationBanner.defaults,e,normaliseDataset(t.dataset)),"alert"!==this.$module.getAttribute("role")||this.config.disableAutoFocus||setFocus(this.$module)}}NotificationBanner.moduleName="govuk-notification-banner",NotificationBanner.defaults=Object.freeze({disableAutoFocus:!1});class Radios extends GOVUKFrontendComponent{constructor(t){if(super(),this.$module=void 0,this.$inputs=void 0,!(t instanceof HTMLElement))throw new ElementError({componentName:"Radios",element:t,identifier:"Root element (`$module`)"});const e=t.querySelectorAll('input[type="radio"]');if(!e.length)throw new ElementError({componentName:"Radios",identifier:'Form inputs (`<input type="radio">`)'});this.$module=t,this.$inputs=e,this.$inputs.forEach((t=>{const e=t.getAttribute("data-aria-controls");if(e){if(!document.getElementById(e))throw new ElementError({componentName:"Radios",identifier:`Conditional reveal (\`id="${e}"\`)`});t.setAttribute("aria-controls",e),t.removeAttribute("data-aria-controls")}})),window.addEventListener("pageshow",(()=>this.syncAllConditionalReveals())),this.syncAllConditionalReveals(),this.$module.addEventListener("click",(t=>this.handleClick(t)))}syncAllConditionalReveals(){this.$inputs.forEach((t=>this.syncConditionalRevealWithInputState(t)))}syncConditionalRevealWithInputState(t){const e=t.getAttribute("aria-controls");if(!e)return;const n=document.getElementById(e);if(null!=n&&n.classList.contains("govuk-radios__conditional")){const e=t.checked;t.setAttribute("aria-expanded",e.toString()),n.classList.toggle("govuk-radios__conditional--hidden",!e)}}handleClick(t){const e=t.target;if(!(e instanceof HTMLInputElement)||"radio"!==e.type)return;const n=document.querySelectorAll('input[type="radio"][aria-controls]'),i=e.form,s=e.name;n.forEach((t=>{const e=t.form===i;t.name===s&&e&&this.syncConditionalRevealWithInputState(t)}))}}Radios.moduleName="govuk-radios";class SkipLink extends GOVUKFrontendComponent{constructor(t){var e;if(super(),this.$module=void 0,!(t instanceof HTMLAnchorElement))throw new ElementError({componentName:"Skip link",element:t,expectedType:"HTMLAnchorElement",identifier:"Root element (`$module`)"});this.$module=t;const n=this.$module.hash,i=null!=(e=this.$module.getAttribute("href"))?e:"";let s;try{s=new window.URL(this.$module.href)}catch(a){throw new ElementError(`Skip link: Target link (\`href="${i}"\`) is invalid`)}if(s.origin!==window.location.origin||s.pathname!==window.location.pathname)return;const o=getFragmentFromUrl(n);if(!o)throw new ElementError(`Skip link: Target link (\`href="${i}"\`) has no hash fragment`);const r=document.getElementById(o);if(!r)throw new ElementError({componentName:"Skip link",element:r,identifier:`Target content (\`id="${o}"\`)`});this.$module.addEventListener("click",(()=>setFocus(r,{onBeforeFocus(){r.classList.add("govuk-skip-link-focused-element")},onBlur(){r.classList.remove("govuk-skip-link-focused-element")}})))}}SkipLink.moduleName="govuk-skip-link";class Tabs extends GOVUKFrontendComponent{constructor(t){if(super(),this.$module=void 0,this.$tabs=void 0,this.$tabList=void 0,this.$tabListItems=void 0,this.keys={left:37,right:39,up:38,down:40},this.jsHiddenClass="govuk-tabs__panel--hidden",this.changingHash=!1,this.boundTabClick=void 0,this.boundTabKeydown=void 0,this.boundOnHashChange=void 0,this.mql=null,!t)throw new ElementError({componentName:"Tabs",element:t,identifier:"Root element (`$module`)"});const e=t.querySelectorAll("a.govuk-tabs__tab");if(!e.length)throw new ElementError({componentName:"Tabs",identifier:'Links (`<a class="govuk-tabs__tab">`)'});this.$module=t,this.$tabs=e,this.boundTabClick=this.onTabClick.bind(this),this.boundTabKeydown=this.onTabKeydown.bind(this),this.boundOnHashChange=this.onHashChange.bind(this);const n=this.$module.querySelector(".govuk-tabs__list"),i=this.$module.querySelectorAll("li.govuk-tabs__list-item");if(!n)throw new ElementError({componentName:"Tabs",identifier:'List (`<ul class="govuk-tabs__list">`)'});if(!i.length)throw new ElementError({componentName:"Tabs",identifier:'List items (`<li class="govuk-tabs__list-item">`)'});this.$tabList=n,this.$tabListItems=i,this.setupResponsiveChecks()}setupResponsiveChecks(){const t=getBreakpoint("tablet");if(!t.value)throw new ElementError({componentName:"Tabs",identifier:`CSS custom property (\`${t.property}\`) on pseudo-class \`:root\``});this.mql=window.matchMedia(`(min-width: ${t.value})`),"addEventListener"in this.mql?this.mql.addEventListener("change",(()=>this.checkMode())):this.mql.addListener((()=>this.checkMode())),this.checkMode()}checkMode(){var t;null!=(t=this.mql)&&t.matches?this.setup():this.teardown()}setup(){var t;this.$tabList.setAttribute("role","tablist"),this.$tabListItems.forEach((t=>{t.setAttribute("role","presentation")})),this.$tabs.forEach((t=>{this.setAttributes(t),t.addEventListener("click",this.boundTabClick,!0),t.addEventListener("keydown",this.boundTabKeydown,!0),this.hideTab(t)}));const e=null!=(t=this.getTab(window.location.hash))?t:this.$tabs[0];this.showTab(e),window.addEventListener("hashchange",this.boundOnHashChange,!0)}teardown(){this.$tabList.removeAttribute("role"),this.$tabListItems.forEach((t=>{t.removeAttribute("role")})),this.$tabs.forEach((t=>{t.removeEventListener("click",this.boundTabClick,!0),t.removeEventListener("keydown",this.boundTabKeydown,!0),this.unsetAttributes(t)})),window.removeEventListener("hashchange",this.boundOnHashChange,!0)}onHashChange(){const t=window.location.hash,e=this.getTab(t);if(!e)return;if(this.changingHash)return void(this.changingHash=!1);const n=this.getCurrentTab();n&&(this.hideTab(n),this.showTab(e),e.focus())}hideTab(t){this.unhighlightTab(t),this.hidePanel(t)}showTab(t){this.highlightTab(t),this.showPanel(t)}getTab(t){return this.$module.querySelector(`a.govuk-tabs__tab[href="${t}"]`)}setAttributes(t){const e=getFragmentFromUrl(t.href);if(!e)return;t.setAttribute("id",`tab_${e}`),t.setAttribute("role","tab"),t.setAttribute("aria-controls",e),t.setAttribute("aria-selected","false"),t.setAttribute("tabindex","-1");const n=this.getPanel(t);n&&(n.setAttribute("role","tabpanel"),n.setAttribute("aria-labelledby",t.id),n.classList.add(this.jsHiddenClass))}unsetAttributes(t){t.removeAttribute("id"),t.removeAttribute("role"),t.removeAttribute("aria-controls"),t.removeAttribute("aria-selected"),t.removeAttribute("tabindex");const e=this.getPanel(t);e&&(e.removeAttribute("role"),e.removeAttribute("aria-labelledby"),e.classList.remove(this.jsHiddenClass))}onTabClick(t){const e=this.getCurrentTab(),n=t.currentTarget;e&&n instanceof HTMLAnchorElement&&(t.preventDefault(),this.hideTab(e),this.showTab(n),this.createHistoryEntry(n))}createHistoryEntry(t){const e=this.getPanel(t);if(!e)return;const n=e.id;e.id="",this.changingHash=!0,window.location.hash=n,e.id=n}onTabKeydown(t){switch(t.keyCode){case this.keys.left:case this.keys.up:this.activatePreviousTab(),t.preventDefault();break;case this.keys.right:case this.keys.down:this.activateNextTab(),t.preventDefault()}}activateNextTab(){const t=this.getCurrentTab();if(null==t||!t.parentElement)return;const e=t.parentElement.nextElementSibling;if(!e)return;const n=e.querySelector("a.govuk-tabs__tab");n&&(this.hideTab(t),this.showTab(n),n.focus(),this.createHistoryEntry(n))}activatePreviousTab(){const t=this.getCurrentTab();if(null==t||!t.parentElement)return;const e=t.parentElement.previousElementSibling;if(!e)return;const n=e.querySelector("a.govuk-tabs__tab");n&&(this.hideTab(t),this.showTab(n),n.focus(),this.createHistoryEntry(n))}getPanel(t){const e=getFragmentFromUrl(t.href);return e?this.$module.querySelector(`#${e}`):null}showPanel(t){const e=this.getPanel(t);e&&e.classList.remove(this.jsHiddenClass)}hidePanel(t){const e=this.getPanel(t);e&&e.classList.add(this.jsHiddenClass)}unhighlightTab(t){t.parentElement&&(t.setAttribute("aria-selected","false"),t.parentElement.classList.remove("govuk-tabs__list-item--selected"),t.setAttribute("tabindex","-1"))}highlightTab(t){t.parentElement&&(t.setAttribute("aria-selected","true"),t.parentElement.classList.add("govuk-tabs__list-item--selected"),t.setAttribute("tabindex","0"))}getCurrentTab(){return this.$module.querySelector(".govuk-tabs__list-item--selected a.govuk-tabs__tab")}}function initAll(t){var e;if(t=void 0!==t?t:{},!isSupported())return void console.log(new SupportError);const n=[[Accordion,t.accordion],[Button,t.button],[CharacterCount,t.characterCount],[Checkboxes],[ErrorSummary,t.errorSummary],[ExitThisPage,t.exitThisPage],[Header],[NotificationBanner,t.notificationBanner],[Radios],[SkipLink],[Tabs]],i=null!=(e=t.scope)?e:document;n.forEach((([t,e])=>{i.querySelectorAll(`[data-module="${t.moduleName}"]`).forEach((n=>{try{"defaults"in t?new t(n,e):new t(n)}catch(i){console.log(i)}}))}))}Tabs.moduleName="govuk-tabs";export{Accordion,Button,CharacterCount,Checkboxes,ErrorSummary,ExitThisPage,Header,NotificationBanner,Radios,SkipLink,Tabs,initAll,version};//# sourceMappingURL=govuk-frontend.min.js.map |