Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IDS-956 implement validations in pages #10

Merged
merged 14 commits into from
Dec 2, 2024
11 changes: 11 additions & 0 deletions web/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
import { formState } from './store';

window.addEventListener('beforeunload', (event) => {
// If the user has started the form but hasn't finished it, give a warning before
// they close the tab.
if ((formState.hasStartedForm && !formState.hasFinishedForm)) {
event.preventDefault();
event.returnValue = true;
}
});

</script>

<template>
Expand Down
1 change: 1 addition & 0 deletions web/src/assets/btn.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
display: inline-block;
font-family: NationalBold, sans-serif;
text-decoration: none;
user-select: none;
}

.btn-primary {
Expand Down
9 changes: 9 additions & 0 deletions web/src/assets/error.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.error {
border-left: 6px solid rgb(212, 53, 28);
padding-left: 1rem;
}

.error-msg {
color: rgb(212, 53, 28);
font-family: 'NationalBold';
}
31 changes: 31 additions & 0 deletions web/src/assets/form.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.option-list {
margin-top: 1rem;
display: grid;
grid-template-columns: max-content 1fr;
gap: 1rem;
align-items: center;
}

.option-list input[type=radio] {
height: 1.5rem;
width: 1.5rem;
}

.form-group {
display: flex;
flex-direction: column;
margin-top: 1.5rem;
}

input[type=text],
input[type=number],
textarea {
border: 2px solid gray;
padding: 0.35rem;
margin-top: 0.5rem;
}

/*Remove default spinner in number field.*/
input[type=number] {
appearance: textfield;
}
8 changes: 7 additions & 1 deletion web/src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
@tailwind components;
@tailwind utilities; */

/*UoA official fonts.*/
@import url(btn.css);
@import url(text.css);
@import url(error.css);
@import url(form.css);

/*UoA official fonts.*/
@font-face {
font-family: 'NationalBold';
font-weight: bold;
Expand Down Expand Up @@ -103,4 +105,8 @@ strong {

.forward-btn {
margin: 2rem 0;
}

.box {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}
5 changes: 4 additions & 1 deletion web/src/assets/text.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
.page-title {
font-family: "NationalBold";
font-size: 2.5rem;
margin-bottom: 2rem;
}

.app-title {
font-size: 1.5rem;
color: gray;
}

.title-section {
margin-bottom: 2rem;
}
62 changes: 62 additions & 0 deletions web/src/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Fixtures containing a sample project and drive. TODO replace with an API client.
*/
import type { ResearchDriveService, Project, Member, Role } from "./project";

export function getDrive(): ResearchDriveService {
return {
name: "reslig-202200001-Tītoki-metabolomics",
allocated_gb: 25600.0,
used_gb: 1596.0
};
}

export function getProject(): Project {
return {
title: "Tītoki metabolomics",
description: "Stress in plants could be defined as any change in growth condition(s) that disrupts metabolic homeostasis and requires an adjustment of metabolic pathways in a process that is usually referred to as acclimation. Metabolomics could contribute significantly to the study of stress biology in plants and other organisms by identifying different compounds, such as by-products of stress metabolism, stress signal transduction molecules or molecules that are part of the acclimation response of plants.",
division: "Liggins Institute",
members: [
{
person: {
full_name: "Samina Nicholas",
email: "[email protected]",
username: "snic021"
},
roles: [{
name: "Project Owner"
}]
},
{
person: {
full_name: "Zach Luther",
email: "[email protected]",
username: "zlut014"
},
roles: [{
name: "Project Team Member"
}]
},
{
person: {
full_name: "Jarrod Hossam",
email: "[email protected]",
username: "jhos225"
},
roles: [{
name: "Project Team Member"
}]
},
{
person: {
full_name: "Melisa Edric",
email: "[email protected]",
username: "medr894"
},
roles: [{
name: "Project Team Member"
}]
}
]
};
}
87 changes: 87 additions & 0 deletions web/src/project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Type definitions for project information. TODO - generate based on output types in Python.
*/
export interface Person {
email: string | null;
full_name: string;
username: string;
}

export interface Role {
name: string
}

export interface Member {
person: Person;
roles: Role[]
}

export interface Project {
title: string
description: string
division: string
members: Member[]
}

export interface ResearchDriveService {
allocated_gb: number
name: string
used_gb: number
}

export enum DataClassification {
Public = "Public",
Internal = "Internal",
Sensitive = "Sensitive",
Restricted = "Restricted"
}

/**
* Makes and returns an empty Project.
* @returns An empty Project.
*/
export function makeProject(): Project {
return {
title: "",
description: "",
division: "",
members: []
}
}

/**
* Given a list of members, filter for project owners.
* @param members List of members to search through
* @returns Members who are project owners.
*/
export function getProjectOwners(members: Member[]): Member[] {
return members.filter(member =>
member.roles.some(
(role: Role) => role.name === "Project Owner"
)
);
}

/**
* Given a list of project members, filter for ordinary members.
* @param members List of members to search through
* @returns Members who are not project owners.
*/
export function getProjectMembers(members: Member[]): Member[] {
return members.filter(member =>
!member.roles.some(
(role: Role) => role.name === "Project Owner"
)
);
}

/**
* Given a list of members, return a string of their names.
* @param members Members to return names for.
* @returns A string representing all member names.
*/
export function membersToString(members: Member[]): string {
return members.map(member =>
member.person.full_name
).join(", ");
}
20 changes: 20 additions & 0 deletions web/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,21 @@ const router = createRouter({
name: 'summary',
component: () => import('../views/SummaryView.vue')
},
{
path: "/check-details",
name: 'check-details',
component: () => import("../views/CheckDetailsView.vue")
},
{
path: "/update-details",
name: 'update-details',
component: () => import("../views/UpdateDetailsView.vue")
},
{
uoa-noel marked this conversation as resolved.
Show resolved Hide resolved
path: "/update-members",
name: 'update-members',
component: () => import("../views/UpdateMembersView.vue")
},
{
path: "/data-classification",
name: "data-classification",
Expand All @@ -29,6 +39,11 @@ const router = createRouter({
name: "retention-period",
component: () => import("../views/RetentionPeriodView.vue")
},
{
path: "/custom-retention-period",
name: "custom-retention-period",
component: () => import("../views/CustomRetentionPeriodView.vue")
},
{
path: "/confirm",
name: "confirm",
Expand All @@ -38,6 +53,11 @@ const router = createRouter({
path: "/finish",
name: "finish",
component: () => import("../views/FinishView.vue")
},
{
path: "/unable-to-archive",
name: "unable-to-archive",
component: () => import("../views/UnableToArchiveView.vue")
}
]
})
Expand Down
24 changes: 24 additions & 0 deletions web/src/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { reactive } from 'vue';
import { DataClassification, makeProject, type Project } from './project';

interface FormStateStore {
hasStartedForm: boolean;
hasFinishedForm: boolean;
isCorrectDrive: boolean | null;
areProjectDetailsCorrect: boolean | null;
project: Project;
dataClassification: DataClassification | null;
retentionPeriod: number | null;
isRetentionPeriodCustom: boolean | null;
}

export const formState: FormStateStore = reactive({
hasStartedForm: false,
hasFinishedForm: false,
isCorrectDrive: null,
areProjectDetailsCorrect: null,
project: makeProject(),
dataClassification: null,
isRetentionPeriodCustom: null,
retentionPeriod: null
});
Loading