Initial commit

This commit is contained in:
2026-06-24 23:47:55 -07:00
commit d134b480a0
297 changed files with 30726 additions and 0 deletions
@@ -0,0 +1,40 @@
<template>
<v-app-bar
id="top"
elevation="10"
height="60"
class="main-head pl-2"
>
<div class="mr-3">
<AppLogo />
</div>
<v-spacer />
<v-btn
v-if="isSystemAdmin"
icon
variant="text"
class="custom-hover-primary mr-2"
size="small"
title="AI Assistant"
@click="$emit('toggle-ai-panel')"
>
<v-icon size="28">mdi-robot-outline</v-icon>
</v-btn>
<ProfileMenu />
</v-app-bar>
</template>
<script setup lang="ts">
import useCurrentUser from "@/use/use-current-user"
import AppLogo from "@/components/common/AppLogo.vue"
import ProfileMenu from "@/components/layout/ProfileMenu.vue"
defineEmits<{
"toggle-ai-panel": []
}>()
const { isSystemAdmin } = useCurrentUser<true>()
</script>
@@ -0,0 +1,70 @@
<template>
<v-navigation-drawer
v-model="sidebarDrawer"
left
elevation="0"
rail-width="75"
app
class="leftSidebar"
:rail="sidebarMini"
expand-on-hover
width="256"
>
<div class="pa-2">
<v-card
v-if="currentUser"
variant="outlined"
style="overflow: hidden"
>
<v-card-text
v-if="!sidebarMini"
class="pa-2"
>
<div class="font-weight-bold">{{ currentUser.displayName }}</div>
</v-card-text>
</v-card>
</div>
<v-list class="d-none py-3 px-4">
<v-list-subheader
class="text-uppercase text-13 font-weight-semibold textPrimary d-flex align-items-center"
>
<span class="mini-icon">
<IconDotsCircleHorizontal
size="16"
stroke-width="1.5"
class="iconClass"
/>
</span>
<span class="mini-text">Aircraft</span>
</v-list-subheader>
<v-list-item
v-scroll-to="{ el: '#top' }"
:to="{}"
rounded="pill"
class="mb-1 px-3"
>
<template #prepend>
<IconPointFilled
size="24"
class="dot mini-icon"
/>
</template>
<span class="mini-text"> Y-CCAA</span>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script setup lang="ts">
import { IconDotsCircleHorizontal, IconPointFilled } from "@tabler/icons-vue"
import useCurrentUser from "@/use/use-current-user"
import useInterface from "@/use/use-interface"
const { sidebarDrawer, sidebarMini } = useInterface()
const { currentUser } = useCurrentUser()
</script>
@@ -0,0 +1,69 @@
<template>
<v-breadcrumbs
class="flex-wrap"
:items="breadcrumbsWithExactTrueByDefault"
color=""
>
<template #title="{ item }">
<transition
name="breadcrumb-title-fade"
mode="out-in"
>
<v-progress-circular
v-if="isEmpty(item.title)"
key="title-loader"
size="16"
color="secondary"
width="1"
indeterminate
/>
<span
v-else
key="title-text"
>
{{ item.title }}
</span>
</transition>
</template>
<template #divider>
<v-icon>mdi-chevron-right</v-icon>
</template>
</v-breadcrumbs>
</template>
<script lang="ts">
export { type Breadcrumb } from "@/use/use-breadcrumbs"
</script>
<script lang="ts" setup>
import { computed } from "vue"
import { isEmpty } from "lodash"
import { type Breadcrumb } from "@/use/use-breadcrumbs"
const props = defineProps<{
items: Breadcrumb[]
}>()
// Changes https://vuetifyjs.com/en/components/breadcrumbs/ default behavior.
// By default v-breadcrumbs will disable all crumbs up to the current page in a nested paths.
// You can prevent this behavior by using exact: true on each applicable breadcrumb in the items array.
const breadcrumbsWithExactTrueByDefault = computed(() =>
props.items.map((item) => ({
...item,
title: item.title ?? "",
exact: item.exact ?? true,
}))
)
</script>
<style scoped>
.breadcrumb-title-fade-enter-active,
.breadcrumb-title-fade-leave-active {
transition: opacity 0.18s ease;
}
.breadcrumb-title-fade-enter-from,
.breadcrumb-title-fade-leave-to {
opacity: 0;
}
</style>
+160
View File
@@ -0,0 +1,160 @@
<template>
<v-menu
v-model="showMenu"
:close-on-content-click="false"
class="profile_popup"
>
<template #activator="{ props }">
<v-btn
class="ml-2"
variant="tonal"
v-bind="props"
icon
:text="currentUserInitials"
@click="showMenu = true"
/>
</template>
<v-sheet
rounded="md"
width="360"
elevation="11"
>
<div class="px-8 pt-3">
<div class="d-flex align-center mt-4 pb-6">
<div>
<h6 class="text-h6 mb-n1">{{ currentUser.displayName }}</h6>
<div class="d-flex align-center mt-2">
<IconMail
:size="18"
:stroke-width="1.5"
/>
<span class="text-subtitle-1 font-weight-regular textSecondary ml-2">{{
currentUser.email
}}</span>
</div>
</div>
</div>
<v-divider></v-divider>
</div>
<v-list
class="py-0 theme-list"
lines="two"
>
<v-list-item
class="py-4 px-8 custom-text-primary"
:to="{
name: 'ProfilePage',
}"
@click="closeMenu"
>
<template #prepend>
<v-avatar color="info">
<IconUserCircle></IconUserCircle>
</v-avatar>
</template>
<div>
<h6 class="text-subtitle-1 font-weight-semibold mb-2 custom-title">My Profile</h6>
</div>
<p class="text-subtitle-1 font-weight-regular textSecondary">Manage your information</p>
</v-list-item>
<v-list-item
v-if="isSystemAdmin"
class="py-4 px-8 custom-text-primary"
:to="{
name: 'administration/AdministrationDashboardPage',
}"
@click="closeMenu"
>
<template #prepend>
<v-avatar color="warning">
<IconSettings></IconSettings>
</v-avatar>
</template>
<div>
<h6 class="text-subtitle-1 font-weight-semibold mb-2 custom-title">
ROTYR System Administration
</h6>
</div>
<p class="text-subtitle-1 font-weight-regular textSecondary">Manage this application</p>
</v-list-item>
<v-list-item
v-if="isSystemAdmin"
class="py-4 px-8 custom-text-primary"
:to="{
name: 'StatusPage',
}"
@click="closeMenu"
>
<template #prepend>
<v-avatar color="secondary">
<IconClock />
</v-avatar>
</template>
<div>
<h6 class="text-subtitle-1 font-weight-semibold mb-2 custom-title">Version</h6>
</div>
<p class="text-subtitle-1 font-weight-regular textSecondary">
{{ releaseTag || "2024.08.29" }}
</p>
</v-list-item>
</v-list>
<div class="pt-4 pb-6 px-8 text-center">
<v-btn
variant="outlined"
block
@click="signOut"
>Logout</v-btn
>
</div>
</v-sheet>
</v-menu>
</template>
<script setup lang="ts">
import { computed, ref } from "vue"
import { IconClock, IconMail, IconSettings, IconUserCircle } from "@tabler/icons-vue"
import { useAuth0 } from "@auth0/auth0-vue"
import { isEmpty, isNil } from "lodash"
import useCurrentUser from "@/use/use-current-user"
import useStatus from "@/use/use-status"
const { logout } = useAuth0()
const { currentUser, isSystemAdmin, reset: resetCurrentUser } = useCurrentUser<true>()
const showMenu = ref(false)
const { releaseTag } = useStatus()
const currentUserInitials = computed(() => {
if (isNil(currentUser.value)) {
return ""
}
const { firstName, lastName, email } = currentUser.value
if (!isNil(firstName) && !isEmpty(firstName) && !isNil(lastName) && !isEmpty(lastName)) {
const initials = [firstName, lastName].map((name) => name[0]?.toUpperCase()).join("")
return initials
}
const initials = email
.split(".")
.map((part) => part[0]?.toUpperCase())
.join("")
return initials
})
function closeMenu() {
showMenu.value = false
}
function signOut() {
resetCurrentUser()
const returnTo = encodeURI(window.location.origin)
return logout({ logoutParams: { returnTo } })
}
</script>
+59
View File
@@ -0,0 +1,59 @@
<template>
<v-menu
v-model="showMenu"
:close-on-content-click="false"
class="search_popup"
>
<template #activator="{ props }">
<v-btn
icon
variant="text"
class="custom-hover-primary mr-2"
size="small"
v-bind="props"
>
<IconSearch :size="26" />
</v-btn>
</template>
<v-sheet
width="360"
elevation="11"
rounded="md"
>
<v-form class="d-flex flex-column pa-5">
<v-text-field
v-model="search"
placeholder="Search"
color="primary"
density="compact"
variant="outlined"
hide-details
clearable
@click:clear="search = ''"
/>
<v-btn
class="mt-2"
color="primary"
type="submit"
block
>
<v-icon start>mdi-magnify</v-icon> Search
</v-btn>
</v-form>
<v-divider />
<h5 class="text-h5 mt-4 px-5 pb-4">Recently Viewed</h5>
<v-divider />
</v-sheet>
</v-menu>
</template>
<script setup lang="ts">
import { ref } from "vue"
import { IconSearch } from "@tabler/icons-vue"
const showMenu = ref(false)
const search = ref("")
</script>