Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions client/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
declare global {
export interface Window {
// Define window props here
// -->
connections: {
ws: import('vue').Ref<WebSocket | null>
}
}
}

export {} // Important
export { } // Important
4 changes: 4 additions & 0 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ export default defineComponent({
.vue3-notifier-container .text-success {
background-color: rgb(2, 29, 3) !important;
}

button {
cursor: pointer !important;
}
</style>
2 changes: 1 addition & 1 deletion client/src/clients/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export abstract class ApiClientBase {
description: errorDescription
})

panic(err)
panic(errorDescription)
}

// Transform and return the response data
Expand Down
29 changes: 21 additions & 8 deletions client/src/clients/api/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class UsersApi extends ApiClientBase {
readonly skills: UsersSkillsApi
readonly team: UsersTeamApi
readonly supervisors: UsersSupervisorsApi
readonly active: UsersActiveApi

constructor(options: Api.ClientOptions) {
super(options)
Expand All @@ -22,6 +23,7 @@ export class UsersApi extends ApiClientBase {
this.skills = new UsersSkillsApi(options, this.path)
this.team = new UsersTeamApi(options, this.path)
this.supervisors = new UsersSupervisorsApi(options, this.path)
this.active = new UsersActiveApi(options, this.path)
}

list(query?: any) {
Expand All @@ -43,12 +45,12 @@ export class UsersApi extends ApiClientBase {
class UsersAdminApi extends ApiClientBase {
protected readonly path = '/admin'

readonly office_users: UsersAdminofficeUsersApi
readonly office_users: UsersAdminOfficeUsersApi

constructor(options: Api.ClientOptions, prePath: string) {
super(options, prePath)

this.office_users = new UsersAdminofficeUsersApi(options, prePath + this.path)
this.office_users = new UsersAdminOfficeUsersApi(options, prePath + this.path)
}

list(query?: Api.Inputs.List) {
Expand All @@ -73,18 +75,19 @@ class UsersAdminApi extends ApiClientBase {
}
}

class UsersAdminofficeUsersApi extends ApiClientBase {
class UsersAdminOfficeUsersApi extends ApiClientBase {
protected readonly path = '/office_users'

list(query?: Api.Inputs.List) {
list(query?: Api.Inputs.List & { all?: boolean; active_only?: boolean }) {
return this.unwrap(() => this.$http.get(this.getUrl('', query)))
}
}


class UsersBirthdatesApi extends ApiClientBase {
protected readonly path = '/birthdates'

list() {}
list() { }
}

class UsersSetActiveApi extends ApiClientBase {
Expand Down Expand Up @@ -120,9 +123,9 @@ class UsersSkillsApi extends ApiClientBase {
class UsersSupervisorsApi extends ApiClientBase {
protected readonly path = '/supervisors'

async list( query?: Api.Inputs.List) {
async list() {
ApiClientBase.assertUser()
return this.unwrap(() => this.$http.get<Api.Returns.List<Api.User>>(this.getUrl("", query)))
return this.unwrap(() => this.$http.get(this.getUrl()))
}
}

Expand All @@ -138,7 +141,7 @@ class UsersTeamApi extends ApiClientBase {
}

list(query?: any) {
return this.unwrap(() => this.$http.get<Api.Returns.List<Api.User>>(this.getUrl('', query)))
return this.unwrap(() => this.$http.get<Api.Returns.List<Api.User>>(this.getUrl('', query)))
}
}

Expand All @@ -154,3 +157,13 @@ class UsersTeamSupervisorsApi extends ApiClientBase {
)
}
}

class UsersActiveApi extends ApiClientBase {
protected readonly path = '/active'

async list() {
return this.unwrap(() => this.$http.get(this.getUrl()), {
transform: (d: any) => d.results
})
}
}
16 changes: 16 additions & 0 deletions client/src/clients/api/vacations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,22 @@ class VacationsAdmin extends ApiClientBase {

return result
}

async debugBalance(userId: number, query: { year: number, reason: string }) {
ApiClientBase.assertUser()
return this.unwrap(
() => this.$http.get<any>(this.getUrl(`/debug-balance/${userId}`, query)),
{ transform: (d) => d.results }
)
}

async fixBalance(userId: number, data: { year: number, reason: string }) {
ApiClientBase.assertUser()
return this.unwrap(
() => this.$http.post<any>(this.getUrl(`/fix-balance/${userId}`), data),
{ transform: (d) => d.results }
)
}
}

class VacationsUserApi extends ApiClientBase {
Expand Down
104 changes: 74 additions & 30 deletions client/src/components/DashboardList.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
<template>
<v-card variant="outlined" class="pa-2 card-outline">
<v-list active-class="v-list-item__overlay">
<v-list-item v-for="item in items" :key="item.id" @click="selectTab(item)"
:class="{ 'active-item': item.active }">
<v-list-item-content>
<v-card variant="outlined" class="pa-0 card-outline border-opacity-25 shadow-sm">
<v-list v-model:opened="openedGroups" density="comfortable" color="primary">
<template v-for="item in items" :key="item.id">
<!-- Expandable Item (Group) -->
<v-list-group v-if="item.children" :value="item.id">
<template v-slot:activator="{ props }">
<v-list-item v-bind="props" class="py-2" base-color="white">
<template v-slot:prepend>
<v-icon :icon="item.icon" size="small" class="mr-2"></v-icon>
</template>
<v-list-item-title class="font-weight-bold text-uppercase text-caption">
{{ item.name }}
</v-list-item-title>
</v-list-item>
</template>

<v-list-item v-for="child in item.children" :key="child.id" @click="selectTab(child)"
:class="{ 'active-item': child.active }" class="pl-10" min-height="40" rounded="0">
<template v-slot:prepend>
<v-icon :icon="child.icon" size="small" class="mr-2"></v-icon>
</template>
<v-list-item-title class="text-body-2">{{ child.name }}</v-list-item-title>
</v-list-item>
</v-list-group>

<!-- Standard Item -->
<v-list-item v-else @click="selectTab(item)" :class="{ 'active-item': item.active }" class="py-2"
min-height="48">
<template v-slot:prepend>
<v-icon :icon="item.icon" size="small" class="mr-2"></v-icon>
</template>
<v-list-item-title class="font-weight-bold">{{ item.name }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item>
</template>
</v-list>
</v-card>
</template>
Expand All @@ -23,14 +49,20 @@ export default {
setup(_, { emit }) {
const activeTab = useRouteQuery<undefined | string>('tab', undefined);
const $route = useRoute();
const openedGroups = ref<number[]>([]);

const itemsRef = ref(items);
const itemsRef = items;

const resetActiveTab = () => {
itemsRef.value.forEach(item => item.active = false);
itemsRef.value.forEach(item => {
item.active = false
if (item.children) {
item.children.forEach(child => child.active = false)
}
});
};

const selectTab = (item: { id: number; name: string; active: boolean }) => {
const selectTab = (item: any) => {
resetActiveTab();
item.active = true;
activeTab.value = `${item.id}`;
Expand All @@ -40,13 +72,27 @@ export default {
onMounted(() => {
const tabQuery = $route.query['tab'];
if (tabQuery && !isNaN(+tabQuery)) {
const item = itemsRef.value.find(i => i.id === +tabQuery);
if (item) {
let foundItem: any = itemsRef.value.find(i => i.id === +tabQuery);

if (!foundItem) {
// Search in children
for (const parent of itemsRef.value) {
if (parent.children) {
const child = parent.children.find(c => c.id === +tabQuery);
if (child) {
foundItem = child;
// Auto-open parent group
openedGroups.value = [parent.id];
break;
}
}
}
}

if (foundItem) {
resetActiveTab();
item.active = true;
emit('item-selected', item);
} else {
activeTab.value = undefined;
foundItem.active = true;
emit('item-selected', foundItem);
}
} else {
resetActiveTab();
Expand All @@ -56,30 +102,28 @@ export default {

return {
items: itemsRef,
selectTab
selectTab,
openedGroups
};
}
};
</script>

<style>
.v-list-item__overlay {
background-color: #47a2ff !important;
}

.v-list-item:hover>.v-list-item__overlay {
background-color: #47a2ff !important;
<style scoped>
.active-item {
background-color: rgb(var(--v-theme-primary)) !important;
color: white !important;
}

.v-list-item--variant-text .v-list-item__overlay {
background-color: #47a2ff !important;
.card-outline {
border-color: rgba(255, 255, 255, 0.12) !important;
}

.active-item {
background-color: #143453 !important;
.v-list-group__header.v-list-item--active {
color: rgb(var(--v-theme-primary)) !important;
}

.card-outline {
border-color: #67696b !important;
:deep(.v-list-item__spacer) {
display: none !important;
}
</style>
25 changes: 6 additions & 19 deletions client/src/components/SideDrawer.vue
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
<template>
<v-card class="layout-container">
<v-navigation-drawer
v-if="$route.path != '/login'"
v-model="drawer"
theme="dark"
:permanent="!isMobile"
:temporary="isMobile"
class="fixed-sidebar">
<v-navigation-drawer v-if="$route.path != '/login'" v-model="drawer" theme="dark" :permanent="!isMobile"
:temporary="isMobile" class="fixed-sidebar">
<router-link to="/">
<v-img :src="logo" max-width="110" class="ml-3 mt-5 mb-4"></v-img>
</router-link>
<v-divider></v-divider>
<v-list color="transparent">
<template v-for="item in filteredItems" :key="item.title">
<v-list-item
:to="item.path"
:prepend-icon="item.icon"
color="white"
:class="{ 'router-link-active': $route.path === item.path }"
@click="isMobile && (drawer = false)">
<v-list-item :to="item.path" :prepend-icon="item.icon" color="white"
:class="{ 'router-link-active': $route.path === item.path }" @click="isMobile && (drawer = false)">
{{ item.title }}
</v-list-item>
</template>
</v-list>
</v-navigation-drawer>

<div :class="$route.path != '/login' ? 'main-container' : ''">
<CshrToolbar v-if="$route.path != '/login'" @logout="logout" @toggle-drawer="drawer = !drawer" :is-mobile="isMobile" />
<CshrToolbar v-if="$route.path != '/login'" @logout="logout" @toggle-drawer="drawer = !drawer"
:is-mobile="isMobile" />
<div class="scrollable-content">
<template v-if="isReadyRouter.state.value">
<router-view />
Expand Down Expand Up @@ -94,11 +86,6 @@ export default {
title: 'Requests',
path: '/requests'
},
{
icon: 'mdi-account-clock',
title: 'Pending Requests',
path: '/pending-requests'
},
{
icon: 'mdi-bell',
title: 'Notifications',
Expand Down
4 changes: 1 addition & 3 deletions client/src/components/dashboard/AddUser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default {
isLoading.value = true
clearForm();
locations.value = await listOffices({ count: 10, key: 'location', items: [], page: 1 })
teamLeads.value = await listTeamLeads({ count: 10, key: 'reporting_to', items: [], page: 1 })
teamLeads.value = await listTeamLeads()
} catch (error) {
console.error(error)
} finally {
Expand All @@ -106,8 +106,6 @@ export default {
options.page += 1
if (options.key === 'location') {
locations.value = await listOffices(options)
} else if (options.key === 'reporting_to') {
teamLeads.value = await listTeamLeads(options)
}
}

Expand Down
Loading