Update dependencies and refactor ListDetailPage for drag-and-drop functionality
All checks were successful
Deploy to Production, build images and push to Gitea Registry / build_and_push (pull_request) Successful in 1m23s
All checks were successful
Deploy to Production, build images and push to Gitea Registry / build_and_push (pull_request) Successful in 1m23s
- Updated `vue-i18n` and related dependencies to version 9.14.4 for improved localization support. - Added `vuedraggable` to enable drag-and-drop functionality for list items in `ListDetailPage.vue`. - Refactored the item list structure to accommodate drag handles and improved item actions. - Enhanced styling for drag-and-drop interactions and item actions for better user experience.
This commit is contained in:
parent
d8db5721f4
commit
b9aace0c4e
133
fe/package-lock.json
generated
133
fe/package-lock.json
generated
@ -18,8 +18,9 @@
|
||||
"motion": "^12.15.0",
|
||||
"pinia": "^3.0.2",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^12.0.0-alpha.2",
|
||||
"vue-i18n": "^9.9.1",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"workbox-background-sync": "^7.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -2585,11 +2586,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/bundle-utils/node_modules/@intlify/message-compiler": {
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.4.tgz",
|
||||
"integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/message-compiler": "9.14.4",
|
||||
"@intlify/shared": "9.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.4.tgz",
|
||||
"integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.14.4",
|
||||
"source-map-js": "^1.0.2"
|
||||
@ -2601,54 +2618,10 @@
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/bundle-utils/node_modules/@intlify/shared": {
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.4.tgz",
|
||||
"integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "12.0.0-alpha.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-12.0.0-alpha.2.tgz",
|
||||
"integrity": "sha512-sPWvQ1Z4Wyw9Kp8xqjAk2sMOeZ4pO7p/NL3Eol8l9a7iPyMTuHyJ2DZVbOBG6zDnCupvLAqnRMAT1LAgvx0QRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/message-compiler": "12.0.0-alpha.2",
|
||||
"@intlify/shared": "12.0.0-alpha.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "12.0.0-alpha.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-12.0.0-alpha.2.tgz",
|
||||
"integrity": "sha512-PD9C+oQbb7BF52hec0+vLnScaFkvnfX+R7zSbODYuRo/E2niAtGmHd0wPvEMsDhf9Z9b8f/qyDsVeZnD/ya9Ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "12.0.0-alpha.2",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "12.0.0-alpha.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-12.0.0-alpha.2.tgz",
|
||||
"integrity": "sha512-P2DULVX9nz3y8zKNqLw9Es1aAgQ1JGC+kgpx5q7yLmrnAKkPR5MybQWoEhxanefNJgUY5ehsgo+GKif59SrncA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
@ -2696,18 +2669,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/unplugin-vue-i18n/node_modules/@intlify/shared": {
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.4.tgz",
|
||||
"integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/unplugin-vue-i18n/node_modules/pathe": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
||||
@ -2715,29 +2676,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@intlify/vue-i18n-core": {
|
||||
"version": "12.0.0-alpha.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/vue-i18n-core/-/vue-i18n-core-12.0.0-alpha.2.tgz",
|
||||
"integrity": "sha512-y1NPEcPbD8xqWGiaEREkA9WxxWbxmd8IurN176w39MenZKEf5P20rYyk0w4r718+w/9jjm0m5zR1ed6uMaZT2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "12.0.0-alpha.2",
|
||||
"@intlify/shared": "12.0.0-alpha.2",
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/vue-i18n-core/node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@ -11211,6 +11149,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
|
||||
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@ -12820,14 +12764,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "12.0.0-alpha.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-12.0.0-alpha.2.tgz",
|
||||
"integrity": "sha512-ZSaZrDV/PhD4hLVybo84bkaNRnkGDF7GpI0Fcmn9Yj+2Kq5C3nE7I5iRbo+DiHyRGrwZ/IV1VP3naFAhcNJGsg==",
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.4.tgz",
|
||||
"integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "12.0.0-alpha.2",
|
||||
"@intlify/shared": "12.0.0-alpha.2",
|
||||
"@intlify/vue-i18n-core": "12.0.0-alpha.2",
|
||||
"@intlify/core-base": "9.14.4",
|
||||
"@intlify/shared": "9.14.4",
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -12894,6 +12837,18 @@
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vuedraggable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
||||
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sortablejs": "1.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
|
@ -31,6 +31,7 @@
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^9.9.1",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"workbox-background-sync": "^7.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -74,4 +75,4 @@
|
||||
"workbox-routing": "^7.3.0",
|
||||
"workbox-strategies": "^7.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,76 +47,123 @@
|
||||
</div>
|
||||
<!-- End Integrated Header -->
|
||||
|
||||
<ul class="neo-item-list">
|
||||
<li v-for="item in list.items" :key="item.id" class="neo-list-item"
|
||||
:class="{ 'bg-gray-100 opacity-70': item.is_complete, 'item-pending-sync': isItemPendingSync(item) }">
|
||||
<div class="neo-item-content">
|
||||
<!-- Content when NOT editing -->
|
||||
<template v-if="!item.isEditing">
|
||||
<label class="neo-checkbox-label" @click.stop>
|
||||
<input type="checkbox" :checked="item.is_complete" @change="handleCheckboxChange(item, $event)" />
|
||||
<div class="checkbox-content">
|
||||
<span class="checkbox-text-span"
|
||||
:class="{ 'neo-completed-static': item.is_complete && !item.updating }">{{
|
||||
item.name }}</span>
|
||||
<span v-if="item.quantity" class="text-sm text-gray-500 ml-1">× {{ item.quantity }}</span>
|
||||
<div v-if="item.is_complete" class="neo-price-input">
|
||||
<VInput type="number" :model-value="item.priceInput || ''"
|
||||
@update:modelValue="item.priceInput = $event"
|
||||
:placeholder="$t('listDetailPage.items.pricePlaceholder')" size="sm" class="w-24" step="0.01"
|
||||
@blur="updateItemPrice(item)"
|
||||
@keydown.enter.prevent="($event.target as HTMLInputElement).blur()" />
|
||||
<draggable v-model="list.items" item-key="id" handle=".drag-handle" @end="handleDragEnd" :disabled="!isOnline"
|
||||
class="neo-item-list">
|
||||
<template #item="{ element: item }">
|
||||
<li class="neo-list-item"
|
||||
:class="{ 'bg-gray-100 opacity-70': item.is_complete, 'item-pending-sync': isItemPendingSync(item) }">
|
||||
<div class="neo-item-content">
|
||||
<!-- Drag Handle -->
|
||||
<div class="drag-handle" v-if="isOnline">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="9" cy="12" r="1"></circle>
|
||||
<circle cx="9" cy="5" r="1"></circle>
|
||||
<circle cx="9" cy="19" r="1"></circle>
|
||||
<circle cx="15" cy="12" r="1"></circle>
|
||||
<circle cx="15" cy="5" r="1"></circle>
|
||||
<circle cx="15" cy="19" r="1"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- Content when NOT editing -->
|
||||
<template v-if="!item.isEditing">
|
||||
<label class="neo-checkbox-label" @click.stop>
|
||||
<input type="checkbox" :checked="item.is_complete" @change="handleCheckboxChange(item, $event)" />
|
||||
<div class="checkbox-content">
|
||||
<span class="checkbox-text-span"
|
||||
:class="{ 'neo-completed-static': item.is_complete && !item.updating }">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
<span v-if="item.quantity" class="text-sm text-gray-500 ml-1">× {{ item.quantity }}</span>
|
||||
<div v-if="item.is_complete" class="neo-price-input">
|
||||
<VInput type="number" :model-value="item.priceInput || ''"
|
||||
@update:modelValue="item.priceInput = $event"
|
||||
:placeholder="$t('listDetailPage.items.pricePlaceholder')" size="sm" class="w-24" step="0.01"
|
||||
@blur="updateItemPrice(item)"
|
||||
@keydown.enter.prevent="($event.target as HTMLInputElement).blur()" />
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<div class="neo-item-actions">
|
||||
<button class="neo-icon-button neo-edit-button" @click.stop="startItemEdit(item)"
|
||||
:aria-label="$t('listDetailPage.items.editItemAriaLabel')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="neo-icon-button neo-delete-button" @click.stop="deleteItem(item)"
|
||||
:disabled="item.deleting" :aria-label="$t('listDetailPage.items.deleteItemAriaLabel')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 6h18"></path>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<div class="neo-item-actions">
|
||||
<button class="neo-icon-button neo-edit-button" @click.stop="startItemEdit(item)"
|
||||
:aria-label="$t('listDetailPage.items.editItemAriaLabel')">
|
||||
<VIcon name="edit" />
|
||||
</button>
|
||||
<button class="neo-icon-button neo-delete-button" @click.stop="deleteItem(item)"
|
||||
:disabled="item.deleting" :aria-label="$t('listDetailPage.items.deleteItemAriaLabel')">
|
||||
<VIcon name="trash" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Content WHEN editing -->
|
||||
<template v-else>
|
||||
<div class="inline-edit-form flex-grow flex items-center gap-2">
|
||||
<VInput type="text" :model-value="item.editName ?? ''" @update:modelValue="item.editName = $event"
|
||||
required class="flex-grow" size="sm" @keydown.enter.prevent="saveItemEdit(item)"
|
||||
@keydown.esc.prevent="cancelItemEdit(item)" />
|
||||
<VInput type="number" :model-value="item.editQuantity || ''"
|
||||
@update:modelValue="item.editQuantity = $event" min="1" class="w-20" size="sm"
|
||||
@keydown.enter.prevent="saveItemEdit(item)" @keydown.esc.prevent="cancelItemEdit(item)" />
|
||||
</div>
|
||||
<div class="neo-item-actions">
|
||||
<button class="neo-icon-button neo-save-button" @click.stop="saveItemEdit(item)"
|
||||
:aria-label="$t('listDetailPage.buttons.saveChanges')">
|
||||
<VIcon name="save" />
|
||||
</button>
|
||||
<button class="neo-icon-button neo-cancel-button" @click.stop="cancelItemEdit(item)"
|
||||
:aria-label="$t('listDetailPage.buttons.cancel')">
|
||||
<VIcon name="x-circle" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</li>
|
||||
<!-- New Add Item LI, integrated into the list -->
|
||||
<li class="neo-list-item new-item-input-container">
|
||||
<label class="neo-checkbox-label">
|
||||
<input type="checkbox" disabled />
|
||||
<input type="text" class="neo-new-item-input"
|
||||
:placeholder="$t('listDetailPage.items.addItemForm.placeholder')" ref="itemNameInputRef"
|
||||
:data-list-id="list?.id" @keyup.enter="onAddItem" @blur="handleNewItemBlur" v-model="newItem.name"
|
||||
@click.stop />
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<!-- Content WHEN editing -->
|
||||
<template v-else>
|
||||
<div class="inline-edit-form flex-grow flex items-center gap-2">
|
||||
<VInput type="text" :model-value="item.editName ?? ''" @update:modelValue="item.editName = $event"
|
||||
required class="flex-grow" size="sm" @keydown.enter.prevent="saveItemEdit(item)"
|
||||
@keydown.esc.prevent="cancelItemEdit(item)" />
|
||||
<VInput type="number" :model-value="item.editQuantity || ''"
|
||||
@update:modelValue="item.editQuantity = $event" min="1" class="w-20" size="sm"
|
||||
@keydown.enter.prevent="saveItemEdit(item)" @keydown.esc.prevent="cancelItemEdit(item)" />
|
||||
</div>
|
||||
<div class="neo-item-actions">
|
||||
<button class="neo-icon-button neo-save-button" @click.stop="saveItemEdit(item)"
|
||||
:aria-label="$t('listDetailPage.buttons.saveChanges')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
|
||||
<polyline points="17 21 17 13 7 13 7 21"></polyline>
|
||||
<polyline points="7 3 7 8 15 8"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="neo-icon-button neo-cancel-button" @click.stop="cancelItemEdit(item)"
|
||||
:aria-label="$t('listDetailPage.buttons.cancel')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="15" y1="9" x2="9" y2="15"></line>
|
||||
<line x1="9" y1="9" x2="15" y2="15"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="neo-icon-button neo-delete-button" @click.stop="deleteItem(item)"
|
||||
:disabled="item.deleting" :aria-label="$t('listDetailPage.items.deleteItemAriaLabel')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 6h18"></path>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<!-- New Add Item LI, integrated into the list -->
|
||||
<li class="neo-list-item new-item-input-container">
|
||||
<label class="neo-checkbox-label">
|
||||
<input type="checkbox" disabled />
|
||||
<input type="text" class="neo-new-item-input"
|
||||
:placeholder="$t('listDetailPage.items.addItemForm.placeholder')" ref="itemNameInputRef"
|
||||
:data-list-id="list?.id" @keyup.enter="onAddItem" @blur="handleNewItemBlur" v-model="newItem.name"
|
||||
@click.stop />
|
||||
</label>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<!-- Expenses Section (Original Content - Part 3 will refactor this) -->
|
||||
<!-- Expenses Section -->
|
||||
<section v-if="list && !itemsAreLoading" class="neo-expenses-section">
|
||||
<div class="neo-expenses-header">
|
||||
<h2 class="neo-expenses-title">{{ $t('listDetailPage.expensesSection.title') }}</h2>
|
||||
@ -191,7 +238,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Create Expense Form (Original Content) -->
|
||||
<!-- Create Expense Form -->
|
||||
<CreateExpenseForm v-if="showCreateExpenseForm" :list-id="list?.id" :group-id="list?.group_id ?? undefined"
|
||||
@close="showCreateExpenseForm = false" @created="handleExpenseCreated" />
|
||||
|
||||
@ -299,9 +346,9 @@
|
||||
<template #footer>
|
||||
<VButton variant="neutral" @click="closeSettleShareModal">{{
|
||||
$t('listDetailPage.settleShareModal.cancelButton')
|
||||
}}</VButton>
|
||||
}}</VButton>
|
||||
<VButton variant="primary" @click="handleConfirmSettle">{{ $t('listDetailPage.settleShareModal.confirmButton')
|
||||
}}</VButton>
|
||||
}}</VButton>
|
||||
</template>
|
||||
</VModal>
|
||||
|
||||
@ -314,8 +361,8 @@
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { apiClient, API_ENDPOINTS } from '@/config/api'; // Keep for item management
|
||||
import { useEventListener, useFileDialog, useNetwork } from '@vueuse/core'; // onClickOutside removed
|
||||
import { apiClient, API_ENDPOINTS } from '@/config/api';
|
||||
import { useEventListener, useFileDialog, useNetwork } from '@vueuse/core';
|
||||
import { useNotificationStore } from '@/stores/notifications';
|
||||
import { useOfflineStore, type CreateListItemPayload } from '@/stores/offline';
|
||||
import { useListDetailStore } from '@/stores/listDetailStore';
|
||||
@ -340,7 +387,7 @@ import VInput from '@/components/valerie/VInput.vue';
|
||||
import VList from '@/components/valerie/VList.vue';
|
||||
import VListItem from '@/components/valerie/VListItem.vue';
|
||||
import VCheckbox from '@/components/valerie/VCheckbox.vue';
|
||||
// VTextarea and VSelect are not used in this part of the refactor for ListDetailPage
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -1198,6 +1245,32 @@ const handleCheckboxChange = (item: ItemWithUI, event: Event) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragEnd = async (evt: any) => {
|
||||
if (!list.value || evt.oldIndex === evt.newIndex) return;
|
||||
|
||||
const item = list.value.items[evt.newIndex];
|
||||
const newPosition = evt.newIndex + 1; // Assuming backend uses 1-based indexing
|
||||
|
||||
try {
|
||||
await apiClient.put(
|
||||
API_ENDPOINTS.LISTS.ITEM(String(list.value.id), String(item.id)),
|
||||
{ position: newPosition, version: item.version }
|
||||
);
|
||||
item.version++;
|
||||
notificationStore.addNotification({
|
||||
message: t('listDetailPage.notifications.itemReorderedSuccess'),
|
||||
type: 'success'
|
||||
});
|
||||
} catch (err) {
|
||||
// Revert the order on error
|
||||
list.value.items = [...list.value.items];
|
||||
notificationStore.addNotification({
|
||||
message: getApiErrorMessage(err, 'listDetailPage.errors.reorderItemFailed'),
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -1437,12 +1510,49 @@ const handleCheckboxChange = (item: ItemWithUI, event: Event) => {
|
||||
|
||||
.item-pending-sync {}
|
||||
|
||||
.neo-icon-button {
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.neo-icon-button:hover {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.neo-edit-button {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.neo-edit-button:hover {
|
||||
background: #eef7fd;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.neo-delete-button {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.neo-delete-button:hover {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.neo-save-button {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.neo-save-button:hover {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.neo-cancel-button {
|
||||
@ -1451,14 +1561,9 @@ const handleCheckboxChange = (item: ItemWithUI, event: Event) => {
|
||||
|
||||
.neo-cancel-button:hover {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.neo-edit-button:hover {
|
||||
background: #eef7fd;
|
||||
}
|
||||
|
||||
.neo-delete-button {}
|
||||
|
||||
/* Custom Checkbox Styles */
|
||||
.neo-checkbox-label {
|
||||
display: grid;
|
||||
@ -1772,18 +1877,6 @@ const handleCheckboxChange = (item: ItemWithUI, event: Event) => {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.neo-icon-button {
|
||||
padding: 0.25rem;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.neo-icon-button:hover {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.inline-edit-form {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
@ -1799,4 +1892,48 @@ const handleCheckboxChange = (item: ItemWithUI, event: Event) => {
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
cursor: grab;
|
||||
padding: 0.5rem;
|
||||
color: #666;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.neo-list-item:hover .drag-handle {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
opacity: 1 !important;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* Update neo-item-content to accommodate drag handle */
|
||||
.neo-item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Add styles for dragging state */
|
||||
.sortable-ghost {
|
||||
opacity: 0.5;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.sortable-drag {
|
||||
background: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user