Skip to content

Commit e0480e5

Browse files
authored
feat: new task actions (#98)
1 parent 6bd7157 commit e0480e5

15 files changed

Lines changed: 845 additions & 35 deletions

File tree

apps/atrium-telegram/app/assets/css/styles.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
--ui-border: var(--tg-theme-section-separator-color);
55
--ui-border-accented: var(--tg-theme-hint-color);
66
--ui-text: var(--tg-theme-text-color);
7+
--ui-text-dimmed: var(--tg-theme-hint-color);
78
--ui-text-muted: var(--tg-theme-hint-color);
89
--ui-bg: var(--tg-theme-section-bg-color);
910
--ui-bg-accented: var(--tg-theme-hint-color);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="flex flex-col gap-4 justify-center items-center h-full min-h-40 border border-default border-dashed rounded-lg">
2+
<div class="flex flex-col gap-4 justify-center items-center h-full min-h-40 border-2 border-default border-dashed rounded-lg">
33
<UIcon :name="icon" class="size-10 text-muted/50" />
44

55
<UButton

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
isClosing && '!translate-y-full',
1919
]"
2020
>
21-
<div ref="target" class="relative mb-10 p-4 md:p-6 lg:p-8 space-y-3 tg-bg-section tg-text rounded-xl shadow-lg max-h-[70dvh] overflow-y-auto !overflow-visible">
22-
<h3 class="text-xl md:text-2xl font-medium leading-tight">
21+
<div ref="target" class="relative mb-10 p-4 space-y-3 tg-bg-section tg-text rounded-xl shadow-lg max-h-[70dvh] overflow-y-auto !overflow-visible">
22+
<h3 class="text-xl font-medium leading-tight">
2323
{{ title }}
2424
</h3>
2525

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
size="xl"
1313
icon="i-lucide-check"
1414
class="shrink-0 mt-1.5 duration-200 motion-preset-bounce"
15-
@change="onStartCompleting"
15+
@change="handleComplete"
1616
/>
1717
<UCheckbox
1818
v-else
@@ -56,13 +56,12 @@
5656
</div>
5757

5858
<div class="flex flex-row gap-y-1 gap-x-2 items-center">
59-
<UTooltip v-if="task.performerId" :text="`${performer?.name} ${performer?.surname}`">
60-
<UAvatar
61-
:src="performer?.avatarUrl ?? ''"
62-
size="xs"
63-
class="shrink-0"
64-
/>
65-
</UTooltip>
59+
<UAvatar
60+
v-if="performer"
61+
:src="performer?.avatarUrl ?? ''"
62+
size="xs"
63+
class="shrink-0"
64+
/>
6665

6766
<UBadge
6867
v-if="task?.date"
@@ -123,21 +122,30 @@ const items = computed<DropdownMenuItem[]>(() => {
123122
label: 'Выполнить',
124123
icon: 'i-lucide-check',
125124
disabled: !canComplete.value,
126-
onSelect: () => modalCompleteTask.open({ taskId: task.id }),
125+
onSelect: () => {
126+
vibrate()
127+
modalCompleteTask.open({ taskId: task.id })
128+
},
127129
condition: canComplete.value,
128130
},
129131
{
130132
label: isFocused.value ? 'Убрать фокус' : 'Сфокусироваться',
131133
icon: 'i-lucide-goal',
132134
disabled: false,
133-
onSelect: isFocused.value ? onUnfocus : onFocus,
135+
onSelect: () => {
136+
vibrate()
137+
isFocused.value ? onUnfocus() : onFocus()
138+
},
134139
condition: canFocus.value,
135140
},
136141
{
137142
label: 'Редактировать',
138143
icon: 'i-lucide-edit',
139144
disabled: isCompleted.value,
140-
onSelect: () => modalUpdateTask.open({ taskId: task.id }),
145+
onSelect: () => {
146+
vibrate()
147+
modalUpdateTask.open({ taskId: task.id })
148+
},
141149
condition: canEdit.value,
142150
},
143151
]
@@ -175,7 +183,7 @@ async function onUnfocus() {
175183
}
176184
}
177185
178-
function onStartCompleting() {
186+
function handleComplete() {
179187
vibrate()
180188
181189
if (!checkbox.value) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
:default-value="taskStore.isTodayOnly"
88
:ui="{
99
root: 'flex-row-reverse items-center',
10-
label: 'mr-3 text-base/5 md:text-lg/5 font-semibold',
10+
label: 'mr-3 text-base/5 font-semibold',
1111
}"
1212
@change="vibrate"
1313
/>

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
@submit="onSubmit"
77
>
88
<div class="mb-4 flex flex-col gap-1">
9-
<h2 class="text-base/5 font-semibold">
9+
<h2 class="text-base/5 font-medium">
1010
{{ task?.name }}
1111
</h2>
1212
<p class="text-sm/4 text-muted">
@@ -18,7 +18,7 @@
1818
<UTextarea
1919
v-model="state.report"
2020
:rows="4"
21-
size="xl"
21+
size="lg"
2222
class="w-full"
2323
/>
2424
</UFormField>
@@ -27,7 +27,7 @@
2727
type="submit"
2828
variant="solid"
2929
color="secondary"
30-
size="xl"
30+
size="lg"
3131
trailing-icon="i-lucide-flag"
3232
block
3333
:label="$t('app.update.task.close')"
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
<template>
2+
<UForm
3+
:validate="createValidator(createTaskSchema)"
4+
:state="state"
5+
class="flex flex-col gap-3"
6+
@submit="onSubmit"
7+
>
8+
<UFormField
9+
:label="$t('common.title')"
10+
name="name"
11+
required
12+
>
13+
<UInput
14+
v-model="state.name"
15+
:placeholder="$t('app.task.name-placeholder')"
16+
size="lg"
17+
class="w-full items-center justify-center"
18+
/>
19+
</UFormField>
20+
21+
<UFormField :label="$t('app.task.description')" name="description">
22+
<UTextarea
23+
v-model="state.description"
24+
:rows="4"
25+
size="lg"
26+
class="w-full"
27+
/>
28+
</UFormField>
29+
30+
<UFormField label="Исполнитель" name="performerId">
31+
<USelectMenu
32+
v-model="selectedPerformer"
33+
:items="availablePerformersItems"
34+
:avatar="selectedPerformer?.avatar"
35+
:placeholder="$t('common.select')"
36+
:content="{
37+
side: 'top',
38+
}"
39+
size="lg"
40+
class="w-full"
41+
/>
42+
</UFormField>
43+
44+
<div class="grid grid-cols-1 md:grid-cols-2">
45+
<UPopover>
46+
<UFormField :label="$t('common.date')" name="date">
47+
<UInput
48+
:value="selectedDate ? df.format(selectedDate.toDate(getLocalTimeZone())) : ''"
49+
placeholder="Выберите дату"
50+
size="lg"
51+
class="w-full items-center justify-center cursor-pointer"
52+
:ui="{ trailing: 'pe-1.5' }"
53+
>
54+
<template v-if="selectedDate" #trailing>
55+
<UButton
56+
color="neutral"
57+
variant="ghost"
58+
size="md"
59+
icon="i-lucide-x"
60+
@click="selectedDate = undefined"
61+
/>
62+
</template>
63+
</UInput>
64+
</UFormField>
65+
66+
<template #content>
67+
<UCalendar v-model="selectedDate" class="p-2" />
68+
</template>
69+
</UPopover>
70+
</div>
71+
72+
<UButton
73+
type="submit"
74+
variant="solid"
75+
color="secondary"
76+
size="lg"
77+
block
78+
class="mt-3"
79+
:label="$t('common.create')"
80+
/>
81+
</UForm>
82+
</template>
83+
84+
<script setup lang="ts">
85+
import type { CreateTask } from '#shared/services/task'
86+
import type { CalendarDate } from '@internationalized/date'
87+
import type { FormSubmitEvent } from '@nuxt/ui'
88+
import { createTaskSchema } from '#shared/services/task'
89+
import { DateFormatter, getLocalTimeZone } from '@internationalized/date'
90+
91+
const { performerId, listId } = defineProps<{
92+
performerId?: string
93+
listId: string
94+
}>()
95+
96+
const emit = defineEmits(['success', 'submitted'])
97+
98+
const { t } = useI18n()
99+
const actionToast = useActionToast()
100+
101+
const taskStore = useTaskStore()
102+
const userStore = useUserStore()
103+
104+
const list = computed(() => taskStore.lists.find((list) => list.id === listId))
105+
106+
const availablePerformers = computed(() => userStore.staff.filter((s) => list.value?.chat?.members.some((m) => m.user.id === s.id)))
107+
const availablePerformersItems = computed(() => [{
108+
label: 'Без исполнителя',
109+
value: '',
110+
avatar: {
111+
src: undefined,
112+
alt: '',
113+
},
114+
}, ...availablePerformers.value.map((staff) => ({
115+
label: `${staff.name} ${staff.surname}`,
116+
value: staff.id,
117+
avatar: {
118+
src: staff.avatarUrl ?? undefined,
119+
alt: '',
120+
},
121+
}))])
122+
123+
const state = ref<Partial<CreateTask>>({
124+
name: undefined,
125+
description: undefined,
126+
date: undefined,
127+
performerId,
128+
listId,
129+
})
130+
131+
const selectedPerformer = ref<{ label: string, value: string, avatar: { src: string | undefined, alt: string } } | undefined>(state.value.performerId ? availablePerformersItems.value.find((performer) => performer?.value === state.value.performerId) : undefined)
132+
133+
watch(selectedPerformer, () => {
134+
if (selectedPerformer.value?.value === '') {
135+
state.value.performerId = null
136+
return
137+
}
138+
139+
state.value.performerId = selectedPerformer.value?.value
140+
})
141+
142+
const df = new DateFormatter('ru-RU', {
143+
dateStyle: 'long',
144+
})
145+
146+
const selectedDate = shallowRef<CalendarDate | undefined>()
147+
148+
watch(selectedDate, () => {
149+
if (!selectedDate.value) {
150+
state.value.date = null
151+
return
152+
}
153+
154+
state.value.date = selectedDate.value.toString()
155+
})
156+
157+
async function onSubmit(event: FormSubmitEvent<CreateTask>) {
158+
const toastId = actionToast.start()
159+
emit('submitted')
160+
161+
try {
162+
await $fetch(`/api/task`, {
163+
method: 'POST',
164+
headers: {
165+
Authorization: `tma ${userStore.initDataRaw}`,
166+
},
167+
body: event.data,
168+
})
169+
170+
await taskStore.update()
171+
172+
actionToast.success(toastId, t('toast.task-created'))
173+
emit('success')
174+
} catch (error) {
175+
console.error(error)
176+
actionToast.error(toastId)
177+
}
178+
}
179+
</script>

0 commit comments

Comments
 (0)