-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from fayazara/feat-fayaz-changes
feat: Project overview
- Loading branch information
Showing
9 changed files
with
277 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<template> | ||
<header> | ||
<div | ||
class="col-span-full border-b dark:border-white/10 flex flex-col items-start justify-between gap-x-8 gap-y-4 bg-gray-100 dark:bg-gray-700/10 px-4 py-4 sm:flex-row sm:items-center sm:px-6 lg:px-8" | ||
> | ||
<div> | ||
<div class="flex items-center gap-x-3"> | ||
<UAvatar :src="project.avatar" size="sm" class="ring-white/10 ring-2"/> | ||
<h1 class="flex gap-x-3 text-base leading-7"> | ||
<span class="font-semibold">{{ project.name }}</span> | ||
</h1> | ||
</div> | ||
<p class="mt-1 text-xs leading-6 text-gray-500"> | ||
{{ project.description }} | ||
</p> | ||
</div> | ||
<UButton | ||
label="Add" | ||
icon="i-heroicons-plus-circle" | ||
color="white" | ||
></UButton> | ||
</div> | ||
<div | ||
class="grid grid-cols-2 lg:grid-cols-6 gap-px bg-gray-200 dark:bg-white/10" | ||
> | ||
<div | ||
v-for="stat in stats" | ||
:key="stat.name" | ||
class="py-4 sm:py-6 px-4 sm:px-6 lg:px-8 bg-gray-50 dark:bg-gray-950" | ||
> | ||
<p | ||
class="text-sm font-medium leading-6 text-gray-600 dark:text-gray-400" | ||
> | ||
{{ stat.name }} | ||
</p> | ||
<p class="mt-1 sm:mt-2 flex items-baseline gap-x-2"> | ||
<span | ||
class="text-2xl lg:text-4xl font-semibold tracking-tight text-gray-700 dark:text-white/70" | ||
>{{ stat.value }}</span | ||
> | ||
<span | ||
v-if="stat.unit" | ||
class="text-sm text-gray-600 dark:text-gray-400" | ||
>{{ stat.unit }}</span | ||
> | ||
</p> | ||
</div> | ||
</div> | ||
</header> | ||
</template> | ||
|
||
<script setup> | ||
const props = defineProps({ | ||
project: { | ||
type: Object, | ||
required: true, | ||
}, | ||
stats: { | ||
type: Object, | ||
required: true, | ||
}, | ||
}); | ||
const stats = computed(() => { | ||
return [ | ||
{ name: "Total feedbacks", value: props.stats?.feedbackCount }, | ||
{ name: "Ideas", value: props.stats?.idea || 0 }, | ||
{ name: "Issues", value: props.stats?.issue || 0 }, | ||
{ name: "Other", value: props.stats?.other || 0 }, | ||
{ name: "Open", value: props.stats?.open || 0 }, | ||
{ name: "Closed", value: props.stats?.closed || 0 }, | ||
]; | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,86 +1,80 @@ | ||
<template> | ||
<main> | ||
<header> | ||
<div | ||
class="col-span-full border-b dark:border-white/10 flex flex-col items-start justify-between gap-x-8 gap-y-4 bg-gray-100 dark:bg-gray-700/10 px-4 py-4 sm:flex-row sm:items-center sm:px-6 lg:px-8" | ||
> | ||
<div> | ||
<div class="flex items-center gap-x-3"> | ||
<UAvatar | ||
src="https://cdn.dribbble.com/assets/dribbble-ball-192-23ecbdf987832231e87c642bb25de821af1ba6734a626c8c259a20a0ca51a247.png" | ||
size="2xs" | ||
/> | ||
<h1 class="flex gap-x-3 text-base leading-7"> | ||
<span class="font-semibold">Dribbble</span> | ||
</h1> | ||
</div> | ||
<p class="mt-1 text-xs leading-6 text-gray-500"> | ||
Feedback collected from the Dribble website | ||
</p> | ||
</div> | ||
<UButton | ||
label="Add" | ||
icon="i-heroicons-plus-circle" | ||
color="white" | ||
></UButton> | ||
</div> | ||
<div | ||
class="grid grid-cols-2 lg:grid-cols-6 gap-px bg-gray-200 dark:bg-white/10" | ||
> | ||
<div | ||
v-for="stat in stats" | ||
:key="stat.name" | ||
class="py-4 sm:py-6 px-4 sm:px-6 lg:px-8 bg-gray-50 dark:bg-gray-950" | ||
> | ||
<p | ||
class="text-sm font-medium leading-6 text-gray-600 dark:text-gray-400" | ||
> | ||
{{ stat.name }} | ||
</p> | ||
<p class="mt-1 sm:mt-2 flex items-baseline gap-x-2"> | ||
<span | ||
class="text-2xl lg:text-4xl font-semibold tracking-tight text-gray-700 dark:text-white/70" | ||
>{{ stat.value }}</span | ||
> | ||
<span | ||
v-if="stat.unit" | ||
class="text-sm text-gray-600 dark:text-gray-400" | ||
>{{ stat.unit }}</span | ||
> | ||
</p> | ||
</div> | ||
</div> | ||
</header> | ||
<DashboardProjectsHeader | ||
:project="project.project" | ||
:stats="project.stats" | ||
/> | ||
|
||
<!-- Activity list --> | ||
<div class="border-t dark:border-white/10 pt-11"> | ||
<h2 class="px-4 text-base font-semibold leading-7 sm:px-6 lg:px-8"> | ||
Latest activity | ||
</h2> | ||
<div> | ||
<UButton @click="isOpen = true">Open</UButton> | ||
<div class="pt-4"> | ||
<ul role="list" class="divide-y divide-gray-200 dark:divide-white/10"> | ||
<li | ||
v-for="feedback in project.feedbacks" | ||
:key="feedback.id" | ||
class="relative grid grid-cols-8 gap-x-6 px-4 py-5 hover:bg-gray-100 dark:hover:bg-gray-900 sm:px-6 lg:px-8" | ||
> | ||
<div class="min-w-0 flex-grow col-span-6"> | ||
<p class="truncate font-mono text-sm leading-6"> | ||
{{ feedback.feedback }} | ||
</p> | ||
</div> | ||
<div class="flex items-center gap-x-2 col-span-4 sm:col-span-1"> | ||
<div | ||
:class="[ | ||
statuses[feedback.category].class, | ||
'flex-none rounded-full p-1', | ||
]" | ||
> | ||
<div class="h-1.5 w-1.5 rounded-full bg-current" /> | ||
</div> | ||
<div class="text-sm sm:block"> | ||
{{ statuses[feedback.category].label }} | ||
</div> | ||
</div> | ||
<div class="text-sm sm:block col-span-4 sm:col-span-1"> | ||
{{ timeAgo(feedback.createdAt) }} | ||
</div> | ||
</li> | ||
</ul> | ||
<!-- <UButton @click="isOpen = true">Open</UButton> --> | ||
<USlideover v-model="isOpen" :ui="feedbackStylesBase"> | ||
<DashboardFeedbackDetails /> | ||
<DashboardFeedbackDetails | ||
@toggleFeedbackDetails="toggleFeedbackDetails" | ||
/> | ||
</USlideover> | ||
</div> | ||
</div> | ||
</main> | ||
</template> | ||
|
||
<script setup> | ||
import { formatTimeAgo } from "@vueuse/core"; | ||
const route = useRoute(); | ||
const { projectId } = route.params; | ||
const { data: project } = useFetch(`/api/projects/${projectId}`); | ||
const stats = [ | ||
{ name: "Total feedbacks", value: "405" }, | ||
{ name: "Issues", value: "134" }, | ||
{ name: "Ideas", value: "233" }, | ||
{ name: "Other", value: "90" }, | ||
{ name: "Open", value: "112" }, | ||
{ name: "Closed", value: "335" }, | ||
]; | ||
const { data: project } = useFetch(`/api/projects/${projectId}/overview`); | ||
const feedbackStylesBase = { | ||
base: "relative flex-1 flex flex-col w-full focus:outline-none m-2 rounded-xl", | ||
}; | ||
const isOpen = ref(false); | ||
const toggleFeedbackDetails = () => (isOpen.value = !isOpen.value); | ||
const timeAgo = (date) => formatTimeAgo(new Date(date)); | ||
const statuses = { | ||
idea: { | ||
class: "text-sky-400 bg-sky-400/10", | ||
label: "Idea", | ||
}, | ||
issue: { | ||
class: "text-rose-400 bg-rose-400/10", | ||
label: "Issue", | ||
}, | ||
other: { | ||
class: "text-yellow-400 bg-yellow-400/10", | ||
label: "Other", | ||
}, | ||
}; | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { and, eq, sql, desc } from "drizzle-orm"; | ||
import { | ||
getFeedbackCountOfProject, | ||
feedbackCountByStatus, | ||
feedbackCountByCategory, | ||
countForStatus, | ||
} from "@/server/db/query/analytics"; | ||
|
||
import { getFeedbacks } from "@/server/db/query/feedback"; | ||
import { getProject } from "@/server/db/query/project"; | ||
import { Feedback, Project } from "@/lib/types/project"; | ||
|
||
export default eventHandler(async (event) => { | ||
const session = await requireUserSession(event); | ||
const userId = session.user.id; | ||
|
||
const { getProjectId } = useValidation(event); | ||
const projectId = await getProjectId(); | ||
|
||
let filterBy: any = and( | ||
eq(tables.feedbacks.userId, userId), | ||
eq(tables.feedbacks.projectId, projectId) | ||
); | ||
|
||
const feedbackCount = await getFeedbackCountOfProject(filterBy); | ||
const countByStatusQs = await feedbackCountByStatus(filterBy); | ||
const countByCategoryQs = await feedbackCountByCategory(filterBy); | ||
|
||
let countByStatus: any = {}; | ||
for (const entry of countByStatusQs) { | ||
const { status, count } = entry; | ||
countByStatus[status] = count; | ||
} | ||
|
||
let countByCategory: any = {}; | ||
for (const entry of countByCategoryQs) { | ||
const { category, count } = entry; | ||
countByCategory[category] = count; | ||
} | ||
|
||
const feedbacks: Feedback[] = await getFeedbacks( | ||
{ | ||
id: tables.feedbacks.id, | ||
userId: tables.feedbacks.userId, | ||
userEmail: tables.feedbacks.userEmail, | ||
userName: tables.feedbacks.userName, | ||
category: tables.feedbacks.category, | ||
projectId: tables.feedbacks.projectId, | ||
feedback: tables.feedbacks.feedback, | ||
status: tables.feedbacks.status, | ||
createdAt: tables.feedbacks.createdAt, | ||
updatedAt: tables.feedbacks.updatedAt, | ||
}, | ||
filterBy, | ||
desc(tables.feedbacks.updatedAt), | ||
0, | ||
20 | ||
); | ||
|
||
const project: Project = await getProject(projectId); | ||
|
||
const result = { | ||
stats: { | ||
feedbackCount: feedbackCount.length, | ||
...countByStatus, | ||
...countByCategory, | ||
}, | ||
feedbacks, | ||
project | ||
}; | ||
|
||
return result; | ||
}); | ||
|
||
// Weekly calculation | ||
// SELECT idx, COUNT(idx) | ||
// FROM | ||
// (SELECT ((created_at-min)/(60*60*24*7)) AS idx | ||
// FROM | ||
// (SELECT min(created_at) AS min | ||
// FROM feedbacks) | ||
// LEFT JOIN feedbacks WHERE created_at NOT NULL) | ||
// GROUP BY idx; | ||
|
||
// SELECT strftime('%Y-%W', datetime(created_at,'unixepoch')) AS w, count(1) FROM feedbacks GROUP BY w; |
Oops, something went wrong.