Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions apps/atrium-telegram/app/components/NavigationButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div
class="relative py-1 w-full rounded-2xl flex flex-row items-center justify-center"
:class="[
isThisRoute && 'tg-bg-button tg-text-button motion-translate-y-in',
(isThisRoute || isThisName) && 'tg-bg-button tg-text-button motion-translate-y-in',
]"
>
<UIcon
Expand All @@ -15,20 +15,29 @@
class="size-6 motion-preset-shake"
/>
<UIcon
v-else-if="isFlowInnerPage && canReturnToMain && route.name === 'flow'"
v-else-if="router.currentRoute.value.meta.canReturn && isThisName"
name="i-lucide-undo-2"
class="size-6 motion-preset-shake"
/>
<UIcon
<UChip
v-else
:name="route.icon"
class="size-6 motion-preset-shake"
/>
size="3xl"
:show="!!route.badge"
:text="route.badge"
:ui="{
base: '-right-1 px-1.5 py-2 ring-2 tg-text-button font-bold motion-translate-y-loop-25 motion-duration-3500',
}"
>
<UIcon
:name="route.icon"
class="size-6 motion-preset-shake"
/>
</UChip>
Comment on lines +22 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don’t show chip when badge is '0'

Guard with a numeric check to avoid rendering a “0” badge.

-      <UChip
-        v-else
-        size="3xl"
-        :show="!!route.badge"
-        :text="route.badge"
+      <UChip
+        v-else
+        size="3xl"
+        :show="Number(route.badge) > 0"
+        :text="route.badge"
         :ui="{
           base: '-right-1 px-1.5 py-2 ring-2 tg-text-button font-bold motion-translate-y-loop-25 motion-duration-3500',
         }"
       >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<UChip
v-else
:name="route.icon"
class="size-6 motion-preset-shake"
/>
size="3xl"
:show="!!route.badge"
:text="route.badge"
:ui="{
base: '-right-1 px-1.5 py-2 ring-2 tg-text-button font-bold motion-translate-y-loop-25 motion-duration-3500',
}"
>
<UIcon
:name="route.icon"
class="size-6 motion-preset-shake"
/>
</UChip>
<UChip
v-else
size="3xl"
:show="Number(route.badge) > 0"
:text="route.badge"
:ui="{
base: '-right-1 px-1.5 py-2 ring-2 tg-text-button font-bold motion-translate-y-loop-25 motion-duration-3500',
}"
>
<UIcon
:name="route.icon"
class="size-6 motion-preset-shake"
/>
</UChip>
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/NavigationButton.vue around lines 22–35,
the UChip currently uses :show="!!route.badge" which causes a "0" badge to
render; change the show binding to a numeric check like
:show="Number(route.badge) > 0" (or use parseInt and isFinite if you expect
non-numeric values) so the chip only renders when the badge value is a positive
number.

</div>
<p
class="text-xs font-medium"
:class="[
isThisRoute && 'tg-text',
(isThisRoute || isThisName) && 'tg-text',
]"
>
{{ route.title }}
Expand All @@ -40,10 +49,11 @@
const { route } = defineProps<{ route: NavigationRoute }>()

const { vibrate } = useFeedback()
const { canScrollToTop, isMainPage, isFlowInnerPage, canReturnToMain } = useNavigation()
const { canScrollToTop, isMainPage } = useNavigation()
const router = useRouter()

const isThisRoute = computed(() => route.exact ? router.currentRoute.value.path === route.path : router.currentRoute.value.path.startsWith(route.path))
const isThisName = computed(() => route.names.includes(router.currentRoute.value.name))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Type-safety fix: route.name can be RouteRecordName | null.

Guard before includes to avoid TS errors and edge cases.

-const isThisName = computed(() => route.names.includes(router.currentRoute.value.name))
+const isThisName = computed(() => {
+  const n = router.currentRoute.value.name
+  return typeof n === 'string' && route.names.includes(n)
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isThisName = computed(() => route.names.includes(router.currentRoute.value.name))
const isThisName = computed(() => {
const n = router.currentRoute.value.name
return typeof n === 'string' && route.names.includes(n)
})
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/NavigationButton.vue around line 47, the
computed uses route.names.includes(router.currentRoute.value.name) but
router.currentRoute.value.name can be RouteRecordName | null; guard against null
before calling includes by first reading router.currentRoute.value.name into a
local const, check it is not null (or is a string/RouteRecordName) and only then
call route.names.includes with the narrowed value so TypeScript is satisfied and
runtime edge cases are avoided.


function handleScrollToTop() {
vibrate()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<Section>
<Section class="motion-preset-slide-down motion-delay-600">
<div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 items-center">
<UIcon name="i-lucide-users-round" class="size-8 text-primary" />
Expand Down
4 changes: 2 additions & 2 deletions apps/atrium-telegram/app/components/flow/ItemCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<ActiveCard>
<UIcon name="i-lucide-clipboard-check" class="size-8 text-primary" />

<h3 class="text-lg/5 font-bold">
<h3 class="text-2xl/5 font-bold">
{{ item.title }}
</h3>

<div class="w-full text-sm/4 font-normal whitespace-pre-wrap break-words line-clamp-16">
<div class="w-full text-base/5 font-normal whitespace-pre-wrap break-words line-clamp-12">
{{ item.description }}
</div>

Expand Down
4 changes: 2 additions & 2 deletions apps/atrium-telegram/app/components/flow/KitchensOnline.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<Section v-if="kitchensOnline">
<Section v-if="kitchensOnline" class="motion-preset-slide-down motion-delay-100">
<div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 items-center">
<UChip
Expand All @@ -20,7 +20,7 @@
</div>
</Section>

<Section v-else>
<Section v-else class="motion-preset-slide-down motion-delay-100">
<div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 items-center">
<UIcon name="i-lucide-store" class="size-8 text-primary" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<Section>
<Section class="motion-preset-slide-down motion-delay-400">
<div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 items-center">
<UIcon name="i-lucide-receipt-text" class="size-8 text-primary" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<Section>
<Section class="motion-preset-slide-down motion-delay-200">
<div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 items-center">
<UIcon name="i-lucide-shopping-bag" class="size-8 text-primary" />
Expand Down
15 changes: 6 additions & 9 deletions apps/atrium-telegram/app/composables/useNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@ function _useNavigation() {
const { t } = useI18n()
const { y } = useWindowScroll()

const taskStore = useTaskStore()

const mainRoutes = computed<NavigationRoute[]>(() => [
{
path: '/',
name: 'flow',
names: ['index', 'flow-itemId'],
title: t('app.flow'),
icon: 'i-lucide-waves',
exact: true,
},
{
path: '/epic',
name: 'epic',
names: ['epic', 'epic-epicId'],
title: t('app.epics'),
icon: 'i-lucide-crown',
},
{
path: '/tasks',
name: 'tasks',
names: ['tasks'],
title: t('app.my-tasks'),
icon: 'i-lucide-layout-dashboard',
badge: taskStore.myTodayTasks.length.toString(),
},
Comment on lines 23 to 28

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid showing a “0” badge for /tasks

Currently badge is always a string; '0' is truthy and will render a chip. Return undefined when count is zero.

     {
       path: '/tasks',
       names: ['tasks'],
       title: t('app.my-tasks'),
       icon: 'i-lucide-layout-dashboard',
-      badge: taskStore.myTodayTasks.length.toString(),
+      badge: taskStore.myTodayTasks.length > 0
+        ? String(taskStore.myTodayTasks.length)
+        : undefined,
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
path: '/tasks',
name: 'tasks',
names: ['tasks'],
title: t('app.my-tasks'),
icon: 'i-lucide-layout-dashboard',
badge: taskStore.myTodayTasks.length.toString(),
},
path: '/tasks',
names: ['tasks'],
title: t('app.my-tasks'),
icon: 'i-lucide-layout-dashboard',
badge: taskStore.myTodayTasks.length > 0
? String(taskStore.myTodayTasks.length)
: undefined,
},
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/composables/useNavigation.ts around lines 23 to 28,
the badge is always set to a string which renders a "0" chip; change the badge
to return undefined when the task count is zero. Replace the current badge
expression with a conditional that checks taskStore.myTodayTasks.length === 0
and returns undefined in that case, otherwise returns the numeric count as a
string (or number) so no "0" badge is shown.

])

Expand All @@ -30,15 +33,9 @@ function _useNavigation() {
const isMainPage = computed(() => router.currentRoute.value.path === '/')
const canScrollToTop = computed(() => y.value > 650)

const isFlowInnerPage = computed(() => router.currentRoute.value.path.startsWith('/flow'))
const canReturnToMain = computed(() => isFlowInnerPage.value && router.currentRoute.value.path !== '/')

return {
isNavigationShown,

isFlowInnerPage,
canReturnToMain,

isMainPage,
canScrollToTop,

Expand Down
5 changes: 5 additions & 0 deletions apps/atrium-telegram/app/pages/epic/[epicId]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
<script setup lang="ts">
import { ModalUpdateEpic } from '#components'

definePageMeta({
name: 'epic-epicId',
canReturn: true,
})

const { params } = useRoute('epic-epicId')

const { vibrate } = useFeedback()
Expand Down
7 changes: 6 additions & 1 deletion apps/atrium-telegram/app/pages/flow/[itemId]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<UIcon name="i-lucide-clipboard-check" class="size-10 text-primary" />
</div>

<h2 class="text-xl font-bold">
<h2 class="text-2xl/6 font-bold">
{{ item?.title }}
</h2>

Expand All @@ -17,6 +17,11 @@
</template>

<script setup lang="ts">
definePageMeta({
name: 'flow-itemId',
canReturn: true,
})

const { params } = useRoute('flow-itemId')

const flowStore = useFlowStore()
Expand Down
6 changes: 5 additions & 1 deletion apps/atrium-telegram/app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<FlowFeedbackAverage />
</div>

<div class="flex flex-col gap-2">
<div class="flex flex-col gap-4">
<NuxtLink
v-for="item in flowStore.items"
:key="item.id"
Expand All @@ -24,6 +24,10 @@
</template>

<script setup lang="ts">
definePageMeta({
name: 'index',
})

const flowStore = useFlowStore()

useHead({
Expand Down
14 changes: 3 additions & 11 deletions apps/atrium-telegram/app/pages/tasks/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
{{ userStore.name }}, привет!
</h2>
<p class="text-base/5">
<template v-if="myTodayTasks.length">
<template v-if="taskStore.myTodayTasks.length">
Сегодня по плану еще
<ULink
as="button"
Expand All @@ -22,7 +22,7 @@
]"
@click="taskStore.isTodayOnly = !taskStore.isTodayOnly"
>
{{ myTodayTasks.length }} {{ pluralizationRu(myTodayTasks.length, ['задача', 'задачи', 'задач']) }}
{{ taskStore.myTodayTasks.length }} {{ pluralizationRu(taskStore.myTodayTasks.length, ['задача', 'задачи', 'задач']) }}
</ULink>.
</template>
<span>
Expand All @@ -38,7 +38,7 @@
</Section>

<TaskList
v-for="taskList in myLists"
v-for="taskList in taskStore.myLists"
:key="taskList.id"
:list-id="taskList.id"
:current-user-id="userStore.id as string"
Expand All @@ -61,7 +61,6 @@

<script setup lang="ts">
import { ModalCreateTaskList, ModalUploadUserAvatar } from '#components'
import { getLocalTimeZone, isToday, parseDate } from '@internationalized/date'

const { vibrate } = useFeedback()

Expand All @@ -72,13 +71,6 @@ const modalUploadUserAvatar = overlay.create(ModalUploadUserAvatar)
const userStore = useUserStore()
const taskStore = useTaskStore()

const myLists = computed(() =>
taskStore.lists.filter(
(taskList) => taskList.chat?.members.some((member) => member.userId === userStore.id),
).filter((taskList) => taskStore.isTodayOnly ? taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone())).length : true),
)
const myTodayTasks = computed(() => myLists.value.flatMap((taskList) => taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone()))))

function handleUploadUserAvatar() {
vibrate()
modalUploadUserAvatar.open()
Expand Down
13 changes: 13 additions & 0 deletions apps/atrium-telegram/app/stores/task.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Chat, ChatMember, Task, TaskList, User } from '@roll-stack/database'
import { getLocalTimeZone, isToday, parseDate } from '@internationalized/date'
import { initDataRaw as _initDataRaw, useSignal } from '@telegram-apps/sdk-vue'

type ChatWithData = Chat & {
Expand All @@ -15,6 +16,15 @@ export const useTaskStore = defineStore('task', () => {
const isTodayOnly = ref(false)
const isInitialized = ref(false)

const userStore = useUserStore()

const myLists = computed(() =>
lists.value.filter(
(taskList) => taskList.chat?.members.some((member) => member.userId === userStore.id),
).filter((taskList) => isTodayOnly.value ? taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone())).length : true),
)
const myTodayTasks = computed(() => myLists.value.flatMap((taskList) => taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone()))))

Comment on lines +19 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Predicate returns number; fix boolean logic and avoid repeated TZ lookups

The second filter relies on a numeric length for truthiness, which is a TS type mismatch. Also, you call getLocalTimeZone repeatedly inside tight loops.

   const userStore = useUserStore()
 
-  const myLists = computed(() =>
-    lists.value.filter(
-      (taskList) => taskList.chat?.members.some((member) => member.userId === userStore.id),
-    ).filter((taskList) => isTodayOnly.value ? taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone())).length : true),
-  )
-  const myTodayTasks = computed(() => myLists.value.flatMap((taskList) => taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone()))))
+  const tz = getLocalTimeZone()
+  const isTaskForToday = (task: Task) =>
+    !task.completedAt && task.date && isToday(parseDate(task.date), tz)
+
+  const myLists = computed(() =>
+    lists.value
+      .filter((taskList) =>
+        taskList.chat?.members.some((member) => member.userId === userStore.id),
+      )
+      .filter((taskList) => !isTodayOnly.value || taskList.tasks.some(isTaskForToday)),
+  )
+  const myTodayTasks = computed(() =>
+    myLists.value.flatMap((taskList) => taskList.tasks.filter(isTaskForToday)),
+  )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const userStore = useUserStore()
const myLists = computed(() =>
lists.value.filter(
(taskList) => taskList.chat?.members.some((member) => member.userId === userStore.id),
).filter((taskList) => isTodayOnly.value ? taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone())).length : true),
)
const myTodayTasks = computed(() => myLists.value.flatMap((taskList) => taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone()))))
const userStore = useUserStore()
const tz = getLocalTimeZone()
const isTaskForToday = (task: Task) =>
!task.completedAt && task.date && isToday(parseDate(task.date), tz)
const myLists = computed(() =>
lists.value
.filter((taskList) =>
taskList.chat?.members.some((member) => member.userId === userStore.id),
)
.filter((taskList) => !isTodayOnly.value || taskList.tasks.some(isTaskForToday)),
)
const myTodayTasks = computed(() =>
myLists.value.flatMap((taskList) => taskList.tasks.filter(isTaskForToday)),
)
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/stores/task.ts around lines 19 to 27, the second
.filter uses a numeric .length as a boolean (causing a TS type mismatch) and
calls getLocalTimeZone() repeatedly inside loops; change the predicate to
explicitly return a boolean (e.g., use .some(...) or .filter(...).length > 0)
and compute getLocalTimeZone() once outside the computed to reuse the value, and
also avoid redundant parseDate calls by evaluating task date/isToday once per
task (e.g., create a small helper or reuse a local variable inside the
callbacks) so the filters return proper booleans and eliminate repeated TZ
lookups.

const initDataRaw = useSignal(_initDataRaw)

async function update() {
Expand Down Expand Up @@ -82,6 +92,9 @@ export const useTaskStore = defineStore('task', () => {
isTodayOnly,
isInitialized,

myLists,
myTodayTasks,

update,
setAsFocused,
setAsUnfocused,
Expand Down
3 changes: 2 additions & 1 deletion apps/atrium-telegram/shared/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ export type FlowItemWithData = FlowItem & {

export type NavigationRoute = {
path: string
name: string
names: string[]
title: string
icon: string
exact?: boolean
badge?: string
}
2 changes: 1 addition & 1 deletion apps/web-app/server/tasks/ai/daily-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default defineTask({
const date = format(new Date(), 'd MMMM', { locale: ru })
await repository.flow.createItem({
type: 'daily_task_report',
title: date,
title: `Задачи ${date}`,
description: finalMessage,
})

Expand Down