Skip to content

Commit

Permalink
GCAP Phase 1
Browse files Browse the repository at this point in the history
  • Loading branch information
dhawton committed Nov 8, 2023
1 parent 1f851ff commit 427efbc
Show file tree
Hide file tree
Showing 21 changed files with 2,140 additions and 1,819 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@
"leaflet": "^1.9.4",
"pinia": "^2.1.3",
"pinia-plugin-persistedstate": "^3.1.0",
"sortablejs": "^1.15.0",
"sortablejs-vue3": "^1.2.10",
"tailwind-loves-sass": "^1.1.3",
"v-calendar": "^3.0.3",
"vue": "^3.3.4",
"vue-router": "4.2.2"
"vue-router": "4.2.2",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.3",
"@types/node": "20.2.5",
"@types/sortablejs": "^1.15.4",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"@vitejs/plugin-basic-ssl": "^1.0.1",
Expand Down
3 changes: 3 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ import Header from "@/views/partials/Header.vue";
import ReloadPrompt from "@/components/ReloadPrompt.vue";
import Spinner from "@/components/Spinner.vue";
import useUserStore from "@/stores/users";
import useRosterStore from "@/stores/roster";
const userStore = useUserStore();
const rosterStore = useRosterStore();
userStore.fetchPermissionGroups();
rosterStore.fetchCertifications();
</script>

<style lang="scss">
Expand Down
6 changes: 3 additions & 3 deletions src/components/AuthLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ const props = withDefaults(
hideUnauthed?: boolean | undefined;
auth?: boolean | undefined;
roles?: string[];
rostered_controller?: boolean;
rosteredController?: boolean;
}>(),
{
to: undefined,
href: undefined,
auth: undefined,
hideUnauthed: undefined,
rostered_controller: false,
rosteredController: false,
roles: () => [],
}
);
Expand All @@ -45,7 +45,7 @@ const display = computed(() => {
if (props.roles !== undefined && props.roles.length > 0) {
return store.user !== null && props.roles.some((role) => store.user?.roles.includes(role));
}
if (props.rostered_controller !== undefined && props.rostered_controller) {
if (props.rosteredController !== undefined && props.rosteredController) {
return store.user !== null && ["home", "visitor"].includes(store.user?.controller_type);
}
Expand Down
20 changes: 20 additions & 0 deletions src/components/Badge.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<div
class="px-4 h-6 w-fit rounded-full text-sm font-semibold flex items-center cursor-pointer"
:class="`${props.backgroundColor} ${props.textColor}`"
>
<slot />
</div>
</template>

<script setup lang="ts">
interface Props {
backgroundColor?: string;
textColor?: string;
}
const props = withDefaults(defineProps<Props>(), {
backgroundColor: "bg-gray-200",
textColor: "text-gray-800",
});
</script>
123 changes: 28 additions & 95 deletions src/components/ControllerCertificationBadges.vue
Original file line number Diff line number Diff line change
@@ -1,55 +1,32 @@
<template>
<div class="mr-2 overflow-hidden dark:mr-2" style="border-radius: 0.2rem">
<div v-if="props.showActive" class="controller-badge" :class="genActiveClass(props.controller.status)">
<span class="badge-text">{{ props.controller.status || "unknown" }}</span>
</div>
<div class="controller-badge" :class="genClass(props.controller.certifications.ground)">GC</div>
<div v-if="!fac.skipMajor" class="controller-badge" :class="genClass(props.controller.certifications.major_ground)">
MAJ GC
</div>
<div class="controller-badge" :class="genClass(props.controller.certifications.local)">LC</div>
<div v-if="!fac.skipMajor" class="controller-badge" :class="genClass(props.controller.certifications.major_local)">
MAJ LC
</div>
<div class="controller-badge" :class="genClass(props.controller.certifications.approach)">APP</div>
<div
v-if="!fac.skipMajor"
class="controller-badge"
:class="genClass(props.controller.certifications.major_approach)"
<div class="mr-2 overflow-hidden dark:mr-2 flex">
<Badge
v-for="(value, certification) in sortedCertifications(props.controller.certifications)"
:key="certification"
:background-color="genClass(value.value)"
class="mr-2"
>{{ value.display_name }}</Badge
>
MAJ APP
</div>
<div class="controller-badge" :class="genClass(props.controller.certifications.enroute)">ENR</div>
<div v-if="fac.hasOceanicCert" class="controller-badge" :class="genClass(props.controller.certifications.oceanic)">
OCA
</div>
</div>
</template>

<script setup lang="ts">
import type { Controller } from "@/types";
import fac from "@/facility";
import { ref } from "vue";
import type { CertificationItem, Controller } from "@/types";
import Badge from "./Badge.vue";
interface Props {
controller: Controller;
showActive?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
showActive: false,
});
const width = ref(7);
const props = withDefaults(defineProps<Props>(), {});
if (fac.skipMajor) {
width.value -= 3;
}
if (props.showActive) {
width.value += 1;
}
if (fac.hasOceanicCert) {
width.value += 1;
// Sort by certification item's order property
function sortedCertifications(certs: { [key: string]: CertificationItem }): { [key: string]: CertificationItem } {
return Object.fromEntries(
Object.entries(certs)
.filter(([, cert]) => !cert.hidden)
.sort(([, a], [, b]) => a.order - b.order)
);
}
function genClass(cert: string): string {
Expand All @@ -59,78 +36,34 @@ function genClass(cert: string): string {
case "solo":
case "training":
case "none":
return `w-1/${width.value} color-${cert} dark:color-${cert} truncate`;
default:
return `w-1/${width.value} color-none dark:color-none truncate`;
}
}
function genActiveClass(status: string): string {
switch (status) {
case "active":
return `w-1/${width.value} color-certified dark:color-certified capitalize truncate`;
case "inactive":
return `w-1/${width.value} color-training dark:color-training capitalize truncate`;
case "loa":
return `w-1/${width.value} color-cantrain dark:color-cantrain uppercase truncate`;
return `cert-color-${cert} truncate`;
default:
return `w-1/${width.value} color-none dark:color-none capitalize truncate`;
return `cert-color-none truncate`;
}
}
</script>

<style scoped>
.controller-badge {
display: inline-block;
padding: 0.25rem 0.4rem;
font-weight: 700;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
color: white;
font-size: 0.75rem;
}
</style>

<style>
.color-certified {
.cert-color-certified,
.color-active {
background-color: #2ecc71;
}
.color-cantrain {
.cert-color-cantrain,
.color-loa {
background-color: violet;
}
.color-solo {
.cert-color-solo {
background-color: #3498db;
}
.color-training {
.cert-color-training,
.color-inactive {
background-color: orange;
}
.color-none {
background-color: #6c757d;
}
.dark .dark\:color-certified {
background-color: rgba(46, 204, 113, 0.3);
}
.dark .dark\:color-cantrain {
background-color: rgba(238, 130, 238, 0.3);
}
.dark .dark\:color-solo {
background-color: rgba(52, 152, 219, 0.6);
}
.dark .dark\:color-training {
background-color: rgba(255, 165, 0, 0.5);
}
.dark .dark\:color-none {
background-color: rgba(108, 117, 125, 0.2);
.cert-color-none {
background-color: #d7dce0;
}
</style>
2 changes: 1 addition & 1 deletion src/components/ControllerHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="col-span-3">
<h1 class="text-2xl mb-0">
{{ props.controller.first_name }} {{ props.controller.last_name }} -
{{ props.controller.operating_initials }} ({{ props.controller.cid }}/{{ props.controller.rating }})
{{ props.controller.operating_initials }} ({{ props.controller.cid }}/{{ props.controller.rating }}) <span class="capitalize">[{{ controller.status }}]</span>
</h1>
<h2 class="text-sm mt-0 mb-1 font-normal">{{ getControllerTitle(props.controller) }}</h2>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/header/HeaderNavItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:href="props.href"
:auth="props.auth"
:roles="props.roles"
:controller="props.controller"
:rostered-controller="props.rosteredController"
:hide-unauthed="props.auth === true"
class="block text-[16px] font-semibold group-hover:text-colorado-yellow py-7 text-white group-hover:cursor-pointer"
@click="checkClick()"
Expand All @@ -22,7 +22,7 @@
:href="link.href"
:auth="link.auth"
:roles="link.roles"
:controller="link.controller"
:rostered-controller="link.rosteredController"
:hide-unauthed="link.auth === true"
class="block text-heading-light dark:text-white-deep font-semibold py-1 px-4 hover:text-primary dark:hover:text-colorado-yellow hover:bg-gray-light dark:hover:bg-body"
>
Expand Down
4 changes: 2 additions & 2 deletions src/components/header/OffcanvasMenuItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:href="props.href"
:auth="props.auth"
:roles="props.roles"
:controller="props.controller"
:rostered-controller="props.rosteredController"
:hide-unauthed="props.auth === true"
class="font-semibold leading-9 capitalize text-heading-light dark:text-white dark:group-hover:text-colorado-yellow group-hover:text-colorado-red"
@click="checkClick()"
Expand All @@ -28,7 +28,7 @@
:to="sl.to"
:href="sl.href"
:auth="sl.auth"
:controller="sl.controller"
:rostered-controller="sl.rosteredController"
:roles="sl.roles"
:hide-unauthed="sl.auth === true"
>
Expand Down
6 changes: 6 additions & 0 deletions src/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ const Links: Link[] = [
title: "Facility",
to: "#",
sublinks: [
{
to: { name: "Certifications" },
title: "Certifications",
auth: true,
roles: ["atm", "datm", "ta", "wm"],
},
{
to: { name: "Staff" },
title: "Staff",
Expand Down
15 changes: 14 additions & 1 deletion src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import fac from "@/facility";
import Home from "@/views/pages/HomePage.vue";
import { nextTick } from "vue";
import useUserStore from "@/stores/users";
import { inGroup } from "@/utils/auth";

declare module "vue-router" {
interface RouteMeta {
requiresAuth?: boolean;
requiresRole?: string[] | string;
requiresGroup?: string[] | string;
cidOverridesRole?: boolean;
}
}
Expand Down Expand Up @@ -58,6 +60,11 @@ const routes = [
name: "Roster",
component: () => import("@/views/pages/RosterPage.vue"),
},
{
path: "/certifications",
name: "Certifications",
component: () => import("@/views/pages/Certifications.vue"),
},
{
path: "/roster/inactive",
name: "InactiveRoster",
Expand Down Expand Up @@ -188,7 +195,7 @@ const check: NavigationGuard = (to, from, next): void => {
}

if (requiresAuth && userStore.user) {
const { requiresRole, cidOverridesRole } = to.meta;
const { requiresRole, requiresGroup, cidOverridesRole } = to.meta;
if (cidOverridesRole && to.params.cid === userStore.user.cid.toString()) {
next();
return;
Expand All @@ -199,6 +206,12 @@ const check: NavigationGuard = (to, from, next): void => {
return;
}
}
if (requiresGroup) {
if (!inGroup(requiresGroup)) {
next({ name: "Forbidden" });
return;
}
}
}

next();
Expand Down
15 changes: 14 additions & 1 deletion src/stores/roster.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { defineStore } from "pinia";

import { Controller } from "@/types";
import { CertificationItem, Controller } from "@/types";
import { ZDVAPI } from "@/utils/api";

interface RosterState {
controllers: Controller[];
certifications: CertificationItem[];
fetching: boolean;
hasFetched: boolean;
fetched: Date;
Expand All @@ -15,6 +16,7 @@ const useRosterStore = defineStore("roster", {
state: () =>
({
controllers: [],
certifications: [],
fetching: false,
hasFetched: false,
fetched: new Date(),
Expand All @@ -39,6 +41,9 @@ const useRosterStore = defineStore("roster", {
getInactiveRoster: (state) => {
return state.controllers.filter((c) => c.controller_type === "none");
},
getCertification: (state) => (name: string) => {
return state.certifications.find((c) => c.name === name);
},
},
actions: {
updateController(cid: number, controller: Controller) {
Expand All @@ -63,6 +68,14 @@ const useRosterStore = defineStore("roster", {
this.fetched = new Date();
}
},
async fetchCertifications() {
try {
const { data } = await ZDVAPI.get("/v1/certifications");
this.certifications = data;
} catch (e) {
this.certifications = [];
}
},
},
});

Expand Down
Loading

0 comments on commit 427efbc

Please sign in to comment.