Skip to content

Commit

Permalink
feat: very quality of life
Browse files Browse the repository at this point in the history
  • Loading branch information
qin-guan committed Jul 15, 2023
1 parent 6aab424 commit b9b31e9
Show file tree
Hide file tree
Showing 22 changed files with 479 additions and 351 deletions.
7 changes: 7 additions & 0 deletions app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
import ConfirmPopup from 'primevue/confirmpopup'
import Toast from 'primevue/toast'
const config = useRuntimeConfig()
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${config.public.appName} - ${titleChunk}` : config.public.appName
},
})
const cookieRaw = useCookie('theme')
const route = useRoute()
const resolvedTheme = computed(() => {
Expand Down
19 changes: 19 additions & 0 deletions components/dashboard/Error.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup lang="ts">
const props = defineProps<{
message?: string
data?: any
}>()
</script>

<template>
<div>
<span text-xl font-semibold>
{{ props.message ?? 'An error occurred' }}
</span>
<br>
<br>
<details v-if="props.data">
{{ props.data }}
</details>
</div>
</template>
2 changes: 1 addition & 1 deletion middleware/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export default defineNuxtRouteMiddleware(async () => {
if (error.value?.data?.code === 'UNAUTHORIZED')
return '/login'

if (!data.value.admin)
if (!data.value?.admin)
return '/thanks'
})
4 changes: 1 addition & 3 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
experimental: {
componentIslands: true,
},
devtools: { enabled: true },
routeRules: {
'/dashboard/surveys/*/print': { ssr: false },
},
modules: [
'@unocss/nuxt',
'@vueuse/nuxt',
'@nuxtjs/html-validator',
],
typescript: {
strict: true,
Expand Down
4 changes: 2 additions & 2 deletions pages/dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ definePageMeta({
})
const { $client } = useNuxtApp()
const { data } = await $client.me.get.useQuery()
const { data } = await $client.me.get.useQuery(undefined, { lazy: true })
</script>

<template>
<div class="h-full flex flex-col bg-$surface-ground">
<DashboardTheHeader :name="data.name" />
<DashboardTheHeader :name="data?.name ?? 'W'" />
<NuxtPage />
</div>
Expand Down
62 changes: 33 additions & 29 deletions pages/dashboard/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import Dialog from 'primevue/dialog'
import InputText from 'primevue/inputtext'
import InputSwitch from 'primevue/inputswitch'
import Badge from 'primevue/badge'
const config = useRuntimeConfig()
import Skeleton from 'primevue/skeleton'
useSeoMeta({
title: config.public.appName,
title: 'Dashboard',
})
const { $client } = useNuxtApp()
const { data: surveys } = await $client.survey.list.useQuery()
const { data: surveys, pending: surveysPending, error: surveysError } = await $client.survey.list.useQuery(undefined, { lazy: true })
const visible = ref(false)
const createForm = reactive({
Expand Down Expand Up @@ -79,33 +78,38 @@ async function create() {

<br>

<div v-if="surveys.length === 0" mt-20 flex flex-col items-center>
<span text-xl font-semibold>No surveys</span>
<br>
<Button size="small" label="Create new" @click="visible = true" />
</div>

<DataTable v-else :value="surveys" table-style="width: 100%;">
<Column field="id" header="ID" style="width: 20%;" />
<Column field="title" header="Title" style="width: 50%" />
<Column field="workshop" header="Type" style="width: 20%">
<template #body="slotProps">
<Badge v-if="slotProps.data.workshop" value="Workshop" />
<Badge v-else value="Booth" severity="warning" />
</template>
</Column>
<Column header="Actions">
<template #body="slotProps">
<NuxtLink :to="`/dashboard/surveys/${slotProps.data.id}`">
<Button label="Edit" size="small" link />
</NuxtLink>
</template>
</Column>
</DataTable>
<Skeleton v-if="surveysPending" height="300px" />
<DashboardError v-else-if="surveysError" v-bind="surveysError" />

<br>
<template v-else>
<div v-if="surveys.length === 0" mt-20 flex flex-col items-center>
<span text-xl font-semibold>No surveys</span>
<br>
<Button size="small" label="Create new" @click="visible = true" />
</div>

<DataTable v-else :value="surveys" table-style="width: 100%;">
<Column field="id" header="ID" style="width: 20%;" />
<Column field="title" header="Title" style="width: 50%" />
<Column field="workshop" header="Type" style="width: 20%">
<template #body="slotProps">
<Badge v-if="slotProps.data.workshop" value="Workshop" />
<Badge v-else value="Booth" severity="warning" />
</template>
</Column>
<Column header="Actions">
<template #body="slotProps">
<NuxtLink :to="`/dashboard/surveys/${slotProps.data.id}`">
<Button label="Edit" size="small" link />
</NuxtLink>
</template>
</Column>
</DataTable>

<br>

<Button v-if="surveys.length === 0" size="small" label="Create new" @click="visible = true" />
<Button v-if="surveys.length !== 0" size="small" label="Create new" @click="visible = true" />
</template>
</main>
</template>

Expand Down
63 changes: 46 additions & 17 deletions pages/dashboard/surveys/[id]/analytics.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import Breadcrumb from 'primevue/breadcrumb'
import Dialog from 'primevue/dialog'
import TabView from 'primevue/tabview'
import TabPanel from 'primevue/tabpanel'
Expand All @@ -8,6 +9,7 @@ import Textarea from 'primevue/textarea'
import Column from 'primevue/column'
import Chart from 'primevue/chart'
import RadioButton from 'primevue/radiobutton'
import Skeleton from 'primevue/skeleton'
const route = useRoute()
const { $client } = useNuxtApp()
Expand All @@ -17,27 +19,34 @@ const responsePreview = reactive({
idx: -1,
})
const { data } = await $client.analytics.listResponses.useQuery({ id: route.params.id as string })
const { data: allChartData } = await $client.analytics.chartResponses.useQuery({ id: route.params.id as string })
const { data: survey, pending: surveyPending, error: surveyError } = await $client.survey.get.useQuery({ id: route.params.id as string }, { lazy: true })
const { data: responses, pending: responsesPending, error: responsesError } = await $client.response.list.useQuery({ surveyId: route.params.id as string }, { lazy: true })
const { data: chart, pending: chartPending, error: chartError } = await $client.analytics.chartResponses.useQuery({ id: route.params.id as string }, { lazy: true })
useSeoMeta({
title: `${survey.value?.title} Analytics` ?? 'Loading...',
})
</script>

<template>
<main mx-auto p-6 container>
<Dialog v-model:visible="responsePreview.visible" modal header="Response" :style="{ width: '50vw' }">
<div flex flex-col divide-y divide-gray>
<div v-for="(question, idx) in data.schema" :key="idx" flex flex-col gap3 py6>
<Skeleton v-if="surveyPending" height="100px" />
<DashboardError v-else-if="surveyError" v-bind="surveyError" />
<div v-else-if="survey" flex flex-col divide-y divide-gray>
<div v-for="(question, idx) in survey.schema" :key="idx" flex flex-col gap3 py6>
<span font-semibold>{{ question.title }}</span>

<div
v-if="
// @ts-expect-error This exists
data.responses[responsePreview.idx].data[idx]
responses[responsePreview.idx].data[idx]
"
>
<Textarea
v-if="question.type === 'text'" disabled :value="
// @ts-expect-error Answer exists on text type
data.responses[responsePreview.idx].data[idx].answer
responses[responsePreview.idx].data[idx].answer
"
/>
<div v-else-if="question.type === 'mcq'" flex flex-col gap3>
Expand All @@ -46,7 +55,7 @@ const { data: allChartData } = await $client.analytics.chartResponses.useQuery({
disabled
:model-value="
// @ts-expect-error Option does exist, but discriminated unions don't work well here
data.responses[responsePreview.idx].data[idx].option
responses[responsePreview.idx].data[idx].option
"
:value="optionIdx"
:input-id="option"
Expand All @@ -64,15 +73,33 @@ const { data: allChartData } = await $client.analytics.chartResponses.useQuery({
</div>
</Dialog>

<h1 text-3xl font-bold>
Analytics
</h1>

<br>
<Skeleton v-if="surveyPending" height="45px" />
<Breadcrumb
v-else-if="survey"
:home="{
to: '/dashboard',
label: 'Dashboard',
}"
:model="[
{
label: 'Surveys',
to: '/dashboard',
},
{
label: `${survey.title}`,
to: `/dashboard/surveys/${survey.id}`,
},
{
label: `Analytics`,
},
]"
/>

<TabView>
<Skeleton v-if="surveyPending || responsesPending" height="500px" />
<DashboardError v-else-if="responsesError" v-bind="responsesError" />
<TabView v-else-if="responses">
<TabPanel header="Data table">
<DataTable :value="data.responses" table-class="w-full">
<DataTable :value="responses" table-class="w-full">
<Column field="id" header="ID" />
<Column field="timestamp" header="Timestamp" />
<Column header="Respondent">
Expand All @@ -95,16 +122,18 @@ const { data: allChartData } = await $client.analytics.chartResponses.useQuery({
</DataTable>
</TabPanel>
<TabPanel header="Charts">
<div flex flex-col gap6>
<Skeleton v-if="chartPending" height="300px" />
<DashboardError v-if="chartError" v-bind="chartError" />
<div v-else-if="chart" flex flex-col gap6>
<div
v-for="(qnChartData, idx) in allChartData"
v-for="(qnChartData, idx) in chart"
:key="idx"
>
<div
v-if="qnChartData.labels.length > 0"
>
<span font-semibold>
{{ data.schema[idx].title }}
{{ survey?.schema[idx].title }}
</span>
<Chart
type="bar" :data="qnChartData" :options="{
Expand Down
47 changes: 27 additions & 20 deletions pages/dashboard/surveys/[id]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ const {
{ lazy: true },
)
useSeoMeta({
title: survey.value?.title ?? 'Loading...',
})
const pending = ref(false)
const formData = ref(survey.value)
Expand All @@ -35,12 +39,16 @@ const options = [
{ name: 'MCQ', code: 'mcq' },
]
function printQR() {
navigateTo(`/dashboard/surveys/${route.params.id}/print`, { open: { target: '_blank' } })
async function printQR() {
await navigateTo(`/dashboard/surveys/${route.params.id}/print`, { open: { target: '_blank' } })
}
function openAnalytics() {
navigateTo(`/dashboard/surveys/${route.params.id}/analytics`)
async function openAnalytics() {
await navigateTo(`/dashboard/surveys/${route.params.id}/analytics`)
}
async function openPreview() {
await navigateTo(`/s/${route.params.id}`)
}
async function update() {
Expand Down Expand Up @@ -97,28 +105,20 @@ function deleteOption(questionIdx: number, optionIdx: number) {
<Skeleton height="300px" />
</div>

<div v-else-if="surveyError">
<span text-xl font-semibold>
{{ surveyError.message }}
</span>
<br>
<br>
<details>
{{ surveyError.data }}
</details>
</div>
<DashboardError v-else-if="surveyError" v-bind="surveyError" />

<!-- formData should be populated when survey loads -->
<div v-if="formData">
<div v-else-if="formData">
<div flex flex-wrap items-center justify-between gap-3>
<Breadcrumb
:home="{
to: '/',
to: '/dashboard',
label: 'Dashboard',
}"
:model="[
{
label: 'Surveys',
to: '/dashboard',
},
{
label: `${formData.title}`,
Expand All @@ -127,8 +127,15 @@ function deleteOption(questionIdx: number, optionIdx: number) {
/>

<div flex gap3>
<Button text label="Print QR" @click="printQR" />
<Button label="Analytics" outline @click="openAnalytics" />
<div>
<Button text label="Preview" :pt="{ root: { class: 'p2!' } }" @click="openPreview" />
</div>
<div>
<Button text label="Print QR" :pt="{ root: { class: 'p2!' } }" @click="printQR" />
</div>
<div>
<Button label="Analytics" outline :pt="{ root: { class: 'p2!' } }" @click="openAnalytics" />
</div>
</div>
</div>

Expand Down Expand Up @@ -198,7 +205,7 @@ function deleteOption(questionIdx: number, optionIdx: number) {
<div v-if="formData.schema[idx].type === 'mcq'" class="flex flex-1 flex-col gap-2">
<div flex items-center justify-between>
<span mb2 font-semibold>Options</span>
<Button label="Add" size="small" @click="addOption(idx)" />
<Button label="Add" :pt="{ root: { class: 'p2!' } }" @click="addOption(idx)" />
</div>
<div v-for="(_, optionIdx) in formData.schema[idx].options" :key="optionIdx" flex items-center>

Check warning on line 210 in pages/dashboard/surveys/[id]/index.vue

View workflow job for this annotation

GitHub Actions / ci

Variable '_' is already declared in the upper scope
<span mr4>
Expand All @@ -215,7 +222,7 @@ function deleteOption(questionIdx: number, optionIdx: number) {
</Card>

<div>
<Button type="submit" label="Save" :loading="pending" />
<Button type="submit" label="Save" severity="success" :pt="{ root: { class: 'p2!' } }" :loading="pending" />
</div>
</form>
</div>
Expand Down
Loading

0 comments on commit b9b31e9

Please sign in to comment.