Merge pull request 'refactor: Update frontend components and Dockerfile for production' (#5) from ph4 into prod
Reviewed-on: #5
This commit is contained in:
commit
258798846d
@ -32,7 +32,7 @@ COPY . .
|
|||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
# Build the application
|
# Build the application
|
||||||
RUN npm run build
|
RUN npm run build-only
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM node:slim AS production
|
FROM node:slim AS production
|
||||||
|
@ -84,7 +84,7 @@ export default defineComponent({
|
|||||||
// Add other common styling for list item content area
|
// Add other common styling for list item content area
|
||||||
// e.g., text color, font size
|
// e.g., text color, font size
|
||||||
background-color: inherit; // Inherit from .list-item, can be overridden
|
background-color: inherit; // Inherit from .list-item, can be overridden
|
||||||
// Useful so that when it slides, it has the right bg
|
// Useful so that when it slides, it has the right bg
|
||||||
}
|
}
|
||||||
|
|
||||||
.swipe-actions {
|
.swipe-actions {
|
||||||
@ -108,7 +108,8 @@ export default defineComponent({
|
|||||||
transform: translateX(-100%); // Initially hidden to the left
|
transform: translateX(-100%); // Initially hidden to the left
|
||||||
transition: transform 0.3s ease-out;
|
transition: transform 0.3s ease-out;
|
||||||
|
|
||||||
.list-item.is-swiped & { // When swiped to reveal left actions
|
.list-item.is-swiped & {
|
||||||
|
// When swiped to reveal left actions
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,9 +162,10 @@ export default defineComponent({
|
|||||||
// If wrapper translates right, it reveals the list-item's left-actions.
|
// If wrapper translates right, it reveals the list-item's left-actions.
|
||||||
|
|
||||||
// Reveal right actions by translating the list-item-content-wrapper to the left
|
// Reveal right actions by translating the list-item-content-wrapper to the left
|
||||||
.list-item.is-swiped & { // This assumes isSwiped means revealing RIGHT actions.
|
.list-item.is-swiped & {
|
||||||
// Need differentiation if both left/right can be revealed independently.
|
// This assumes isSwiped means revealing RIGHT actions.
|
||||||
// For now, isSwiped reveals right.
|
// Need differentiation if both left/right can be revealed independently.
|
||||||
|
// For now, isSwiped reveals right.
|
||||||
// This class is on .list-item. The .swipe-actions-right is inside the wrapper.
|
// This class is on .list-item. The .swipe-actions-right is inside the wrapper.
|
||||||
// So, the wrapper needs to translate.
|
// So, the wrapper needs to translate.
|
||||||
// No, this is fine. .list-item.is-swiped controls the transform of list-item-content-wrapper.
|
// No, this is fine. .list-item.is-swiped controls the transform of list-item-content-wrapper.
|
||||||
@ -175,6 +177,7 @@ export default defineComponent({
|
|||||||
// This logic should be on .list-item-content-wrapper based on .is-swiped of parent.
|
// This logic should be on .list-item-content-wrapper based on .is-swiped of parent.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjusting transform on list-item-content-wrapper based on parent .is-swiped
|
// Adjusting transform on list-item-content-wrapper based on parent .is-swiped
|
||||||
.list-item.is-swiped .list-item-content-wrapper {
|
.list-item.is-swiped .list-item-content-wrapper {
|
||||||
// This needs to be dynamic based on which actions are shown and their width.
|
// This needs to be dynamic based on which actions are shown and their width.
|
||||||
@ -231,15 +234,18 @@ export default defineComponent({
|
|||||||
// A true swipe needs JS to measure or fixed widths.
|
// A true swipe needs JS to measure or fixed widths.
|
||||||
// Let's go with a fixed transform for now for demo purposes.
|
// Let's go with a fixed transform for now for demo purposes.
|
||||||
.list-item.is-swiped .list-item-content-wrapper {
|
.list-item.is-swiped .list-item-content-wrapper {
|
||||||
transform: translateX(-80px); // Assumes right actions are 80px.
|
transform: translateX(-80px); // Assumes right actions are 80px.
|
||||||
}
|
}
|
||||||
// And left actions (if any)
|
|
||||||
.list-item.is-left-swiped .list-item-content-wrapper { // Hypothetical class
|
// And left actions (if any)
|
||||||
transform: translateX(80px); // Assumes left actions are 80px.
|
.list-item.is-left-swiped .list-item-content-wrapper {
|
||||||
|
// Hypothetical class
|
||||||
|
transform: translateX(80px); // Assumes left actions are 80px.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since `isSwiped` is boolean, it can only control one state.
|
// Since `isSwiped` is boolean, it can only control one state.
|
||||||
// Let's assume `isSwiped` means "the right actions are visible".
|
// Let's assume `isSwiped` means "the right actions are visible".
|
||||||
|
}
|
||||||
|
|
||||||
.list-item.completed {
|
.list-item.completed {
|
||||||
.list-item-content {
|
.list-item-content {
|
||||||
@ -248,6 +254,7 @@ export default defineComponent({
|
|||||||
// text-decoration: line-through;
|
// text-decoration: line-through;
|
||||||
background-color: #f0f8ff; // Light blue background for completed
|
background-color: #f0f8ff; // Light blue background for completed
|
||||||
}
|
}
|
||||||
|
|
||||||
// You might want to disable swipe on completed items or style them differently
|
// You might want to disable swipe on completed items or style them differently
|
||||||
&.swipable .list-item-content {
|
&.swipable .list-item-content {
|
||||||
// Specific style for swipable AND completed
|
// Specific style for swipable AND completed
|
||||||
|
@ -5,17 +5,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VAlert v-else-if="error && !list" type="error" :message="error" class="mb-4">
|
<VAlert v-else-if="error && !list" type="error" :message="error" class="mb-4">
|
||||||
<template #actions> <VButton @click="fetchListDetails">Retry</VButton> </template>
|
<template #actions>
|
||||||
|
<VButton @click="fetchListDetails">Retry</VButton>
|
||||||
|
</template>
|
||||||
</VAlert>
|
</VAlert>
|
||||||
|
|
||||||
<template v-else-if="list">
|
<template v-else-if="list">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="neo-list-header">
|
<div class="neo-list-header">
|
||||||
<VHeading level="1" :text="list.name" class="mb-3 neo-title" /> {/* Kept neo-title for existing style */}
|
<VHeading :level="1" :text="list.name" class="mb-3 neo-title" />
|
||||||
<div class="neo-header-actions">
|
<div class="neo-header-actions">
|
||||||
<VButton @click="showCostSummaryDialog = true" :disabled="!isOnline" icon-left="clipboard">Cost Summary</VButton>
|
<VButton @click="showCostSummaryDialog = true" :disabled="!isOnline" icon-left="clipboard">Cost Summary
|
||||||
|
</VButton>
|
||||||
<VButton @click="openOcrDialog" :disabled="!isOnline" icon-left="plus">Add via OCR</VButton>
|
<VButton @click="openOcrDialog" :disabled="!isOnline" icon-left="plus">Add via OCR</VButton>
|
||||||
<VBadge :text="list.group_id ? 'Group List' : 'Personal List'" :variant="list.group_id ? 'info' : 'success'" class="neo-status" /> {/* Kept neo-status for existing style */}
|
<VBadge :text="list.group_id ? 'Group List' : 'Personal List'" :variant="list.group_id ? 'accent' : 'settled'"
|
||||||
|
class="neo-status" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="list.description" class="neo-description">{{ list.description }}</p>
|
<p v-if="list.description" class="neo-description">{{ list.description }}</p>
|
||||||
@ -24,41 +28,34 @@
|
|||||||
<VCard v-if="itemsAreLoading" class="py-10 text-center mt-4">
|
<VCard v-if="itemsAreLoading" class="py-10 text-center mt-4">
|
||||||
<VSpinner label="Loading items..." size="lg" />
|
<VSpinner label="Loading items..." size="lg" />
|
||||||
</VCard>
|
</VCard>
|
||||||
<VCard v-else-if="!itemsAreLoading && list.items.length === 0" variant="empty-state" empty-icon="clipboard" empty-title="No Items Yet!" empty-message="Add some items using the form below." class="mt-4" />
|
<VCard v-else-if="!itemsAreLoading && list.items.length === 0" variant="empty-state" empty-icon="clipboard"
|
||||||
|
empty-title="No Items Yet!" empty-message="Add some items using the form below." class="mt-4" />
|
||||||
<VCard v-else class="mt-4">
|
<VCard v-else class="mt-4">
|
||||||
<VList class="item-list-tight"> {/* Assuming item-list-tight might be needed or VList default is fine */}
|
<VList class="item-list-tight">
|
||||||
<VListItem v-for="item in list.items" :key="item.id" class="item-with-actions" :class="{ 'bg-gray-100 opacity-70': item.is_complete }">
|
<VListItem v-for="item in list.items" :key="item.id" class="item-with-actions"
|
||||||
|
:class="{ 'bg-gray-100 opacity-70': item.is_complete }">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="flex items-center flex-grow gap-2">
|
<div class="flex items-center flex-grow gap-2">
|
||||||
<VCheckbox
|
<VCheckbox :model-value="item.is_complete" @update:modelValue="confirmUpdateItem(item, $event)"
|
||||||
:model-value="item.is_complete"
|
:disabled="item.updating" :aria-label="item.name" />
|
||||||
@update:modelValue="confirmUpdateItem(item, $event)"
|
|
||||||
:disabled="item.updating"
|
|
||||||
:aria-label="item.name"
|
|
||||||
/>
|
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<span class="item-name" :class="{'line-through': item.is_complete}">{{ item.name }}</span>
|
<span class="item-name" :class="{ 'line-through': item.is_complete }">{{ item.name }}</span>
|
||||||
<span v-if="item.quantity" class="text-sm text-gray-500 ml-1">× {{ item.quantity }}</span>
|
<span v-if="item.quantity" class="text-sm text-gray-500 ml-1">× {{ item.quantity }}</span>
|
||||||
<div v-if="item.is_complete" class="mt-1">
|
<div v-if="item.is_complete" class="mt-1">
|
||||||
<VInput
|
<VInput type="number" :model-value="item.priceInput || ''"
|
||||||
type="number"
|
@update:modelValue="item.priceInput = $event" placeholder="Price" size="sm" class="w-24"
|
||||||
:model-value="item.priceInput"
|
step="0.01" @blur="updateItemPrice(item)"
|
||||||
@update:modelValue="item.priceInput = $event"
|
@keydown.enter.prevent="($event.target as HTMLInputElement).blur()" />
|
||||||
placeholder="Price"
|
|
||||||
size="sm"
|
|
||||||
class="w-24"
|
|
||||||
step="0.01"
|
|
||||||
@blur="updateItemPrice(item)"
|
|
||||||
@keydown.enter.prevent="($event.target as HTMLInputElement).blur()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 ml-2">
|
<div class="flex items-center gap-1 ml-2">
|
||||||
<VButton icon-only="true" size="sm" variant="ghost" @click.stop="editItem(item)" aria-label="Edit item">
|
<VButton :icon-only="true" size="sm" variant="neutral" @click.stop="editItem(item)"
|
||||||
|
aria-label="Edit item">
|
||||||
<VIcon name="edit" />
|
<VIcon name="edit" />
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton icon-only="true" size="sm" variant="ghost" color="danger" @click.stop="confirmDeleteItem(item)" :disabled="item.deleting" aria-label="Delete item">
|
<VButton :icon-only="true" size="sm" variant="neutral" color="danger"
|
||||||
|
@click.stop="confirmDeleteItem(item)" :disabled="item.deleting" aria-label="Delete item">
|
||||||
<VIcon name="trash" />
|
<VIcon name="trash" />
|
||||||
</VButton>
|
</VButton>
|
||||||
</div>
|
</div>
|
||||||
@ -69,24 +66,15 @@
|
|||||||
|
|
||||||
<!-- Add New Item Form -->
|
<!-- Add New Item Form -->
|
||||||
<form @submit.prevent="onAddItem" class="add-item-form mt-4 p-4 border rounded-lg shadow flex items-center gap-2">
|
<form @submit.prevent="onAddItem" class="add-item-form mt-4 p-4 border rounded-lg shadow flex items-center gap-2">
|
||||||
<VIcon name="plus-circle" class="text-gray-400 shrink-0" /> {/* Added shrink-0 */}
|
<VIcon name="plus-circle" class="text-gray-400 shrink-0" />
|
||||||
<VFormField class="flex-grow" label="New item name" :label-sr-only="true">
|
<VFormField class="flex-grow" label="New item name" :label-sr-only="true">
|
||||||
<VInput
|
<VInput v-model="newItem.name" placeholder="Add a new item" required ref="itemNameInputRef" />
|
||||||
v-model="newItem.name"
|
|
||||||
placeholder="Add a new item"
|
|
||||||
required
|
|
||||||
ref="itemNameInputRef"
|
|
||||||
/>
|
|
||||||
</VFormField>
|
</VFormField>
|
||||||
<VFormField label="Quantity" :label-sr-only="true" class="w-24 shrink-0"> {/* Added shrink-0 and changed w-20 to w-24 for better fit */}
|
<VFormField label="Quantity" :label-sr-only="true" class="w-24 shrink-0">
|
||||||
<VInput
|
<VInput type="number" :model-value="newItem.quantity || ''" @update:modelValue="newItem.quantity = $event"
|
||||||
type="number"
|
placeholder="Qty" min="1" />
|
||||||
v-model="newItem.quantity"
|
|
||||||
placeholder="Qty"
|
|
||||||
min="1"
|
|
||||||
/>
|
|
||||||
</VFormField>
|
</VFormField>
|
||||||
<VButton type="submit" :disabled="addingItem" class="shrink-0"> {/* Added shrink-0 */}
|
<VButton type="submit" :disabled="addingItem" class="shrink-0">
|
||||||
<VSpinner v-if="addingItem" size="sm" />
|
<VSpinner v-if="addingItem" size="sm" />
|
||||||
<span v-else>Add</span>
|
<span v-else>Add</span>
|
||||||
</VButton>
|
</VButton>
|
||||||
@ -167,32 +155,38 @@
|
|||||||
<!-- OCR Dialog -->
|
<!-- OCR Dialog -->
|
||||||
<VModal v-model="showOcrDialogState" title="Add Items via OCR" @update:modelValue="!$event && closeOcrDialog()">
|
<VModal v-model="showOcrDialogState" title="Add Items via OCR" @update:modelValue="!$event && closeOcrDialog()">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div v-if="ocrLoading" class="text-center"><VSpinner label="Processing image..." /></div>
|
<div v-if="ocrLoading" class="text-center">
|
||||||
|
<VSpinner label="Processing image..." />
|
||||||
|
</div>
|
||||||
<VList v-else-if="ocrItems.length > 0">
|
<VList v-else-if="ocrItems.length > 0">
|
||||||
<VListItem v-for="(ocrItem, index) in ocrItems" :key="index">
|
<VListItem v-for="(ocrItem, index) in ocrItems" :key="index">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<VInput type="text" v-model="ocrItem.name" class="flex-grow" required />
|
<VInput type="text" v-model="ocrItem.name" class="flex-grow" required />
|
||||||
<VButton variant="danger" size="sm" :icon-only="true" iconLeft="trash" @click="ocrItems.splice(index, 1)" />
|
<VButton variant="danger" size="sm" :icon-only="true" iconLeft="trash"
|
||||||
|
@click="ocrItems.splice(index, 1)" />
|
||||||
</div>
|
</div>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
</VList>
|
</VList>
|
||||||
<VFormField v-else label="Upload Image" :error-message="ocrError">
|
<VFormField v-else label="Upload Image" :error-message="ocrError || undefined">
|
||||||
<VInput type="file" id="ocrFile" accept="image/*" @change="handleOcrFileUpload" ref="ocrFileInputRef" />
|
<VInput type="file" id="ocrFile" accept="image/*" @change="handleOcrFileUpload" ref="ocrFileInputRef"
|
||||||
|
:model-value="''" />
|
||||||
</VFormField>
|
</VFormField>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<VButton variant="neutral" @click="closeOcrDialog">Cancel</VButton>
|
<VButton variant="neutral" @click="closeOcrDialog">Cancel</VButton>
|
||||||
<VButton v-if="ocrItems.length > 0" type="button" variant="primary" @click="addOcrItems" :disabled="addingOcrItems">
|
<VButton v-if="ocrItems.length > 0" type="button" variant="primary" @click="addOcrItems"
|
||||||
<VSpinner v-if="addingOcrItems" size="sm"/> Add Items
|
:disabled="addingOcrItems">
|
||||||
|
<VSpinner v-if="addingOcrItems" size="sm" /> Add Items
|
||||||
</VButton>
|
</VButton>
|
||||||
</template>
|
</template>
|
||||||
</VModal>
|
</VModal>
|
||||||
|
|
||||||
<!-- Confirmation Dialog -->
|
<!-- Confirmation Dialog -->
|
||||||
<VModal v-model="showConfirmDialogState" title="Confirmation" @update:modelValue="!$event && cancelConfirmation()" size="sm">
|
<VModal v-model="showConfirmDialogState" title="Confirmation" @update:modelValue="!$event && cancelConfirmation()"
|
||||||
|
size="sm">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<VIcon name="alert-triangle" size="lg" class="text-yellow-500 mb-2" /> {/* Ensure text-yellow-500 is defined or use VAlert type="warning" */}
|
<VIcon name="alert-triangle" size="lg" class="text-yellow-500 mb-2" />
|
||||||
<p>{{ confirmDialogMessage }}</p>
|
<p>{{ confirmDialogMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -203,9 +197,12 @@
|
|||||||
</VModal>
|
</VModal>
|
||||||
|
|
||||||
<!-- Cost Summary Dialog -->
|
<!-- Cost Summary Dialog -->
|
||||||
<VModal v-model="showCostSummaryDialog" title="List Cost Summary" @update:modelValue="showCostSummaryDialog = false" size="lg">
|
<VModal v-model="showCostSummaryDialog" title="List Cost Summary" @update:modelValue="showCostSummaryDialog = false"
|
||||||
|
size="lg">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div v-if="costSummaryLoading" class="text-center"><VSpinner label="Loading summary..." /></div>
|
<div v-if="costSummaryLoading" class="text-center">
|
||||||
|
<VSpinner label="Loading summary..." />
|
||||||
|
</div>
|
||||||
<VAlert v-else-if="costSummaryError" type="error" :message="costSummaryError" />
|
<VAlert v-else-if="costSummaryError" type="error" :message="costSummaryError" />
|
||||||
<div v-else-if="listCostSummary">
|
<div v-else-if="listCostSummary">
|
||||||
<div class="mb-3 cost-overview">
|
<div class="mb-3 cost-overview">
|
||||||
@ -230,7 +227,8 @@
|
|||||||
<td class="text-right">{{ formatCurrency(userShare.items_added_value) }}</td>
|
<td class="text-right">{{ formatCurrency(userShare.items_added_value) }}</td>
|
||||||
<td class="text-right">{{ formatCurrency(userShare.amount_due) }}</td>
|
<td class="text-right">{{ formatCurrency(userShare.amount_due) }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<VBadge :text="formatCurrency(userShare.balance)" :variant="parseFloat(String(userShare.balance)) >= 0 ? 'success' : 'pending'" />
|
<VBadge :text="formatCurrency(userShare.balance)"
|
||||||
|
:variant="parseFloat(String(userShare.balance)) >= 0 ? 'settled' : 'pending'" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -245,19 +243,23 @@
|
|||||||
</VModal>
|
</VModal>
|
||||||
|
|
||||||
<!-- Settle Share Modal -->
|
<!-- Settle Share Modal -->
|
||||||
<VModal v-model="showSettleModal" title="Settle Share" @update:modelValue="!$event && closeSettleShareModal()" size="md">
|
<VModal v-model="showSettleModal" title="Settle Share" @update:modelValue="!$event && closeSettleShareModal()"
|
||||||
|
size="md">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div v-if="isSettlementLoading" class="text-center"><VSpinner label="Processing settlement..." /></div>
|
<div v-if="isSettlementLoading" class="text-center">
|
||||||
|
<VSpinner label="Processing settlement..." />
|
||||||
|
</div>
|
||||||
<VAlert v-else-if="settleAmountError" type="error" :message="settleAmountError" />
|
<VAlert v-else-if="settleAmountError" type="error" :message="settleAmountError" />
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<p>Settle amount for {{ selectedSplitForSettlement?.user?.name || selectedSplitForSettlement?.user?.email || `User ID: ${selectedSplitForSettlement?.user_id}` }}:</p>
|
<p>Settle amount for {{ selectedSplitForSettlement?.user?.name || selectedSplitForSettlement?.user?.email ||
|
||||||
<VFormField label="Amount" :error-message="settleAmountError"> {/* Error message was shown above, consider if needed here too */}
|
`User ID: ${selectedSplitForSettlement?.user_id}` }}:</p>
|
||||||
|
<VFormField label="Amount" :error-message="settleAmountError || undefined">
|
||||||
<VInput type="number" v-model="settleAmount" id="settleAmount" required />
|
<VInput type="number" v-model="settleAmount" id="settleAmount" required />
|
||||||
</VFormField>
|
</VFormField>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<VButton variant="neutral" @click="closeSettleShareModal">Cancel</VButton> {/* Added Cancel for consistency */}
|
<VButton variant="neutral" @click="closeSettleShareModal">Cancel</VButton>
|
||||||
<VButton variant="primary" @click="handleConfirmSettle">Confirm</VButton>
|
<VButton variant="primary" @click="handleConfirmSettle">Confirm</VButton>
|
||||||
</template>
|
</template>
|
||||||
</VModal>
|
</VModal>
|
||||||
@ -265,20 +267,22 @@
|
|||||||
<!-- Edit Item Dialog -->
|
<!-- Edit Item Dialog -->
|
||||||
<VModal v-model="showEditDialog" title="Edit Item" @update:modelValue="!$event && closeEditDialog()">
|
<VModal v-model="showEditDialog" title="Edit Item" @update:modelValue="!$event && closeEditDialog()">
|
||||||
<template #default>
|
<template #default>
|
||||||
<VFormField v-if="editingItem" label="Item Name" class="mb-4"> {/* Added margin */}
|
<VFormField v-if="editingItem" label="Item Name" class="mb-4">
|
||||||
<VInput type="text" id="editItemName" v-model="editingItem.name" required />
|
<VInput type="text" id="editItemName" v-model="editingItem.name" required />
|
||||||
</VFormField>
|
</VFormField>
|
||||||
<VFormField v-if="editingItem" label="Quantity">
|
<VFormField v-if="editingItem" label="Quantity">
|
||||||
<VInput type="number" id="editItemQuantity" v-model.number="editingItem.quantity" min="1" />
|
<VInput type="number" id="editItemQuantity" :model-value="editingItem.quantity || ''"
|
||||||
|
@update:modelValue="editingItem.quantity = $event" min="1" />
|
||||||
</VFormField>
|
</VFormField>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<VButton variant="neutral" @click="closeEditDialog">Cancel</VButton>
|
<VButton variant="neutral" @click="closeEditDialog">Cancel</VButton>
|
||||||
<VButton variant="primary" @click="handleConfirmEdit" :disabled="!editingItem?.name.trim()">Save Changes</VButton>
|
<VButton variant="primary" @click="handleConfirmEdit" :disabled="!editingItem?.name.trim()">Save Changes
|
||||||
|
</VButton>
|
||||||
</template>
|
</template>
|
||||||
</VModal>
|
</VModal>
|
||||||
|
|
||||||
<VAlert v-else type="info" message="Group not found or an error occurred." />
|
<VAlert v-if="!list && !pageInitialLoad" type="info" message="Group not found or an error occurred." />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -533,7 +537,9 @@ const isItemPendingSync = (item: Item) => {
|
|||||||
const onAddItem = async () => {
|
const onAddItem = async () => {
|
||||||
if (!list.value || !newItem.value.name.trim()) {
|
if (!list.value || !newItem.value.name.trim()) {
|
||||||
notificationStore.addNotification({ message: 'Please enter an item name.', type: 'warning' });
|
notificationStore.addNotification({ message: 'Please enter an item name.', type: 'warning' });
|
||||||
itemNameInputRef.value?.focus?.(); // Updated focus call
|
if (itemNameInputRef.value?.$el) {
|
||||||
|
(itemNameInputRef.value.$el as HTMLElement).focus();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addingItem.value = true;
|
addingItem.value = true;
|
||||||
@ -569,7 +575,9 @@ const onAddItem = async () => {
|
|||||||
};
|
};
|
||||||
list.value.items.push(optimisticItem);
|
list.value.items.push(optimisticItem);
|
||||||
newItem.value = { name: '' };
|
newItem.value = { name: '' };
|
||||||
itemNameInputRef.value?.focus?.(); // Updated focus call
|
if (itemNameInputRef.value?.$el) {
|
||||||
|
(itemNameInputRef.value.$el as HTMLElement).focus();
|
||||||
|
}
|
||||||
addingItem.value = false;
|
addingItem.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -585,7 +593,9 @@ const onAddItem = async () => {
|
|||||||
const addedItem = response.data as Item;
|
const addedItem = response.data as Item;
|
||||||
list.value.items.push(processListItems([addedItem])[0]);
|
list.value.items.push(processListItems([addedItem])[0]);
|
||||||
newItem.value = { name: '' };
|
newItem.value = { name: '' };
|
||||||
itemNameInputRef.value?.focus?.(); // Updated focus call
|
if (itemNameInputRef.value?.$el) {
|
||||||
|
(itemNameInputRef.value.$el as HTMLElement).focus();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notificationStore.addNotification({ message: (err instanceof Error ? err.message : String(err)) || 'Failed to add item.', type: 'error' });
|
notificationStore.addNotification({ message: (err instanceof Error ? err.message : String(err)) || 'Failed to add item.', type: 'error' });
|
||||||
} finally {
|
} finally {
|
||||||
@ -728,10 +738,10 @@ const openOcrDialog = () => {
|
|||||||
// For VInput type file, direct .value = '' might not work or be needed.
|
// For VInput type file, direct .value = '' might not work or be needed.
|
||||||
// VInput should handle its own reset if necessary, or this ref might target the native input inside.
|
// VInput should handle its own reset if necessary, or this ref might target the native input inside.
|
||||||
if (ocrFileInputRef.value && ocrFileInputRef.value.$el) { // Assuming VInput exposes $el
|
if (ocrFileInputRef.value && ocrFileInputRef.value.$el) { // Assuming VInput exposes $el
|
||||||
const inputElement = ocrFileInputRef.value.$el.querySelector('input[type="file"]') || ocrFileInputRef.value.$el;
|
const inputElement = ocrFileInputRef.value.$el.querySelector('input[type="file"]') || ocrFileInputRef.value.$el;
|
||||||
if(inputElement) (inputElement as HTMLInputElement).value = '';
|
if (inputElement) (inputElement as HTMLInputElement).value = '';
|
||||||
} else if (ocrFileInputRef.value) { // Fallback if ref is native input
|
} else if (ocrFileInputRef.value) { // Fallback if ref is native input
|
||||||
(ocrFileInputRef.value as any).value = '';
|
(ocrFileInputRef.value as any).value = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -774,11 +784,11 @@ const handleOcrUpload = async (file: File) => {
|
|||||||
ocrError.value = (err instanceof Error ? err.message : String(err)) || 'Failed to process image.';
|
ocrError.value = (err instanceof Error ? err.message : String(err)) || 'Failed to process image.';
|
||||||
} finally {
|
} finally {
|
||||||
ocrLoading.value = false;
|
ocrLoading.value = false;
|
||||||
if (ocrFileInputRef.value && ocrFileInputRef.value.$el) {
|
if (ocrFileInputRef.value && ocrFileInputRef.value.$el) {
|
||||||
const inputElement = ocrFileInputRef.value.$el.querySelector('input[type="file"]') || ocrFileInputRef.value.$el;
|
const inputElement = ocrFileInputRef.value.$el.querySelector('input[type="file"]') || ocrFileInputRef.value.$el;
|
||||||
if(inputElement) (inputElement as HTMLInputElement).value = '';
|
if (inputElement) (inputElement as HTMLInputElement).value = '';
|
||||||
} else if (ocrFileInputRef.value) {
|
} else if (ocrFileInputRef.value) {
|
||||||
(ocrFileInputRef.value as any).value = '';
|
(ocrFileInputRef.value as any).value = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -872,7 +882,9 @@ useEventListener(window, 'keydown', (event: KeyboardEvent) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
itemNameInputRef.value?.focus?.(); // Updated focus call
|
if (itemNameInputRef.value?.$el) {
|
||||||
|
(itemNameInputRef.value.$el as HTMLElement).focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1340,12 +1352,14 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-name { /* Added for VListItem content */
|
.item-name {
|
||||||
|
/* Added for VListItem content */
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-item-complete .item-name { /* Adjusted for VListItem */
|
.neo-item-complete .item-name {
|
||||||
|
/* Adjusted for VListItem */
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
/* opacity: 0.6; Combined with bg-gray-100 opacity-70 on VListItem */
|
/* opacity: 0.6; Combined with bg-gray-100 opacity-70 on VListItem */
|
||||||
}
|
}
|
||||||
@ -1409,17 +1423,14 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.neo-action-button {
|
.neo-action-button {
|
||||||
background: #fff;
|
/* General button, mostly replaced by VButton */
|
||||||
border: 3px solid #111;
|
background: #111;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 0.6rem 1rem;
|
padding: 0.6rem 1rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
box-shadow: 3px 3px 0 #111;
|
|
||||||
transition: transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-action-button:hover {
|
.neo-action-button:hover {
|
||||||
@ -1437,7 +1448,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-form { /* Added for new form styling */
|
.add-item-form {
|
||||||
|
/* Added for new form styling */
|
||||||
/* display: flex; (already on class) */
|
/* display: flex; (already on class) */
|
||||||
/* gap: 0.5rem; (already on class) */
|
/* gap: 0.5rem; (already on class) */
|
||||||
/* margin-top: 1rem; (original was 2rem, now mt-4) */
|
/* margin-top: 1rem; (original was 2rem, now mt-4) */
|
||||||
@ -1449,12 +1461,14 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.neo-new-item-form { /* Kept for reference, but form tag itself is now styled */
|
.neo-new-item-form {
|
||||||
|
/* Kept for reference, but form tag itself is now styled */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-text-input { /* Not directly used by VInput, but kept for reference */
|
.neo-text-input {
|
||||||
|
/* Not directly used by VInput, but kept for reference */
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
border: 2px solid #111;
|
border: 2px solid #111;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -1463,7 +1477,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-new-item-input { /* Not directly used by VInput, but kept for reference */
|
.neo-new-item-input {
|
||||||
|
/* Not directly used by VInput, but kept for reference */
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -1475,13 +1490,16 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-new-item-input::placeholder { /* VInput handles its own placeholder styling */
|
.neo-new-item-input::placeholder {
|
||||||
|
/* VInput handles its own placeholder styling */
|
||||||
color: #999;
|
color: #999;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-quantity-input { /* Not directly used by VInput, but kept for reference */
|
.neo-quantity-input {
|
||||||
width: 80px; /* This specific width is now on VFormField for quantity */
|
/* Not directly used by VInput, but kept for reference */
|
||||||
|
width: 80px;
|
||||||
|
/* This specific width is now on VFormField for quantity */
|
||||||
border: 2px solid #111;
|
border: 2px solid #111;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 0.4rem;
|
padding: 0.4rem;
|
||||||
@ -1489,7 +1507,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-number-input { /* For price input, now VInput with class="w-24" */
|
.neo-number-input {
|
||||||
|
/* For price input, now VInput with class="w-24" */
|
||||||
border: 2px solid #111;
|
border: 2px solid #111;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
@ -1497,7 +1516,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-add-button { /* Replaced by VButton */
|
.neo-add-button {
|
||||||
|
/* Replaced by VButton */
|
||||||
background: #111;
|
background: #111;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
@ -1509,7 +1529,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-button { /* General button, mostly replaced by VButton */
|
.neo-button {
|
||||||
|
/* General button, mostly replaced by VButton */
|
||||||
background: #111;
|
background: #111;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
@ -1520,7 +1541,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-item-input { /* Styling for the old li wrapper of add item form, can be removed */
|
.new-item-input {
|
||||||
|
/* Styling for the old li wrapper of add item form, can be removed */
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
@ -1561,7 +1583,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.neo-action-button { /* VButton has its own sizing */
|
.neo-action-button {
|
||||||
|
/* VButton has its own sizing */
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
@ -1575,7 +1598,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
.item-name { /* Adjusted for VListItem */
|
.item-name {
|
||||||
|
/* Adjusted for VListItem */
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1588,11 +1612,13 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
height: 1.4em;
|
height: 1.4em;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
.neo-icon-button { /* VButton icon-only replaces this */
|
.neo-icon-button {
|
||||||
|
/* VButton icon-only replaces this */
|
||||||
padding: 0.6rem;
|
padding: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-item-form { /* Adjusted form class */
|
.add-item-form {
|
||||||
|
/* Adjusted form class */
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
@ -1617,21 +1643,25 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
/* Optimize modals for mobile */
|
/* Optimize modals for mobile */
|
||||||
.modal-container { /* VModal has its own responsive sizing via props/CSS */
|
.modal-container {
|
||||||
|
/* VModal has its own responsive sizing via props/CSS */
|
||||||
width: 95%;
|
width: 95%;
|
||||||
max-height: 85vh;
|
max-height: 85vh;
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header { /* VModal slot */
|
.modal-header {
|
||||||
|
/* VModal slot */
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body { /* VModal slot */
|
.modal-body {
|
||||||
|
/* VModal slot */
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer { /* VModal slot */
|
.modal-footer {
|
||||||
|
/* VModal slot */
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1643,17 +1673,20 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
/* Optimize loading states for mobile */
|
/* Optimize loading states for mobile */
|
||||||
.neo-loading-state { /* VSpinner used instead */
|
.neo-loading-state {
|
||||||
|
/* VSpinner used instead */
|
||||||
padding: 2rem 1rem;
|
padding: 2rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner-dots span { /* VSpinner has its own dot styling */
|
.spinner-dots span {
|
||||||
|
/* VSpinner has its own dot styling */
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Improve scrolling performance */
|
/* Improve scrolling performance */
|
||||||
.item-list-tight { /* Assuming VList with this class */
|
.item-list-tight {
|
||||||
|
/* Assuming VList with this class */
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1689,7 +1722,8 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
/* Improve scrolling performance */
|
/* Improve scrolling performance */
|
||||||
.item-list-tight { /* Assuming VList with this class */
|
.item-list-tight {
|
||||||
|
/* Assuming VList with this class */
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
@ -1725,42 +1759,152 @@ const handleExpenseCreated = (expense: any) => {
|
|||||||
/* @keyframes dot-pulse { ... } */
|
/* @keyframes dot-pulse { ... } */
|
||||||
|
|
||||||
/* Utility classes that might still be used or can be replaced by Tailwind/global equivalents */
|
/* Utility classes that might still be used or can be replaced by Tailwind/global equivalents */
|
||||||
.flex { display: flex; }
|
.flex {
|
||||||
.items-center { align-items: center; }
|
display: flex;
|
||||||
.justify-between { justify-content: space-between; }
|
}
|
||||||
.gap-1 { gap: 0.25rem; }
|
|
||||||
.gap-2 { gap: 0.5rem; }
|
.items-center {
|
||||||
.ml-1 { margin-left: 0.25rem; }
|
align-items: center;
|
||||||
.ml-2 { margin-left: 0.5rem; }
|
}
|
||||||
.mt-1 { margin-top: 0.25rem; }
|
|
||||||
.mt-2 { margin-top: 0.5rem; }
|
.justify-between {
|
||||||
.mt-4 { margin-top: 1rem; }
|
justify-content: space-between;
|
||||||
.mb-2 { margin-bottom: 0.5rem; }
|
}
|
||||||
.mb-3 { margin-bottom: 1rem; } /* Adjusted from 1.5rem to match common spacing */
|
|
||||||
.mb-4 { margin-bottom: 1.5rem; }
|
.gap-1 {
|
||||||
.py-10 { padding-top: 2.5rem; padding-bottom: 2.5rem; }
|
gap: 0.25rem;
|
||||||
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
|
}
|
||||||
.p-4 { padding: 1rem; }
|
|
||||||
.border { border-width: 1px; /* Assuming default border color from global styles or Tailwind */ }
|
.gap-2 {
|
||||||
.rounded-lg { border-radius: 0.5rem; }
|
gap: 0.5rem;
|
||||||
.shadow { box-shadow: 0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px 0 rgba(0,0,0,0.06); /* Example shadow */}
|
}
|
||||||
.flex-grow { flex-grow: 1; }
|
|
||||||
.w-24 { width: 6rem; } /* Tailwind w-24 */
|
.ml-1 {
|
||||||
.text-sm { font-size: 0.875rem; }
|
margin-left: 0.25rem;
|
||||||
.text-gray-500 { color: #6b7280; } /* Tailwind gray-500 */
|
}
|
||||||
.text-gray-400 { color: #9ca3af; } /* Tailwind gray-400 */
|
|
||||||
.text-green-600 { color: #16a34a; } /* Tailwind green-600 */
|
.ml-2 {
|
||||||
.text-yellow-500 { color: #eab308; } /* Tailwind yellow-500 */
|
margin-left: 0.5rem;
|
||||||
.line-through { text-decoration: line-through; }
|
}
|
||||||
.opacity-50 { opacity: 0.5; }
|
|
||||||
.opacity-60 { opacity: 0.6; } /* Added for completed item name */
|
.mt-1 {
|
||||||
.opacity-70 { opacity: 0.7; } /* Added for completed item background */
|
margin-top: 0.25rem;
|
||||||
.shrink-0 { flex-shrink: 0; }
|
}
|
||||||
.bg-gray-100 { background-color: #f3f4f6; } /* Tailwind gray-100 */
|
|
||||||
|
.mt-2 {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-2 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-3 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjusted from 1.5rem to match common spacing */
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-10 {
|
||||||
|
padding-top: 2.5rem;
|
||||||
|
padding-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-4 {
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border {
|
||||||
|
border-width: 1px;
|
||||||
|
/* Assuming default border color from global styles or Tailwind */
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-lg {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
/* Example shadow */
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-24 {
|
||||||
|
width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tailwind w-24 */
|
||||||
|
.text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray-500 {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tailwind gray-500 */
|
||||||
|
.text-gray-400 {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tailwind gray-400 */
|
||||||
|
.text-green-600 {
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tailwind green-600 */
|
||||||
|
.text-yellow-500 {
|
||||||
|
color: #eab308;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tailwind yellow-500 */
|
||||||
|
.line-through {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-50 {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-60 {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Added for completed item name */
|
||||||
|
.opacity-70 {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Added for completed item background */
|
||||||
|
.shrink-0 {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gray-100 {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tailwind gray-100 */
|
||||||
|
|
||||||
/* Styles for .neo-list-card, .neo-item-list, .neo-item might be replaced by VCard/VList/VListItem defaults or props */
|
/* Styles for .neo-list-card, .neo-item-list, .neo-item might be replaced by VCard/VList/VListItem defaults or props */
|
||||||
/* Keeping some specific styles for .neo-item-details, .item-name, etc. if they are distinct. */
|
/* Keeping some specific styles for .neo-item-details, .item-name, etc. if they are distinct. */
|
||||||
.item-with-actions { /* Custom class for VListItem if needed for specific layout */
|
.item-with-actions {
|
||||||
|
/* Custom class for VListItem if needed for specific layout */
|
||||||
/* Default VListItem is display:flex, so this might not be needed or just for minor tweaks */
|
/* Default VListItem is display:flex, so this might not be needed or just for minor tweaks */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -41,11 +41,7 @@ const initializeSW = async () => {
|
|||||||
|
|
||||||
// Use with precache injection
|
// Use with precache injection
|
||||||
// vite-plugin-pwa will populate self.__WB_MANIFEST
|
// vite-plugin-pwa will populate self.__WB_MANIFEST
|
||||||
if (self.__WB_MANIFEST) {
|
precacheAndRoute(self.__WB_MANIFEST);
|
||||||
precacheAndRoute(self.__WB_MANIFEST);
|
|
||||||
} else {
|
|
||||||
console.warn('No manifest found for precaching');
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupOutdatedCaches();
|
cleanupOutdatedCaches();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user