Skip to content

Commit

Permalink
add the dashboard, finally
Browse files Browse the repository at this point in the history
  • Loading branch information
NyaomiDEV committed Aug 14, 2024
1 parent c0d5af2 commit 537d0ce
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 8 deletions.
102 changes: 102 additions & 0 deletions src/components/dashboard/CurrentFrontersCarousel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<script setup lang="ts">
import { IonCard, IonCardContent, IonLabel, IonListHeader } from '@ionic/vue';
import FrontingEntryAvatar from '../frontingEntry/FrontingEntryAvatar.vue';
import { onMounted, onUnmounted, ref, shallowRef, watch, WatchStopHandle } from 'vue';
import { PartialBy } from '../../lib/db/types';
import { FrontingEntryComplete, getFrontingEntriesTable, toFrontingEntryComplete } from '../../lib/db/entities/frontingEntries';
import { from, useObservable } from '@vueuse/rxjs';
import { liveQuery } from 'dexie';
import { getMembersTable } from '../../lib/db/entities/members';
import { formatWrittenTime } from "../../lib/util/misc";
import FrontingEntryEdit from '../../modals/FrontingEntryEdit.vue';
const frontingEntryModal = ref();
const emptyFrontingEntry: PartialBy<FrontingEntryComplete, "uuid" | "member"> = {
isMainFronter: false,
startTime: new Date(),
endTime: new Date(),
};
const frontingEntry = shallowRef({...emptyFrontingEntry});
const frontingEntries = shallowRef<FrontingEntryComplete[]>([]);
const now = ref(new Date());
let handle: WatchStopHandle;
let interval: NodeJS.Timeout;
onMounted(async () => {
interval = setInterval(() => now.value = new Date(), 1000);
handle = watch([
useObservable(from(liveQuery(() => getFrontingEntriesTable().toArray()))),
useObservable(from(liveQuery(() => getMembersTable().toArray())))
], async () => {
frontingEntries.value = await Promise.all(
(await getFrontingEntriesTable()
.filter(x => !x.endTime)
.toArray()
)
.sort((a, b) => b.isMainFronter ? 1 : b.startTime.getTime() - a.startTime.getTime())
.map(x => toFrontingEntryComplete(x))
);
}, { immediate: true });
frontingEntry.value = { ...frontingEntries.value[0] };
});
onUnmounted(async () => {
clearInterval(interval);
handle();
});
async function showModal(clickedFrontingEntry: FrontingEntryComplete){
frontingEntry.value = {...clickedFrontingEntry};
await frontingEntryModal.value.$el.present();
}
</script>

<template>
<IonListHeader v-if="frontingEntries.length">
<IonLabel>{{ $t("dashboard:nowFronting") }}</IonLabel>
</IonListHeader>
<div class="carousel" v-if="frontingEntries.length">
<IonCard button :class="{outlined: !fronting.isMainFronter, elevated: fronting.isMainFronter}" @click="showModal(fronting)" v-for="fronting in frontingEntries" :key="JSON.stringify(fronting)">
<IonCardContent>
<FrontingEntryAvatar :entry="fronting" />
<IonLabel>
<h2>
{{ fronting.member.name }}
</h2>
<p>
{{ formatWrittenTime(now, fronting.startTime) }}
</p>
<p v-if="fronting.customStatus">
{{ fronting.customStatus }}
</p>
</IonLabel>
</IonCardContent>
</IonCard>
</div>
<FrontingEntryEdit v-if="frontingEntries.length" ref="frontingEntryModal" :frontingEntry />
</template>

<style scoped>
div {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: scroll;
}
ion-card {
width: 160px;
}
ion-card ion-avatar {
margin: 16px auto;
}
ion-card ion-card-content {
text-align: center;
}
</style>
69 changes: 69 additions & 0 deletions src/components/dashboard/FrontingHistoryCarousel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { IonList, IonItem, IonListHeader, IonLabel } from '@ionic/vue';
import { inject, onMounted, onUnmounted, ref, ShallowRef, shallowRef, watch, WatchStopHandle } from 'vue';
import FrontingEntryAvatar from "../frontingEntry/FrontingEntryAvatar.vue";
import FrontingEntryLabel from "../frontingEntry/FrontingEntryLabel.vue";
import { FrontingEntryComplete, getFrontingEntriesTable, toFrontingEntryComplete } from '../../lib/db/entities/frontingEntries';
import FrontingEntryEdit from "../../modals/FrontingEntryEdit.vue";
import dayjs from 'dayjs';
import { from, useObservable } from '@vueuse/rxjs';
import { liveQuery } from 'dexie';
import { getMembersTable } from '../../lib/db/entities/members';
import { PartialBy } from '../../lib/db/types';
const isIOS = inject<boolean>("isIOS");
const frontingEntryModal = ref();
const emptyFrontingEntry: PartialBy<FrontingEntryComplete, "uuid" | "member"> = {
isMainFronter: false,
startTime: new Date(),
endTime: new Date(),
};
const frontingEntry = shallowRef({...emptyFrontingEntry});
const frontingEntries: ShallowRef<FrontingEntryComplete[]> = shallowRef([]);
let handle: WatchStopHandle;
onMounted(() => {
handle = watch([
useObservable(from(liveQuery(() => getFrontingEntriesTable().toArray()))),
useObservable(from(liveQuery(() => getMembersTable().toArray()))),
], async () => {
frontingEntries.value = await Promise.all(
(
await getFrontingEntriesTable()
.filter(x => !!x.endTime && dayjs.duration(dayjs().diff(x.endTime)).asHours() < 48)
.toArray()
)
.sort((a, b) => b.endTime!.getTime() - a.endTime!.getTime())
.map(x => toFrontingEntryComplete(x))
);
}, { immediate: true });
});
onUnmounted(async () => {
handle();
});
async function showModal(clickedFrontingEntry: FrontingEntryComplete){
frontingEntry.value = {...clickedFrontingEntry};
await frontingEntryModal.value.$el.present();
}
</script>

<template>
<IonListHeader>
<IonLabel>{{ $t("dashboard:recentFrontingHistory") }}</IonLabel>
</IonListHeader>

<IonList :inset="isIOS">
<IonItem button v-for="entry in frontingEntries" :key="JSON.stringify(entry)" @click="showModal(entry)">
<FrontingEntryAvatar slot="start" :entry />
<FrontingEntryLabel :entry />
</IonItem>
</IonList>

<FrontingEntryEdit :frontingEntry ref="frontingEntryModal" />
</template>
90 changes: 90 additions & 0 deletions src/components/dashboard/MessageBoardCarousel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<script setup lang="ts">
import { IonList, IonLabel, IonButton, IonListHeader } from '@ionic/vue';
import { inject, onMounted, onUnmounted, ref, shallowRef, watch, WatchStopHandle } from 'vue';
import { BoardMessageComplete, getBoardMessagesTable, toBoardMessageComplete } from '../../lib/db/entities/boardMessages';
import BoardMessageEdit from "../../modals/BoardMessageEdit.vue";
import { PartialBy } from '../../lib/db/types';
import dayjs from 'dayjs';
import { from, useObservable } from '@vueuse/rxjs';
import { liveQuery } from 'dexie';
import { getMembersTable } from '../../lib/db/entities/members';
import { getFronting, getMainFronter } from '../../lib/db/entities/frontingEntries';
import MessageBoardCard from '../MessageBoardCard.vue';
const isIOS = inject<boolean>("isIOS");
const emptyBoardMessage: PartialBy<BoardMessageComplete, "uuid" | "member"> = {
title: "",
body: "",
date: new Date()
}
const boardMessage = shallowRef<PartialBy<BoardMessageComplete, "uuid" | "member">>({...emptyBoardMessage});
const boardMessageEditModal = ref();
const boardMessages = shallowRef<BoardMessageComplete[]>([]);
let handle: WatchStopHandle;
onMounted(() => {
handle = watch([
useObservable(from(liveQuery(() => getBoardMessagesTable().toArray()))),
useObservable(from(liveQuery(() => getMembersTable().toArray()))),
], async () => {
boardMessages.value = await Promise.all(
(
await getBoardMessagesTable()
.filter(x => dayjs(x.date).startOf('day').valueOf() === dayjs().startOf('day').valueOf())
.toArray()
).map(x => toBoardMessageComplete(x))
);
}, { immediate: true });
});
onUnmounted(() => {
handle();
});
async function showModal(_boardMessage?: BoardMessageComplete){
if(_boardMessage)
boardMessage.value = {..._boardMessage};
else {
boardMessage.value = {
...emptyBoardMessage,
date: new Date(),
member: await getMainFronter() || (await getFronting())[0]
};
}
await boardMessageEditModal.value.$el.present();
}
</script>

<template>
<IonListHeader>
<IonLabel>{{ $t("dashboard:messageBoard.header") }}</IonLabel>
</IonListHeader>

<IonList :inset="isIOS">
<MessageBoardCard :boardMessage v-for="boardMessage in boardMessages" :key="JSON.stringify(boardMessage)" @click="showModal(boardMessage)" />
</IonList>

<div>
<IonButton @click="showModal()">{{ $t("dashboard:messageBoard.add") }}</IonButton>
<IonButton fill="clear" router-link="/options/messageBoard">{{ $t("dashboard:messageBoard.view") }}</IonButton>
</div>

<BoardMessageEdit :boardMessage ref="boardMessageEditModal" />
</template>

<style scoped>
div {
display: flex;
justify-content: center;
gap: 16px;
}
.ios ion-list {
background-color: transparent;
}
</style>
2 changes: 1 addition & 1 deletion src/views/TabbedHomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
{{ $t("journal:header") }}
</IonTabButton>

<IonTabButton tab="dashboard" href="/dashboard" v-if="false">
<IonTabButton tab="dashboard" href="/dashboard">
<IonIcon :ios="HomeIOS" :md="HomeMD" />
{{ $t("dashboard:header") }}
</IonTabButton>
Expand Down
3 changes: 3 additions & 0 deletions src/views/options/AppSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
<IonList :inset="isIOS">
<IonItem>
<IonSelect :label="$t('options:appSettings.view.title')" interface="popover" v-model="appConfig.view">
<IonSelectOption value="dashboard">
{{ $t("options:appSettings.view.dashboard") }}
</IonSelectOption>
<IonSelectOption value="members">
{{ $t("options:appSettings.view.members") }}
</IonSelectOption>
Expand Down
39 changes: 33 additions & 6 deletions src/views/tabbed/Dashboard.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
<script setup lang="ts">
import { IonContent, IonHeader, IonList, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
import { inject } from 'vue';
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, onIonViewWillEnter, onIonViewWillLeave } from '@ionic/vue';
import { inject, ref, watch, WatchStopHandle } from 'vue';
import { getFrontingEntriesTable, getMainFronter } from '../../lib/db/entities/frontingEntries';
import { Member } from '../../lib/db/entities/members';
import { useObservable } from '@vueuse/rxjs';
import { from } from 'rxjs';
import { liveQuery } from 'dexie';
import CurrentFrontersCarousel from '../../components/dashboard/CurrentFrontersCarousel.vue';
import MessageBoardCarousel from '../../components/dashboard/MessageBoardCarousel.vue';
import FrontingHistoryCarousel from '../../components/dashboard/FrontingHistoryCarousel.vue';
const mainFronter = ref<Member>();
let handle: WatchStopHandle;
onIonViewWillEnter(() => {
handle = watch(
useObservable(from(liveQuery(() => getFrontingEntriesTable().toArray()))),
async () => {
mainFronter.value = await getMainFronter();
},
{ immediate: true }
);
});
onIonViewWillLeave(() => handle());
const isIOS = inject<boolean>("isIOS");
</script>
Expand All @@ -10,15 +34,18 @@
<IonHeader>
<IonToolbar>
<IonTitle>
{{ $t("dashboard:header") }}
{{
mainFronter
? $t("dashboard:header_mainfronter", { fronterName: mainFronter.name })
: $t("dashboard:header_salute") }}
</IonTitle>
</IonToolbar>
</IonHeader>

<IonContent>
<IonList :inset="isIOS">

</IonList>
<CurrentFrontersCarousel />
<MessageBoardCarousel />
<FrontingHistoryCarousel />
</IonContent>
</IonPage>
</template>
11 changes: 10 additions & 1 deletion translations/en/dashboard.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
{
"header": "Dashboard"
"header": "Dashboard",
"header_salute": "Hello!",
"header_mainfronter": "Hi, {{fronterName}}!",
"nowFronting": "Now fronting",
"recentFrontingHistory": "Recently switched out",
"messageBoard": {
"header": "Message board",
"add": "Add to message board",
"view": "View more..."
}
}

0 comments on commit 537d0ce

Please sign in to comment.