A simple to-do app focused on tasks that can be completed within a specific time span.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

first version of time points

ToBinio e3caf294 0bec6a55

+147 -25
+7 -1
app/components/edit/DataSelect.vue
··· 6 6 TabsRoot, 7 7 TabsTrigger, 8 8 } from "reka-ui"; 9 - import type { Time, TimeRange, TimeRecurring, UUID } from "~~/shared/types"; 9 + import type { Time, UUID } from "~~/shared/types"; 10 10 import CategorySelect from "../utils/label/CategorySelect.vue"; 11 11 import TagSelect from "../utils/label/TagSelect.vue"; 12 12 import NoteSelect from "./NoteSelect.vue"; 13 + import TimePointSelect from "./time/TimePointSelect.vue"; 13 14 import TimeRangeSelect from "./time/TimeRangeSelect.vue"; 14 15 import TimeRecurringSelect from "./time/TimeRecurringSelect.vue"; 15 16 ··· 94 95 <select v-model="selectedTimeType" :disabled="activeTab !== 'date'"> 95 96 <option value="range">{{ t("date-range") }} </option> 96 97 <option value="recurring">{{ t("date-recurring") }} </option> 98 + <option value="point">{{ t("date-point") }} </option> 97 99 </select> 98 100 </TabsTrigger> 99 101 </TabsList> ··· 117 119 <TimeRecurringSelect 118 120 v-if="selectedTimeType === 'recurring'" 119 121 v-model:timeRecurring="timeRecurring" 122 + /> 123 + <TimePointSelect 124 + v-if="selectedTimeType === 'point'" 125 + v-model:timePoint="timePoint" 120 126 /> 121 127 </TabsContent> 122 128 </div>
+60
app/components/edit/time/TimePointSelect.vue
··· 1 + <script setup lang="ts"> 2 + import { 3 + type DateValue, 4 + getLocalTimeZone, 5 + parseAbsoluteToLocal, 6 + toZoned, 7 + } from "@internationalized/date"; 8 + import { DateFieldInput, DateFieldRoot } from "reka-ui"; 9 + import type { TimePoint } from "~~/shared/types"; 10 + 11 + const { t, locale } = useI18n(); 12 + const timePoint = defineModel<TimePoint | undefined>("timePoint"); 13 + 14 + const dateTime = computed({ 15 + get: () => { 16 + if (timePoint.value) { 17 + return parseAbsoluteToLocal(timePoint.value.time); 18 + } 19 + 20 + return null; 21 + }, 22 + set: (value: DateValue | undefined) => { 23 + if (value) { 24 + timePoint.value = { 25 + time: toZoned(value, getLocalTimeZone()).toAbsoluteString(), 26 + }; 27 + } else { 28 + timePoint.value = undefined; 29 + } 30 + }, 31 + }); 32 + </script> 33 + 34 + <template> 35 + <div class="flex h-full justify-center"> 36 + <div class="mt-2 flex gap-1 max-h-8 border border-secondary rounded"> 37 + <DateFieldRoot 38 + :granularity="'minute'" 39 + :hourCycle="24" 40 + :locale="locale" 41 + v-model="dateTime" 42 + v-slot="{ segments }" 43 + class="flex select-none items-center" 44 + > 45 + <template v-for="item in segments" :key="item.part"> 46 + <DateFieldInput v-if="item.part === 'literal'" :part="item.part"> 47 + {{ item.value }} 48 + </DateFieldInput> 49 + <DateFieldInput 50 + v-else 51 + :part="item.part" 52 + class="px-1 focus:bg-secondary outline-0 rounded" 53 + > 54 + {{ item.value }} 55 + </DateFieldInput> 56 + </template> 57 + </DateFieldRoot> 58 + </div> 59 + </div> 60 + </template>
+1 -3
app/components/edit/time/TimeRangeSelect.vue
··· 17 17 } from "reka-ui"; 18 18 import type { TimeRange } from "~~/shared/types"; 19 19 20 - const { t } = useI18n(); 21 - 22 - const { locale } = useI18n(); 20 + const { t, locale } = useI18n(); 23 21 24 22 const firstDayOfWeek = computed(() => { 25 23 return locale.value === "de" ? 1 : 0;
+21 -3
app/components/todo/TimeDisplay.vue
··· 1 1 <script setup lang="ts"> 2 - import { getLocalTimeZone, parseDate } from "@internationalized/date"; 2 + import { 3 + getLocalTimeZone, 4 + parseAbsolute, 5 + parseAbsoluteToLocal, 6 + parseDate, 7 + } from "@internationalized/date"; 3 8 import { addDays, sanitizeDate } from "~~/shared/date"; 4 - import type { TimeRange } from "~~/shared/types"; 9 + import type { TimePoint, TimeRange } from "~~/shared/types"; 5 10 6 - const props = defineProps<{ timeframe: TimeRange }>(); 11 + const props = defineProps<{ timeframe: TimeRange | TimePoint }>(); 7 12 8 13 const timeFrameString = computed(() => { 14 + if ("time" in props.timeframe) { 15 + return props.timeframe.time; 16 + } 17 + 9 18 return `${props.timeframe.start.toWellFormed()} - ${props.timeframe.end.toWellFormed()}`; 10 19 }); 11 20 12 21 const time = computed(() => { 22 + if ("time" in props.timeframe) { 23 + let time = parseAbsoluteToLocal(props.timeframe.time).toDate().getTime(); 24 + 25 + return { 26 + start: time - 1000 * 60 * 30, 27 + end: time + 1000 * 60 * 30, 28 + }; 29 + } 30 + 13 31 return { 14 32 start: parseDate(props.timeframe.start) 15 33 .toDate(getLocalTimeZone())
+4
app/components/todo/Todo.vue
··· 88 88 } 89 89 90 90 const timeRange = computed(() => { 91 + if (props.data.time?.type === "point") { 92 + return props.data.time; 93 + } 94 + 91 95 return getTimeRange(props.data)?.visuel; 92 96 }); 93 97
+31 -7
app/composables/useFilteredTodos.ts
··· 1 - import { getLocalTimeZone, parseDate, today } from "@internationalized/date"; 1 + import { 2 + getLocalTimeZone, 3 + parseAbsoluteToLocal, 4 + parseDate, 5 + today, 6 + } from "@internationalized/date"; 2 7 import { getTimeRange } from "~~/shared/todo"; 3 8 import type { Todo } from "~~/shared/types"; 4 9 ··· 32 37 //filter by date 33 38 if (filter.time === "all") return true; 34 39 40 + const weekStart = today(getLocalTimeZone()); 41 + const weekEnd = weekStart.add({ weeks: 1 }); 42 + 43 + if (todo.time?.type === "point") { 44 + const time = parseAbsoluteToLocal(todo.time.time); 45 + 46 + if (filter.time === "today") { 47 + if ( 48 + time.compare(weekStart) < 0 || 49 + time.compare(weekStart.add({ days: 1 })) > 0 50 + ) { 51 + return false; 52 + } 53 + } else if (filter.time === "week") { 54 + if ( 55 + time.compare(weekStart) < 0 || 56 + time.compare(weekEnd.add({ days: 1 })) > 0 57 + ) { 58 + return false; 59 + } 60 + } 61 + return true; 62 + } 63 + 35 64 const time = getTimeRange(todo); 36 65 if (time === undefined) return false; 37 66 ··· 39 68 const end = parseDate(time.visuel.end); 40 69 41 70 if (filter.time === "today") { 42 - const now = today(getLocalTimeZone()); 43 - 44 - if (start.compare(now) > 0 || end.compare(now) < 0) { 71 + if (start.compare(weekStart) > 0 || end.compare(weekStart) < 0) { 45 72 return false; 46 73 } 47 74 } else if (filter.time === "week") { 48 - const weekStart = today(getLocalTimeZone()); 49 - const weekEnd = weekStart.add({ weeks: 1 }); 50 - 51 75 if (start.compare(weekEnd) > 0 || end.compare(weekStart) < 0) { 52 76 return false; 53 77 }
+1
i18n/locales/de.json
··· 27 27 "create": "Erstellen", 28 28 "date-range": "Datumbereich", 29 29 "date-recurring": "Wiederholung", 30 + "date-point": "Zeitpunkt", 30 31 "recurring-none": "Keine", 31 32 "recurring-daily": "Täglich", 32 33 "recurring-weekly": "Wöchentlich"
+1
i18n/locales/en.json
··· 27 27 "create": "Create", 28 28 "date-range": "Date Range", 29 29 "date-recurring": "Date Recurring", 30 + "date-point": "Date Point", 30 31 "recurring-none": "None", 31 32 "recurring-daily": "Daily", 32 33 "recurring-weekly": "Weekly"
+2 -2
package.json
··· 10 10 "postinstall": "nuxt prepare", 11 11 "lint": "biome check", 12 12 "lint:fix": "biome check --write", 13 - "lint:full": "npm run lint && npm run lint:typecheck", 13 + "lint:full": "npm run lint:typecheck && npm run lint", 14 14 "lint:typecheck": "nuxi typecheck", 15 15 "lint:summary": "biome check --reporter summary", 16 16 "test": "vitest", ··· 33 33 "marked": "^17.0.5", 34 34 "nuxt": "4.4.2", 35 35 "pinia": "^3.0.4", 36 - "reka-ui": "^2.9.0", 36 + "reka-ui": "^2.9.3", 37 37 "tailwindcss": "^4.2.2", 38 38 "unstorage": "^1.17.5", 39 39 "uuid": "^13.0.0",
+8 -8
pnpm-lock.yaml
··· 54 54 specifier: ^3.0.4 55 55 version: 3.0.4(typescript@6.0.2)(vue@3.5.31(typescript@6.0.2)) 56 56 reka-ui: 57 - specifier: ^2.9.0 58 - version: 2.9.2(vue@3.5.31(typescript@6.0.2)) 57 + specifier: ^2.9.3 58 + version: 2.9.3(vue@3.5.31(typescript@6.0.2)) 59 59 tailwindcss: 60 60 specifier: ^4.2.2 61 61 version: 4.2.2 ··· 67 67 version: 13.0.0 68 68 vaul-vue: 69 69 specifier: ^0.4.1 70 - version: 0.4.1(reka-ui@2.9.2(vue@3.5.31(typescript@6.0.2)))(vue@3.5.31(typescript@6.0.2)) 70 + version: 0.4.1(reka-ui@2.9.3(vue@3.5.31(typescript@6.0.2)))(vue@3.5.31(typescript@6.0.2)) 71 71 vue: 72 72 specifier: ^3.5.31 73 73 version: 3.5.31(typescript@6.0.2) ··· 4270 4270 resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} 4271 4271 hasBin: true 4272 4272 4273 - reka-ui@2.9.2: 4274 - resolution: {integrity: sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw==} 4273 + reka-ui@2.9.3: 4274 + resolution: {integrity: sha512-C9lCVxsSC7uYD0Nbgik1+14FNndHNprZmf0zGQt0ZDYIt5KxXV3zD0hEqNcfRUsEEJvVmoRsUkJnASBVBeaaUw==} 4275 4275 peerDependencies: 4276 4276 vue: '>= 3.4.0' 4277 4277 ··· 9385 9385 9386 9386 regexp-tree@0.1.27: {} 9387 9387 9388 - reka-ui@2.9.2(vue@3.5.31(typescript@6.0.2)): 9388 + reka-ui@2.9.3(vue@3.5.31(typescript@6.0.2)): 9389 9389 dependencies: 9390 9390 '@floating-ui/dom': 1.7.6 9391 9391 '@floating-ui/vue': 1.1.11(vue@3.5.31(typescript@6.0.2)) ··· 9886 9886 9887 9887 uuid@13.0.0: {} 9888 9888 9889 - vaul-vue@0.4.1(reka-ui@2.9.2(vue@3.5.31(typescript@6.0.2)))(vue@3.5.31(typescript@6.0.2)): 9889 + vaul-vue@0.4.1(reka-ui@2.9.3(vue@3.5.31(typescript@6.0.2)))(vue@3.5.31(typescript@6.0.2)): 9890 9890 dependencies: 9891 9891 '@vueuse/core': 10.11.1(vue@3.5.31(typescript@6.0.2)) 9892 - reka-ui: 2.9.2(vue@3.5.31(typescript@6.0.2)) 9892 + reka-ui: 2.9.3(vue@3.5.31(typescript@6.0.2)) 9893 9893 vue: 3.5.31(typescript@6.0.2) 9894 9894 transitivePeerDependencies: 9895 9895 - '@vue/composition-api'
+11 -1
shared/types.ts
··· 9 9 }); 10 10 export type TimeRange = Omit<z.infer<typeof TimeRange>, "type">; 11 11 12 + const TimePoint = z.object({ 13 + type: z.literal("point"), 14 + time: z.iso.datetime(), 15 + }); 16 + export type TimePoint = Omit<z.infer<typeof TimePoint>, "type">; 17 + 12 18 const TimeRecurringDaily = z.object({ 13 19 type: z.literal("recurring"), 14 20 mode: z.literal("daily"), ··· 30 36 | Omit<z.infer<typeof TimeRecurringDaily>, "type"> 31 37 | Omit<z.infer<typeof TimeRecurringWeekly>, "type">; 32 38 33 - export const Time = z.discriminatedUnion("type", [TimeRange, TimeRecurring]); 39 + export const Time = z.discriminatedUnion("type", [ 40 + TimeRange, 41 + TimePoint, 42 + TimeRecurring, 43 + ]); 34 44 export type Time = z.infer<typeof Time>; 35 45 36 46 export const Todo = z.object({