Two month of work
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { Todo } from "@/types";
|
||||
import { toLocalDate, toDuration, isToday, daysFromNow } from "@/lib/utils";
|
||||
import { Mail, PhoneCall, ClipboardCheck } from "lucide-vue-next"
|
||||
import { Badge } from '@/components/ui/crm-badge'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: Todo[] | null
|
||||
showCompleted?: boolean
|
||||
}>()
|
||||
const todos = ref<Todo[]>([])
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const showCompleted = ref(props.showCompleted)
|
||||
|
||||
watch(() => props.modelValue, value => {
|
||||
todos.value = value as Todo[];
|
||||
})
|
||||
|
||||
const groupedTodos = computed(() => {
|
||||
const groups: Record<string, Todo[]> = {};
|
||||
|
||||
if (todos.value) {
|
||||
for (let todo of todos.value) {
|
||||
if (!todo.dueDate) continue
|
||||
|
||||
let dueDate = new Date(todo.dueDate)
|
||||
|
||||
// today
|
||||
if (isToday(dueDate)) {
|
||||
if (!groups['today']) groups['today'] = []
|
||||
groups['today'].push(todo)
|
||||
}
|
||||
// overdue
|
||||
else if (daysFromNow(dueDate) < 0) {
|
||||
if (!groups['overdue']) groups['overdue'] = []
|
||||
groups['overdue'].push(todo)
|
||||
}
|
||||
// by month
|
||||
else {
|
||||
let month = dueDate.toLocaleDateString('de-DE', { month: 'long' })
|
||||
if (!groups[month]) groups[month] = []
|
||||
groups[month].push(todo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return groups
|
||||
})
|
||||
|
||||
const todoTitle = (title: string) => {
|
||||
// If there's no title, return empty string
|
||||
if (!title) return '';
|
||||
|
||||
// Check if the title contains brackets
|
||||
const match = title.match(/^\[([^\]]+)\]\s*(.*)$/);
|
||||
|
||||
// If there's a match, return the part after the brackets
|
||||
// Otherwise, return the full title
|
||||
return match ? match[2] : title;
|
||||
}
|
||||
|
||||
const todoBadge = (title: string) => {
|
||||
// If there's no title, return empty string
|
||||
if (!title) return '';
|
||||
|
||||
// Check if the title contains brackets
|
||||
const match = title.match(/^\[([^\]]+)\]\s*(.*)$/);
|
||||
|
||||
// If there's a match, return the part inside the brackets
|
||||
// Otherwise, return an empty string
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
|
||||
const shouldDisplay = (todo: Todo) => {
|
||||
if (todo.status?.toLowerCase() !== 'completed') return true
|
||||
|
||||
return showCompleted.value; // && moment(todo.dueDate).isSameOrAfter(moment(new Date()), 'day')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="todos" v-for="(todos, groupKey) in groupedTodos" :key="groupKey">
|
||||
<!-- Group header -->
|
||||
<h3 class="mt-4 mb-2 text-sm text-muted-foreground"
|
||||
:class="{ 'text-destructive! font-bold': groupKey === 'overdue', 'text-foreground! font-bold': groupKey === 'today' }">
|
||||
{{ groupKey === 'today' ? 'Heute' : groupKey === 'overdue' ? 'Verspätet' : groupKey }}
|
||||
</h3>
|
||||
<hr>
|
||||
|
||||
<ul>
|
||||
<li v-for="todo in todos" class="flex gap-3 items-baseline py-2.5 pr-1 transition-all"
|
||||
:class="{ 'scale-y-0 h-0 py-0! my-0 origin-top': !shouldDisplay(todo) }">
|
||||
|
||||
<!-- Check mark -->
|
||||
<div
|
||||
class="relative top-0.75 shrink-0 h-4 aspect-square rounded-full border-muted-foreground has-[input:checked]:border-primary border flex items-center justify-center">
|
||||
<div class="absolute inset-0.5 rounded-full bg-transparent has-[input:checked]:bg-primary">
|
||||
<input type="checkbox" class="absolute -inset-2 opacity-0"
|
||||
:checked="todo.status?.toLowerCase() == 'completed'" :id="todo.id">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Text -->
|
||||
<div class="grow overflow-hidden truncate">
|
||||
<!-- Priority -->
|
||||
<span v-if="todo.priority < 5 && todo.priority > 0" class="mr-2 text-destructive">!!!</span>
|
||||
<span v-if="todo.priority == 5" class="mr-2 text-warning-foreground">!!</span>
|
||||
<span v-if="todo.priority > 5" class="mr-2 text-muted-foreground">!</span>
|
||||
|
||||
<!-- Title -->
|
||||
<label :for="todo.id" class="my-0 px-0 border-0 outline-0 shadow-none" :class="{
|
||||
'line-through text-muted-foreground': todo.status?.toLowerCase() == 'completed'
|
||||
}">
|
||||
{{ todoTitle(todo.title) }}
|
||||
</label>
|
||||
|
||||
<!-- Date -->
|
||||
<div class="text-xs text-muted-foreground flex gap-3 items-center mt-1">
|
||||
<Badge v-if="todoBadge(todo.title)" variant="outline">{{ todoBadge(todo.title)
|
||||
}}
|
||||
</Badge>
|
||||
<span v-if="todo.dueDate"
|
||||
:class="{ 'text-destructive font-bold': todo.status?.toLowerCase() != 'completed' && daysFromNow(todo.dueDate) < 0 }">
|
||||
{{ toDuration(todo.dueDate) }}</span>
|
||||
<!-- <Repeat v-if="todo.recurring" stroke-width="2" :size="14" /> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Icon -->
|
||||
<div class="relative top-0.75 text-muted-foreground shrink-0">
|
||||
<PhoneCall v-if="todo.type?.name === 'phoneCall'" :size="18" />
|
||||
<ClipboardCheck v-else-if="todo.type?.name === 'todo'" :size="18" />
|
||||
<Mail v-else-if="todo.type?.name === 'mail'" :size="18" />
|
||||
</div>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user