feat: replace all native selects and date inputs with custom components

This commit is contained in:
Your Name
2026-02-17 22:27:51 +02:00
parent 5fea155332
commit b646dcd801
5 changed files with 60 additions and 92 deletions

View File

@@ -6,35 +6,28 @@
<div class="bg-bg-surface rounded-lg p-4 mb-6 flex flex-wrap items-end gap-4"> <div class="bg-bg-surface rounded-lg p-4 mb-6 flex flex-wrap items-end gap-4">
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Start Date</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Start Date</label>
<input <AppDatePicker
v-model="startDate" v-model="startDate"
type="date" placeholder="Start date"
class="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"
/> />
</div> </div>
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">End Date</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">End Date</label>
<input <AppDatePicker
v-model="endDate" v-model="endDate"
type="date" placeholder="End date"
class="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"
/> />
</div> </div>
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project</label>
<select <AppSelect
v-model="filterProject" v-model="filterProject"
class="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" :options="projectsStore.projects"
> label-key="name"
<option :value="null">All Projects</option> value-key="id"
<option placeholder="All Projects"
v-for="project in projectsStore.projects" :placeholder-value="null"
:key="project.id" />
:value="project.id"
>
{{ project.name }}
</option>
</select>
</div> </div>
<button <button
@click="applyFilters" @click="applyFilters"
@@ -139,19 +132,13 @@
<!-- Project --> <!-- Project -->
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project *</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project *</label>
<select <AppSelect
v-model="editForm.project_id" v-model="editForm.project_id"
required :options="projectsStore.projects"
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" label-key="name"
> value-key="id"
<option placeholder="Select project"
v-for="project in projectsStore.projects" />
:key="project.id"
:value="project.id"
>
{{ project.name }}
</option>
</select>
</div> </div>
<!-- Description --> <!-- Description -->
@@ -239,6 +226,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue' import { ref, reactive, computed, onMounted } from 'vue'
import { List as ListIcon } from 'lucide-vue-next' import { List as ListIcon } from 'lucide-vue-next'
import AppSelect from '../components/AppSelect.vue'
import AppDatePicker from '../components/AppDatePicker.vue'
import { useEntriesStore, type TimeEntry } from '../stores/entries' import { useEntriesStore, type TimeEntry } from '../stores/entries'
import { useProjectsStore } from '../stores/projects' import { useProjectsStore } from '../stores/projects'

View File

@@ -126,39 +126,30 @@
<!-- Client --> <!-- Client -->
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Client *</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Client *</label>
<select <AppSelect
v-model="createForm.client_id" v-model="createForm.client_id"
required :options="clientsStore.clients"
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" label-key="name"
> value-key="id"
<option :value="0">Select a client</option> placeholder="Select a client"
<option :placeholder-value="0"
v-for="client in clientsStore.clients" />
:key="client.id"
:value="client.id"
>
{{ client.name }}
</option>
</select>
</div> </div>
<!-- Date --> <!-- Date -->
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Invoice Date *</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Invoice Date *</label>
<input <AppDatePicker
v-model="createForm.date" v-model="createForm.date"
type="date" placeholder="Invoice date"
required
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"
/> />
</div> </div>
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Due Date</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Due Date</label>
<input <AppDatePicker
v-model="createForm.due_date" v-model="createForm.due_date"
type="date" placeholder="Due date"
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"
/> />
</div> </div>
</div> </div>
@@ -341,6 +332,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { FileText } from 'lucide-vue-next' import { FileText } from 'lucide-vue-next'
import AppSelect from '../components/AppSelect.vue'
import AppDatePicker from '../components/AppDatePicker.vue'
import { useInvoicesStore, type Invoice } from '../stores/invoices' import { useInvoicesStore, type Invoice } from '../stores/invoices'
import { useClientsStore } from '../stores/clients' import { useClientsStore } from '../stores/clients'
import { useToastStore } from '../stores/toast' import { useToastStore } from '../stores/toast'

View File

@@ -92,19 +92,14 @@
<!-- Client --> <!-- Client -->
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Client</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Client</label>
<select <AppSelect
v-model="formData.client_id" v-model="formData.client_id"
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" :options="clientsStore.clients"
> label-key="name"
<option :value="undefined">No client</option> value-key="id"
<option placeholder="No client"
v-for="client in clientsStore.clients" :placeholder-value="undefined"
:key="client.id" />
:value="client.id"
>
{{ client.name }}
</option>
</select>
</div> </div>
<!-- Hourly Rate --> <!-- Hourly Rate -->
@@ -201,6 +196,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { FolderKanban } from 'lucide-vue-next' import { FolderKanban } from 'lucide-vue-next'
import AppSelect from '../components/AppSelect.vue'
import { useProjectsStore, type Project } from '../stores/projects' import { useProjectsStore, type Project } from '../stores/projects'
import { useClientsStore } from '../stores/clients' import { useClientsStore } from '../stores/clients'

View File

@@ -6,18 +6,16 @@
<div class="bg-bg-surface rounded-lg p-4 mb-6 flex flex-wrap items-end gap-4"> <div class="bg-bg-surface rounded-lg p-4 mb-6 flex flex-wrap items-end gap-4">
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Start Date</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Start Date</label>
<input <AppDatePicker
v-model="startDate" v-model="startDate"
type="date" placeholder="Start date"
class="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"
/> />
</div> </div>
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">End Date</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">End Date</label>
<input <AppDatePicker
v-model="endDate" v-model="endDate"
type="date" placeholder="End date"
class="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"
/> />
</div> </div>
<button <button
@@ -109,6 +107,7 @@ import { ref, computed, onMounted } from 'vue'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { Bar } from 'vue-chartjs' import { Bar } from 'vue-chartjs'
import { BarChart3 } from 'lucide-vue-next' import { BarChart3 } from 'lucide-vue-next'
import AppDatePicker from '../components/AppDatePicker.vue'
import { useToastStore } from '../stores/toast' import { useToastStore } from '../stores/toast'
import { import {
Chart as ChartJS, Chart as ChartJS,

View File

@@ -26,37 +26,27 @@
<div class="grid grid-cols-2 gap-4 mb-4"> <div class="grid grid-cols-2 gap-4 mb-4">
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project</label>
<select <AppSelect
v-model="selectedProject" v-model="selectedProject"
:options="activeProjects"
label-key="name"
value-key="id"
placeholder="Select project"
:placeholder-value="null"
:disabled="timerStore.isRunning" :disabled="timerStore.isRunning"
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 disabled:opacity-40 disabled:cursor-not-allowed" />
>
<option :value="null">Select project</option>
<option
v-for="project in activeProjects"
:key="project.id"
:value="project.id"
>
{{ project.name }}
</option>
</select>
</div> </div>
<div> <div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Task</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Task</label>
<select <AppSelect
v-model="selectedTask" v-model="selectedTask"
:options="projectTasks"
label-key="name"
value-key="id"
placeholder="Select task"
:placeholder-value="null"
:disabled="timerStore.isRunning || !selectedProject" :disabled="timerStore.isRunning || !selectedProject"
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 disabled:opacity-40 disabled:cursor-not-allowed" />
>
<option :value="null">Select task</option>
<option
v-for="task in projectTasks"
:key="task.id"
:value="task.id"
>
{{ task.name }}
</option>
</select>
</div> </div>
</div> </div>
<div> <div>
@@ -119,6 +109,7 @@ import { useProjectsStore, type Task } from '../stores/projects'
import { useEntriesStore } from '../stores/entries' import { useEntriesStore } from '../stores/entries'
import { useToastStore } from '../stores/toast' import { useToastStore } from '../stores/toast'
import { Timer as TimerIcon } from 'lucide-vue-next' import { Timer as TimerIcon } from 'lucide-vue-next'
import AppSelect from '../components/AppSelect.vue'
const timerStore = useTimerStore() const timerStore = useTimerStore()
const projectsStore = useProjectsStore() const projectsStore = useProjectsStore()