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:
parent
e66d6ad1f2
commit
2774b4050f
|
|
@ -253,7 +253,7 @@ const downloadAllReceipts = async () => {
|
|||
}
|
||||
|
||||
.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 {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expense Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Expense Grid - Mobile optimized -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div
|
||||
v-for="expense in expenses"
|
||||
:key="expense.Id"
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ watch(() => props.modelValue, (isOpen) => {
|
|||
}
|
||||
|
||||
.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 {
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ watch(() => props.modelValue, (isOpen) => {
|
|||
}
|
||||
|
||||
.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 {
|
||||
|
|
|
|||
|
|
@ -1,88 +1,123 @@
|
|||
<template>
|
||||
<div class="container mx-auto px-4 py-6">
|
||||
<!-- 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>
|
||||
<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
|
||||
</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
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<!-- Export Actions -->
|
||||
<!-- Mobile-optimized export buttons -->
|
||||
<div class="flex flex-col sm:flex-row gap-2 sm:gap-3 w-full sm:w-auto">
|
||||
<button
|
||||
@click="exportCSV"
|
||||
: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" />
|
||||
Export CSV
|
||||
<span class="hidden xs:inline">Export </span>CSV
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="showPDFModal = true"
|
||||
: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" />
|
||||
Generate PDF
|
||||
<span class="hidden xs:inline">Generate </span>PDF
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Range Filter -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6">
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-end">
|
||||
<div class="flex-1">
|
||||
<label class="label">
|
||||
<span class="label-text">Start Date</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="filters.startDate"
|
||||
type="date"
|
||||
class="input input-bordered w-full"
|
||||
@change="fetchExpenses"
|
||||
/>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-3 sm:p-4 mb-6">
|
||||
<div class="flex flex-col gap-3 sm:gap-4">
|
||||
<!-- Mobile: Stack date inputs in first row -->
|
||||
<div class="grid grid-cols-2 sm:flex sm:flex-row gap-3 sm:gap-4">
|
||||
<div class="flex-1">
|
||||
<label class="label">
|
||||
<span class="label-text text-xs sm:text-sm">Start Date</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="filters.startDate"
|
||||
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 class="flex-1">
|
||||
<label class="label">
|
||||
<span class="label-text">End Date</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="filters.endDate"
|
||||
type="date"
|
||||
class="input input-bordered w-full"
|
||||
@change="fetchExpenses"
|
||||
/>
|
||||
</div>
|
||||
<!-- Mobile: Category and button in second row -->
|
||||
<div class="flex gap-3 sm:hidden">
|
||||
<div class="flex-1">
|
||||
<label class="label">
|
||||
<span class="label-text text-xs">Category</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="filters.category"
|
||||
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-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 class="flex items-end">
|
||||
<button
|
||||
@click="resetToCurrentMonth"
|
||||
class="btn btn-ghost btn-sm text-xs px-2"
|
||||
>
|
||||
Current Month
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="resetToCurrentMonth"
|
||||
class="btn btn-ghost btn-sm"
|
||||
>
|
||||
Current Month
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -224,9 +259,8 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
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 PDFOptionsModal = defineAsyncComponent(() => import('@/components/PDFOptionsModal.vue'));
|
||||
const ExpenseDetailsModal = defineAsyncComponent(() => import('@/components/ExpenseDetailsModal.vue'));
|
||||
|
|
|
|||
Loading…
Reference in New Issue