From 7e0d00f2cd6fbe7a8037ac696933490e3e01d1a8 Mon Sep 17 00:00:00 2001
From: Raajheer1
Date: Sun, 27 Oct 2024 17:48:14 -0500
Subject: [PATCH] Events & Facilities
---
.github/workflows/upgrade.yaml | 43 +++++
src/components/Modal.vue | 29 ++++
src/components/event/EventPositions.vue | 100 ++++++++++++
src/components/event/PositionList.vue | 136 ++++++++++++++++
src/components/event/PositionTooltip.vue | 154 ++++++++++++++++++
src/components/profile/Notifications.vue | 17 +-
src/data/sidebar.ts | 12 +-
src/router/index.ts | 57 ++++++-
src/stores/event.ts | 129 +++++++++++++++
src/stores/facility.ts | 16 +-
src/stores/feedback.ts | 3 +-
src/stores/sidebar.ts | 118 ++++++++++++++
src/stores/user.ts | 29 +++-
src/types/index.d.ts | 62 ++++++-
src/utils/auth.ts | 37 +++++
src/views/Event.vue | 196 +++++++++++++++++++++++
src/views/Home.vue | 39 ++++-
src/views/Profile.vue | 2 +-
src/views/controllers/MyFeedback.vue | 2 +-
src/views/facility/Roster.vue | 157 ++++++++++++++++++
src/views/partials/Footer.vue | 1 +
src/views/partials/Sidebar.vue | 40 +++--
src/views/pilots/LeaveFeedback.vue | 9 +-
23 files changed, 1340 insertions(+), 48 deletions(-)
create mode 100644 .github/workflows/upgrade.yaml
create mode 100644 src/components/Modal.vue
create mode 100644 src/components/event/EventPositions.vue
create mode 100644 src/components/event/PositionList.vue
create mode 100644 src/components/event/PositionTooltip.vue
create mode 100644 src/stores/event.ts
create mode 100644 src/stores/sidebar.ts
create mode 100644 src/utils/auth.ts
create mode 100644 src/views/Event.vue
create mode 100644 src/views/facility/Roster.vue
diff --git a/.github/workflows/upgrade.yaml b/.github/workflows/upgrade.yaml
new file mode 100644
index 0000000..a76bae8
--- /dev/null
+++ b/.github/workflows/upgrade.yaml
@@ -0,0 +1,43 @@
+name: Upgrade Dependencies
+
+on:
+ schedule:
+ - cron: '1 0 * * 0'
+ workflow_dispatch:
+
+jobs:
+ yarn-upgrade:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ cache: 'yarn'
+ - name: Install Yarn
+ run: npm install -g yarn
+ - name: Setup git and create branch
+ env:
+ GITHUB_TOKEN: ${{ secrets.G_TOKEN }}
+ run: |
+ git config --global user.name "Raajheer1"
+ git config --global user.email "raaj.patel229@gmail.com"
+
+ git checkout -b chore/upgrade-dependencies-$(date +%Y-%m-%d)
+ git push --set-upstream origin chore/upgrade-dependencies-$(date +%Y-%m-%d)
+ - name: Upgrade dependencies
+ run: yarn upgrade --latest
+ - name: Lint fix
+ run: yarn lint:fix
+ - name: Commit and push changes
+ env:
+ GITHUB_TOKEN: ${{ secrets.G_TOKEN }}
+ run: |
+ git add .
+ git commit -m "chore: upgrade dependencies"
+ git push
+ - name: Create PR
+ env:
+ GITHUB_TOKEN: ${{ secrets.G_TOKEN }}
+ run: |
+ gh pr create -t "Chore: Upgrade Dependencies" -B master -H chore/upgrade-dependencies-$(date +%Y-%m-%d) --body "Weekly automated PR to upgrade dependencies using \`yarn upgrade --latest\`. Please verify the changes and merge."
diff --git a/src/components/Modal.vue b/src/components/Modal.vue
new file mode 100644
index 0000000..356fad1
--- /dev/null
+++ b/src/components/Modal.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
{{ props.title }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/event/EventPositions.vue b/src/components/event/EventPositions.vue
new file mode 100644
index 0000000..68d176b
--- /dev/null
+++ b/src/components/event/EventPositions.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
diff --git a/src/components/event/PositionList.vue b/src/components/event/PositionList.vue
new file mode 100644
index 0000000..ace3773
--- /dev/null
+++ b/src/components/event/PositionList.vue
@@ -0,0 +1,136 @@
+
+
+
No Positions Posted
+
+
{{ position.position }}
+
+
+
+ Vacant (Requested)
+ Vacant
+
+ {{ position.assignee_name }}
+
+
+ {{ position.assignee_name }} (Requested)
+
+ {{ position.assignee_name }}
+
+
+ Vacant (Requested)
+ Vacant
+
+ {{ position.secondary_assignee_name }}
+
+
+ {{ position.secondary_assignee_name }} (Requested)
+
+ {{ position.secondary_assignee_name }}
+
+
+
+
+
+
+ Vacant (Requested)
+ Vacant
+
+ {{ position.assignee_name }}
+
+
+ {{ position.assignee_name }} (Requested)
+
+ {{ position.assignee_name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/event/PositionTooltip.vue b/src/components/event/PositionTooltip.vue
new file mode 100644
index 0000000..2fb8459
--- /dev/null
+++ b/src/components/event/PositionTooltip.vue
@@ -0,0 +1,154 @@
+
+
+
+
+
Shift {{ props.shift }}
+
+ {{ position.assignee_name }}
+
+ Unassign
+
+
+
Vacant
+
+ {{ props.position.secondary_assignee_name }}
+
+ Unassign
+
+
+
Vacant
+
+
+
+
+
+
+ {{ signup.name }}
+
+ {{ signup.name }}
+
+
+ No Signups
+
+
+
+
+
+
+
+
diff --git a/src/components/profile/Notifications.vue b/src/components/profile/Notifications.vue
index c58879d..3563f68 100644
--- a/src/components/profile/Notifications.vue
+++ b/src/components/profile/Notifications.vue
@@ -102,14 +102,23 @@ import Primary from "@/components/buttons/Primary.vue";
import Spinner from "@/components/animations/Spinner.vue";
const userStore = useUserStore();
-const settings = ref();
+const settings = ref({
+ discord: false,
+ training: false,
+ events: false,
+ feedback: false,
+ email: false,
+});
onMounted(async () => {
- settings.value = await userStore.fetchNotificationSettings(userStore.self.cid);
+ const resp = await userStore.fetchNotificationSettings(userStore.self!.cid);
+ if (resp) {
+ settings.value = resp;
+ }
});
-function updateSettings(): null {
- userStore.updateNotificationSettings(userStore.self.cid, settings.value);
+function updateSettings(): void {
+ userStore.updateNotificationSettings(userStore.self!.cid, settings.value);
}
function setDiscord(state: boolean): void {
diff --git a/src/data/sidebar.ts b/src/data/sidebar.ts
index 29a7e04..78a9e96 100644
--- a/src/data/sidebar.ts
+++ b/src/data/sidebar.ts
@@ -6,10 +6,15 @@ const SidebarLinks: Link[] = [
icon: "fa-solid fa-home",
to: { name: "Home" },
},
+ {
+ title: "My Profile",
+ icon: "fa-solid fa-user",
+ to: { name: "Profile" },
+ },
{
title: "My Flights",
icon: "fa-solid fa-plane",
- // to: { name: "My Flights" },
+ to: { name: "Home" },
separator: true,
separatorTitle: "Pilots",
},
@@ -77,11 +82,6 @@ const SidebarLinks: Link[] = [
// to: { name: "Support" },
// separator: true,
// },
- {
- title: "My Profile",
- icon: "fa-solid fa-user",
- to: { name: "Profile" },
- },
];
export default SidebarLinks;
diff --git a/src/router/index.ts b/src/router/index.ts
index e2d3dc9..1eebf5f 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -1,6 +1,7 @@
import { createRouter, createWebHistory, NavigationGuard, Router } from "vue-router";
import useUserStore from "@/stores/user";
import apiUrl from "@/utils/api";
+import useSidebarStore from "@/stores/sidebar";
declare module "vue-router" {
interface RouteMeta {
@@ -18,6 +19,14 @@ const routes = [
requiresAuth: true,
},
},
+ {
+ path: "/events/:facility_id/:id",
+ name: "Event",
+ component: () => import("@/views/Event.vue"),
+ meta: {
+ requiresAuth: true,
+ },
+ },
{
path: "/pilots",
children: [
@@ -49,6 +58,49 @@ const routes = [
requiresAuth: true,
},
},
+ {
+ path: "/:facility_id",
+ children: [
+ {
+ path: "management",
+ name: "Management",
+ component: () => import("@/views/controllers/Facility.vue"),
+ },
+ {
+ path: "roster",
+ name: "Roster",
+ component: () => import("@/views/facility/Roster.vue"),
+ },
+ {
+ path: "engineering",
+ name: "Engineering",
+ component: () => import("@/views/controllers/MyFeedback.vue"),
+ },
+ {
+ path: "web-config",
+ name: "Web Config",
+ component: () => import("@/views/controllers/MyFeedback.vue"),
+ },
+ {
+ path: "events",
+ name: "ARTCC Events",
+ component: () => import("@/views/controllers/MyFeedback.vue"),
+ },
+ {
+ path: "training/notes",
+ name: "Training Notes",
+ component: () => import("@/views/controllers/MyFeedback.vue"),
+ },
+ {
+ path: "training/calendar",
+ name: "Training Calendar",
+ component: () => import("@/views/controllers/MyFeedback.vue"),
+ },
+ ],
+ meta: {
+ requiresAuth: true,
+ },
+ },
{
path: "/profile",
name: "Profile",
@@ -94,12 +146,15 @@ const check: NavigationGuard = (to, from, next): void => {
next();
};
-router.beforeEach((to, from, next) => {
+router.beforeEach(async (to, from, next) => {
const userStore = useUserStore();
+ const sidebarStore = useSidebarStore();
if (!userStore.hasFetched) {
if (userStore.loading === null || userStore.loading === undefined) {
console.log("fetching user");
userStore.loading = userStore.fetchUser();
+ await userStore.loading;
+ sidebarStore.updateSidebar(userStore.roles!);
}
userStore.loading.then(() => {
check(to, from, next);
diff --git a/src/stores/event.ts b/src/stores/event.ts
new file mode 100644
index 0000000..c13a137
--- /dev/null
+++ b/src/stores/event.ts
@@ -0,0 +1,129 @@
+import { defineStore } from "pinia";
+import { API } from "@/utils/api";
+import { Event, EventPosition } from "@/types";
+import useUserStore from "@/stores/user";
+
+interface EventState {
+ upcoming_events: Event[] | null;
+ error: string | null;
+ fetching: boolean;
+}
+
+const useEventStore = defineStore({
+ id: "event",
+ state: () =>
+ ({
+ upcoming_events: null,
+ error: null,
+ fetching: false,
+ }) as EventState,
+ getters: {
+ upcoming: (state) => state.upcoming_events,
+ },
+ actions: {
+ async fetchUpcomingEvents(): Promise {
+ this.fetching = true;
+ try {
+ const { data } = await API.get("/v3/events");
+ this.upcoming_events = data;
+ } catch (e) {
+ this.upcoming_events = null;
+ } finally {
+ this.fetching = false;
+ }
+ },
+ async fetchEvents(facility: string): Promise {
+ try {
+ const { data } = await API.get(`/v3/facility/${facility}/events`);
+ return data;
+ } catch (e) {
+ return [];
+ }
+ },
+ async fetchEvent(facility: string, id: number): Promise {
+ try {
+ const { data } = await API.get(`/v3/facility/${facility}/events/${id}`);
+ return data;
+ } catch (e) {
+ return null;
+ }
+ },
+ async fetchEventPositions(facility: string, id: number): Promise {
+ try {
+ const { data } = await API.get(`/v3/facility/${facility}/events/${id}/positions`);
+ return data;
+ } catch (e) {
+ return [];
+ }
+ },
+ async createEventSignup(
+ facility_id: string,
+ event_id: number,
+ position_id: number,
+ shift: number
+ ): Promise {
+ try {
+ const userStore = useUserStore();
+ await API.post(`/v3/facility/${facility_id}/events/${event_id}/signups`, {
+ cid: userStore.self!.cid,
+ position_id,
+ shift,
+ });
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ async deleteEventSignup(facility_id: string, event_id: number, event_signup_id: number): Promise {
+ try {
+ await API.delete(`/v3/facility/${facility_id}/events/${event_id}/signups/${event_signup_id}`);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ async assignEventPosition(
+ facility_id: string,
+ event_id: number,
+ position_id: number,
+ shift: number,
+ cid: number
+ ): Promise {
+ try {
+ const body = {};
+ if (shift === 1) {
+ body.assignee = cid;
+ }
+ if (shift === 2) {
+ body.secondary_assignee = cid;
+ }
+ await API.patch(`/v3/facility/${facility_id}/events/${event_id}/positions/${position_id}`, body);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ async unassignEventPosition(
+ facility_id: string,
+ event_id: number,
+ position_id: number,
+ shift: number
+ ): Promise {
+ try {
+ const body = {};
+ if (shift === 1) {
+ body.assignee = 0;
+ }
+ if (shift === 2) {
+ body.secondary_assignee = 0;
+ }
+ await API.patch(`/v3/facility/${facility_id}/events/${event_id}/positions/${position_id}`, body);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ },
+});
+
+export default useEventStore;
diff --git a/src/stores/facility.ts b/src/stores/facility.ts
index 5333350..3d2544a 100644
--- a/src/stores/facility.ts
+++ b/src/stores/facility.ts
@@ -1,6 +1,6 @@
import { defineStore } from "pinia";
import { API } from "@/utils/api";
-import { Facility, Roster } from "@/types";
+import { Facility, Roster, RosterRequest } from "@/types";
interface FacilityState {
facilities: Facility[];
@@ -18,6 +18,7 @@ const useFacilityStore = defineStore({
error: null,
fetching: false,
hasFetched: false,
+ loading: null,
}) as FacilityState,
getters: {
getFacility: (state) => (id: string) => {
@@ -49,6 +50,19 @@ const useFacilityStore = defineStore({
this.fetching = false;
}
},
+ async fetchRosterRequests(facility: string, status: string): Promise {
+ this.fetching = true;
+ try {
+ const { data } = await API.get(`/v3/facility/${facility}/roster-request`, {
+ params: { status },
+ });
+ return data;
+ } catch (e) {
+ return [];
+ } finally {
+ this.fetching = false;
+ }
+ },
},
});
diff --git a/src/stores/feedback.ts b/src/stores/feedback.ts
index 396a6e3..531d199 100644
--- a/src/stores/feedback.ts
+++ b/src/stores/feedback.ts
@@ -3,7 +3,7 @@ import { API } from "@/utils/api";
import { Feedback, FeedbackRequest } from "@/types";
interface FeedbackState {
- myFeedback: Feedback;
+ myFeedback: Feedback[];
error: string | null;
fetching: boolean;
hasFetched: boolean;
@@ -18,6 +18,7 @@ const useFeedbackStore = defineStore({
error: null,
fetching: false,
hasFetched: false,
+ loading: null,
}) as FeedbackState,
getters: {},
actions: {
diff --git a/src/stores/sidebar.ts b/src/stores/sidebar.ts
new file mode 100644
index 0000000..6664ff7
--- /dev/null
+++ b/src/stores/sidebar.ts
@@ -0,0 +1,118 @@
+import { defineStore } from "pinia";
+import { Link, Role } from "@/types";
+import SidebarLinks from "@/data/sidebar";
+
+interface SidebarState {
+ sidebar: Link[];
+ fetched: boolean;
+ loading: Promise | null;
+}
+
+interface FacRoleMap {
+ [facility_id: string]: string[];
+}
+
+const useSidebarStore = defineStore({
+ id: "sidebar",
+ state: () =>
+ ({
+ sidebar: SidebarLinks,
+ fetched: false,
+ }) as SidebarState,
+ getters: {
+ links: (state) => state.sidebar,
+ },
+ actions: {
+ updateSidebar(roles: Role[]): void {
+ if (!this.fetched) {
+ const facilityRolesMap = roles.reduce((acc: FacRoleMap, { facility_id, role_id }) => {
+ if (!acc[facility_id]) {
+ acc[facility_id] = [];
+ }
+ acc[facility_id].push(role_id);
+ return acc;
+ }, {});
+
+ facilityRolesMap.ZDV = ["ATM"];
+
+ for (const facility in facilityRolesMap) {
+ // If ZHQ
+ if (facility === "ZHQ") {
+ this.sidebar.push({
+ title: "User Management",
+ icon: "fa-solid fa-users",
+ to: { name: "Home" },
+ separator: true,
+ separatorTitle: "VATUSA",
+ });
+ } else {
+ const facRoles: string[] = facilityRolesMap[facility];
+ const temp: Link[] = [];
+ if (facRoles.includes("ATM") || facRoles.includes("DATM") || facRoles.includes("TA")) {
+ temp.push(
+ {
+ title: "Management",
+ icon: "fa-solid fa-gear",
+ to: { name: "Management", params: { facility_id: facility } },
+ },
+ {
+ title: "Roster",
+ icon: "fa-solid fa-users",
+ to: { name: "Roster", params: { facility_id: facility } },
+ }
+ );
+ } else {
+ if (facRoles.includes("FE") || facRoles.includes("AFE")) {
+ temp.push({
+ title: "Engineering",
+ icon: "fa-solid fa-tools",
+ to: { name: "Engineering", params: { facility_id: facility } },
+ });
+ }
+ if (facRoles.includes("WM") || facRoles.includes("AWM")) {
+ temp.push({
+ title: "Web Config",
+ icon: "fa-solid fa-cogs",
+ to: { name: "Web Config", params: { facility_id: facility } },
+ });
+ }
+ if (facRoles.includes("EC") || facRoles.includes("AEC")) {
+ temp.push({
+ title: "Events",
+ icon: "fa-solid fa-calendar",
+ to: { name: "ARTCC Events", params: { facility_id: facility } },
+ });
+ }
+
+ if (facRoles.includes("MTR") || facRoles.includes("INS")) {
+ temp.push({
+ title: "Training Actions",
+ icon: "fa-solid fa-chalkboard-user",
+ subLinks: [
+ {
+ title: "Calendar",
+ icon: "fa-solid fa-calendar",
+ to: { name: "Training Calendar", params: { facility_id: facility } },
+ },
+ {
+ title: "Notes",
+ icon: "fa-solid fa-book",
+ to: { name: "Training-Notes", params: { facility_id: facility } },
+ },
+ ],
+ showSubLinks: true,
+ });
+ }
+ }
+
+ temp[0].separator = true;
+ temp[0].separatorTitle = facility;
+ this.sidebar.push(...temp);
+ }
+ }
+ }
+ },
+ },
+});
+
+export default useSidebarStore;
diff --git a/src/stores/user.ts b/src/stores/user.ts
index ab64eb1..42c9f6d 100644
--- a/src/stores/user.ts
+++ b/src/stores/user.ts
@@ -1,11 +1,12 @@
import { defineStore } from "pinia";
import { API } from "@/utils/api";
-import { ActionLog, NotificationSettings, User } from "@/types";
+import { ActionLog, NotificationSettings, User, Role } from "@/types";
import { getControllerRating, getPilotRating } from "@/utils/rating";
import { notify } from "notiwind";
interface UserState {
user: User | null;
+ roles: Role[] | null;
error: string | null;
fetching: boolean;
hasFetched: boolean;
@@ -49,6 +50,9 @@ const useUserStore = defineStore({
this.fetching = false;
this.hasFetched = true;
+ await this.fetchRoles();
+ await this.fetchRosters();
+
if (this.isLoggedIn) {
notify(
{
@@ -61,16 +65,14 @@ const useUserStore = defineStore({
}
}
},
- async patchUser(cid, body): Promise {
+ async patchUser(cid: number, body: { [p: string]: string }): Promise {
try {
const { data } = await API.patch(`/v3/user/${cid}`, body);
const storeRoster = this.user?.rosters;
this.user = data;
- this.user.rosters = storeRoster;
- if (this.user?.controller_rating) {
+ if (this.user) {
+ this.user.rosters = storeRoster;
this.user.controller_rating_string = getControllerRating(this.user.controller_rating);
- }
- if (this.user?.pilot_rating) {
this.user.pilot_rating_string = getPilotRating(this.user.pilot_rating);
}
notify(
@@ -92,6 +94,15 @@ const useUserStore = defineStore({
);
}
},
+ async fetchRoles(): Promise {
+ if (!this.isLoggedIn) return;
+ try {
+ const { data } = await API.get(`/v3/user/${this.user?.cid}/roles`);
+ this.roles = data;
+ } catch (e) {
+ this.roles = [];
+ }
+ },
async fetchRosters(): Promise {
if (!this.isLoggedIn) return;
try {
@@ -112,7 +123,7 @@ const useUserStore = defineStore({
return [];
}
},
- async fetchNotificationSettings(cid: number): Promise {
+ async fetchNotificationSettings(cid: number): Promise {
try {
const { data } = await API.get(`/v3/user/${cid}/notification-settings`);
return data;
@@ -153,7 +164,9 @@ const useUserStore = defineStore({
},
4000
);
- this.user!.discord_id = null;
+ if (this.user) {
+ this.user.discord_id = "";
+ }
}
return null;
} catch (e) {
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index 3d3f5ba..64ac83f 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -45,7 +45,7 @@ export interface Feedback {
id: number;
controller_cid: number;
facility: string;
- position: number;
+ position: string;
comment: string;
feedback: string;
rating: string;
@@ -79,8 +79,20 @@ export interface Roster {
deleted_at?: string;
}
+export interface RosterRequest {
+ id: number;
+ cid: number;
+ first_name: string;
+ last_name: string;
+ request_type: string;
+ status: string;
+ reason: string;
+ created_at: string;
+ updated_at: string;
+}
+
export interface Role {
- role: string;
+ role_id: string;
facility_id: string;
roster_id: number;
created_at: string;
@@ -109,3 +121,49 @@ export interface NotificationSettings {
feedback: boolean;
training: boolean;
}
+
+export interface Event {
+ id: number;
+ title: string;
+ description: string;
+ banner_url: string;
+ start_date: string;
+ end_date: string;
+ facilities: string[];
+ fields: string[];
+ positions: EventPosition[];
+ routing: EventRouting[];
+}
+
+export interface EventPosition {
+ id: number;
+ event_id: number;
+ position: string;
+ facility: string;
+ assignee: number;
+ assignee_name: string;
+ secondary_assignee: number;
+ secondary_assignee_name: string;
+ shifts: boolean;
+ signups: EventSignups[];
+}
+
+export interface EventSignups {
+ id: number;
+ event_id: number;
+ position_id: number;
+ cid: number;
+ name: string;
+ shift: number;
+ created_at: string;
+}
+
+export interface EventRouting {
+ id: number;
+ event_id: number;
+ origin: string;
+ destination: string;
+ route: string;
+ notes: string;
+ created_at: string;
+}
diff --git a/src/utils/auth.ts b/src/utils/auth.ts
new file mode 100644
index 0000000..158878a
--- /dev/null
+++ b/src/utils/auth.ts
@@ -0,0 +1,37 @@
+import useUserStore from "@/stores/user";
+
+const hasRole = (role?: string[] | string, facility?: string): boolean => {
+ const userStore = useUserStore();
+
+ if (userStore.user == null) {
+ return false;
+ }
+
+ if (userStore.roles == null) {
+ return false;
+ }
+
+ if (role === undefined) {
+ return true;
+ }
+
+ let isVATUSA = false;
+ userStore.roles.forEach((r): void => {
+ if (r.role_id.includes("USA")) {
+ isVATUSA = true;
+ }
+ });
+ if (isVATUSA) {
+ return true;
+ }
+
+ const tempRole: string[] = typeof role === "string" ? [role] : role;
+
+ if (facility === undefined) {
+ return userStore.roles.some((r) => tempRole.includes(r.role_id));
+ }
+
+ return userStore.roles.some((r) => tempRole.includes(r.role_id) && r.facility_id === facility);
+};
+
+export default hasRole;
diff --git a/src/views/Event.vue b/src/views/Event.vue
new file mode 100644
index 0000000..c13a55a
--- /dev/null
+++ b/src/views/Event.vue
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
{{ event.title }}
+
+ ARTCCs: {{ event.facilities.toString() }}
+
+
+ Fields: {{ event.fields.toString() }}
+
+
+ Starts:
+ Completed
+ In progress
+
+ {{ timeTill(new Date(event.start_date)) }}
+
+
+ {{ new Date(event.start_date).toLocaleDateString() }} -
+ {{ new Date(event.start_date).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }}
+
+
+
+ Ends:
+ Completed
+
+ {{ timeTill(new Date(event.end_date)) }}
+
+
+ {{ new Date(event.end_date).toLocaleDateString() }} -
+ {{ new Date(event.end_date).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }}
+
+
+
+ {{ event.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ route.origin }} → {{ route.destination }}
+
+
Route: {{ route.route }}
+
Notes: {{ route.notes }}
+
+
+
+ No standard routes have been added for this event.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The page you were looking for was not found.
+ If this is a recurring issue please report it to VATUSA6.
+
+
+
+
+
{{ assignPosition?.position || "err" }}
+ Coming Soon
+
+ Error fetching position, please report this to staff.
+
+
+
+
+ Loading...
+
+
+
+
+
+
diff --git a/src/views/Home.vue b/src/views/Home.vue
index 59e3116..1247a54 100644
--- a/src/views/Home.vue
+++ b/src/views/Home.vue
@@ -7,16 +7,38 @@
-
To Do
+
To Do
No pending tasks
-
Upcoming Events
-
No pending tasks
+
Upcoming Events
+
+
+
+
+
+ {{ event.title }} ({{ event.fields.toString() }})
+
+
+ {{ new Date(event.start_date).toLocaleDateString() }}
+ {{ new Date(event.start_date).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }}
+ -
+ {{ new Date(event.end_date).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }}
+
+
+
+
+
+
-
Help
-
No pending tasks
+
Help
+
Frequently Asked Questions
+
Created a Ticket
@@ -28,6 +50,13 @@ import Page from "@/components/Page.vue";
import Card from "@/components/Card.vue";
import useUserStore from "@/stores/user";
+import useEventStore from "@/stores/event";
+import { onMounted } from "vue";
const userStore = useUserStore();
+const eventStore = useEventStore();
+
+onMounted(() => {
+ eventStore.fetchUpcomingEvents();
+});
diff --git a/src/views/Profile.vue b/src/views/Profile.vue
index ff9c06c..59af065 100644
--- a/src/views/Profile.vue
+++ b/src/views/Profile.vue
@@ -75,7 +75,7 @@ watch(currentHashTab, (newTab) => {
// Initial check on component mount
onMounted(() => {
selectedTab.value = currentHashTab.value;
- userStore.fetchRosters();
+ if (!userStore.hasFetchedRosters) userStore.fetchRosters();
// Check if an account was just linked
const query = route.query;
diff --git a/src/views/controllers/MyFeedback.vue b/src/views/controllers/MyFeedback.vue
index 471d847..dcd1748 100644
--- a/src/views/controllers/MyFeedback.vue
+++ b/src/views/controllers/MyFeedback.vue
@@ -117,7 +117,7 @@ const feedbackRating = computed(() => {
onMounted(() => {
if (!feedbackStore.hasFetched) {
- feedbackStore.fetchFeedback(userStore.self.cid);
+ feedbackStore.fetchFeedback(userStore.self!.cid);
}
});
diff --git a/src/views/facility/Roster.vue b/src/views/facility/Roster.vue
new file mode 100644
index 0000000..0ba81f6
--- /dev/null
+++ b/src/views/facility/Roster.vue
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+ CID
+ Name
+ Type
+ Reason
+ Requested Date
+ Actions
+
+
+
+
+
+ {{ rosterReq.cid }}
+
+ {{ rosterReq.first_name }} {{ rosterReq.last_name }}
+
+ {{ rosterReq.request_type }}
+
+
+ {{ rosterReq.reason }}
+
+
+ {{ new Date(rosterReq.created_at).toLocaleDateString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
No Feedback Found
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Home Controllers: {{ activeRoster.filter((roster) => roster.home).length }}
+
+
+ Visiting Controllers: {{ activeRoster.filter((roster) => roster.visiting).length }}
+
+
+
+
+
+
+
+
+ CID
+ Name
+ OIs
+ Type
+ Join Date
+ Actions
+
+
+
+
+
+ {{ roster.cid }}
+
+ {{ roster.first_name }} {{ roster.last_name }}
+
+ {{ roster.operating_initials }}
+
+
+ {{ roster.home ? "Home" : "" }}
+ {{ roster.visiting ? "Visiting" : "" }}
+
+
+ {{ new Date(roster.created_at).toLocaleDateString() }}
+
+
+ Edit
+
+
+
+
+
No Feedback Found
+
+
+
+
+
+
+
+
+
diff --git a/src/views/partials/Footer.vue b/src/views/partials/Footer.vue
index 6cb0909..4c2517d 100644
--- a/src/views/partials/Footer.vue
+++ b/src/views/partials/Footer.vue
@@ -9,6 +9,7 @@
Any and all content on this website are for use with the Virtual Air Traffic Simulation Network (VATSIM) and may
not be used for real-world navigation or aviation purposes and doing so could be a violation of federal law.
+ All times on the website unless otherwise noted with "Z" are in your local time.
© 2016-{{ new Date().getFullYear() }} VATUSA Inc. | 501(c)(3) Nonprofit Organization | All Rights Reserved.
diff --git a/src/views/partials/Sidebar.vue b/src/views/partials/Sidebar.vue
index 42f3110..ce96b66 100644
--- a/src/views/partials/Sidebar.vue
+++ b/src/views/partials/Sidebar.vue
@@ -21,31 +21,39 @@
{{ link.title }}
-
+
+
-
-
-
-
-
-
{{ link.title }}
-
-
-
+
+
+
+
+
+
+
{{ link.title }}
+
+
+
-
{{ subLink.title }}
+
+
+
+
+
{{ subLink.title }}
+
diff --git a/src/views/pilots/LeaveFeedback.vue b/src/views/pilots/LeaveFeedback.vue
index 0274f71..56c5bb1 100644
--- a/src/views/pilots/LeaveFeedback.vue
+++ b/src/views/pilots/LeaveFeedback.vue
@@ -186,6 +186,11 @@ const userStore = useUserStore();
const facilityStore = useFacilityStore();
const feedback = ref({
+ callsign: "",
+ controller_cid: 0,
+ feedback: "",
+ position: "",
+ rating: "",
pilot_cid: userStore.self.cid,
comment: "",
status: "pending",
@@ -221,7 +226,7 @@ const closeFacilityDropdown = (): void => {
// Controller List
const controllerDropdown = ref(false);
-const controllerList = ref([]);
+const controllerList = ref([]);
watchEffect(() => {
if (!isValidFacility.value) {
@@ -300,7 +305,7 @@ const submitFeedback = (): void => {
return;
}
- feedbackStore.submitFeedback(facility, feedback);
+ feedbackStore.submitFeedback(facility.value, feedback.value);
};
onMounted(() => {