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 {
|
.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 {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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'));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue