Okay, I've made some changes to integrate the Valerie UI components into the Account, Group Detail, and List Detail pages. This is part of the ongoing effort to standardize the UI and make the code easier to maintain.
Here's a breakdown of the changes: 1. **`AccountPage.vue`**: * I replaced the main heading with `VHeading`. * I updated the loading spinner to `VSpinner`. * I converted the error alert to `VAlert` with an action button. * I refactored the Profile, Password, and Notifications sections to use `VCard` for their structure. * The form elements within these cards (name, email, passwords) now use `VFormField` and `VInput`. * Action buttons like "Save Changes" and "Change Password" are now `VButton` with an integrated `VSpinner` for loading states. * The notification preferences list uses `VList` and `VListItem`, with each preference toggle converted to `VToggleSwitch`. 2. **`GroupDetailPage.vue`**: * I updated the page-level loading spinner, error alert, and main heading to `VSpinner`, `VAlert`, and `VHeading`. * I refactored the "Group Members", "Invite Members", "Chores", and "Expenses" sections from custom "neo-card" styling to use `VCard`. * Headers within these cards use `VHeading` and action buttons use `VButton` (I kept Material Icons where `VIcon` wasn't a direct replacement). * Lists of members, chores, and expenses now use `VList` and `VListItem`. * Buttons within list items (e.g., "Remove member") are `VButton` with `VSpinner`. * Role indicators and frequency/split type "chips" are now `VBadge` components, and I updated the helper functions to return VBadge-compatible variants. * The "Invite Members" form elements (input for code, copy button) use `VFormField`, `VInput`, and `VButton`. * I simplified empty states within card bodies using `VIcon` and text. 3. **`ListDetailPage.vue`**: This complex page required several steps to refactor: * **Page-Level & Header:** I updated the loading state to `VSpinner`, the error alert to `VAlert`, and the main title to `VHeading`. Header action buttons are `VButton` with icons, and the list status is `VBadge`. * **Modals:** I converted all five custom modals (OCR, Confirmation, Edit Item, Settle Share, Cost Summary shell) to use `VModal`. Internal forms and actions within these modals now use `VFormField`, `VInput`, `VButton`, `VSpinner`, `VList`, `VListItem`, and `VAlert` as appropriate. I removed the `onClickOutside` logic. * **Main Items List:** The loading state uses `VCard` with `VSpinner`, and the empty state uses `VCard variant="empty-state"`. The list itself is now a `VCard` containing a `VList`. Each item is a `VListItem` with internal content refactored to use `VCheckbox`, `VInput` (for price), and `VButton` with `VIcon` for actions. * **Add Item Form:** I re-structured this below the items list, using `VFormField`, `VInput`, and `VButton` with `VIcon`. * **Expenses Section:** The main card uses `VCard` with `VHeading` and `VButton` in the header. Loading/error/empty states use `VSpinner`, `VAlert`, `VIcon`. The expenses list is `VList`, with each expense item as a `VListItem`. Statuses are `VBadge`. This refactoring significantly increases the usage of the Valerie UI component library across these key application pages. This should help create a more consistent experience for you and make development smoother. Next, I'll focus on the Chores-related pages.
This commit is contained in:
parent
272e5abe41
commit
813ed911f1
@ -1,116 +1,84 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="container page-padding">
|
<main class="container page-padding">
|
||||||
<h1 class="mb-3">Account Settings</h1>
|
<VHeading level="1" text="Account Settings" class="mb-3" />
|
||||||
|
|
||||||
<div v-if="loading" class="text-center">
|
<div v-if="loading" class="text-center">
|
||||||
<div class="spinner-dots" role="status"><span /><span /><span /></div>
|
<VSpinner label="Loading profile..." />
|
||||||
<p>Loading profile...</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="error" class="alert alert-error mb-3" role="alert">
|
<VAlert v-else-if="error" type="error" :message="error" class="mb-3">
|
||||||
<div class="alert-content">
|
<template #actions>
|
||||||
<svg class="icon" aria-hidden="true">
|
<VButton variant="danger" size="sm" @click="fetchProfile">Retry</VButton>
|
||||||
<use xlink:href="#icon-alert-triangle" />
|
</template>
|
||||||
</svg>
|
</VAlert>
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-sm btn-danger" @click="fetchProfile">Retry</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form v-else @submit.prevent="onSubmitProfile">
|
<form v-else @submit.prevent="onSubmitProfile">
|
||||||
<!-- Profile Section -->
|
<!-- Profile Section -->
|
||||||
<div class="card mb-3">
|
<VCard class="mb-3">
|
||||||
<div class="card-header">
|
<template #header><VHeading level="3">Profile Information</VHeading></template>
|
||||||
<h3>Profile Information</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="flex flex-wrap" style="gap: 1rem;">
|
<div class="flex flex-wrap" style="gap: 1rem;">
|
||||||
<div class="form-group flex-grow">
|
<VFormField label="Name" class="flex-grow">
|
||||||
<label for="profileName" class="form-label">Name</label>
|
<VInput id="profileName" v-model="profile.name" required />
|
||||||
<input type="text" id="profileName" v-model="profile.name" class="form-input" required />
|
</VFormField>
|
||||||
</div>
|
<VFormField label="Email" class="flex-grow">
|
||||||
<div class="form-group flex-grow">
|
<VInput type="email" id="profileEmail" v-model="profile.email" required readonly />
|
||||||
<label for="profileEmail" class="form-label">Email</label>
|
</VFormField>
|
||||||
<input type="email" id="profileEmail" v-model="profile.email" class="form-input" required readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
|
||||||
<span v-if="saving" class="spinner-dots-sm" role="status"><span /><span /><span /></span>
|
|
||||||
Save Changes
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<VButton type="submit" variant="primary" :disabled="saving">
|
||||||
|
<VSpinner v-if="saving" size="sm" /> Save Changes
|
||||||
|
</VButton>
|
||||||
|
</template>
|
||||||
|
</VCard>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Password Section -->
|
<!-- Password Section -->
|
||||||
<form @submit.prevent="onChangePassword">
|
<form @submit.prevent="onChangePassword">
|
||||||
<div class="card mb-3">
|
<VCard class="mb-3">
|
||||||
<div class="card-header">
|
<template #header><VHeading level="3">Change Password</VHeading></template>
|
||||||
<h3>Change Password</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="flex flex-wrap" style="gap: 1rem;">
|
<div class="flex flex-wrap" style="gap: 1rem;">
|
||||||
<div class="form-group flex-grow">
|
<VFormField label="Current Password" class="flex-grow">
|
||||||
<label for="currentPassword" class="form-label">Current Password</label>
|
<VInput type="password" id="currentPassword" v-model="password.current" required />
|
||||||
<input type="password" id="currentPassword" v-model="password.current" class="form-input" required />
|
</VFormField>
|
||||||
</div>
|
<VFormField label="New Password" class="flex-grow">
|
||||||
<div class="form-group flex-grow">
|
<VInput type="password" id="newPassword" v-model="password.newPassword" required />
|
||||||
<label for="newPassword" class="form-label">New Password</label>
|
</VFormField>
|
||||||
<input type="password" id="newPassword" v-model="password.newPassword" class="form-input" required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
<button type="submit" class="btn btn-primary" :disabled="changingPassword">
|
|
||||||
<span v-if="changingPassword" class="spinner-dots-sm" role="status"><span /><span /><span /></span>
|
|
||||||
Change Password
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<VButton type="submit" variant="primary" :disabled="changingPassword">
|
||||||
|
<VSpinner v-if="changingPassword" size="sm" /> Change Password
|
||||||
|
</VButton>
|
||||||
|
</template>
|
||||||
|
</VCard>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Notifications Section -->
|
<!-- Notifications Section -->
|
||||||
<div class="card">
|
<VCard>
|
||||||
<div class="card-header">
|
<template #header><VHeading level="3">Notification Preferences</VHeading></template>
|
||||||
<h3>Notification Preferences</h3>
|
<VList class="preference-list">
|
||||||
</div>
|
<VListItem class="preference-item">
|
||||||
<div class="card-body">
|
|
||||||
<ul class="item-list preference-list">
|
|
||||||
<li class="preference-item">
|
|
||||||
<div class="preference-label">
|
<div class="preference-label">
|
||||||
<span>Email Notifications</span>
|
<span>Email Notifications</span>
|
||||||
<small>Receive email notifications for important updates</small>
|
<small>Receive email notifications for important updates</small>
|
||||||
</div>
|
</div>
|
||||||
<label class="switch-container">
|
<VToggleSwitch v-model="preferences.emailNotifications" @change="onPreferenceChange" label="Email Notifications" id="emailNotificationsToggle" />
|
||||||
<input type="checkbox" v-model="preferences.emailNotifications" @change="onPreferenceChange" />
|
</VListItem>
|
||||||
<span class="switch" aria-hidden="true"></span>
|
<VListItem class="preference-item">
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li class="preference-item">
|
|
||||||
<div class="preference-label">
|
<div class="preference-label">
|
||||||
<span>List Updates</span>
|
<span>List Updates</span>
|
||||||
<small>Get notified when lists are updated</small>
|
<small>Get notified when lists are updated</small>
|
||||||
</div>
|
</div>
|
||||||
<label class="switch-container">
|
<VToggleSwitch v-model="preferences.listUpdates" @change="onPreferenceChange" label="List Updates" id="listUpdatesToggle"/>
|
||||||
<input type="checkbox" v-model="preferences.listUpdates" @change="onPreferenceChange" />
|
</VListItem>
|
||||||
<span class="switch" aria-hidden="true"></span>
|
<VListItem class="preference-item">
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li class="preference-item">
|
|
||||||
<div class="preference-label">
|
<div class="preference-label">
|
||||||
<span>Group Activities</span>
|
<span>Group Activities</span>
|
||||||
<small>Receive notifications for group activities</small>
|
<small>Receive notifications for group activities</small>
|
||||||
</div>
|
</div>
|
||||||
<label class="switch-container">
|
<VToggleSwitch v-model="preferences.groupActivities" @change="onPreferenceChange" label="Group Activities" id="groupActivitiesToggle"/>
|
||||||
<input type="checkbox" v-model="preferences.groupActivities" @change="onPreferenceChange" />
|
</VListItem>
|
||||||
<span class="switch" aria-hidden="true"></span>
|
</VList>
|
||||||
</label>
|
</VCard>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -118,6 +86,16 @@
|
|||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { apiClient, API_ENDPOINTS } from '@/config/api'; // Assuming path
|
import { apiClient, API_ENDPOINTS } from '@/config/api'; // Assuming path
|
||||||
import { useNotificationStore } from '@/stores/notifications';
|
import { useNotificationStore } from '@/stores/notifications';
|
||||||
|
import VHeading from '@/components/valerie/VHeading.vue';
|
||||||
|
import VSpinner from '@/components/valerie/VSpinner.vue';
|
||||||
|
import VAlert from '@/components/valerie/VAlert.vue';
|
||||||
|
import VCard from '@/components/valerie/VCard.vue';
|
||||||
|
import VFormField from '@/components/valerie/VFormField.vue';
|
||||||
|
import VInput from '@/components/valerie/VInput.vue';
|
||||||
|
import VButton from '@/components/valerie/VButton.vue';
|
||||||
|
import VToggleSwitch from '@/components/valerie/VToggleSwitch.vue';
|
||||||
|
import VList from '@/components/valerie/VList.vue';
|
||||||
|
import VListItem from '@/components/valerie/VListItem.vue';
|
||||||
|
|
||||||
interface Profile {
|
interface Profile {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -1,82 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="container page-padding">
|
<main class="container page-padding">
|
||||||
<div v-if="loading" class="text-center">
|
<div v-if="loading" class="text-center">
|
||||||
<div class="spinner-dots" role="status"><span /><span /><span /></div>
|
<VSpinner label="Loading group details..." />
|
||||||
<p>Loading group details...</p>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="error" class="alert alert-error mb-3" role="alert">
|
|
||||||
<div class="alert-content">
|
|
||||||
<svg class="icon" aria-hidden="true">
|
|
||||||
<use xlink:href="#icon-alert-triangle" />
|
|
||||||
</svg>
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-sm btn-danger" @click="fetchGroupDetails">Retry</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<VAlert v-else-if="error" type="error" :message="error" class="mb-3">
|
||||||
|
<template #actions>
|
||||||
|
<VButton variant="danger" size="sm" @click="fetchGroupDetails">Retry</VButton>
|
||||||
|
</template>
|
||||||
|
</VAlert>
|
||||||
<div v-else-if="group">
|
<div v-else-if="group">
|
||||||
<h1 class="mb-3">{{ group.name }}</h1>
|
<VHeading level="1" :text="group.name" class="mb-3" />
|
||||||
|
|
||||||
<div class="neo-grid">
|
<div class="neo-grid">
|
||||||
<!-- Group Members Section -->
|
<!-- Group Members Section -->
|
||||||
<div class="neo-card">
|
<VCard>
|
||||||
<div class="neo-card-header">
|
<template #header><VHeading level="3">Group Members</VHeading></template>
|
||||||
<h3>Group Members</h3>
|
<VList v-if="group.members && group.members.length > 0">
|
||||||
</div>
|
<VListItem v-for="member in group.members" :key="member.id" class="flex justify-between items-center">
|
||||||
<div class="neo-card-body">
|
|
||||||
<div v-if="group.members && group.members.length > 0" class="neo-members-list">
|
|
||||||
<div v-for="member in group.members" :key="member.id" class="neo-member-item">
|
|
||||||
<div class="neo-member-info">
|
<div class="neo-member-info">
|
||||||
<span class="neo-member-name">{{ member.email }}</span>
|
<span class="neo-member-name">{{ member.email }}</span>
|
||||||
<span class="neo-member-role" :class="member.role?.toLowerCase()">{{ member.role || 'Member' }}</span>
|
<VBadge :text="member.role || 'Member'" :variant="member.role?.toLowerCase() === 'owner' ? 'primary' : 'secondary'" />
|
||||||
</div>
|
</div>
|
||||||
<button v-if="canRemoveMember(member)" class="btn btn-danger btn-sm" @click="removeMember(member.id)"
|
<VButton v-if="canRemoveMember(member)" variant="danger" size="sm" @click="removeMember(member.id)" :disabled="removingMember === member.id">
|
||||||
:disabled="removingMember === member.id">
|
<VSpinner v-if="removingMember === member.id" size="sm"/> Remove
|
||||||
<span v-if="removingMember === member.id" class="spinner-dots-sm"
|
</VButton>
|
||||||
role="status"><span /><span /><span /></span>
|
</VListItem>
|
||||||
Remove
|
</VList>
|
||||||
</button>
|
<div v-else class="text-center py-4">
|
||||||
</div>
|
<VIcon name="users" size="lg" class="opacity-50 mb-2" />
|
||||||
</div>
|
|
||||||
<div v-else class="neo-empty-state">
|
|
||||||
<svg class="icon icon-lg" aria-hidden="true">
|
|
||||||
<use xlink:href="#icon-users" />
|
|
||||||
</svg>
|
|
||||||
<p>No members found.</p>
|
<p>No members found.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</VCard>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Invite Members Section -->
|
<!-- Invite Members Section -->
|
||||||
<div class="neo-card">
|
<VCard>
|
||||||
<div class="neo-card-header">
|
<template #header><VHeading level="3">Invite Members</VHeading></template>
|
||||||
<h3>Invite Members</h3>
|
<VButton variant="primary" class="w-full" @click="generateInviteCode" :disabled="generatingInvite">
|
||||||
</div>
|
<VSpinner v-if="generatingInvite" size="sm"/> {{ inviteCode ? 'Regenerate Invite Code' : 'Generate Invite Code' }}
|
||||||
<div class="neo-card-body">
|
</VButton>
|
||||||
<button class="btn btn-primary w-full" @click="generateInviteCode" :disabled="generatingInvite">
|
|
||||||
<span v-if="generatingInvite" class="spinner-dots-sm" role="status"><span /><span /><span /></span>
|
|
||||||
{{ inviteCode ? 'Regenerate Invite Code' : 'Generate Invite Code' }}
|
|
||||||
</button>
|
|
||||||
<div v-if="inviteCode" class="neo-invite-code mt-3">
|
<div v-if="inviteCode" class="neo-invite-code mt-3">
|
||||||
<label for="inviteCodeInput" class="neo-label">Current Active Invite Code:</label>
|
<VFormField label="Current Active Invite Code:" :label-sr-only="false">
|
||||||
<div class="neo-input-group">
|
<div class="flex items-center gap-2">
|
||||||
<input id="inviteCodeInput" type="text" :value="inviteCode" class="neo-input" readonly />
|
<VInput id="inviteCodeInput" :model-value="inviteCode" readonly class="flex-grow" />
|
||||||
<button class="btn btn-neutral btn-icon-only" @click="copyInviteCodeHandler"
|
<VButton variant="neutral" :icon-only="true" iconLeft="clipboard" @click="copyInviteCodeHandler" aria-label="Copy invite code" />
|
||||||
aria-label="Copy invite code">
|
|
||||||
<svg class="icon">
|
|
||||||
<use xlink:href="#icon-clipboard"></use>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<p v-if="copySuccess" class="neo-success-text">Invite code copied to clipboard!</p>
|
</VFormField>
|
||||||
|
<p v-if="copySuccess" class="text-sm text-green-600 mt-1">Invite code copied to clipboard!</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="neo-empty-state mt-3">
|
<div v-else class="text-center py-4 mt-3">
|
||||||
<svg class="icon icon-lg" aria-hidden="true">
|
<VIcon name="link" size="lg" class="opacity-50 mb-2" />
|
||||||
<use xlink:href="#icon-link" />
|
|
||||||
</svg>
|
|
||||||
<p>No active invite code. Click the button above to generate one.</p>
|
<p>No active invite code. Click the button above to generate one.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</VCard>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Lists Section -->
|
<!-- Lists Section -->
|
||||||
@ -85,78 +60,61 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chores Section -->
|
<!-- Chores Section -->
|
||||||
<div class="mt-4">
|
<VCard class="mt-4">
|
||||||
<div class="neo-card">
|
<template #header>
|
||||||
<div class="neo-card-header">
|
<div class="flex justify-between items-center w-full">
|
||||||
<h3>Group Chores</h3>
|
<VHeading level="3">Group Chores</VHeading>
|
||||||
<router-link :to="`/groups/${groupId}/chores`" class="btn btn-primary">
|
<VButton :to="`/groups/${groupId}/chores`" variant="primary">
|
||||||
<span class="material-icons">cleaning_services</span>
|
<span class="material-icons" style="margin-right: 0.25em;">cleaning_services</span> Manage Chores
|
||||||
Manage Chores
|
</VButton>
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="neo-card-body">
|
</template>
|
||||||
<div v-if="upcomingChores.length > 0" class="neo-chores-list">
|
<VList v-if="upcomingChores.length > 0">
|
||||||
<div v-for="chore in upcomingChores" :key="chore.id" class="neo-chore-item">
|
<VListItem v-for="chore in upcomingChores" :key="chore.id" class="flex justify-between items-center">
|
||||||
<div class="neo-chore-info">
|
<div class="neo-chore-info">
|
||||||
<span class="neo-chore-name">{{ chore.name }}</span>
|
<span class="neo-chore-name">{{ chore.name }}</span>
|
||||||
<span class="neo-chore-due">Due: {{ formatDate(chore.next_due_date) }}</span>
|
<span class="neo-chore-due">Due: {{ formatDate(chore.next_due_date) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="neo-chip" :class="getFrequencyColor(chore.frequency)">
|
<VBadge :text="formatFrequency(chore.frequency)" :variant="getFrequencyBadgeVariant(chore.frequency)" />
|
||||||
{{ formatFrequency(chore.frequency) }}
|
</VListItem>
|
||||||
</span>
|
</VList>
|
||||||
</div>
|
<div v-else class="text-center py-4">
|
||||||
</div>
|
<VIcon name="cleaning_services" size="lg" class="opacity-50 mb-2" /> {/* Assuming cleaning_services is a valid VIcon name or will be added */}
|
||||||
<div v-else class="neo-empty-state">
|
|
||||||
<svg class="icon icon-lg" aria-hidden="true">
|
|
||||||
<use xlink:href="#icon-cleaning_services" />
|
|
||||||
</svg>
|
|
||||||
<p>No chores scheduled. Click "Manage Chores" to create some!</p>
|
<p>No chores scheduled. Click "Manage Chores" to create some!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</VCard>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Expenses Section -->
|
<!-- Expenses Section -->
|
||||||
<div class="mt-4">
|
<VCard class="mt-4">
|
||||||
<div class="neo-card">
|
<template #header>
|
||||||
<div class="neo-card-header">
|
<div class="flex justify-between items-center w-full">
|
||||||
<h3>Group Expenses</h3>
|
<VHeading level="3">Group Expenses</VHeading>
|
||||||
<router-link :to="`/groups/${groupId}/expenses`" class="btn btn-primary">
|
<VButton :to="`/groups/${groupId}/expenses`" variant="primary">
|
||||||
<span class="material-icons">payments</span>
|
<span class="material-icons" style="margin-right: 0.25em;">payments</span> Manage Expenses
|
||||||
Manage Expenses
|
</VButton>
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="neo-card-body">
|
</template>
|
||||||
<div v-if="recentExpenses.length > 0" class="neo-expenses-list">
|
<VList v-if="recentExpenses.length > 0">
|
||||||
<div v-for="expense in recentExpenses" :key="expense.id" class="neo-expense-item">
|
<VListItem v-for="expense in recentExpenses" :key="expense.id" class="flex justify-between items-center">
|
||||||
<div class="neo-expense-info">
|
<div class="neo-expense-info">
|
||||||
<span class="neo-expense-name">{{ expense.description }}</span>
|
<span class="neo-expense-name">{{ expense.description }}</span>
|
||||||
<span class="neo-expense-date">{{ formatDate(expense.expense_date) }}</span>
|
<span class="neo-expense-date">{{ formatDate(expense.expense_date) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="neo-expense-details">
|
<div class="neo-expense-details">
|
||||||
<span class="neo-expense-amount">{{ expense.currency }} {{ formatAmount(expense.total_amount)
|
<span class="neo-expense-amount">{{ expense.currency }} {{ formatAmount(expense.total_amount) }}</span>
|
||||||
}}</span>
|
<VBadge :text="formatSplitType(expense.split_type)" :variant="getSplitTypeBadgeVariant(expense.split_type)" />
|
||||||
<span class="neo-chip" :class="getSplitTypeColor(expense.split_type)">
|
|
||||||
{{ formatSplitType(expense.split_type) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</VListItem>
|
||||||
</div>
|
</VList>
|
||||||
<div v-else class="neo-empty-state">
|
<div v-else class="text-center py-4">
|
||||||
<svg class="icon icon-lg" aria-hidden="true">
|
<VIcon name="payments" size="lg" class="opacity-50 mb-2" /> {/* Assuming payments is a valid VIcon name or will be added */}
|
||||||
<use xlink:href="#icon-payments" />
|
|
||||||
</svg>
|
|
||||||
<p>No expenses recorded. Click "Manage Expenses" to add some!</p>
|
<p>No expenses recorded. Click "Manage Expenses" to add some!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</VCard>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="alert alert-info" role="status">
|
<VAlert v-else type="info" message="Group not found or an error occurred." />
|
||||||
<div class="alert-content">Group not found or an error occurred.</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -171,6 +129,17 @@ import { choreService } from '../services/choreService'
|
|||||||
import type { Chore, ChoreFrequency } from '../types/chore'
|
import type { Chore, ChoreFrequency } from '../types/chore'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import type { Expense } from '@/types/expense'
|
import type { Expense } from '@/types/expense'
|
||||||
|
import VHeading from '@/components/valerie/VHeading.vue';
|
||||||
|
import VSpinner from '@/components/valerie/VSpinner.vue';
|
||||||
|
import VAlert from '@/components/valerie/VAlert.vue';
|
||||||
|
import VCard from '@/components/valerie/VCard.vue';
|
||||||
|
import VList from '@/components/valerie/VList.vue';
|
||||||
|
import VListItem from '@/components/valerie/VListItem.vue';
|
||||||
|
import VButton from '@/components/valerie/VButton.vue';
|
||||||
|
import VBadge from '@/components/valerie/VBadge.vue';
|
||||||
|
import VInput from '@/components/valerie/VInput.vue';
|
||||||
|
import VFormField from '@/components/valerie/VFormField.vue';
|
||||||
|
import VIcon from '@/components/valerie/VIcon.vue';
|
||||||
|
|
||||||
interface Group {
|
interface Group {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
@ -355,16 +324,16 @@ const formatFrequency = (frequency: ChoreFrequency) => {
|
|||||||
return options[frequency] || frequency
|
return options[frequency] || frequency
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFrequencyColor = (frequency: ChoreFrequency) => {
|
const getFrequencyBadgeVariant = (frequency: ChoreFrequency): string => {
|
||||||
const colors: Record<ChoreFrequency, string> = {
|
const colorMap: Record<ChoreFrequency, string> = {
|
||||||
one_time: 'grey',
|
one_time: 'neutral',
|
||||||
daily: 'blue',
|
daily: 'info',
|
||||||
weekly: 'green',
|
weekly: 'success',
|
||||||
monthly: 'purple',
|
monthly: 'accent', // Using accent for purple as an example
|
||||||
custom: 'orange'
|
custom: 'warning'
|
||||||
}
|
};
|
||||||
return colors[frequency]
|
return colorMap[frequency] || 'secondary';
|
||||||
}
|
};
|
||||||
|
|
||||||
// Add new methods for expenses
|
// Add new methods for expenses
|
||||||
const loadRecentExpenses = async () => {
|
const loadRecentExpenses = async () => {
|
||||||
@ -387,16 +356,16 @@ const formatSplitType = (type: string) => {
|
|||||||
).join(' ')
|
).join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSplitTypeColor = (type: string) => {
|
const getSplitTypeBadgeVariant = (type: string): string => {
|
||||||
const colors: Record<string, string> = {
|
const colorMap: Record<string, string> = {
|
||||||
equal: 'blue',
|
equal: 'info',
|
||||||
exact_amounts: 'green',
|
exact_amounts: 'success',
|
||||||
percentage: 'purple',
|
percentage: 'accent', // Using accent for purple
|
||||||
shares: 'orange',
|
shares: 'warning',
|
||||||
item_based: 'teal'
|
item_based: 'secondary', // Using secondary for teal as an example
|
||||||
}
|
};
|
||||||
return colors[type] || 'grey'
|
return colorMap[type] || 'neutral';
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchGroupDetails();
|
fetchGroupDetails();
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user