# Date Filter Component Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add a reusable date filter component to the time-tracking page using `@vuepic/vue-datepicker`, enabling filtering by single day or date range.

**Architecture:** A wrapper component `DateFilter.vue` encapsulates `VueDatePicker` with project-consistent styling. It integrates into the existing filter bar on the time-tracking page. Filtering is client-side, matching the existing project/tag filter pattern.

**Tech Stack:** Vue 3, @vuepic/vue-datepicker, Tailwind CSS, @nuxtjs/i18n

---

## Chunk 1: Setup and Component

### Task 1: Install @vuepic/vue-datepicker and configure Nuxt

**Files:**
- Modify: `frontend/package.json`
- Modify: `frontend/nuxt.config.ts:1-66`

- [ ] **Step 1: Install the package**

Run inside the PHP container (where Node is available):

```bash
cd /home/r-dev/Lesstime/frontend && npm install @vuepic/vue-datepicker
```

- [ ] **Step 2: Add transpile config to nuxt.config.ts**

In `frontend/nuxt.config.ts`, add `build.transpile` after the `typescript` block:

```typescript
export default defineNuxtConfig({
    // ... existing config ...
    typescript: {
        strict: true
    },
    build: {
        transpile: ['@vuepic/vue-datepicker']
    }
})
```

- [ ] **Step 3: Commit**

```bash
git add frontend/package.json frontend/package-lock.json frontend/nuxt.config.ts
git commit -m "feat(frontend) : add @vuepic/vue-datepicker dependency"
```

---

### Task 2: Add i18n translations

**Files:**
- Modify: `frontend/i18n/locales/fr.json:167-170`

- [ ] **Step 1: Add date filter translations to fr.json**

In `frontend/i18n/locales/fr.json`, add keys inside the existing `"common"` block:

```json
"common": {
    "cancel": "Annuler",
    "loading": "Chargement...",
    "dateFilter": "Date",
    "today": "Aujourd'hui",
    "thisWeek": "Cette semaine",
    "clear": "Effacer"
}
```

- [ ] **Step 2: Commit**

```bash
git add frontend/i18n/locales/fr.json
git commit -m "feat(frontend) : add date filter i18n translations"
```

---

### Task 3: Create DateFilter.vue component

**Files:**
- Create: `frontend/components/ui/DateFilter.vue`

- [ ] **Step 1: Create the component**

Create `frontend/components/ui/DateFilter.vue`:

```vue
<template>
    <div class="date-filter">
        <VueDatePicker
            v-model="internalValue"
            :range="isRange"
            :enable-time-picker="false"
            :text-input="textInputConfig"
            :locale="'fr'"
            :format="formatDate"
            :preview-format="formatDate"
            auto-apply
            :multi-calendars="false"
            position="left"
            @update:model-value="onUpdate"
            @cleared="onClear"
        >
            <template #dp-input="{ value, onInput, onEnter, onTab, onClear, openMenu }">
                <div class="relative">
                    <input
                        :value="value"
                        class="w-full cursor-pointer rounded-md border border-neutral-300 bg-white px-3 py-[7px] text-sm text-neutral-700 outline-none transition placeholder:text-neutral-400 focus:border-primary-500"
                        :placeholder="placeholder || t('common.dateFilter')"
                        readonly
                        @click="openMenu"
                        @input="onInput"
                        @keydown.enter="onEnter"
                        @keydown.tab="onTab"
                    />
                    <button
                        v-if="value"
                        class="absolute right-2 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-neutral-600"
                        @click.stop="onClear"
                    >
                        <Icon name="mdi:close-circle" size="16" />
                    </button>
                    <Icon
                        v-else
                        name="mdi:calendar"
                        size="16"
                        class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-neutral-400"
                    />
                </div>
            </template>

            <template #action-buttons>
                <div class="flex gap-2 px-3 pb-2">
                    <button
                        class="rounded px-2 py-1 text-xs font-medium text-primary-500 hover:bg-primary-500/10 transition"
                        @click="selectToday"
                    >
                        {{ t('common.today') }}
                    </button>
                    <button
                        class="rounded px-2 py-1 text-xs font-medium text-primary-500 hover:bg-primary-500/10 transition"
                        @click="selectThisWeek"
                    >
                        {{ t('common.thisWeek') }}
                    </button>
                </div>
            </template>
        </VueDatePicker>
    </div>
</template>

<script setup lang="ts">
import VueDatePicker from '@vuepic/vue-datepicker'
import '@vuepic/vue-datepicker/dist/main.css'

const { t } = useI18n()

const props = defineProps<{
    modelValue?: Date | [Date, Date] | null
    placeholder?: string
}>()

const emit = defineEmits<{
    'update:modelValue': [value: Date | [Date, Date] | null]
}>()

const isRange = ref(false)
const internalValue = ref<Date | Date[] | null>(null)
const firstClick = ref<Date | null>(null)

const textInputConfig = {
    enterSubmit: true,
    tabSubmit: true,
    format: 'dd/MM/yyyy',
    rangeSeparator: ' - ',
}

function formatDate(date: Date | Date[]): string {
    if (Array.isArray(date)) {
        return date.map(d => formatSingleDate(d)).join(' - ')
    }
    return formatSingleDate(date)
}

function formatSingleDate(d: Date): string {
    const day = String(d.getDate()).padStart(2, '0')
    const month = String(d.getMonth() + 1).padStart(2, '0')
    const year = d.getFullYear()
    return `${day}/${month}/${year}`
}

function onUpdate(value: Date | Date[] | null) {
    if (value === null) {
        firstClick.value = null
        isRange.value = false
        emit('update:modelValue', null)
        return
    }

    if (Array.isArray(value) && value.length === 2) {
        emit('update:modelValue', [value[0], value[1]])
        return
    }

    if (value instanceof Date) {
        if (firstClick.value === null) {
            // First click — select single day, store for potential range
            firstClick.value = value
            emit('update:modelValue', value)
            // Enable range mode for next click
            nextTick(() => {
                isRange.value = true
            })
        }
    }
}

function onClear() {
    internalValue.value = null
    firstClick.value = null
    isRange.value = false
    emit('update:modelValue', null)
}

function selectToday() {
    const today = new Date()
    today.setHours(0, 0, 0, 0)
    isRange.value = false
    firstClick.value = null
    internalValue.value = today
    emit('update:modelValue', today)
}

function selectThisWeek() {
    const now = new Date()
    const day = now.getDay()
    const monday = new Date(now)
    monday.setDate(now.getDate() - day + (day === 0 ? -6 : 1))
    monday.setHours(0, 0, 0, 0)
    const sunday = new Date(monday)
    sunday.setDate(monday.getDate() + 6)
    sunday.setHours(23, 59, 59, 999)
    isRange.value = true
    firstClick.value = null
    internalValue.value = [monday, sunday]
    emit('update:modelValue', [monday, sunday])
}

// Sync external modelValue to internal state
watch(() => props.modelValue, (val) => {
    if (val === null || val === undefined) {
        internalValue.value = null
        firstClick.value = null
        isRange.value = false
    } else if (Array.isArray(val)) {
        isRange.value = true
        internalValue.value = [...val]
    } else {
        isRange.value = false
        internalValue.value = val
    }
}, { immediate: true })
</script>

<style>
.date-filter .dp__theme_light {
    --dp-primary-color: #222783;
    --dp-primary-text-color: #fff;
    --dp-border-color: #d4d4d8;
    --dp-menu-border-color: #d4d4d8;
    --dp-border-color-hover: #222783;
    --dp-hover-color: #f3f4f8;
    --dp-font-size: 0.875rem;
}

.date-filter .dp__input_wrap {
    width: auto;
}

.date-filter .dp__main {
    font-family: inherit;
}
</style>
```

- [ ] **Step 2: Verify the component renders**

Run `make dev-nuxt` and navigate to the time-tracking page (integration comes in Task 4). Check that no build errors occur.

- [ ] **Step 3: Commit**

```bash
git add frontend/components/ui/DateFilter.vue
git commit -m "feat(frontend) : create DateFilter reusable component"
```

---

## Chunk 2: Integration

### Task 4: Integrate DateFilter into time-tracking page

**Files:**
- Modify: `frontend/pages/time-tracking.vue:15-73` (template filter bar)
- Modify: `frontend/pages/time-tracking.vue:138` (add ref)
- Modify: `frontend/pages/time-tracking.vue:184-193` (filteredEntries computed)

- [ ] **Step 1: Add the date filter ref**

In `frontend/pages/time-tracking.vue`, after line 138 (`selectedProjectId`), add:

```typescript
const selectedDateFilter = ref<Date | [Date, Date] | null>(null)
```

- [ ] **Step 2: Add DateFilter to the template filter bar**

In the filter bar `<div>` (line 15), after the tag MalioSelect block (after line 72), add:

```vue
                <DateFilter
                    v-model="selectedDateFilter"
                />
```

- [ ] **Step 3: Add date filtering to filteredEntries computed**

In `frontend/pages/time-tracking.vue`, update the `filteredEntries` computed (around line 184) to include date filtering:

```typescript
const filteredEntries = computed(() => {
    let result = entries.value
    if (selectedProjectId.value) {
        result = result.filter((e) => e.project?.id === selectedProjectId.value)
    }
    if (selectedTagId.value) {
        result = result.filter((e) => e.tags.some((t) => t.id === selectedTagId.value))
    }
    if (selectedDateFilter.value) {
        if (Array.isArray(selectedDateFilter.value)) {
            const [start, end] = selectedDateFilter.value
            const startDay = new Date(start)
            startDay.setHours(0, 0, 0, 0)
            const endDay = new Date(end)
            endDay.setHours(23, 59, 59, 999)
            result = result.filter((e) => {
                const entryDate = new Date(e.startedAt)
                return entryDate >= startDay && entryDate <= endDay
            })
        } else {
            const day = new Date(selectedDateFilter.value)
            day.setHours(0, 0, 0, 0)
            const nextDay = new Date(day)
            nextDay.setDate(nextDay.getDate() + 1)
            result = result.filter((e) => {
                const entryDate = new Date(e.startedAt)
                return entryDate >= day && entryDate < nextDay
            })
        }
    }
    return result
})
```

- [ ] **Step 4: Verify manually**

Run `make dev-nuxt`, navigate to time-tracking page:
1. Verify DateFilter appears in the filter bar
2. Click a single day — entries filter to that day
3. Click a second day — entries filter to the range
4. Click "Aujourd'hui" — filters to today
5. Click "Cette semaine" — filters to current week
6. Clear the filter — all entries show again

- [ ] **Step 5: Commit**

```bash
git add frontend/pages/time-tracking.vue
git commit -m "feat(frontend) : integrate date filter into time-tracking page"
```
