<template>
  <div class="p-6">
    <h1 class="text-[1.75rem] font-bold font-[family-name:var(--font-heading)] tracking-tight text-text-primary mb-4">Entries</h1>

    <!-- Running timer indicator -->
    <div
      v-if="timerStore.timerState === 'RUNNING'"
      class="flex items-center gap-3 px-4 py-2 mb-4 bg-status-running/10 border border-status-running/20 rounded-lg"
      role="status"
      aria-live="polite"
    >
      <span class="relative flex h-2 w-2 shrink-0" aria-hidden="true">
        <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-status-running opacity-75" />
        <span class="relative inline-flex rounded-full h-2 w-2 bg-status-running" />
      </span>
      <span class="text-[0.75rem] font-medium text-text-primary">Currently tracking</span>
      <div
        v-if="timerStore.selectedProjectId"
        class="w-2 h-2 rounded-full shrink-0"
        :style="{ backgroundColor: projectsStore.projects.find(p => p.id === timerStore.selectedProjectId)?.color || '#6B7280' }"
        aria-hidden="true"
      />
      <span class="text-[0.75rem] text-text-secondary truncate">{{ projectsStore.projects.find(p => p.id === timerStore.selectedProjectId)?.name || '' }}</span>
      <span class="text-[0.75rem] font-mono text-status-running ml-auto shrink-0">{{ formatRunningDuration(timerStore.elapsedSeconds) }}</span>
    </div>

    <!-- Tab Toggle: Time | Expenses -->
    <div data-tour-id="entries-tabs" class="flex items-center gap-2 mb-4" role="tablist" aria-label="Entry type" @keydown="onMainTabKeydown">
      <button
        id="tab-time"
        role="tab"
        :aria-selected="activeTab === 'time'"
        aria-controls="tabpanel-time"
        :tabindex="activeTab === 'time' ? 0 : -1"
        @click="activeTab = 'time'"
        class="px-4 py-2 text-[0.75rem] font-medium rounded-lg transition-colors"
        :class="activeTab === 'time' ? 'bg-accent text-bg-base' : 'text-text-secondary hover:text-text-primary'"
      >
        <span class="flex items-center gap-1.5">
          <Clock class="w-3.5 h-3.5" :stroke-width="1.5" aria-hidden="true" />
          Time
        </span>
      </button>
      <button
        id="tab-expenses"
        role="tab"
        :aria-selected="activeTab === 'expenses'"
        aria-controls="tabpanel-expenses"
        :tabindex="activeTab === 'expenses' ? 0 : -1"
        @click="switchToExpenses"
        class="px-4 py-2 text-[0.75rem] font-medium rounded-lg transition-colors"
        :class="activeTab === 'expenses' ? 'bg-accent text-bg-base' : 'text-text-secondary hover:text-text-primary'"
      >
        <span class="flex items-center gap-1.5">
          <Receipt class="w-3.5 h-3.5" :stroke-width="1.5" aria-hidden="true" />
          Expenses
        </span>
      </button>
    </div>

    <!-- ===== TIME TAB ===== -->
    <template v-if="activeTab === 'time'">
      <div id="tabpanel-time" role="tabpanel" aria-labelledby="tab-time">
    <!-- Filters -->
    <div class="bg-bg-surface rounded-lg p-4 mb-6 flex flex-wrap items-end gap-4">
      <AppDateRangePresets
        :start-date="startDate"
        :end-date="endDate"
        @select="({ start, end }) => { startDate = start; endDate = end; applyFilters() }"
        class="mb-3 w-full"
      />
      <div>
        <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Start Date</label>
        <AppDatePicker
          v-model="startDate"
          placeholder="Start date"
        />
      </div>
      <div>
        <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">End Date</label>
        <AppDatePicker
          v-model="endDate"
          placeholder="End date"
        />
      </div>
      <div>
        <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project</label>
        <AppSelect
          v-model="filterProject"
          :options="projectsStore.projects"
          label-key="name"
          value-key="id"
          placeholder="All Projects"
          :placeholder-value="null"
        />
      </div>
      <div class="flex items-center gap-3">
        <button
          @click="applyFilters"
          class="px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150"
        >
          Apply
        </button>
        <button
          @click="clearFilters"
          class="text-text-secondary text-xs hover:text-text-primary transition-colors"
        >
          Clear
        </button>
      </div>
      <div class="ml-auto flex items-center gap-2">
        <!-- Add entries dropdown -->
        <div class="relative">
          <button
            @click.stop="showActionsMenu = !showActionsMenu"
            class="inline-flex items-center gap-1.5 text-text-secondary text-xs hover:text-text-primary transition-colors border border-border-subtle rounded-lg px-3 py-1.5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
            aria-haspopup="menu"
            :aria-expanded="showActionsMenu"
          >
            <Plus class="w-3 h-3" :stroke-width="2" aria-hidden="true" />
            Add
            <ChevronDown class="w-3 h-3" :stroke-width="2" aria-hidden="true" />
          </button>
          <Transition name="dropdown">
            <div
              v-if="showActionsMenu"
              class="absolute right-0 top-full mt-1 w-44 bg-bg-surface border border-border-visible rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.4)] overflow-hidden z-30 py-1"
              role="menu"
              @click.stop
            >
              <button
                role="menuitem"
                @click="copyPreviousDay(); showActionsMenu = false"
                class="w-full text-left px-3 py-2 text-[0.8125rem] text-text-primary hover:bg-bg-elevated transition-colors"
              >
                Copy Yesterday
              </button>
              <button
                role="menuitem"
                @click="copyPreviousWeek(); showActionsMenu = false"
                class="w-full text-left px-3 py-2 text-[0.8125rem] text-text-primary hover:bg-bg-elevated transition-colors"
              >
                Copy Last Week
              </button>
              <div class="border-t border-border-subtle my-1" />
              <button
                role="menuitem"
                @click="showTemplatePicker = true; showActionsMenu = false"
                class="w-full text-left px-3 py-2 text-[0.8125rem] text-text-primary hover:bg-bg-elevated transition-colors"
              >
                From Template
              </button>
              <button
                role="menuitem"
                @click="openBulkAdd(); showActionsMenu = false"
                class="w-full text-left px-3 py-2 text-[0.8125rem] text-text-primary hover:bg-bg-elevated transition-colors"
              >
                Bulk Add
              </button>
            </div>
          </Transition>
        </div>
        <button
          @click="exportEntriesCSV"
          class="text-text-secondary text-xs hover:text-text-primary transition-colors border border-border-subtle rounded-lg px-3 py-1.5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent inline-flex items-center gap-1"
        >
          <Download class="w-3 h-3" :stroke-width="2" aria-hidden="true" />
          Export CSV
        </button>
      </div>
    </div>

    <!-- Entries count -->
    <p v-if="entriesStore.total > 0" class="text-[0.6875rem] text-text-tertiary mb-2" aria-live="polite">
      Showing {{ entriesStore.entries.length }} of {{ entriesStore.total }} entries
    </p>

    <!-- Entries Table -->
    <div v-if="filteredEntries.length > 0" class="overflow-x-auto">
      <table class="w-full">
        <thead>
          <tr class="border-b border-border-subtle bg-bg-surface">
            <th class="px-4 py-3 w-10">
              <input type="checkbox" :checked="allSelected" :indeterminate="someSelected" @change="toggleSelectAll" v-tooltip="'Select all'" aria-label="Select all entries" class="w-4 h-4 rounded border-border-visible accent-accent" />
            </th>
            <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Date</th>
            <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Project</th>
            <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Task</th>
            <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Description</th>
            <th class="px-4 py-3 text-right text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Duration</th>
            <th class="px-4 py-3 w-20"><span class="sr-only">Actions</span></th>
          </tr>
        </thead>
        <TransitionGroup name="list" tag="tbody">
          <tr
            v-for="(entry, index) in filteredEntries"
            :key="entry.id"
            class="group border-b border-border-subtle hover:bg-bg-elevated transition-colors duration-150"
            :style="{ transitionDelay: `${index * 30}ms` }"
          >
            <td class="px-4 py-3 w-10">
              <input type="checkbox" :checked="selectedIds.has(entry.id!)" @change="toggleSelect(entry.id!)" :aria-label="'Select entry ' + (entry.description || 'untitled')" class="w-4 h-4 rounded border-border-visible accent-accent" />
            </td>
            <td class="px-4 py-3 text-[0.75rem] text-text-primary">
              {{ formatDate(entry.start_time) }}
            </td>
            <td class="px-4 py-3">
              <div class="flex items-center gap-2">
                <div
                  class="w-2 h-2 rounded-full shrink-0"
                  :style="{ backgroundColor: getProjectColor(entry.project_id) }"
                  aria-hidden="true"
                />
                <span class="text-[0.75rem] text-text-primary">{{ getProjectName(entry.project_id) }}</span>
              </div>
            </td>
            <td class="px-4 py-3 text-[0.75rem] text-text-secondary">
              {{ getTaskName(entry.task_id) || '-' }}
            </td>
            <td class="px-4 py-3 text-[0.75rem] text-text-secondary">
              <span v-if="entry.description" v-html="renderMarkdown(entry.description)" class="markdown-inline" />
              <span v-else>-</span>
              <div v-if="entryTags[entry.id!]?.length" class="flex flex-wrap gap-1 mt-1">
                <span
                  v-for="tagId in entryTags[entry.id!]"
                  :key="tagId"
                  class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[0.5625rem]"
                  :style="{ backgroundColor: getTagColor(tagId) + '22', color: getTagColor(tagId) }"
                >
                  {{ getTagName(tagId) }}
                </span>
              </div>
            </td>
            <td class="px-4 py-3 text-right text-[0.75rem] font-mono text-accent-text">
              <div class="flex items-center justify-end gap-1.5">
                <span
                  v-if="entry.billable === 0"
                  class="text-[0.5625rem] font-sans font-medium text-text-tertiary bg-bg-elevated px-1 py-0.5 rounded"
                ><span class="sr-only">Non-billable</span><span aria-hidden="true">NB</span></span>
                {{ formatDuration(entry.duration) }}
                <span
                  v-if="getRoundedDuration(entry.duration) !== null"
                  class="ml-1 text-[0.5625rem] font-sans text-text-tertiary"
                >
                  <Clock class="inline w-3 h-3 mr-0.5" aria-hidden="true" :stroke-width="1.5" />
                  <span class="sr-only">Duration rounded from {{ formatDuration(entry.duration) }} to {{ formatDuration(getRoundedDuration(entry.duration)!) }}</span>
                  Rounded
                </span>
              </div>
            </td>
            <td class="px-4 py-3">
              <div class="flex items-center justify-end gap-1">
                <template v-if="isEntryLocked(entry)">
                  <span class="flex items-center gap-1 text-[0.6875rem] text-amber-500">
                    <Lock class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                    <span class="sr-only">This entry is in a locked week</span>
                  </span>
                </template>
                <template v-else>
                  <div class="flex items-center justify-end gap-1 ">
                    <button
                      @click="replayEntry(entry)"
                      v-tooltip="'Replay entry'"
                      class="p-1.5 text-text-tertiary hover:text-status-running transition-colors duration-150"
                      aria-label="Start timer with this entry"
                    >
                      <Play class="h-3.5 w-3.5" :stroke-width="2" fill="currentColor" aria-hidden="true" />
                    </button>
                    <button
                      @click="duplicateEntry(entry)"
                      v-tooltip="'Duplicate'"
                      class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
                      aria-label="Duplicate entry"
                    >
                      <Copy class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                    </button>
                    <button
                      v-if="entry.duration > 120"
                      @click="openSplitDialog(entry)"
                      v-tooltip="'Split entry'"
                      class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
                      aria-label="Split entry"
                    >
                      <Scissors class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                    </button>
                    <button
                      @click="openEditDialog(entry)"
                      v-tooltip="'Edit'"
                      class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
                      aria-label="Edit entry"
                    >
                      <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
                      </svg>
                    </button>
                    <button
                      @click="confirmDelete(entry)"
                      v-tooltip="'Delete'"
                      class="p-1.5 text-text-tertiary hover:text-status-error transition-colors duration-150"
                      aria-label="Delete entry"
                    >
                      <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                      </svg>
                    </button>
                  </div>
                </template>
              </div>
            </td>
          </tr>
        </TransitionGroup>
      </table>

      <div v-if="entriesStore.hasMore" class="flex items-center justify-center py-4">
        <button
          @click="loadMore"
          :aria-label="'Load more entries, showing ' + entriesStore.entries.length + ' of ' + entriesStore.total"
          class="px-4 py-2 text-[0.75rem] font-medium border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
        >
          Load More
        </button>
      </div>

      <Transition name="slide-up">
        <div
          v-if="selectedIds.size > 0"
          role="toolbar"
          :aria-label="selectedIds.size + ' entries selected'"
          class="sticky bottom-0 z-10 bg-bg-surface border-t border-border-subtle p-3 flex items-center justify-between"
        >
          <span class="text-[0.75rem] text-text-secondary font-medium" aria-live="polite">
            {{ selectedIds.size }} selected
          </span>
          <div class="flex items-center gap-2">
            <button @click="bulkToggleBillable" class="px-3 py-1.5 text-[0.6875rem] border border-border-subtle text-text-secondary rounded-md hover:bg-bg-elevated transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
              Toggle Billable
            </button>
            <div class="relative">
              <button @click="showBulkProjectPicker = !showBulkProjectPicker" class="px-3 py-1.5 text-[0.6875rem] border border-border-subtle text-text-secondary rounded-md hover:bg-bg-elevated transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
                Change Project
              </button>
              <div v-if="showBulkProjectPicker" class="absolute bottom-full left-0 mb-2 w-56 z-20">
                <AppSelect
                  :model-value="null"
                  :options="projectsStore.projects"
                  label-key="name"
                  value-key="id"
                  placeholder="Select project..."
                  searchable
                  @update:model-value="bulkChangeProject($event)"
                />
              </div>
            </div>
            <template v-if="showBulkDeleteConfirm">
              <span class="text-[0.6875rem] text-status-error-text font-medium">Delete {{ selectedIds.size }} entries?</span>
              <button @click="executeBulkDelete" class="px-3 py-1.5 text-[0.6875rem] bg-status-error text-white rounded-md hover:bg-red-600 transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
                Confirm
              </button>
              <button @click="showBulkDeleteConfirm = false" class="px-3 py-1.5 text-[0.6875rem] text-text-tertiary hover:text-text-secondary transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
                No
              </button>
            </template>
            <button v-else @click="confirmBulkDelete" class="px-3 py-1.5 text-[0.6875rem] bg-status-error text-white rounded-md hover:bg-red-600 transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
              Delete
            </button>
            <button @click="clearSelection" class="px-3 py-1.5 text-[0.6875rem] text-text-tertiary hover:text-text-secondary transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">
              Deselect
            </button>
          </div>
        </div>
      </Transition>
    </div>

    <div v-else class="flex flex-col items-center justify-center py-16">
      <ListIcon class="w-12 h-12 text-text-tertiary animate-float" :stroke-width="1.5" aria-hidden="true" />
      <p class="text-sm text-text-secondary mt-4">No entries found</p>
      <p class="text-xs text-text-tertiary mt-2 max-w-xs text-center">Time entries will appear here as you track your work. Try adjusting the date range if you have existing entries.</p>
      <router-link to="/timer" class="mt-4 px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors">
        Go to Timer
      </router-link>
    </div>
      </div>
    </template>

    <!-- ===== EXPENSES TAB ===== -->
    <template v-if="activeTab === 'expenses'">
      <div id="tabpanel-expenses" role="tabpanel" aria-labelledby="tab-expenses">
    <!-- Expense Filters -->
    <div class="bg-bg-surface rounded-lg p-4 mb-6 flex flex-wrap items-end gap-4">
      <AppDateRangePresets
        :start-date="expStartDate"
        :end-date="expEndDate"
        @select="({ start, end }) => { expStartDate = start; expEndDate = end; applyExpenseFilters() }"
        class="mb-3 w-full"
      />
      <div>
        <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Start Date</label>
        <AppDatePicker
          v-model="expStartDate"
          placeholder="Start date"
        />
      </div>
      <div>
        <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">End Date</label>
        <AppDatePicker
          v-model="expEndDate"
          placeholder="End date"
        />
      </div>
      <div>
        <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project</label>
        <AppSelect
          v-model="expFilterProject"
          :options="projectsStore.projects"
          label-key="name"
          value-key="id"
          placeholder="All Projects"
          :placeholder-value="null"
        />
      </div>
      <div>
        <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Category</label>
        <AppSelect
          v-model="expFilterCategory"
          :options="categoryFilterOptions"
          label-key="label"
          value-key="value"
          placeholder="All Categories"
          :placeholder-value="null"
        />
      </div>
      <div class="flex items-center gap-3">
        <button
          @click="applyExpenseFilters"
          class="px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150"
        >
          Apply
        </button>
        <button
          @click="clearExpenseFilters"
          class="text-text-secondary text-xs hover:text-text-primary transition-colors"
        >
          Clear
        </button>
      </div>
      <div class="ml-auto">
        <button
          @click="openAddExpenseDialog"
          class="flex items-center gap-1.5 px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150"
        >
          <Plus class="w-3.5 h-3.5" :stroke-width="2" aria-hidden="true" />
          Add Expense
        </button>
      </div>
    </div>

    <!-- Expense Table -->
    <div v-if="filteredExpenses.length > 0" class="overflow-x-auto">
      <table class="w-full">
        <thead>
          <tr class="border-b border-border-subtle bg-bg-surface">
            <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Date</th>
            <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Project</th>
            <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Category</th>
            <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Description</th>
            <th class="px-4 py-3 text-right text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Amount</th>
            <th class="px-4 py-3 text-center text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Receipt</th>
            <th class="px-4 py-3 text-center text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Invoiced</th>
            <th class="px-4 py-3 w-20"><span class="sr-only">Actions</span></th>
          </tr>
        </thead>
        <TransitionGroup name="list" tag="tbody">
          <tr
            v-for="(expense, index) in filteredExpenses"
            :key="expense.id"
            class="group border-b border-border-subtle hover:bg-bg-elevated transition-colors duration-150"
            :style="{ transitionDelay: `${index * 30}ms` }"
          >
            <td class="px-4 py-3 text-[0.75rem] text-text-primary">
              {{ formatDate(expense.date) }}
            </td>
            <td class="px-4 py-3">
              <div class="flex items-center gap-2">
                <div
                  class="w-2 h-2 rounded-full shrink-0"
                  :style="{ backgroundColor: getProjectColor(expense.project_id) }"
                  aria-hidden="true"
                />
                <span class="text-[0.75rem] text-text-primary">{{ getProjectName(expense.project_id) }}</span>
              </div>
            </td>
            <td class="px-4 py-3 text-[0.75rem] text-text-secondary capitalize">
              {{ expense.category }}
            </td>
            <td class="px-4 py-3 text-[0.75rem] text-text-secondary">
              {{ expense.description || '-' }}
            </td>
            <td class="px-4 py-3 text-right text-[0.75rem] font-mono text-accent-text">
              {{ formatCurrency(expense.amount) }}
            </td>
            <td class="px-4 py-3 text-center">
              <button
                v-if="expense.receipt_path"
                @click="openReceiptLightbox(expense)"
                class="inline-flex items-center gap-1 px-2 py-1 text-[0.6875rem] text-accent-text bg-accent-muted rounded-md hover:bg-accent/20 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
                :aria-label="'View receipt for ' + (expense.description || expense.category)"
              >
                <Image class="w-3.5 h-3.5" :stroke-width="2" aria-hidden="true" />
                View
              </button>
              <span v-else class="text-[0.6875rem] text-text-tertiary">-</span>
            </td>
            <td class="px-4 py-3 text-center">
              <span
                class="text-[0.6875rem] font-medium"
                :class="expense.invoiced ? 'text-status-running' : 'text-text-tertiary'"
              >
                {{ expense.invoiced ? 'Yes' : 'No' }}
              </span>
            </td>
            <td class="px-4 py-3">
              <div class="flex items-center justify-end gap-1 ">
                <button
                  @click="openEditExpenseDialog(expense)"
                  v-tooltip="'Edit'"
                  class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
                  aria-label="Edit expense"
                >
                  <Pencil class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                </button>
                <button
                  @click="confirmDeleteExpense(expense)"
                  v-tooltip="'Delete'"
                  class="p-1.5 text-text-tertiary hover:text-status-error transition-colors duration-150"
                  aria-label="Delete expense"
                >
                  <Trash2 class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                </button>
              </div>
            </td>
          </tr>
        </TransitionGroup>
      </table>
    </div>

    <div v-else class="flex flex-col items-center justify-center py-16">
      <Receipt class="w-12 h-12 text-text-tertiary animate-float" :stroke-width="1.5" aria-hidden="true" />
      <p class="text-sm text-text-secondary mt-4">No expenses found</p>
      <p class="text-xs text-text-tertiary mt-2 max-w-xs text-center">Track project expenses here. Click "Add Expense" to get started.</p>
      <button
        @click="openAddExpenseDialog"
        class="mt-4 px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors"
      >
        Add Expense
      </button>
    </div>
      </div>
    </template>

    <!-- Edit Entry Dialog -->
    <Transition name="modal">
    <div
      v-if="showEditDialog"
      class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
      @click.self="tryCloseEditDialog"
    >
      <div class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-xl p-6 max-h-[calc(100vh-2rem)] overflow-y-auto" role="dialog" aria-modal="true" aria-labelledby="edit-entry-title">
        <h2 id="edit-entry-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-4">{{ editingEntry ? 'Edit Entry' : 'New Entry' }}</h2>

        <form @submit.prevent="handleEdit" class="space-y-4">
          <!-- Two-column layout: Details | Time -->
          <div class="grid grid-cols-2 gap-6">
            <!-- Left column: What -->
            <div class="space-y-4">
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project *</label>
                <AppSelect
                  v-model="editForm.project_id"
                  :options="projectsStore.projects"
                  label-key="name"
                  value-key="id"
                  placeholder="Select project"
                />
              </div>

              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Description</label>
                <input
                  v-model="editForm.description"
                  type="text"
                  class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
                  placeholder="What did you work on?"
                />
              </div>

              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Tags</label>
                <AppTagInput v-model="editEntryTags" />
              </div>

              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Billable</label>
                <button
                  type="button"
                  @click="editForm.billable = editForm.billable === 1 ? 0 : 1"
                  :aria-label="editForm.billable === 1 ? 'Mark as non-billable' : 'Mark as billable'"
                  class="flex items-center gap-2 px-3 py-2 rounded-lg text-[0.8125rem] transition-colors"
                  :class="editForm.billable === 1
                    ? 'bg-accent-muted text-accent-text'
                    : 'bg-bg-elevated text-text-tertiary'"
                >
                  <DollarSign class="w-4 h-4" aria-hidden="true" />
                  {{ editForm.billable === 1 ? 'Billable' : 'Non-billable' }}
                </button>
              </div>
            </div>

            <!-- Right column: When -->
            <div class="space-y-4">
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Duration (minutes)</label>
                <AppNumberInput
                  v-model="durationMinutes"
                  :min="1"
                  :step="1"
                  suffix="min"
                />
              </div>

              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Start Date & Time</label>
                <AppDatePicker
                  v-model="editDate"
                  :show-time="true"
                  v-model:hour="editHour"
                  v-model:minute="editMinute"
                  placeholder="Date & Time"
                />
              </div>
            </div>
          </div>

          <!-- Buttons -->
          <div class="flex justify-end gap-3 pt-4">
            <button
              v-if="editingEntry"
              @click="saveAsTemplate"
              type="button"
              class="mr-auto px-3 py-1.5 text-[0.75rem] text-accent-text border border-border-subtle rounded-lg hover:bg-bg-elevated transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
            >
              Save as Template
            </button>
            <button
              type="button"
              @click="closeEditDialog"
              class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
            >
              Cancel
            </button>
            <button
              type="submit"
              class="px-4 py-2 bg-accent text-bg-base font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150"
            >
              Save
            </button>
          </div>
        </form>
      </div>
    </div>
    </Transition>

    <!-- Delete Entry Confirmation Dialog -->
    <Transition name="modal">
    <div
      v-if="showDeleteDialog"
      class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
      @click.self="cancelDelete"
    >
      <div class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-sm p-6" role="alertdialog" aria-modal="true" aria-labelledby="delete-entry-title" aria-describedby="delete-entry-desc">
        <h2 id="delete-entry-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-2">Delete Entry</h2>
        <p id="delete-entry-desc" class="text-[0.75rem] text-text-secondary mb-6">
          Are you sure you want to delete this time entry? This action cannot be undone.
        </p>
        <div class="flex justify-end gap-3">
          <button
            @click="cancelDelete"
            class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
          >
            Cancel
          </button>
          <button
            @click="handleDelete"
            class="px-4 py-2 border border-status-error text-status-error-text font-medium rounded-lg hover:bg-status-error/10 transition-colors duration-150"
          >
            Delete
          </button>
        </div>
      </div>
    </div>
    </Transition>

    <!-- Add/Edit Expense Dialog -->
    <Transition name="modal">
    <div
      v-if="showExpenseDialog"
      class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
      @click.self="tryCloseExpenseDialog"
    >
      <div class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-md p-6 max-h-[calc(100vh-2rem)] overflow-y-auto" role="dialog" aria-modal="true" aria-labelledby="expense-dialog-title">
        <h2 id="expense-dialog-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-4">
          {{ editingExpense ? 'Edit Expense' : 'Add Expense' }}
        </h2>

        <form @submit.prevent="handleExpenseSave" class="space-y-4">
          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project *</label>
            <AppSelect
              v-model="expenseForm.project_id"
              :options="projectsStore.projects"
              label-key="name"
              value-key="id"
              placeholder="Select project"
            />
          </div>

          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Category *</label>
            <AppSelect
              v-model="expenseForm.category"
              :options="EXPENSE_CATEGORIES"
              label-key="label"
              value-key="value"
              placeholder="Select category"
            />
          </div>

          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Description</label>
            <input
              v-model="expenseForm.description"
              type="text"
              class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
              placeholder="What was this expense for?"
            />
          </div>

          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Amount *</label>
            <AppNumberInput
              v-model="expenseForm.amount"
              :min="0"
              :step="0.01"
              :precision="2"
              :prefix="getCurrencySymbol()"
            />
          </div>

          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Date *</label>
            <AppDatePicker
              v-model="expenseForm.date"
              placeholder="Expense date"
            />
          </div>

          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Receipt</label>
            <div class="flex items-center gap-2">
              <button
                type="button"
                @click="chooseReceipt"
                role="button"
                aria-label="Choose receipt file"
                class="flex items-center gap-2 px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-secondary hover:bg-bg-elevated transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
              >
                <Upload class="w-4 h-4" aria-hidden="true" />
                {{ expenseForm.receipt_path ? 'Change' : 'Upload' }}
              </button>
              <span v-if="expenseForm.receipt_path" class="text-[0.6875rem] text-text-tertiary truncate max-w-[200px]">
                {{ expenseForm.receipt_path.split(/[\\/]/).pop() }}
              </span>
            </div>
          </div>

          <div class="flex justify-end gap-3 pt-4">
            <button
              type="button"
              @click="closeExpenseDialog"
              class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
            >
              Cancel
            </button>
            <button
              type="submit"
              class="px-4 py-2 bg-accent text-bg-base font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150"
            >
              {{ editingExpense ? 'Save' : 'Add' }}
            </button>
          </div>
        </form>
      </div>
    </div>
    </Transition>

    <!-- Delete Expense Confirmation Dialog -->
    <Transition name="modal">
    <div
      v-if="showDeleteExpenseDialog"
      class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
      @click.self="cancelDeleteExpense"
    >
      <div class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-sm p-6" role="alertdialog" aria-modal="true" aria-labelledby="delete-expense-title" aria-describedby="delete-expense-desc">
        <h2 id="delete-expense-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-2">Delete Expense</h2>
        <p id="delete-expense-desc" class="text-[0.75rem] text-text-secondary mb-6">
          Are you sure you want to delete this expense? This action cannot be undone.
        </p>
        <div class="flex justify-end gap-3">
          <button
            @click="cancelDeleteExpense"
            class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
          >
            Cancel
          </button>
          <button
            @click="handleDeleteExpense"
            class="px-4 py-2 border border-status-error text-status-error-text font-medium rounded-lg hover:bg-status-error/10 transition-colors duration-150"
          >
            Delete
          </button>
        </div>
      </div>
    </div>
    </Transition>

    <EntryTemplatePicker
      :show="showTemplatePicker"
      @select="handleTemplateSelect"
      @cancel="showTemplatePicker = false"
    />

    <EntrySplitDialog
      :show="showSplitDialog"
      :entry="splitTarget"
      @close="showSplitDialog = false; splitTarget = null"
      @split="handleSplit"
    />

    <!-- Bulk Add Dialog -->
    <Transition name="modal">
    <div
      v-if="showBulkAdd"
      class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
      @click.self="showBulkAdd = false"
    >
      <div class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-3xl p-6 max-h-[calc(100vh-2rem)] overflow-y-auto" role="dialog" aria-modal="true" aria-labelledby="bulk-add-title">
        <h2 id="bulk-add-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-4">Bulk Add Entries</h2>
        <p class="text-[0.75rem] text-text-tertiary mb-4">Add multiple time entries at once. Each row creates one entry.</p>

        <div class="overflow-x-auto">
          <table class="w-full">
            <thead>
              <tr class="border-b border-border-subtle">
                <th class="px-2 py-2 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium w-28">Date</th>
                <th class="px-2 py-2 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Project</th>
                <th class="px-2 py-2 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Description</th>
                <th class="px-2 py-2 text-right text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium w-20">Hours</th>
                <th class="px-2 py-2 w-8"></th>
              </tr>
            </thead>
            <tbody>
              <tr
                v-for="(row, i) in bulkRows"
                :key="i"
                class="border-b border-border-subtle last:border-0"
              >
                <td class="px-2 py-1.5">
                  <AppDatePicker v-model="row.date" placeholder="Date" />
                </td>
                <td class="px-2 py-1.5">
                  <AppSelect
                    v-model="row.project_id"
                    :options="projectsStore.activeProjects"
                    label-key="name"
                    value-key="id"
                    placeholder="Project"
                    :placeholder-value="0"
                  />
                </td>
                <td class="px-2 py-1.5">
                  <input
                    v-model="row.description"
                    type="text"
                    class="w-full px-2 py-1 bg-bg-inset border border-border-subtle rounded text-[0.75rem] text-text-primary focus:outline-none focus:border-border-visible"
                    placeholder="Description"
                  />
                </td>
                <td class="px-2 py-1.5">
                  <AppNumberInput
                    v-model="row.hours"
                    :min="0"
                    :step="0.25"
                    :precision="2"
                    compact
                  />
                </td>
                <td class="px-2 py-1.5">
                  <button
                    @click="bulkRows.splice(i, 1)"
                    v-tooltip="'Remove row'"
                    class="p-1 text-text-tertiary hover:text-status-error transition-colors"
                    aria-label="Remove row"
                  >
                    <Trash2 class="w-3 h-3" :stroke-width="1.5" aria-hidden="true" />
                  </button>
                </td>
              </tr>
            </tbody>
          </table>
        </div>

        <button
          @click="bulkRows.push({ date: new Date().toISOString().split('T')[0], project_id: 0, description: '', hours: 0 })"
          class="mt-3 text-[0.75rem] text-accent-text hover:underline"
        >
          + Add Row
        </button>

        <div class="flex items-center justify-between mt-4 pt-4 border-t border-border-subtle">
          <p class="text-[0.75rem] text-text-secondary">
            {{ bulkRows.filter(r => r.project_id && r.hours > 0).length }} valid entries, {{ formatNumber(bulkRows.reduce((s, r) => s + (r.hours || 0), 0), 1) }}h total
          </p>
          <div class="flex gap-3">
            <button
              @click="showBulkAdd = false"
              class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
            >
              Cancel
            </button>
            <button
              @click="submitBulkEntries"
              :disabled="bulkRows.filter(r => r.project_id && r.hours > 0).length === 0"
              class="px-4 py-2 bg-accent text-bg-base font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150 disabled:opacity-40 disabled:cursor-not-allowed"
            >
              Create Entries
            </button>
          </div>
        </div>
      </div>
    </div>
    </Transition>

    <AppDiscardDialog :show="showDiscardDialog" @cancel="cancelDiscard" @discard="confirmDiscard" />

    <ReceiptLightbox
      :show="showReceiptLightbox"
      :image-url="receiptLightboxData.imageUrl"
      :description="receiptLightboxData.description"
      :category="receiptLightboxData.category"
      :date="receiptLightboxData.date"
      :amount="receiptLightboxData.amount"
      @close="showReceiptLightbox = false"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
import { List as ListIcon, Copy, DollarSign, Receipt, Plus, Pencil, Trash2, Lock, Image, Upload, Clock, Scissors, Download, Play, ChevronDown } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { invoke, convertFileSrc } from '@tauri-apps/api/core'
import AppNumberInput from '../components/AppNumberInput.vue'
import AppSelect from '../components/AppSelect.vue'
import AppDatePicker from '../components/AppDatePicker.vue'
import AppDateRangePresets from '../components/AppDateRangePresets.vue'
import AppDiscardDialog from '../components/AppDiscardDialog.vue'
import AppTagInput from '../components/AppTagInput.vue'
import EntryTemplatePicker from '../components/EntryTemplatePicker.vue'
import EntrySplitDialog from '../components/EntrySplitDialog.vue'
import ReceiptLightbox from '../components/ReceiptLightbox.vue'
import { useEntriesStore, type TimeEntry } from '../stores/entries'
import { useExpensesStore, EXPENSE_CATEGORIES, type Expense } from '../stores/expenses'
import { useProjectsStore } from '../stores/projects'
import { useTagsStore } from '../stores/tags'
import { useToastStore } from '../stores/toast'
import { useEntryTemplatesStore } from '../stores/entryTemplates'
import { useTimerStore } from '../stores/timer'
import { formatDate, formatCurrency, getCurrencySymbol, formatNumber } from '../utils/locale'
import { useSettingsStore } from '../stores/settings'
import { useFormGuard } from '../utils/formGuard'
import { renderMarkdown } from '../utils/markdown'
import { entriesToCSV, downloadCSV } from '../utils/csvExport'

const entriesStore = useEntriesStore()
const expensesStore = useExpensesStore()
const projectsStore = useProjectsStore()
const tagsStore = useTagsStore()
const toast = useToastStore()
const entryTemplatesStore = useEntryTemplatesStore()
const settingsStore = useSettingsStore()
const timerStore = useTimerStore()
const router = useRouter()

function formatRunningDuration(seconds: number): string {
  const h = Math.floor(seconds / 3600)
  const m = Math.floor((seconds % 3600) / 60)
  const s = seconds % 60
  return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
}
const showActionsMenu = ref(false)

function closeActionsMenu() { showActionsMenu.value = false }
onMounted(() => document.addEventListener('click', closeActionsMenu))
onBeforeUnmount(() => document.removeEventListener('click', closeActionsMenu))

const showTemplatePicker = ref(false)
const showReceiptLightbox = ref(false)
const receiptLightboxData = ref({ imageUrl: '', description: '', category: '', date: '', amount: '' })
const entryTags = ref<Record<number, number[]>>({})
const editEntryTags = ref<number[]>([])
const lockedWeeks = ref<Set<string>>(new Set())
const taskMap = ref<Map<number, string>>(new Map())

async function loadTaskMap() {
  const projectIds = new Set(entriesStore.entries.map(e => e.project_id))
  const map = new Map<number, string>()
  for (const pid of projectIds) {
    const tasks = await projectsStore.fetchTasks(pid)
    for (const t of tasks) {
      if (t.id) map.set(t.id, t.name)
    }
  }
  taskMap.value = map
}

// =============================================
// BULK SELECTION STATE
// =============================================
const selectedIds = ref<Set<number>>(new Set())

const allSelected = computed(() =>
  entriesStore.entries.length > 0 && entriesStore.entries.every(e => e.id && selectedIds.value.has(e.id))
)

const someSelected = computed(() => selectedIds.value.size > 0 && !allSelected.value)

function toggleSelect(id: number) {
  if (selectedIds.value.has(id)) {
    selectedIds.value.delete(id)
  } else {
    selectedIds.value.add(id)
  }
  selectedIds.value = new Set(selectedIds.value)
}

function toggleSelectAll() {
  if (allSelected.value) {
    selectedIds.value = new Set()
  } else {
    selectedIds.value = new Set(entriesStore.entries.map(e => e.id!).filter(Boolean))
  }
}

function clearSelection() {
  selectedIds.value = new Set()
}

async function bulkToggleBillable() {
  const ids = Array.from(selectedIds.value)
  const selected = entriesStore.entries.filter(e => e.id && selectedIds.value.has(e.id))
  const majorityBillable = selected.filter(e => e.billable === 1).length > selected.length / 2
  try {
    await invoke('bulk_update_entries_billable', { ids, billable: majorityBillable ? 0 : 1 })
    clearSelection()
    await entriesStore.fetchEntriesPaginated(startDate.value || undefined, endDate.value || undefined)
    await loadEntryTags()
    await loadTaskMap()
  } catch (e) {
    console.error('Failed to toggle billable:', e)
  }
}

const showBulkDeleteConfirm = ref(false)

function confirmBulkDelete() {
  showBulkDeleteConfirm.value = true
}

async function executeBulkDelete() {
  showBulkDeleteConfirm.value = false
  const ids = Array.from(selectedIds.value)
  try {
    await invoke('bulk_delete_entries', { ids })
    clearSelection()
    await entriesStore.fetchEntriesPaginated(startDate.value || undefined, endDate.value || undefined)
    await loadEntryTags()
    await loadTaskMap()
  } catch (e) {
    console.error('Failed to bulk delete entries:', e)
  }
}

// Main tab state
const activeTab = ref<'time' | 'expenses'>('time')

// Form guard for entry edit dialog
const { showDiscardDialog, snapshot: snapshotForm, tryClose: tryCloseForm, confirmDiscard, cancelDiscard } = useFormGuard()

// Form guard for expense dialog (reuses the same discard pattern)
const { snapshot: snapshotExpenseForm, tryClose: tryCloseExpenseForm } = useFormGuard()

function getEditFormData() {
  return { project_id: editForm.project_id, description: editForm.description, duration: editForm.duration, date: editDate.value, hour: editHour.value, minute: editMinute.value, billable: editForm.billable }
}

function getExpenseFormData() {
  return { project_id: expenseForm.project_id, category: expenseForm.category, description: expenseForm.description, amount: expenseForm.amount, date: expenseForm.date, receipt_path: expenseForm.receipt_path }
}

function tryCloseEditDialog() {
  tryCloseForm(getEditFormData(), closeEditDialog)
}

function tryCloseExpenseDialog() {
  tryCloseExpenseForm(getExpenseFormData(), closeExpenseDialog)
}

// Tab keyboard navigation
function onMainTabKeydown(e: KeyboardEvent) {
  const tabs: Array<'time' | 'expenses'> = ['time', 'expenses']
  const current = tabs.indexOf(activeTab.value)
  let next = current
  if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
    e.preventDefault()
    next = (current + 1) % tabs.length
  } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
    e.preventDefault()
    next = (current - 1 + tabs.length) % tabs.length
  } else if (e.key === 'Home') {
    e.preventDefault()
    next = 0
  } else if (e.key === 'End') {
    e.preventDefault()
    next = tabs.length - 1
  } else {
    return
  }
  activeTab.value = tabs[next]
  if (tabs[next] === 'expenses') {
    loadExpensesIfNeeded()
  }
  document.getElementById(`tab-${tabs[next]}`)?.focus()
}

function switchToExpenses() {
  activeTab.value = 'expenses'
  loadExpensesIfNeeded()
}

const expensesLoaded = ref(false)
async function loadExpensesIfNeeded() {
  if (!expensesLoaded.value) {
    expensesLoaded.value = true
    await expensesStore.fetchExpenses(
      expFilterProject.value || undefined,
      expStartDate.value || undefined,
      expEndDate.value || undefined
    )
  }
}

// =============================================
// TIME ENTRIES - Filter state
// =============================================
const startDate = ref('')
const endDate = ref('')
const filterProject = ref<number | null>(null)

// Dialog state
const showEditDialog = ref(false)
const showDeleteDialog = ref(false)
const editingEntry = ref<TimeEntry | null>(null)
const entryToDelete = ref<TimeEntry | null>(null)

// Edit form
const editForm = reactive<TimeEntry>({
  id: 0,
  project_id: 0,
  description: '',
  start_time: '',
  duration: 0,
  billable: 1
})

// Split date/time refs for edit dialog
const editDate = ref('')
const editHour = ref(0)
const editMinute = ref(0)

// Duration in minutes (computed for editing)
const durationMinutes = computed({
  get: () => Math.round(editForm.duration / 60),
  set: (val: number) => { editForm.duration = val * 60 }
})

// Filtered entries based on date range and project
const filteredEntries = computed(() => {
  let result = [...entriesStore.entries]

  // Filter by project
  if (filterProject.value !== null) {
    result = result.filter(e => e.project_id === filterProject.value)
  }

  // Filter by date range
  if (startDate.value) {
    const start = new Date(startDate.value)
    result = result.filter(e => new Date(e.start_time) >= start)
  }

  if (endDate.value) {
    const end = new Date(endDate.value)
    end.setHours(23, 59, 59)
    result = result.filter(e => new Date(e.start_time) <= end)
  }

  // Sort by date descending
  result.sort((a, b) => new Date(b.start_time).getTime() - new Date(a.start_time).getTime())

  return result
})

// Get project name by ID
function getProjectName(projectId: number): string {
  const project = projectsStore.projects.find(p => p.id === projectId)
  return project?.name || 'Unknown Project'
}

// Get project color by ID
function getProjectColor(projectId: number): string {
  const project = projectsStore.projects.find(p => p.id === projectId)
  return project?.color || '#6B7280'
}

// Get task name by ID
function getTaskName(taskId?: number): string {
  if (!taskId) return ''
  return taskMap.value.get(taskId) || ''
}

// Format duration from seconds to readable format
function formatDuration(seconds: number): string {
  const hours = Math.floor(seconds / 3600)
  const minutes = Math.floor((seconds % 3600) / 60)
  if (hours > 0) {
    return `${hours}h ${minutes}m`
  }
  return `${minutes}m`
}

// Rounding helpers
function roundDuration(seconds: number, increment: number, method: string): number {
  const incrementSeconds = increment * 60
  if (incrementSeconds <= 0) return seconds
  if (method === 'up') return Math.ceil(seconds / incrementSeconds) * incrementSeconds
  if (method === 'down') return Math.floor(seconds / incrementSeconds) * incrementSeconds
  return Math.round(seconds / incrementSeconds) * incrementSeconds
}

function getRoundedDuration(seconds: number): number | null {
  if (settingsStore.settings.rounding_enabled !== 'true') return null
  const increment = parseInt(settingsStore.settings.rounding_increment) || 0
  const method = settingsStore.settings.rounding_method || 'nearest'
  if (increment <= 0) return null
  const rounded = roundDuration(seconds, increment, method)
  return rounded !== seconds ? rounded : null
}

// Tag helper functions
function getTagName(tagId: number): string {
  const tag = tagsStore.tags.find(t => t.id === tagId)
  return tag?.name || ''
}

function getTagColor(tagId: number): string {
  const tag = tagsStore.tags.find(t => t.id === tagId)
  return tag?.color || '#6B7280'
}

async function loadEntryTags() {
  const tags: Record<number, number[]> = {}
  for (const entry of entriesStore.entries) {
    if (entry.id) {
      const entryTagList = await tagsStore.getEntryTags(entry.id)
      tags[entry.id] = entryTagList.map(t => t.id!).filter(id => id != null)
    }
  }
  entryTags.value = tags
}

// Lock helpers
function getWeekStart(dateStr: string): string {
  const d = new Date(dateStr)
  const day = d.getDay()
  const diff = day === 0 ? -6 : 1 - day
  d.setDate(d.getDate() + diff)
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, '0')
  const dd = String(d.getDate()).padStart(2, '0')
  return `${y}-${m}-${dd}`
}

function isEntryLocked(entry: TimeEntry): boolean {
  const ws = getWeekStart(entry.start_time)
  return lockedWeeks.value.has(ws)
}

// Duplicate an entry with current timestamp
async function duplicateEntry(entry: TimeEntry) {
  const now = new Date()
  const newEntry: TimeEntry = {
    project_id: entry.project_id,
    task_id: entry.task_id,
    description: entry.description,
    start_time: now.toISOString(),
    end_time: new Date(now.getTime() + entry.duration * 1000).toISOString(),
    duration: entry.duration,
    billable: entry.billable,
  }
  try {
    await entriesStore.createEntry(newEntry)
  } catch (error) {
    const msg = String(error)
    if (msg.includes('locked week')) {
      toast.error('Cannot add entries to a locked week')
    } else {
      toast.error('Failed to duplicate entry')
    }
    return
  }
  await entriesStore.fetchEntriesPaginated(startDate.value || undefined, endDate.value || undefined)
  await loadEntryTags()
  await loadTaskMap()
}

function replayEntry(entry: TimeEntry) {
  if (!timerStore.isStopped) {
    toast.info('Timer is already running. Stop it first.')
    return
  }
  timerStore.setProject(entry.project_id)
  timerStore.setTask(entry.task_id || null)
  timerStore.setDescription(entry.description || '')
  timerStore.setBillable(entry.billable ?? 1)
  timerStore.start()
  router.push('/timer')
}

// Entry split
const splitTarget = ref<TimeEntry | null>(null)
const showSplitDialog = ref(false)

function openSplitDialog(entry: TimeEntry) {
  splitTarget.value = entry
  showSplitDialog.value = true
}

async function handleSplit({ splitSeconds, descriptionB }: { splitSeconds: number; descriptionB: string }) {
  const entry = splitTarget.value!
  const startMs = new Date(entry.start_time).getTime()
  const entryA: TimeEntry = {
    ...entry,
    duration: splitSeconds,
    end_time: new Date(startMs + splitSeconds * 1000).toISOString(),
  }
  const entryB: TimeEntry = {
    project_id: entry.project_id,
    task_id: entry.task_id,
    description: descriptionB,
    start_time: new Date(startMs + splitSeconds * 1000).toISOString(),
    end_time: entry.end_time,
    duration: entry.duration - splitSeconds,
    billable: entry.billable,
  }
  await entriesStore.updateEntry(entryA)
  const newId = await entriesStore.createEntry(entryB)
  if (newId && entry.id) {
    const tags = entryTags.value[entry.id]
    if (tags?.length) {
      await tagsStore.setEntryTags(newId, tags)
    }
  }
  toast.success('Entry split into two')
  showSplitDialog.value = false
  splitTarget.value = null
  await entriesStore.fetchEntriesPaginated(startDate.value || undefined, endDate.value || undefined)
  await loadEntryTags()
}

// CSV export
async function exportEntriesCSV() {
  const csv = entriesToCSV(
    entriesStore.entries,
    (id) => getProjectName(id),
    (projectId) => {
      const project = projectsStore.projects.find(p => p.id === projectId)
      if (!project?.client_id) return ''
      return ''
    },
    (taskId) => getTaskName(taskId),
    (entryId) => {
      const tags = entryTags.value[entryId]
      if (!tags?.length) return ''
      return tags.map(id => tagsStore.tags.find(t => t.id === id)?.name || '').filter(Boolean).join('; ')
    },
  )
  const ok = await downloadCSV(csv, `zeroclock-entries-${new Date().toISOString().split('T')[0]}.csv`)
  if (ok) toast.success('Entries exported')
}

// Bulk change project
const showBulkProjectPicker = ref(false)

async function bulkChangeProject(projectId: number | null) {
  if (!projectId) return
  const ids = Array.from(selectedIds.value)
  try {
    await invoke('bulk_update_entries_project', { ids, projectId })
    for (const entry of entriesStore.entries) {
      if (entry.id && ids.includes(entry.id)) entry.project_id = projectId
    }
    toast.success(`Updated ${ids.length} entries`)
    clearSelection()
    showBulkProjectPicker.value = false
  } catch {
    toast.error('Failed to change project')
  }
}

// =============================================
// BULK ADD
// =============================================
const showBulkAdd = ref(false)
const bulkRows = ref<Array<{ date: string; project_id: number; description: string; hours: number }>>([])

function openBulkAdd() {
  const today = new Date().toISOString().split('T')[0]
  bulkRows.value = [
    { date: today, project_id: 0, description: '', hours: 0 },
    { date: today, project_id: 0, description: '', hours: 0 },
    { date: today, project_id: 0, description: '', hours: 0 },
  ]
  showBulkAdd.value = true
}

async function submitBulkEntries() {
  const valid = bulkRows.value.filter(r => r.project_id && r.hours > 0)
  if (valid.length === 0) return
  let created = 0
  for (const row of valid) {
    const rowStart = new Date(`${row.date}T09:00:00`)
    const durationSeconds = Math.round(row.hours * 3600)
    try {
      await entriesStore.createEntry({
        project_id: row.project_id,
        description: row.description || undefined,
        start_time: rowStart.toISOString(),
        end_time: new Date(rowStart.getTime() + durationSeconds * 1000).toISOString(),
        duration: durationSeconds,
        billable: 1,
      })
      created++
    } catch (error) {
      const msg = String(error)
      if (msg.includes('locked week')) {
        toast.error('Cannot add entries to a locked week')
      } else {
        toast.error('Failed to create entry')
      }
    }
  }
  if (created > 0) {
    toast.success(`Created ${created} entries`)
    await entriesStore.fetchEntriesPaginated(startDate.value || undefined, endDate.value || undefined)
    await loadEntryTags()
    await loadTaskMap()
  }
  showBulkAdd.value = false
}

// Copy yesterday's entries to today
async function copyPreviousDay() {
  const yesterday = new Date()
  yesterday.setDate(yesterday.getDate() - 1)
  const yStr = yesterday.toISOString().split('T')[0]
  const prevEntries = await invoke<TimeEntry[]>('get_time_entries', {
    startDate: yStr, endDate: yStr
  })
  const now = new Date()
  const todayStr = now.toISOString().split('T')[0]
  for (const e of prevEntries) {
    const startHour = new Date(e.start_time).getHours()
    const startMin = new Date(e.start_time).getMinutes()
    const newStart = new Date(`${todayStr}T${String(startHour).padStart(2,'0')}:${String(startMin).padStart(2,'0')}:00`)
    try {
      await entriesStore.createEntry({
        project_id: e.project_id,
        task_id: e.task_id,
        description: e.description,
        start_time: newStart.toISOString(),
        end_time: new Date(newStart.getTime() + e.duration * 1000).toISOString(),
        duration: e.duration,
        billable: e.billable,
      })
    } catch (error) {
      const msg = String(error)
      if (msg.includes('locked week')) {
        toast.error('Cannot add entries to a locked week')
      } else {
        toast.error('Failed to copy entry')
      }
      return
    }
  }
  await entriesStore.fetchEntriesPaginated(startDate.value || undefined, endDate.value || undefined)
  await loadEntryTags()
  await loadTaskMap()
}

// Copy last week's entries shifted forward 7 days
async function copyPreviousWeek() {
  const now = new Date()
  const prevWeekStart = new Date(now)
  prevWeekStart.setDate(now.getDate() - now.getDay() + 1 - 7)
  const prevWeekEnd = new Date(prevWeekStart)
  prevWeekEnd.setDate(prevWeekStart.getDate() + 6)
  const prevEntries = await invoke<TimeEntry[]>('get_time_entries', {
    startDate: prevWeekStart.toISOString().split('T')[0],
    endDate: prevWeekEnd.toISOString().split('T')[0],
  })
  for (const e of prevEntries) {
    const entryDate = new Date(e.start_time)
    const newDate = new Date(entryDate)
    newDate.setDate(entryDate.getDate() + 7)
    try {
      await entriesStore.createEntry({
        project_id: e.project_id,
        task_id: e.task_id,
        description: e.description,
        start_time: newDate.toISOString(),
        end_time: new Date(newDate.getTime() + e.duration * 1000).toISOString(),
        duration: e.duration,
        billable: e.billable,
      })
    } catch (error) {
      const msg = String(error)
      if (msg.includes('locked week')) {
        toast.error('Cannot add entries to a locked week')
      } else {
        toast.error('Failed to copy entry')
      }
      return
    }
  }
  await entriesStore.fetchEntriesPaginated(startDate.value || undefined, endDate.value || undefined)
  await loadEntryTags()
  await loadTaskMap()
}

// Apply filters
async function applyFilters() {
  await entriesStore.fetchEntriesPaginated(startDate.value || undefined, endDate.value || undefined)
  await loadEntryTags()
  await loadTaskMap()
}

// Load more paginated entries
async function loadMore() {
  await entriesStore.fetchMore(startDate.value || undefined, endDate.value || undefined)
  await loadEntryTags()
  await loadTaskMap()
}

// Clear filters
async function clearFilters() {
  startDate.value = ''
  endDate.value = ''
  filterProject.value = null
  await entriesStore.fetchEntriesPaginated()
  await loadEntryTags()
}

// Open edit dialog
async function openEditDialog(entry: TimeEntry) {
  editingEntry.value = entry
  editForm.id = entry.id || 0
  editForm.project_id = entry.project_id
  editForm.description = entry.description || ''
  editForm.duration = entry.duration
  editForm.billable = entry.billable ?? 1

  // Split start_time into date and time parts
  const dt = new Date(entry.start_time)
  const y = dt.getFullYear()
  const m = String(dt.getMonth() + 1).padStart(2, '0')
  const d = String(dt.getDate()).padStart(2, '0')
  editDate.value = `${y}-${m}-${d}`
  editHour.value = dt.getHours()
  editMinute.value = dt.getMinutes()

  // Load tags for this entry
  if (entry.id) {
    const tags = await tagsStore.getEntryTags(entry.id)
    editEntryTags.value = tags.map(t => t.id!).filter(id => id != null)
  } else {
    editEntryTags.value = []
  }

  snapshotForm(getEditFormData())
  showEditDialog.value = true
}

// Close edit dialog
function closeEditDialog() {
  showEditDialog.value = false
  editingEntry.value = null
}

// Handle edit submit
async function handleEdit() {
  // Reconstruct start time from split date/time fields
  const [y, m, d] = editDate.value.split('-').map(Number)
  const start = new Date(y, m - 1, d, editHour.value, editMinute.value)
  const end = new Date(start.getTime() + editForm.duration * 1000)

  if (editingEntry.value) {
    const updatedEntry: TimeEntry = {
      id: editForm.id,
      project_id: editForm.project_id,
      description: editForm.description || undefined,
      start_time: start.toISOString(),
      end_time: end.toISOString(),
      duration: editForm.duration,
      billable: editForm.billable,
    }

    try {
      await entriesStore.updateEntry(updatedEntry)
    } catch (error) {
      const msg = String(error)
      if (msg.includes('locked week')) {
        toast.error('Cannot modify entries in a locked week')
      } else {
        toast.error('Failed to update entry')
      }
      return
    }

    // Save tags for the edited entry
    if (editForm.id) {
      await tagsStore.setEntryTags(editForm.id, editEntryTags.value)
      await loadEntryTags()
      await loadTaskMap()
    }

    closeEditDialog()
  } else {
    // Creating a new entry from template
    const newEntry: TimeEntry = {
      project_id: editForm.project_id,
      description: editForm.description || undefined,
      start_time: start.toISOString(),
      end_time: end.toISOString(),
      duration: editForm.duration,
      billable: editForm.billable,
    }

    try {
      await entriesStore.createEntry(newEntry)
      await loadEntryTags()
      await loadTaskMap()
      toast.success('Entry created from template')
    } catch (error) {
      toast.error('Failed to create entry')
      return
    }

    closeEditDialog()
  }
}

// Handle template selection from picker
function handleTemplateSelect(template: any) {
  showTemplatePicker.value = false
  editingEntry.value = null
  editForm.id = 0
  editForm.project_id = template.project_id
  editForm.description = template.description || ''
  editForm.duration = template.duration || 0
  editForm.billable = template.billable ?? 1

  const now = new Date()
  const y = now.getFullYear()
  const m = String(now.getMonth() + 1).padStart(2, '0')
  const d = String(now.getDate()).padStart(2, '0')
  editDate.value = `${y}-${m}-${d}`
  editHour.value = now.getHours()
  editMinute.value = now.getMinutes()
  editEntryTags.value = []

  editForm.start_time = now.toISOString()
  snapshotForm(getEditFormData())
  showEditDialog.value = true
}

// Save current edit form as a reusable template
async function saveAsTemplate() {
  const name = editForm.description || `Template ${new Date().toLocaleDateString()}`
  const id = await entryTemplatesStore.createTemplate({
    name,
    project_id: editForm.project_id,
    task_id: undefined,
    description: editForm.description,
    duration: editForm.duration || 0,
    billable: editForm.billable ?? 1,
  })
  if (id) {
    toast.success('Saved as template')
  }
}

// Confirm delete
function confirmDelete(entry: TimeEntry) {
  entryToDelete.value = entry
  showDeleteDialog.value = true
}

// Cancel delete
function cancelDelete() {
  showDeleteDialog.value = false
  entryToDelete.value = null
}

// Handle delete
async function handleDelete() {
  if (entryToDelete.value?.id) {
    try {
      await entriesStore.deleteEntry(entryToDelete.value.id)
    } catch (error) {
      const msg = String(error)
      if (msg.includes('locked week')) {
        toast.error('Cannot delete entries in a locked week')
      } else {
        toast.error('Failed to delete entry')
      }
    }
  }
  cancelDelete()
}

// =============================================
// EXPENSES
// =============================================

const expStartDate = ref('')
const expEndDate = ref('')
const expFilterProject = ref<number | null>(null)
const expFilterCategory = ref<string | null>(null)

const categoryFilterOptions = EXPENSE_CATEGORIES

// Expense dialog state
const showExpenseDialog = ref(false)
const editingExpense = ref<Expense | null>(null)
const showDeleteExpenseDialog = ref(false)
const expenseToDelete = ref<Expense | null>(null)

// Expense form
const expenseForm = reactive<{
  project_id: number
  category: string
  description: string
  amount: number
  date: string
  receipt_path: string
}>({
  project_id: 0,
  category: '',
  description: '',
  amount: 0,
  date: new Date().toISOString().split('T')[0],
  receipt_path: ''
})

// Filtered expenses
const filteredExpenses = computed(() => {
  let result = [...expensesStore.expenses]

  if (expFilterProject.value !== null) {
    result = result.filter(e => e.project_id === expFilterProject.value)
  }

  if (expFilterCategory.value !== null) {
    result = result.filter(e => e.category === expFilterCategory.value)
  }

  if (expStartDate.value) {
    result = result.filter(e => e.date >= expStartDate.value)
  }

  if (expEndDate.value) {
    result = result.filter(e => e.date <= expEndDate.value)
  }

  // Sort by date descending
  result.sort((a, b) => b.date.localeCompare(a.date))

  return result
})

// Open receipt lightbox
function openReceiptLightbox(expense: Expense) {
  if (!expense.receipt_path) return
  receiptLightboxData.value = {
    imageUrl: convertFileSrc(expense.receipt_path),
    description: expense.description || '',
    category: expense.category || '',
    date: expense.date || '',
    amount: formatCurrency(expense.amount)
  }
  showReceiptLightbox.value = true
}

// Choose receipt file
async function chooseReceipt() {
  try {
    const { open } = await import('@tauri-apps/plugin-dialog')
    const selected = await open({
      title: 'Choose receipt image',
      filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'pdf'] }]
    })
    if (selected && typeof selected === 'string') {
      expenseForm.receipt_path = selected
    }
  } catch {
    // User cancelled
  }
}

// Open add expense dialog
function openAddExpenseDialog() {
  editingExpense.value = null
  expenseForm.project_id = 0
  expenseForm.category = ''
  expenseForm.description = ''
  expenseForm.amount = 0
  expenseForm.date = new Date().toISOString().split('T')[0]
  expenseForm.receipt_path = ''
  snapshotExpenseForm(getExpenseFormData())
  showExpenseDialog.value = true
}

// Open edit expense dialog
function openEditExpenseDialog(expense: Expense) {
  editingExpense.value = expense
  expenseForm.project_id = expense.project_id
  expenseForm.category = expense.category
  expenseForm.description = expense.description || ''
  expenseForm.amount = expense.amount
  expenseForm.date = expense.date
  expenseForm.receipt_path = expense.receipt_path || ''
  snapshotExpenseForm(getExpenseFormData())
  showExpenseDialog.value = true
}

// Close expense dialog
function closeExpenseDialog() {
  showExpenseDialog.value = false
  editingExpense.value = null
}

// Save expense (create or update)
async function handleExpenseSave() {
  if (!expenseForm.project_id || !expenseForm.category || !expenseForm.date) return

  // Find the client_id from the project
  const project = projectsStore.projects.find(p => p.id === expenseForm.project_id)
  const clientId = project?.client_id

  if (editingExpense.value) {
    // Update
    const updated: Expense = {
      id: editingExpense.value.id,
      project_id: expenseForm.project_id,
      client_id: clientId,
      category: expenseForm.category,
      description: expenseForm.description || undefined,
      amount: expenseForm.amount,
      date: expenseForm.date,
      receipt_path: expenseForm.receipt_path || undefined,
      invoiced: editingExpense.value.invoiced,
    }
    await expensesStore.updateExpense(updated)
  } else {
    // Create
    const newExpense: Expense = {
      project_id: expenseForm.project_id,
      client_id: clientId,
      category: expenseForm.category,
      description: expenseForm.description || undefined,
      amount: expenseForm.amount,
      date: expenseForm.date,
      receipt_path: expenseForm.receipt_path || undefined,
      invoiced: 0,
    }
    await expensesStore.createExpense(newExpense)
  }
  closeExpenseDialog()
}

// Delete expense
function confirmDeleteExpense(expense: Expense) {
  expenseToDelete.value = expense
  showDeleteExpenseDialog.value = true
}

function cancelDeleteExpense() {
  showDeleteExpenseDialog.value = false
  expenseToDelete.value = null
}

async function handleDeleteExpense() {
  if (expenseToDelete.value?.id) {
    await expensesStore.deleteExpense(expenseToDelete.value.id)
  }
  cancelDeleteExpense()
}

// Apply expense filters
async function applyExpenseFilters() {
  expensesLoaded.value = true
  await expensesStore.fetchExpenses(
    expFilterProject.value || undefined,
    expStartDate.value || undefined,
    expEndDate.value || undefined
  )
}

// Clear expense filters
async function clearExpenseFilters() {
  expStartDate.value = ''
  expEndDate.value = ''
  expFilterProject.value = null
  expFilterCategory.value = null
  expensesLoaded.value = false
  await expensesStore.fetchExpenses()
}

// Timesheet lock type
interface TimesheetLock {
  id: number
  week_start: string
  status: string
  locked_at: string
}

// Load data on mount
onMounted(async () => {
  await Promise.all([
    entriesStore.fetchEntriesPaginated(),
    projectsStore.fetchProjects(),
    tagsStore.fetchTags(),
    settingsStore.fetchSettings()
  ])

  await loadEntryTags()
  await loadTaskMap()

  // Fetch timesheet locks
  try {
    const locks = await invoke<TimesheetLock[]>('get_timesheet_locks')
    lockedWeeks.value = new Set(locks.map(l => l.week_start))
  } catch (error) {
    console.error('Failed to fetch locks:', error)
  }

  // Set default date range to this week
  const now = new Date()
  const weekStart = new Date(now)
  weekStart.setDate(now.getDate() - now.getDay() + 1)
  startDate.value = weekStart.toISOString().split('T')[0]
  endDate.value = now.toISOString().split('T')[0]

  // Set expense default dates too
  expStartDate.value = weekStart.toISOString().split('T')[0]
  expEndDate.value = now.toISOString().split('T')[0]
})
</script>
