hello. i am a beginner. i am trying to emit changes from my serialnumberdialog.vue to a table of a q-tab-panel in my viewdetails.vue.
please help me get the expected outcome. i already tried almost everything and asked for help using chatgpt but i can’t seem to find the right solution. also, sometimes the output is just a blank white screen and errors in the console so i am having a hard time.
this is my serialnumberdialog code:
<template>
<transition appear enter-active-class="animated fadeIn" leave-active-class="animated fadeOut" :duration="3000">
<div class="template-content">
<div class="q-px-lg q-pt-lg gt-xs">
<!-- HEADER START -->
<div class="flex justify-between items-center q-pt-md">
<div class="flex items-center">
<h5 class="text-26 text-bold q-my-none q-ml-md" style="font-size: 26px">Select Serial Number</h5>
</div>
<q-btn
@click="handleSaveSerialNumber"
unelevated
rounded
label="Save"
class="saveserial no-uppercase-saveserial"
style="border-style: hidden; width: 160px; height: 38px; background-color: #d48806; color: white"
/>
</div>
<h5 class="text-semibold q-my-none" style="margin-left: 20px; font-size: 16px">You have 3 existing serial numbers. Kindly assign it to the respective variant and store.</h5>
<!-- HEADER END -->
<div class="flex items-center">
<!-- STORE BUTTON START -->
<div class="q-pa-md">
<transition appear enter-active-class="animated fadeIn" leave-active-class="animated fadeOut" :duration="2000">
<q-btn-dropdown
unelevated
rounded
:style="{ border: '1px solid #d48806', width: '190px', height: '31px' }"
color="white"
text-color="black"
class="no-uppercase"
>
<template v-slot:label>
<div style="flex: 1; text-align: left; font-size: 14px">{{ selectedStore ? selectedStore : 'Select Store' }}</div>
</template>
<template v-slot:icon-right>
<q-icon name="arrow_drop_down" color="#869AB2" />
</template>
<q-list>
<q-item clickable v-close-popup @click="onStoreClick('Store 1')">
<q-item-section>
<q-item-label>Store 1</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="onStoreClick('Store 2')">
<q-item-section>
<q-item-label>Store 2</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="onStoreClick('Store 3')">
<q-item-section>
<q-item-label>Store 3</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</transition>
</div>
<!-- STORE BUTTON END -->
<!-- PAGINATION START -->
<div class="im-pagination-container flex justify-center items-center" style="height: 36px; margin-left: auto; font-size: 15px; width: 132px;">
<div class="q-pagination row no-wrap items-center pagination" :max="maxPages" input="v-model='current'" aria-disabled="false" role="navigation">
<div class="q-pagination__content row no-wrap items-center">
<button class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--rounded text-grey-8"
tabindex="-1" type="button" @click="prevPage" :disabled="isFirstPage" aria-label="Previous"
style="font-size: 8px; padding: 3px 2px; width: 29px; height: 28px;">
<span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<i class="q-icon notranslate material-icons" aria-hidden="true" role="img">keyboard_arrow_left</i>
</span>
</button>
<div class="q-pagination__middle row justify-center">
<q-btn v-for="page in pages" :key="page" @click="setPage(page)" :label="page" :flat="current !== page" :color="current === page ? 'grey-8' : 'grey-2'" style="font-size: 10px; padding: 3px 2px; min-width: 2em; height: 31px;" />
</div>
<button class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--rectangle text-grey-8"
tabindex="0" type="button" @click="nextPage" :disabled="isLastPage" aria-label="Next"
style="font-size: 8px; padding: 3px 2px; width: 29px; height: 28px;">
<span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<i class="q-icon notranslate material-icons" aria-hidden="true" role="img">keyboard_arrow_right</i>
</span>
</button>
</div>
</div>
<div class="im-pagination-select-input text-no-wrap" style="height: 31px;">
<label class="q-field row no-wrap items-start q-field--outlined q-select q-field--auto-height q-select--without-input q-select--without-chips q-select--single q-field--rounded q-field--float q-field--dense im-input-field" style="height: 31px;">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<div class="q-field__native row items-center">
<span>{{ current }} / {{ maxPages }}</span>
<input class="q-select__focus-target" readonly tabindex="0" role="combobox" aria-readonly="false" aria-autocomplete="none" aria-expanded="false">
</div>
</div>
<div class="q-field__append q-field__marginal row no-wrap items-center q-anchor--skip">
<i class="q-icon notranslate material-icons q-select__dropdown-icon" aria-hidden="true" role="presentation">arrow_drop_down</i>
</div>
</div>
</div>
</label>
</div>
</div>
<!-- PAGINATION END -->
</div>
<!-- Store Preview/Table -->
<div v-if="!selectedStore" class="store-preview flex justify-center items-center">
<span class="text-24">No preview available. Please select store.</span>
</div>
<div v-else class="q-table__container q-table--horizontal-separator column no-wrap q-table__card q-table--flat q-table--no-wrap im-table">
<div class="q-table__middle scroll">
<div class="q-table-container" style="margin-left: 20px;">
<table class="q-table">
<thead>
<tr>
<th class="text-left" style="width: 50%;">Variant</th>
<th class="text-left" style="width: 50%;">Serial Number</th>
</tr>
</thead>
<tbody>
<tr class="text-left text-14" style="height: 50px !important;" v-for="(variant, index) in variants" :key="index">
<td style="height: 50px;">{{ variant }}</td>
<td>
<SerialNumberDropdown
:serialNumbers="serialNumbers"
:selectedSerialNumbers="selectedSerialNumbersMap[selectedStore]?.[index] || []"
:onSerialSelect="onSerialSelect"
:serialNumberId="index.toString()"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="q-table__bottom row items-center q-table__bottom--nodata">
<i class="q-icon notranslate material-icons q-table__bottom-nodata-icon" aria-hidden="true" role="presentation">warning</i>
No data available
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
import { ref, computed } from 'vue';
import SerialNumberDropdown from './SerialNumberDropdown.vue';
import { QBtn, QBtnDropdown, QIcon, QItem, QItemSection, QList } from 'quasar';
export default {
name: 'SerialNumberDialog',
emits: ['save-serial-number', 'update-serial-numbers'], // Declare the events
components: {
QBtn,
QBtnDropdown,
QIcon,
QItem,
QItemSection,
QList,
SerialNumberDropdown,
},
setup() {
const current = ref(1);
const maxPages = ref(5);
const selectedStore = ref(null);
const serialNumbers = ref(['147852AHGSDGDSDA', '3587ADEHJSGDKWG', '985799SAKHSAHS666']);
const selectedSerialNumbersMap = ref({
'Store 1': Array.from({ length: 9 }, () => []),
'Store 2': Array.from({ length: 9 }, () => []),
'Store 3': Array.from({ length: 9 }, () => [])
});
const variants = ref(['Black, Small', 'Black, Medium', 'Black, Large', 'Beige, Small', 'Beige, Medium', 'Beige, Large', 'White, Small', 'White, Medium', 'White, Large']);
const pages = computed(() => Array.from({ length: maxPages.value }, (_, i) => i + 1));
const isFirstPage = computed(() => current.value === 1);
const isLastPage = computed(() => current.value === maxPages.value);
const prevPage = () => { if (!isFirstPage.value) current.value -= 1; };
const nextPage = () => { if (!isLastPage.value) current.value += 1; };
const setPage = (page) => { current.value = page; };
const onStoreClick = (store) => { selectedStore.value = store; };
const onSerialSelect = (serialNumberId, serialNumber) => {
const store = selectedStore.value;
if (!store) return;
const selectedSerials = selectedSerialNumbersMap.value[store][serialNumberId];
const index = selectedSerials.indexOf(serialNumber);
if (index > -1) {
selectedSerials.splice(index, 1);
} else {
selectedSerials.push(serialNumber);
}
// Emit the event with the updated data
this.$emit('update-serial-numbers', selectedSerialNumbersMap.value);
};
return {
current,
maxPages,
pages,
isFirstPage,
isLastPage,
prevPage,
nextPage,
setPage,
onStoreClick,
onSerialSelect,
selectedStore,
serialNumbers,
selectedSerialNumbersMap,
variants,
};
},
methods: {
handleSaveSerialNumber() {
const serialData = (this.selectedSerialNumbersMap[this.selectedStore] || []).map((serialNumbers, index) => ({
serialNumber: serialNumbers.join(', '),
variant: this.variants[index],
store: this.selectedStore,
}));
// Emit save event with serial number data
this.$emit('save-serial-number', serialData);
this.$router.go(-1);
},
},
};
</script>
<style scoped>
.no-uppercase {
text-transform: none !important;
}
.no-uppercase-saveserial {
text-transform: none !important;
}
.im-pagination-container {
border-radius: 19px;
overflow: hidden;
}
.text-left.text-14 {
height: 50px !important;
}
.q-pa-md {
display: flex;
align-items: center;
}
.q-table-container {
width: 979px;
height: 506px;
overflow: auto;
}
</style>
this is my viewdetails.vue:
<q-tab-panel name="serial-number" class="q-px-none" v-if="form && !form.enable_serial_number">
<div class="flex justify-between q-px-xs q-mt-xs">
<div class="flex justify-start items-center">
<div class="text-14">Store:</div>
<q-select
borderless
v-model="store"
:label="store !== null ? '' : 'Select Store'"
:options="storeOption"
class="im-select-field im-border-radius-50 height-31 min-width-190 im-border-accent-0 text-center text-15 q-ma-md"
popup-content-class="im-option-style-light"
/>
<div class="text-14">Status:</div>
<q-select
borderless
v-model="status"
:label="status !== null ? '' : 'Available'"
:options="statusOption"
class="im-select-field im-border-radius-50 height-31 min-width-190 im-border-accent-0 text-center text-15 q-ma-md"
popup-content-class="im-option-style-light"
/>
<div class="text-14">Variant</div>
<q-select
borderless
v-model="variant"
:label="variant !== null ? '' : 'All'"
:options="variantOption"
class="im-select-field im-border-radius-50 height-31 min-width-190 im-border-accent-0 text-center text-15 q-ma-md"
popup-content-class="im-option-style-light"
/>
</div>
<div class="flex justify-start items-center">
<div class="text-14 q-mr-md">Serial Number:</div>
<Filters
dynamicHeight="34"
:searchVisible="true"
:filterDateVisible="false"
:labelVisible="false"
pathEndPoint="projects"
/>
</div>
</div>
<!-- If store is null -->
<div v-if="!store" class="store-preview flex justify-center items-center">
<!-- Then render no preview available -->
<span class="text-24">No preview available. Please select store.</span>
</div>
<!-- Else render the table -->
<q-table
v-else
:rows="serialRows"
:columns="serialColumns"
row-key="serialNumbers"
class="im-table"
flat
/>
<ul>
<li v-for="(serialNumbers, index) in serialNumbers" :key="index">
{{ serialNumbers }}
</li>
</ul>
</q-tab-panel>
and this is my viewdetails.js:
import { ref } from 'vue';
import Filters from '../../../components/Filters.vue';
import Pagination from '../../../components/Pagination.vue';
import SerialNumberDialog from '../SerialNumberDialog.vue';
import { ProductDetails } from '../../../composables/Products';
import AddProduct from '../AddProduct.vue';
import { useRoute, useRouter } from 'vue-router';
export default {
props: {
serialNumbers: {
type: Array,
required: true,
},
},
components: {
Filters,
Pagination,
AddProduct,
SerialNumberDialog,
},
setup() {
const router = useRouter();
const route = useRoute();
// Initialize the form object to avoid undefined issues
const form = ref({
enable_batch_tracking: false,
enable_serial_number: false,
});
// Define state using Composition API
const tab = ref('product-details');
const serialRows = ref([]); // Ensure this is reactive
const serialColumns = [
{ name: 'serialNumber', label: 'Serial Number', align: 'left', field: 'serialNumber' },
{ name: 'rfid', label: 'RFID', align: 'left', field: 'rfid'},
{ name: 'variant', label: 'Variant', align: 'center', field: 'variant' },
{ name: 'statusOption', label: 'Status', align: 'right', field: 'statusOption' },
{ name: 'date', label: 'Date Modified', align: 'right', field: 'date' },
];
// For select option
const store = ref(null);
const storeOption = ref(['Store 1', 'Store 2', 'Store 3']);
const status = ref(null);
const statusOption = ref(['Available', 'Unavailable']);
const variant = ref(null);
const variantOption = ref([
'All',
'Black, Small',
'Black, Medium',
'Black, Large',
'Beige, Small',
'Beige, Medium',
'Beige, Large',
'White, Small',
'White, Medium',
'White, Large',
]);
// For batch table
const batchRows = ref([]);
const batchColumns = [
{ name: 'batchNumber', align: 'left', label: 'Batch Number', field: 'batchNumber' },
{ name: 'rfid', align: 'left', label: 'RFID', field: 'rfid' },
{ name: 'expiryDate', align: 'left', label: 'Expiry Date', field: 'expiryDate' },
{ name: 'originalStock', align: 'left', label: 'Original Stock', field: 'originalStock' },
{ name: 'remainingStock', align: 'left', label: 'Remaining Stock', field: 'remainingStock' },
];
const editProduct = () => {
router.push({
name: 'add-products',
params: {
id: route.params.id,
},
});
};
return {
tab,
form,
serialRows, // Reactive array to store serial numbers
serialColumns,
batchRows,
batchColumns,
store,
storeOption,
status,
statusOption,
variant,
variantOption,
ProductDetails,
editProduct,
};
},
methods: {
updateSerialRows(serialData) {
// Update the serialRows data here
this.serialRows = serialData;
},
updateSerialNumbers(updatedSerialNumbersMap) {
this.selectedSerialNumbersMap = updatedSerialNumbersMap;
// Now you can derive the `serialRows` array from `selectedSerialNumbersMap`
this.serialRows = [];
for (const store in updatedSerialNumbersMap) {
if (updatedSerialNumbersMap.hasOwnProperty(store)) {
updatedSerialNumbersMap[store].forEach((serialNumbers, variantIndex) => {
this.serialRows.push({
variant: this.variantOption[variantIndex + 1], // Adjust the index based on your variant options
serialNumber: serialNumbers.join(', '),
store: store,
});
});
}
}
},
},
};
New contributor
Louren Jan Rodriguez is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.