Improve mobile responsiveness across expense tracking interface

- Add horizontal margins to modals on mobile devices
- Optimize grid layouts with smaller breakpoints (md→sm)
- Make action buttons full-width on mobile with touch optimization
- Adjust text sizes and spacing for better mobile readability
- Enhance date filter and export controls for mobile interaction
This commit is contained in:
Matt 2025-07-04 10:44:42 -04:00
parent e66d6ad1f2
commit 2774b4050f
5 changed files with 96 additions and 62 deletions

View File

@ -253,7 +253,7 @@ const downloadAllReceipts = async () => {
} }
.modal-content { .modal-content {
@apply bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden; @apply bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden mx-4 sm:mx-0;
} }
.modal-header { .modal-header {

View File

@ -22,8 +22,8 @@
</div> </div>
</div> </div>
<!-- Expense Grid --> <!-- Expense Grid - Mobile optimized -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
<div <div
v-for="expense in expenses" v-for="expense in expenses"
:key="expense.Id" :key="expense.Id"

View File

@ -255,7 +255,7 @@ watch(() => props.modelValue, (isOpen) => {
} }
.modal-content { .modal-content {
@apply bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-hidden; @apply bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-hidden mx-4 sm:mx-0;
} }
.modal-header { .modal-header {

View File

@ -258,7 +258,7 @@ watch(() => props.modelValue, (isOpen) => {
} }
.modal-content { .modal-content {
@apply bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden; @apply bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden mx-2 sm:mx-4;
} }
.modal-header { .modal-header {

View File

@ -1,88 +1,123 @@
<template> <template>
<div class="container mx-auto px-4 py-6"> <div class="container mx-auto px-4 py-6">
<!-- Header with Actions --> <!-- Header with Actions -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4"> <div class="flex flex-col gap-4 mb-6">
<div> <div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white"> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
Expense Tracking Expense Tracking
</h1> </h1>
<p class="text-gray-600 dark:text-gray-300 mt-1"> <p class="text-sm sm:text-base text-gray-600 dark:text-gray-300 mt-1">
Track and manage expense receipts with smart grouping and export options Track and manage expense receipts with smart grouping and export options
</p> </p>
</div> </div>
<div class="flex flex-col sm:flex-row gap-3"> <!-- Mobile-optimized export buttons -->
<!-- Export Actions --> <div class="flex flex-col sm:flex-row gap-2 sm:gap-3 w-full sm:w-auto">
<button <button
@click="exportCSV" @click="exportCSV"
:disabled="loading || selectedExpenses.length === 0" :disabled="loading || selectedExpenses.length === 0"
class="btn btn-outline btn-sm" class="btn btn-outline btn-sm sm:btn-md w-full sm:w-auto touch-manipulation"
> >
<Icon name="mdi:file-excel" class="w-4 h-4" /> <Icon name="mdi:file-excel" class="w-4 h-4" />
Export CSV <span class="hidden xs:inline">Export </span>CSV
</button> </button>
<button <button
@click="showPDFModal = true" @click="showPDFModal = true"
:disabled="loading || selectedExpenses.length === 0" :disabled="loading || selectedExpenses.length === 0"
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm sm:btn-md w-full sm:w-auto touch-manipulation"
> >
<Icon name="mdi:file-pdf" class="w-4 h-4" /> <Icon name="mdi:file-pdf" class="w-4 h-4" />
Generate PDF <span class="hidden xs:inline">Generate </span>PDF
</button> </button>
</div> </div>
</div> </div>
<!-- Date Range Filter --> <!-- Date Range Filter -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-3 sm:p-4 mb-6">
<div class="flex flex-col sm:flex-row gap-4 items-end"> <div class="flex flex-col gap-3 sm:gap-4">
<div class="flex-1"> <!-- Mobile: Stack date inputs in first row -->
<label class="label"> <div class="grid grid-cols-2 sm:flex sm:flex-row gap-3 sm:gap-4">
<span class="label-text">Start Date</span> <div class="flex-1">
</label> <label class="label">
<input <span class="label-text text-xs sm:text-sm">Start Date</span>
v-model="filters.startDate" </label>
type="date" <input
class="input input-bordered w-full" v-model="filters.startDate"
@change="fetchExpenses" type="date"
/> class="input input-bordered input-sm sm:input-md w-full text-sm"
@change="fetchExpenses"
/>
</div>
<div class="flex-1">
<label class="label">
<span class="label-text text-xs sm:text-sm">End Date</span>
</label>
<input
v-model="filters.endDate"
type="date"
class="input input-bordered input-sm sm:input-md w-full text-sm"
@change="fetchExpenses"
/>
</div>
<!-- Category and button on larger screens -->
<div class="hidden sm:block flex-1">
<label class="label">
<span class="label-text">Category</span>
</label>
<select
v-model="filters.category"
class="select select-bordered w-full"
@change="fetchExpenses"
>
<option value="">All Categories</option>
<option value="Food/Drinks">Food/Drinks</option>
<option value="Shop">Shop</option>
<option value="Online">Online</option>
<option value="Other">Other</option>
</select>
</div>
<div class="hidden sm:flex items-end">
<button
@click="resetToCurrentMonth"
class="btn btn-ghost btn-sm"
>
Current Month
</button>
</div>
</div> </div>
<div class="flex-1"> <!-- Mobile: Category and button in second row -->
<label class="label"> <div class="flex gap-3 sm:hidden">
<span class="label-text">End Date</span> <div class="flex-1">
</label> <label class="label">
<input <span class="label-text text-xs">Category</span>
v-model="filters.endDate" </label>
type="date" <select
class="input input-bordered w-full" v-model="filters.category"
@change="fetchExpenses" class="select select-bordered select-sm w-full text-sm"
/> @change="fetchExpenses"
>
<option value="">All Categories</option>
<option value="Food/Drinks">Food/Drinks</option>
<option value="Shop">Shop</option>
<option value="Online">Online</option>
<option value="Other">Other</option>
</select>
</div>
<div class="flex items-end">
<button
@click="resetToCurrentMonth"
class="btn btn-ghost btn-sm text-xs px-2"
>
Current Month
</button>
</div>
</div> </div>
<div class="flex-1">
<label class="label">
<span class="label-text">Category</span>
</label>
<select
v-model="filters.category"
class="select select-bordered w-full"
@change="fetchExpenses"
>
<option value="">All Categories</option>
<option value="Food/Drinks">Food/Drinks</option>
<option value="Shop">Shop</option>
<option value="Online">Online</option>
<option value="Other">Other</option>
</select>
</div>
<button
@click="resetToCurrentMonth"
class="btn btn-ghost btn-sm"
>
Current Month
</button>
</div> </div>
</div> </div>
@ -224,9 +259,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import type { Expense } from '@/utils/types'; import type { Expense } from '@/utils/types';
import { groupExpensesByPayer } from '@/server/utils/nocodb';
// Component imports (to be created) // Component imports
const ExpenseList = defineAsyncComponent(() => import('@/components/ExpenseList.vue')); const ExpenseList = defineAsyncComponent(() => import('@/components/ExpenseList.vue'));
const PDFOptionsModal = defineAsyncComponent(() => import('@/components/PDFOptionsModal.vue')); const PDFOptionsModal = defineAsyncComponent(() => import('@/components/PDFOptionsModal.vue'));
const ExpenseDetailsModal = defineAsyncComponent(() => import('@/components/ExpenseDetailsModal.vue')); const ExpenseDetailsModal = defineAsyncComponent(() => import('@/components/ExpenseDetailsModal.vue'));