Skip to content

Commit 217b76e

Browse files
authored
feat: new visual for task card (#229)
* feat: new visual for task card * chore: confetti
1 parent 37b7167 commit 217b76e

10 files changed

Lines changed: 90 additions & 170 deletions

File tree

apps/atrium-telegram/app/components/SectionTitle.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<h2 class="text-2xl/6 font-bold tracking-tight">
2+
<h2 class="text-2xl/7 font-bold tracking-tight">
33
{{ title }}
44
</h2>
55
</template>

apps/atrium-telegram/app/components/StaffBlock.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<div
55
v-for="user in allUsers"
66
:key="user.id"
7-
class="w-18 flex flex-col gap-1 justify-start items-center scroll-ml-6 snap-start motion-preset-slide-right"
7+
class="w-18 flex flex-col gap-1 justify-start items-center scroll-ml-6 snap-start motion-preset-slide-up"
88
@click="handleClick(user.id)"
99
>
1010
<div class="relative">
Lines changed: 10 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,5 @@
11
<template>
2-
<div class="w-full flex flex-row items-start gap-1">
3-
<UIcon
4-
v-if="isCompleted"
5-
name="i-lucide-check"
6-
class="shrink-0 mt-1.5 size-6 text-secondary"
7-
/>
8-
<UCheckbox
9-
v-else-if="canComplete"
10-
v-model="checkbox"
11-
variant="list"
12-
icon="i-lucide-check"
13-
class="shrink-0 mt-1.5 duration-200 motion-preset-bounce"
14-
:ui="{
15-
base: 'size-6',
16-
}"
17-
@change="handleComplete"
18-
/>
19-
<UCheckbox
20-
v-else
21-
v-model="checkbox"
22-
color="secondary"
23-
variant="list"
24-
icon="i-lucide-check"
25-
:ui="{
26-
base: 'size-6',
27-
}"
28-
class="shrink-0 mt-1.5 duration-200 motion-preset-bounce"
29-
disabled
30-
/>
31-
2+
<div>
323
<UDropdownMenu
334
:items="items"
345
:content="{
@@ -39,44 +10,7 @@
3910
item: 'p-2 motion-preset-slide-left motion-duration-200',
4011
}"
4112
>
42-
<UButton
43-
color="secondary"
44-
:variant="isFocused ? 'ghost' : 'ghost'"
45-
:trailing-icon="isFocused ? 'i-lucide-goal' : undefined"
46-
block
47-
:ui="{
48-
trailingIcon: [
49-
'self-start mt-0.5 text-dimmed',
50-
isFocused ? 'text-secondary' : undefined,
51-
],
52-
}"
53-
class="group/task duration-200 motion-preset-bounce"
54-
@click="vibrate()"
55-
>
56-
<div class="w-full flex flex-col gap-2 items-start">
57-
<div class="flex flex-col gap-1 items-start text-left">
58-
<h4 class="text-base/5 font-medium tg-text">
59-
{{ task.name }}
60-
</h4>
61-
<p v-if="task.description" class="text-sm/4 text-muted font-normal">
62-
{{ task.description }}
63-
</p>
64-
</div>
65-
66-
<div class="flex flex-row gap-y-1 gap-x-2 items-center">
67-
<UBadge
68-
v-if="task?.date"
69-
size="md"
70-
color="primary"
71-
variant="soft"
72-
icon="i-lucide-calendar"
73-
class="shrink-0"
74-
>
75-
{{ df.format(parseDate(task.date).toDate(getLocalTimeZone())) }}
76-
</UBadge>
77-
</div>
78-
</div>
79-
</UButton>
13+
<TaskInfoCard :task="task" />
8014
</UDropdownMenu>
8115
</div>
8216
</template>
@@ -85,26 +19,22 @@
8519
import type { DropdownMenuItem } from '@nuxt/ui'
8620
import type { Task } from '@roll-stack/database'
8721
import { ModalCompleteTask, ModalUpdateTask } from '#components'
88-
import { DateFormatter, getLocalTimeZone, parseDate } from '@internationalized/date'
8922
9023
const { task } = defineProps<{
9124
task: Task
9225
}>()
9326
9427
const { vibrate } = useFeedback()
95-
const taskStore = useTaskStore()
28+
9629
const userStore = useUserStore()
30+
const taskStore = useTaskStore()
9731
9832
const list = computed(() => taskStore.lists.find((list) => list.id === task.listId))
9933
10034
const overlay = useOverlay()
10135
const modalUpdateTask = overlay.create(ModalUpdateTask)
10236
const modalCompleteTask = overlay.create(ModalCompleteTask)
10337
104-
const df = new DateFormatter('ru-RU', {
105-
dateStyle: 'long',
106-
})
107-
10838
const isCompleted = computed(() => !!task.completedAt)
10939
const performer = computed(() => userStore.staff.find((staff) => staff.id === task.performerId))
11040
@@ -113,8 +43,6 @@ const canComplete = computed(() => canEdit.value && !isCompleted.value && (task.
11343
const canFocus = computed(() => task.performerId === userStore.id && !isCompleted.value)
11444
const isFocused = computed(() => task.id === performer.value?.focusedTaskId)
11545
116-
const checkbox = ref(false)
117-
11846
const items = computed<DropdownMenuItem[]>(() => {
11947
const menuItems: DropdownMenuItem[] = [
12048
{
@@ -147,6 +75,12 @@ const items = computed<DropdownMenuItem[]>(() => {
14775
},
14876
condition: canEdit.value,
14977
},
78+
{
79+
label: 'Задача закрыта',
80+
icon: 'i-lucide-check',
81+
disabled: true,
82+
condition: isCompleted.value,
83+
},
15084
]
15185
15286
return menuItems.filter((item) => item.condition)
@@ -177,16 +111,4 @@ async function onUnfocus() {
177111
vibrate('error')
178112
}
179113
}
180-
181-
function handleComplete() {
182-
vibrate()
183-
184-
if (!checkbox.value) {
185-
return
186-
}
187-
188-
modalCompleteTask.open({ taskId: task.id })
189-
190-
checkbox.value = false
191-
}
192114
</script>

apps/atrium-telegram/app/components/TaskInfoCard.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<ActiveCard>
2+
<ActiveCard class="motion-preset-slide-left">
33
<Section>
44
<div class="flex flex-row gap-2 items-center">
55
<UAvatar :src="performer?.avatarUrl ?? undefined" class="size-8" />
@@ -38,6 +38,13 @@
3838
{{ task.description }}
3939
</div>
4040

41+
<div v-if="task?.date" class="flex flex-row gap-2 items-center w-full">
42+
<UIcon name="i-lucide-calendar" class="shrink-0 size-5 text-primary" />
43+
<p class="text-base/5 font-semibold whitespace-pre-wrap break-words">
44+
{{ format(new Date(task.date), 'd MMMM yyyy', { locale: ru }) }}
45+
</p>
46+
</div>
47+
4148
<div v-if="task.report" class="flex flex-row gap-2 items-start w-full">
4249
<UIcon name="i-lucide-clipboard-pen" class="shrink-0 size-5 text-primary" />
4350
<p class="text-base/5 font-semibold whitespace-pre-wrap break-words line-clamp-12">

apps/atrium-telegram/app/components/TaskList.vue

Lines changed: 36 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
<template>
2-
<Section>
3-
<div class="flex flex-row gap-2 items-center justify-between">
4-
<div class="flex flex-row gap-2.5 items-center">
5-
<UPopover
6-
v-if="activeChatMembers?.length"
7-
mode="hover"
8-
:content="{
9-
align: 'center',
10-
side: 'bottom',
11-
sideOffset: 8,
12-
}"
13-
>
2+
<div class="flex flex-col gap-2.5">
3+
<div class="flex flex-row gap-2.5 items-center justify-between">
4+
<SectionTitle :title="list?.name ?? ''" />
5+
6+
<UButton
7+
v-if="canEdit"
8+
variant="solid"
9+
color="secondary"
10+
icon="i-lucide-plus"
11+
label="Создать"
12+
@click="handleCreateTask()"
13+
/>
14+
</div>
15+
16+
<Section class="py-2">
17+
<div class="flex flex-row items-center justify-between">
18+
<div class="h-8">
1419
<UAvatarGroup
15-
:max="2"
16-
size="xs"
20+
v-if="activeChatMembers?.length"
21+
:max="3"
22+
size="md"
1723
:ui="{
1824
base: '-me-1.5',
1925
}"
@@ -25,69 +31,37 @@
2531
alt=""
2632
/>
2733
</UAvatarGroup>
34+
</div>
2835

29-
<template #content>
30-
<div class="h-auto w-fit px-1.5 py-2 flex flex-col gap-2">
31-
<UButtonGroup orientation="vertical">
32-
<UButton
33-
v-for="member in activeChatMembers"
34-
:key="member.id"
35-
:to="`/staff/${member.user.id}`"
36-
:avatar="{ src: member.user.avatarUrl ?? undefined }"
37-
:ui="{
38-
leadingAvatarSize: 'sm',
39-
}"
40-
:label="`${member.user.name} ${member.user.surname}`"
41-
block
42-
color="primary"
43-
variant="link"
44-
class="text-sm justify-start"
45-
/>
46-
</UButtonGroup>
47-
</div>
48-
</template>
49-
</UPopover>
50-
51-
<h3 class="text-lg/5 font-bold">
52-
{{ list?.name }}
53-
</h3>
36+
<div v-if="canEdit" class="flex flex-row gap-2">
37+
<UButton
38+
variant="soft"
39+
color="primary"
40+
size="xl"
41+
icon="i-lucide-pencil"
42+
class="h-10"
43+
@click="handleEditTaskList()"
44+
/>
45+
</div>
5446
</div>
55-
56-
<div v-if="canEdit" class="flex flex-row gap-2">
57-
<UButton
58-
variant="soft"
59-
color="primary"
60-
size="md"
61-
icon="i-lucide-pencil"
62-
@click="handleEditTaskList"
63-
/>
64-
65-
<UButton
66-
variant="solid"
67-
color="secondary"
68-
size="md"
69-
icon="i-lucide-plus"
70-
@click="handleCreateTask"
71-
/>
72-
</div>
73-
</div>
47+
</Section>
7448

7549
<div
7650
v-if="tasks.length"
77-
class="w-full flex flex-col gap-3"
51+
class="w-full flex flex-col gap-2.5"
7852
>
79-
<TaskCard
53+
<TaskActiveCard
8054
v-for="task in tasks"
8155
:key="task.id"
8256
:task="task"
8357
/>
8458
</div>
8559
<template v-else>
86-
<p class="text-base text-muted">
60+
<p class="text-center text-base text-muted">
8761
Активных задач нет
8862
</p>
8963
</template>
90-
</Section>
64+
</div>
9165
</template>
9266

9367
<script setup lang="ts">

apps/atrium-telegram/app/components/form/CompleteTask.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const { taskId } = defineProps<{
4949
5050
const emit = defineEmits(['success', 'submitted'])
5151
52+
const { pop } = useConfetti()
5253
const { vibrate } = useFeedback()
5354
const userStore = useUserStore()
5455
const taskStore = useTaskStore()
@@ -77,6 +78,7 @@ async function onSubmit(event: FormSubmitEvent<CompleteTask>) {
7778
userStore.update(),
7879
])
7980
81+
pop()
8082
vibrate('success')
8183
emit('success')
8284
} catch (error) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
function _useConfetti() {
2+
const isShown = ref(false)
3+
4+
function pop() {
5+
isShown.value = true
6+
setTimeout(() => {
7+
isShown.value = false
8+
}, 5000)
9+
}
10+
11+
return { isShown, pop }
12+
}
13+
14+
export const useConfetti = createSharedComposable(_useConfetti)

apps/atrium-telegram/app/layouts/default.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
<template>
2+
<ClientOnly>
3+
<div
4+
v-if="isShown"
5+
ref="confetti"
6+
class="z-40 mx-auto h-dvh w-dvw fixed inset-0 overflow-y-hidden overscroll-y-none"
7+
>
8+
<div v-confetti="{ particleCount: 240, duration: 4500, stageHeight: confetti?.clientHeight, stageWidth: confetti?.clientWidth, force: 0.4 }" />
9+
</div>
10+
</ClientOnly>
11+
212
<main class="tg-text tg-safe-area">
313
<slot />
414
</main>
@@ -7,5 +17,10 @@
717
</template>
818

919
<script setup lang="ts">
20+
import { vConfetti } from '@neoconfetti/vue'
21+
22+
const confetti = ref<HTMLElement | null>(null)
23+
const { isShown } = useConfetti()
24+
1025
const userStore = useUserStore()
1126
</script>

apps/atrium-telegram/app/pages/task/[taskId]/index.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@
4343
</p>
4444
</div>
4545

46-
<div v-if="task?.date" class="flex flex-row gap-2 items-start w-full">
46+
<div v-if="task?.date" class="flex flex-row gap-2 items-center w-full">
4747
<UIcon name="i-lucide-calendar" class="shrink-0 size-6 text-primary" />
4848
<p class="text-base/5 font-semibold whitespace-pre-wrap break-words">
4949
{{ format(new Date(task.date), 'd MMMM yyyy', { locale: ru }) }}
5050
</p>
5151
</div>
5252

53-
<div v-if="taskList" class="flex flex-row gap-2 items-start w-full">
53+
<div v-if="taskList" class="flex flex-row gap-2 items-center w-full">
5454
<UIcon name="i-lucide-book-marked" class="shrink-0 size-6 text-primary" />
5555
<p class="text-base/5 font-semibold whitespace-pre-wrap break-words">
5656
{{ taskList.name }}

0 commit comments

Comments
 (0)