Skip to content

Commit

Permalink
feat: continue work on centrum
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Trost <[email protected]>
  • Loading branch information
galexrt committed Jul 21, 2023
1 parent 5a96a3a commit ebda13d
Show file tree
Hide file tree
Showing 6 changed files with 1,874 additions and 755 deletions.
2 changes: 1 addition & 1 deletion gen/go/proto/services/centrum/dispatches.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var (
)

// TODO does it make sense to distinguish between "All" and "Assigned" dispatches here?
// A unit user would only want to see their assigned dispatches
// A unit user would only get to see their assigned dispatches

func (s *Server) ListDispatches(ctx context.Context, req *ListDispatchesRequest) (*ListDispatchesResponse, error) {
userInfo := auth.MustGetUserInfoFromContext(ctx)
Expand Down
8 changes: 8 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const config = defineNuxtConfig({
telemetry: false,
ssr: false,
modules: [
'@nuxt/devtools',
'@pinia/nuxt',
'@pinia-plugin-persistedstate/nuxt',
'nuxt-typed-router',
Expand All @@ -38,6 +39,13 @@ const config = defineNuxtConfig({
'@nuxtjs/tailwindcss',
'@vee-validate/nuxt',
],
devtools: {
enabled: true,
vscode: {},
timeline: {
enabled: true,
},
},
pinia: {
autoImports: [
// automatically imports `defineStore`
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@nuxt/devtools": "^0.7.0",
"@nuxtjs/i18n": "8.0.0-beta.10",
"@nuxtjs/tailwindcss": "^6.8.0",
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
Expand Down
250 changes: 213 additions & 37 deletions src/components/livemap/CentrumSidebar.vue
Original file line number Diff line number Diff line change
@@ -1,46 +1,209 @@
<script lang="ts" setup>
import SvgIcon from '@jamescoyle/vue-icon';
import { mdiCarEmergency, mdiHoopHouse } from '@mdi/js';
import { Unit } from '~~/gen/ts/resources/dispatch/units';
const unit: Unit = {
id: 0n,
job: 'ambulance',
name: 'Christoph 1',
initials: 'CH1',
users: [],
};
const navigation = [
{ name: 'Update Unit Status' },
{ name: 'Update Dispatch Status' },
{ name: 'TODO' },
{ name: 'TODO' },
{ name: 'TODO' },
{ name: 'TODO' },
{ name: 'Dispatch: Completed' },
import {
mdiCalendarCheck,
mdiCalendarRemove,
mdiCarBack,
mdiCarEmergency,
mdiCheckBold,
mdiCoffee,
mdiHelpCircle,
mdiHoopHouse,
mdiInformationOutline,
mdiListStatus,
mdiMarkerCheck,
} from '@mdi/js';
import { RpcError } from '@protobuf-ts/runtime-rpc/build/types';
import { Dispatch, DispatchStatus } from '~~/gen/ts/resources/dispatch/dispatches';
import { Settings } from '~~/gen/ts/resources/dispatch/settings';
import { Unit, UnitStatus } from '~~/gen/ts/resources/dispatch/units';
import { UserShort } from '~~/gen/ts/resources/users/users';
const { $grpc } = useNuxtApp();
const settings = ref<Settings>();
const unit = ref<Unit>();
const feed = ref<(DispatchStatus | UnitStatus)[]>([]);
const controllers = ref<UserShort[]>([]);
const dispatches = ref<Array<Dispatch>>([]);
type Action = { icon?: string; name: string; action?: Function };
const actionsDispatch: Action[] = [
{ icon: mdiCarBack, name: 'Dispatch: En Route' },
{ icon: mdiMarkerCheck, name: 'Dispatch: At Scene' },
{ icon: mdiHelpCircle, name: 'Dispatch: Need Assistance' },
{ icon: mdiListStatus, name: 'Dispatch: Update Status' },
{ icon: mdiCheckBold, name: 'Dispatch: Complete' },
];
const dispatches = [
{ id: 1, name: 'DSP-2019', color: 'H' },
{ id: 2, name: 'DSP-12345', color: 'T' },
const actionsUnit: Action[] = [
{ icon: mdiCarBack, name: 'Unit: Unavailable' },
{ icon: mdiCalendarCheck, name: 'Unit: Available' },
{ icon: mdiCoffee, name: 'Unit: On Break' },
{ icon: mdiCalendarRemove, name: 'Unit: Busy' },
{ icon: mdiListStatus, name: 'Unit: Update Status', action: () => {} },
];
const abort = ref<AbortController | undefined>();
const error = ref<string | null>(null);
async function startStream(): Promise<void> {
if (abort.value !== undefined) return;
console.debug('Centrum: Starting Data Stream');
try {
abort.value = new AbortController();
const call = $grpc.getCentrumClient().stream(
{},
{
abort: abort.value.signal,
},
);
for await (let resp of call.responses) {
error.value = null;
if (resp === undefined || !resp.change) {
continue;
}
if (!dispatches.value) {
continue;
}
console.debug('Centrum: Received change - Kind:', resp.change.oneofKind, resp.change);
if (resp.change.oneofKind === 'initial') {
settings.value = resp.change.initial.settings;
unit.value = resp.change.initial.unit;
} else if (resp.change.oneofKind === 'dispatchUpdate') {
const id = resp.change.dispatchUpdate.id;
const idx = dispatches.value?.findIndex((d) => d.id === id) ?? -1;
if (idx === -1) {
dispatches.value?.unshift(resp.change.dispatchUpdate);
} else {
dispatches.value![idx] = resp.change.dispatchUpdate;
}
} else if (resp.change.oneofKind === 'dispatchStatus') {
feed.value.unshift(resp.change.dispatchStatus);
} else if (resp.change.oneofKind === 'dispatchUnassigned') {
const id = resp.change.dispatchUnassigned.id;
const idx = dispatches.value?.findIndex((d) => d.id === id) ?? -1;
if (idx === -1) {
dispatches.value?.unshift(resp.change.dispatchUnassigned);
} else {
dispatches.value![idx].units = resp.change.dispatchUnassigned.units;
}
} else if (resp.change.oneofKind === 'dispatchAssigned') {
const id = resp.change.dispatchAssigned.id;
const idx = dispatches.value?.findIndex((d) => d.id === id) ?? -1;
if (idx === -1) {
dispatches.value?.unshift(resp.change.dispatchAssigned);
} else {
dispatches.value![idx] = resp.change.dispatchAssigned;
}
} else if (resp.change.oneofKind === 'unitUpdate') {
const id = resp.change.unitUpdate.id;
if (!unit.value) continue;
if (unit.value.id === id) {
unit.value = resp.change.unitUpdate;
}
} else if (resp.change.oneofKind === 'unitStatus') {
feed.value.unshift(resp.change.unitStatus);
} else if (resp.change.oneofKind === 'unitAssigned') {
// TODO show popup and notification
if (resp.change.unitAssigned.id === 0n) {
// User has been removed from the unit
} else {
// User has been added to unit
}
} else if (resp.change.oneofKind === 'unitDeleted') {
if (!unit.value) continue;
const id = resp.change.unitDeleted;
if (unit.value.id === id) {
unit.value = undefined;
}
// TODO User not in a unit anymore
} else if (resp.change.oneofKind === 'controllers') {
controllers.value = resp.change.controllers.controllers;
// If user is part of controllers list, we need to restart the stream
if (!resp.change.controllers.active) {
stopStream();
setTimeout(() => {
startStream();
}, 250);
}
} else if (resp.change.oneofKind === 'settings') {
settings.value = resp.change.settings;
} else {
console.log('Centrum: Unknown change received - Kind: ', resp.change.oneofKind, resp.change);
}
}
} catch (e) {
const err = e as RpcError;
error.value = err.message;
stopStream();
}
console.debug('Centrum: Data Stream Ended');
}
async function stopStream(): Promise<void> {
console.debug('Centrum: Stopping Data Stream');
abort.value?.abort();
abort.value = undefined;
}
onMounted(() => {
startStream();
});
onBeforeUnmount(() => {
stopStream();
});
</script>

<template>
<!-- Sidebar component, swap this element with another sidebar if you like -->
<div class="h-full flex grow gap-y-5 overflow-y-auto bg-base-600 px-6 py-4">
<div class="h-full flex grow gap-y-5 overflow-y-auto bg-base-600 px-6 py-2">
<nav class="flex flex-1 flex-col">
<ul role="list" class="flex flex-1 flex-col gap-y-7">
<ul role="list" class="flex flex-1 flex-col gap-y-2 divide-y divide-base-400">
<li>
<!-- <div class="text-xs font-semibold leading-6 text-base-200">Your Unit</div> -->
<ul role="list" class="-mx-2 mt-2 space-y-1">
<li v-if="unit">
<button
type="button"
class="text-accent-100 bg-info-700 hover:bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-2 text-xs my-2"
>
<SvgIcon type="mdi" :path="mdiInformationOutline" class="h-6 w-6" aria-hidden="true" />
<span class="mt-2 truncate">{{ unit.initials }}: {{ unit.name }}</span>
</button>
</li>
<li v-else>
<button
type="button"
class="text-accent-100 bg-info-700 hover:bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-2 text-xs my-2"
>
<SvgIcon type="mdi" :path="mdiInformationOutline" class="h-6 w-6" aria-hidden="true" />
<span class="mt-2 truncate">You are not in any Unit.</span>
</button>
</li>
</ul>
</li>
<li>
<ul role="list" class="-mx-2 space-y-1">
<li v-for="item in navigation" :key="item.name">
<li v-for="item in actionsDispatch" :key="item.name">
<button
type="button"
class="text-accent-100 bg-primary hover:bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-3 text-xs my-2"
class="text-accent-100 bg-primary hover:bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-2 text-xs my-2"
>
<SvgIcon
type="mdi"
:path="mdiHoopHouse"
:path="item.icon ?? mdiHoopHouse"
class="text-base-200 group-hover:text-white h-6 w-6 shrink-0"
aria-hidden="true"
/>
Expand All @@ -50,29 +213,42 @@ const dispatches = [
</ul>
</li>
<li>
<div class="text-xs font-semibold leading-6 text-base-200">Your Dispatches</div>
<ul role="list" class="-mx-2 mt-2 space-y-1">
<li v-for="dispatch in dispatches" :key="dispatch.name">
<ul role="list" class="-mx-2 space-y-1">
<li v-for="item in actionsUnit" :key="item.name">
<button
type="button"
class="text-accent-100 bg-error-700 hover:bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-3 text-xs my-2"
class="text-accent-100 bg-primary hover:bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-2 text-xs my-2"
>
<SvgIcon type="mdi" :path="mdiCarEmergency" class="h-6 w-6" aria-hidden="true" />
<span class="mt-2 truncate">{{ dispatch.name }}</span>
<SvgIcon
type="mdi"
:path="item.icon ?? mdiHoopHouse"
class="text-base-200 group-hover:text-white h-6 w-6 shrink-0"
aria-hidden="true"
/>
<span class="mt-2">{{ item.name }}</span>
</button>
</li>
</ul>
</li>
<li>
<div class="text-xs font-semibold leading-6 text-base-200">Your Unit</div>
<!-- <div class="text-xs font-semibold leading-6 text-base-200">Your Dispatches</div> -->
<ul role="list" class="-mx-2 mt-2 space-y-1">
<li>
<li v-if="!dispatches || dispatches.length === 0">
<button
type="button"
class="text-accent-100 bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-2 text-xs my-2"
>
<SvgIcon type="mdi" :path="mdiCarEmergency" class="h-6 w-6" aria-hidden="true" />
<span class="mt-2 truncate">No assigned Dispatches.</span>
</button>
</li>
<li v-else v-for="dispatch in dispatches" :key="dispatch.id.toString()">
<button
type="button"
class="text-accent-100 bg-info-700 hover:bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-3 text-xs my-2"
class="text-accent-100 bg-error-700 hover:bg-primary-100/10 hover:text-neutral font-medium hover:transition-all group flex w-full flex-col items-center rounded-md p-2 text-xs my-2"
>
<SvgIcon type="mdi" :path="mdiCarEmergency" class="h-6 w-6" aria-hidden="true" />
<span class="mt-2 truncate">{{ unit.name }}</span>
<span class="mt-2 truncate">DSP-{{ dispatch.id }}</span>
</button>
</li>
</ul>
Expand Down
2 changes: 1 addition & 1 deletion src/components/livemap/Livemap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ watchDebounced(postalQuery, () => findPostal(), {
</div>
</LControl>
</LMap>
<div v-can="'CentrumService.Stream'" class="lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
<div v-can="'CentrumService.Stream'" class="lg:inset-y-0 lg:flex lg:w-50 lg:flex-col">
<CentrumSidebar />
</div>
</div>
Expand Down
Loading

0 comments on commit ebda13d

Please sign in to comment.