I have a component that it has v-for to showing multiple repetition of data, I use vue3 with composition API, When I initialize table of data and log it, it shows the right value what I want, but the length it is 0 and it's not rendring on template, I don't know why !!
template :
<template>
<base-page>
<form #submit.prevent="submitForm">
<base-header>
<template #title>
<nav class="py-2 rounded-md w-full">
<ol class="list-reset flex">
<li>
<router-link
to="/dashboard"
class="text-blue-600 hover:text-blue-700"
>Accueil</router-link
>
</li>
<li><span class="text-gray-500 mx-2">/</span></li>
<li class="text-gray-500">
Ajouter une réservation pour {{ terrain.name }}
</li>
</ol>
</nav>
</template>
</base-header>
<div class="mt-4">
<div class="flex text-center grid grid-cols-5">
{{sessionList}}
<div v-for="(item, index) in sessionList" :key="index" class="rounded-lg ml-2 shadow-lg bg-white max-w-sm">
<h5 class="text-gray-900 text-center font-medium m-2">Card title</h5>
<a href="#!">
<img class="rounded-t-lg m-auto w-16 h-16" src="/img/green-ball.png" alt=""/>
</a>
<div class="p-2">
<p class="text-gray-700 text-base mb-4">
Some quick example text to {{index}}
</p>
<button type="button" class=" inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out">
Réserver
</button>
</div>
</div>
</div>
</div>
</form>
</base-page>
</template>
script :
<script setup>
import {ref,onMounted} from "vue"
import {mdiContentSavePlusOutline} from "#mdi/js"
import moment from "moment"
moment.locale('fr');
const terrain = ref({
id: 1,
name: "terrain 1",
capacity: 11,
duration: 1,
price: 8,
external: true,
complexe: "Stade 1",
formatted_created_at: "10/10/2022",
})
let date = new moment()
let datesList = []
let sessionList = []
let nextDate = date
for (let index = 0; index < 15; index++) {
datesList.push(nextDate.format('YYYY-MM-DD'))
let tab = []
let hourDate = nextDate
for (let i = 0; i < 4; i++) {
let debut = moment(nextDate).add(i+1,'hours').format('HH:mm:ss')
let fin = moment(nextDate).add(i+2,'hours').format('HH:mm:ss')
tab.push({
debut : debut,
fin : fin,
reserved : i % 2 == 0
})
}
sessionList[datesList[index]] = tab
nextDate = date.add(1,'days');
}
console.log(datesList)
console.log(sessionList)
</script>
log :
Edit
the sessionList variable it would have like this format :
You define sessionList as an array, but you are adding items to it like an object. So the arrays length is still 0, but you added properties with the dates as you can see in the screenshot. This is also why nothing is rendered.
I am not sure what you want to do with the date, but maybe just add it as another property of the tab object and then push this to the array:
tab.push({
debut : debut,
fin : fin,
reserved : i % 2 == 0,
date: datesList[index]
});
sessionList.push(tab);
EDIT after updated question:
Define sessionList as a Map:
const sessionList = new Map();
Then adding entries:
sessionList.set(datesList[index], tab);
In the template you then have to replace the v-for with:
<div v-for="[key, value] in sessionList" :key="key" ...>
I am making a timeslot-picker component and want to toggle the class of each individual timeslot-item so it changes color and also add the timeslot information to an array of objects. If I update the state array with an onClick function the classList.toggle function doesn't seem to work, as none of the timeslots change color. I would appreciate any help on how to combine both functionalites. This is the part of my component required for the functionality:
<ul className="w-[180px] h-[400px] overflow-y-scroll flex flex-col gap-1 pt-4 relative">
<div className="text-center ">{day.day}</div>
{timeslots.map((timeslot) => (
<li key={Math.random()} className="mt-3">
<button
onClick={(e) => {
e.currentTarget.classList.toggle('bg-violet-500');
setRequestedTimeslots([
...requestedTimeslots,
{
day: day.day,
start: timeslot.start,
end: timeslot.end,
},
]);
}}
className="px-4 py-2 bg-[#F5F5F5] rounded-xl w-full text-center"
>
{timeslot.start} - {timeslot.end}
</button>
</li>
))}
</ul>
import { useState } from "react";
const App = () => {
const [open, setOpen] = useState(true);
const Menus = [
{ title: "Home", src: "0" },
{ title: "Site1", src: "1", gap: true },
{ title: "Site2 ", src: "2" },
{ title: "Site3", src: "3" },
{ title: "Site4", src: "4" }
];
return (
<div className="flex">
<div
className={` ${
open ? "w-72" : "w-20 "
} bg-gray-800 p-5 pt-8 sticky top-0 left-0 h-[930px] duration-300`}
>
<img
src="./src/assets/control.png"
className={`absolute cursor-pointer -right-3 top-9 w-7 border-dark-purple
border-2 rounded-full ${!open && "rotate-180"}`}
onClick={() => setOpen(!open)}
/>
<div className="flex gap-x-4 items-center">
<img
src="./src/assets/logo.png"
className={`cursor-pointer duration-500 ${
open && "rotate-[360deg]"
}`}
/>
<h1
className={`text-white origin-left font-medium text-xl duration-200 ${
!open && "scale-0"
}`}
>
Site
</h1>
</div>
<ul className="pt-6">
{Menus.map((Menu, index) => (
<li
key={index}
className={`flex rounded-MD p-2 cursor-pointer hover:bg-light-white text-gray-300 text-sm items-center gap-x-4
${Menu.gap ? "mt-9" : "mt-2"} ${
index === 0 && "bg-light-white"
} `}
>
<img src={`./src/assets/${Menu.src}.png`} />
<span className={`${!open && "hidden"} origin-left duration-200`}>
{Menu.title}
</span>
</li>
))}
</ul>
</div>
This is the code that I got after following this tutorial: Tutorial
How can I link my other pages with the navbar? So clicking for example Site1 will direct the user to Site1?
The problem is that I can't use tags or hfref in this case and I have no idea how to solve my problem as I'm just learning react.
Multiple things you can do here.
If you want to stick with clickable buttons, you could use React Router and the, for each Menu add an onClick={() => router.push(Menu.title)} (subject to whatever your actual url is).
Otherwise, in a more native way, you could use a tags instead of span tags, which can receive href={Menu.title}. The downside here is that you'd have to style them again, as a tags have browser-specific default styles.
Have a look and read into the React-router: https://reactrouter.com/en/main/getting-started/tutorial
To create a link use the tag instead of the tag
I have a Nuxt3 project and my v-for loop is working, but I can't figure out how to order my list.
I know it's a Vue thing and not Nuxt, but I am new to both.
<template>
<div class="max-w-7xl mx-auto my-10 sm:px-6 lg:px-8">
<div v-if="pending">Loading ...</div>
<div v-else>
<ul class="list-none" style="columns: 4">
<li
v-for="nda in ndas"
v-bind:key="nda.id"
class="font-color: text-slate-600 hover:text-red-600 hover:underline"
>
<Nuxt-link :to="'/ndas/' + nda.id">
{{ nda.user_signature }}
</Nuxt-link>
</li>
</ul>
</div>
</div>
</template>
<script setup>
const { pending, data: ndas } = useLazyFetch(
"https://***/nda-data.json"
);
watch(ndas, (newNdas) => {});
</script>
You need to computed filtered array for this. Just create a new computed property and return filtered array.
const filteredNdas = computed(() => {
return ndas.filter(item => item.id > 5)
})
And so use your new array in the v-for loop
v-for="nda in filteredNdas"
In my laravel vue application I have following datatable with some filters in a vue component(department-user-list.vue).
<template>
<div>
<cs-card
:cardButton="false"
:title="`Team members`"
>
<template slot="header-action">
<div class="inline-block mr-4" direction-from="top">
<open-overlay identifier="corporateInviteEmployeeToDepartmentModal">
<cs-button size="small" variant="secondary">
Invite team member
</cs-button>
</open-overlay>
</div>
<div class="inline-block" direction-from="top">
<cs-button #click="redirectToAssignation" size="small">
Assign team member
</cs-button>
</div>
</template>
<Datatable
v-model="selectedForAction"
:data="dataset"
:headers="headers"
:is-loading="isLoading"
:has-action-bar-column="true"
:key-id="`id`"
:search-panel="{placeholder: 'Search team member...'}"
#change="handlePaginationChange"
#paginate="loadDepartmentEmployees"
>
<!--Filter Slot-->
<template v-slot:filters>
<!--Nationality filter-->
<div class="w-2/12 pl-4 h-auto">
<cs-multiple-select
v-model="nationalitiesFilter"
:options="countries"
key-id="id"
label="name"
name="nationality"
placeholder="Nationality"
>
</cs-multiple-select>
</div>
<!--Certificate Status filter-->
<div class="w-6/12 pl-4 h-auto">
<cs-multiple-select
v-model="certificateStatusFilter"
:options="certificateStatus"
name="certificateStatusFilter"
placeholder="Qualification status"
#input="loadDepartmentEmployees"
/>
</div>
<!--Matrix Status filter-->
<div class="w-4/12 pl-4 h-auto">
<cs-multiple-select
v-model="matrixStatusFilter"
:options="matrixStatus"
key-id="value"
label="label"
name="matrixStatusFilter"
placeholder="Matrix status"
#input="loadDepartmentEmployees"
/>
</div>
<!--Employee Type filter-->
<div class="w-4/12 pl-4 h-auto">
<cs-multiple-select
v-model="selectedEmployeeTypes"
:options="employeeType"
key-id="value"
label="label"
name="selectedEmployeeTypes"
placeholder="Employee type"
#input="loadDepartmentEmployees"
>
</cs-multiple-select>
</div>
</template>
<!--Table Header-->
<template v-slot:header.country="{ header}">
<span class="material-icons-outlined">language</span>
</template>
<!--Table Body-->
<template v-slot:item.name="{ item }">
<div class="flex items-center cursor-pointer">
<div class="rounded-full w-8 h-8 mr-4 overflow-hidden">
<img :src="item.profile_image[0].url" alt=""
class="w-full h-full object-cover">
</div>
<a :href="employeeDetailRoute(item)">
<span class="text-certstyle-titles font-bold leading-loose">{{ item.name }}</span>
</a>
<span
v-if="item.is_subcontractor && item.company_name"
:title="item.company_name"
class="text-certstyle-text-light bg-certstyle-background flex font-semibold cursor-help text-xs rounded mx-2 py-1 px-2"
>
{{ item.company_name.substring(0,10) }}
<span v-if="item.company_name.length > 10">...</span>
</span>
</div>
</template>
<template v-slot:item.job_title="{ item }">
{{ item.current_jobtitle || item.department_jobtitle }}
</template>
<template v-slot:item.country="{ item }">
<span v-if="item.country" class="font-normal">
<country-flag width="w-5" :country-code="item.country.country_code"></country-flag>
</span>
</template>
<template v-slot:item.certificate_status="{ item }">
<div class="status--summary--component inline-block mr-2 relative"
#click="getValidityStatus(item.certificate_matrix) !== 'all valid' &&
getValidityStatus(item.certificate_matrix) !== '-'
? openCertificatesModal(item) : null
">
<label :class="getValidityStatusClass(item.certificate_matrix)" class="badge">
{{ getValidityStatus(item.certificate_matrix) }}
</label>
</div>
</template>
<template v-slot:item.matrix_status="{ item }">
<div class="status--summary--component inline-block mr-2 relative"
#click="getMatrixStatus(item.certificate_matrix) === 'non compliant'
? openCertificatesModal(item)
: null
">
<label :class="getMatrixStatusClass(item.certificate_matrix)" class="badge">
{{ getMatrixStatus(item.certificate_matrix) }}
</label>
</div>
</template>
<template v-slot:actionItems="slotProps">
<DatatableActionbarItem
:slot-props="slotProps"
identifier="removeConfirmationModal"
label="Remove"
variant="danger"
/>
</template>
<template v-slot:rowActionItems="slotProps">
<DatatableActionbarItem
icon=""
label="Contact details"
#click.native="openContactDetailsModal(slotProps.item)"
:slot-props="slotProps"
/>
</template>
</Datatable>
<modal-md
v-if="selectedEmployee !== null"
:options="{ useDefaultContentStyling: false }"
:identifier="`statusSummaryComponent`">
<template slot="modal_title">
Qualifications
<span v-if="selectedEmployee !== null && selectedEmployee !== undefined">
{{ selectedEmployee.name }}
</span>
</template>
<div class="bg-white" slot="modal_content">
<div
class="flex items-center justify-between text-certstyle-text-light bg-white border-certstyle-border border-b text-sm py-2 px-10">
<cs-dashboard-table-header-item-unstyled :item="`statusSummaryCertificateTitle`">
<p>Qualification</p>
</cs-dashboard-table-header-item-unstyled>
<cs-dashboard-table-header-item-unstyled :item="`statusSummaryCertificateStatus`">
<p>Validity Status</p>
</cs-dashboard-table-header-item-unstyled>
<cs-dashboard-table-header-item-unstyled :item="`statusSummaryMatrixStatus`">
<p>Matrix Status</p>
</cs-dashboard-table-header-item-unstyled>
</div>
<!--Certificates-->
<div class="text-certstyle-titles">
<div v-for="certificate in selectedEmployee.certificate_matrix"
v-if="selectedEmployee.certificate_matrix.length > 0"
class="table--row flex items-center justify-between border-certstyle-border last:border-b-0 border-b px-10 py-4">
<!-- Title -->
<cs-dashboard-table-item-unstyled
class="w-1/2"
:item="`statusSummaryCertificateTitle`">
<p :title="certificate.title" class=" ">
{{ certificate.title | limitString(30, '...') }}
</p>
</cs-dashboard-table-item-unstyled>
<cs-dashboard-table-item-unstyled class="w-32" :item="`statusSummaryCertificateStatus`">
<div class="flex items-center justify-start w-full">
<!--Expired styling-->
<label v-if="certificate.matrix_status === 0"
class="badge badge-danger">
-
</label>
<!--Expires styling-->
<label v-if="certificate.matrix_status && certificate.expired === 1"
class="badge badge-danger">
expired
</label>
<!--Expires styling-->
<label v-if="certificate.matrix_status && certificate.expire_soon === 1"
class="badge badge-danger">
expires soon
</label>
<!--Active styling-->
<label
v-if="certificate.matrix_status && certificate.expire_soon === 0 && certificate.expired === 0"
class="badge badge-success">
active
</label>
</div>
</cs-dashboard-table-item-unstyled>
<cs-dashboard-table-item-unstyled
class="w-32"
:item="`statusSummaryMatrixStatus`">
<!--Active styling-->
<label v-if="certificate.matrix_status"
class="badge badge-success">
compliant
</label>
<label v-else
class="badge badge-danger">
non-compliant
</label>
</cs-dashboard-table-item-unstyled>
</div>
<div v-else
class="table--row flex items-center justify-between border-certstyle-border last:border-b-0 border-b px-10 py-4">
<cs-dashboard-table-item-unstyled
class="w-1/2"
:item="`statusSummaryCertificateTitle`">
No certificates found
</cs-dashboard-table-item-unstyled>
<cs-dashboard-table-item-unstyled class="w-32"
:item="`statusSummaryCertificateStatus`">
</cs-dashboard-table-item-unstyled>
<cs-dashboard-table-item-unstyled
class="w-32"
:item="`statusSummaryMatrixStatus`">
</cs-dashboard-table-item-unstyled>
</div>
</div>
</div>
</modal-md>
</cs-card>
<contact-details
v-if="userToShowContactDetails"
:user="userToShowContactDetails"
/>
<add-employee-modal
:countries="countries"
:identifier="`addEmployeeModal`"
:invite-modal-title="`Invite to department`"
:suggestion-url="userSuggestionApiURL"
:title="`Assign to ${department ? department.name: ''}`"
#inviteEmployees="handleInvitedEmployees"
#selectEmployee="addEmployeeToDepartment"
#removeEmployee="handleRemoveEmployee"
/>
<cs-confirmation-modal
v-if="checkRole(['admin', 'planner'])"
#proceed="handleRemoveEmployee"
identifier="removeConfirmationModal">
<div class="text-certstyle-titles font-normal" slot="content">
Removing employees from this department can not be undo.
</div>
<div slot="cancelButton"
class="cursor-pointer hover:bg-certstyle-background border border-certstyle-border rounded px-6 py-2 text-certstyle-text-light mr-4">
Cancel
</div>
<cs-button slot="proceedButton">
Remove
</cs-button>
</cs-confirmation-modal>
</div>
</template>
This list and the filters works fine but when I try to search a user by typing their names, it kept giving me following error,
Duplicate keys detected: '1025'. This may cause an update error.
found in
---> at resources/js/components/reusable/datatable/Datatable.vue
at resources/js/components/reusable/csCard.vue
at resources/js/components/dashboard/communities/department/one-departments/department-user-list.vue
But when I try to filter the list by some other param, they get filtered without any error.
I'm struggling to find what I'm doing wrong here....
UPDATE
following is my non-filtered json data for a single user
{
"id": 1038,
"unique_id": "0a3938c1-07d5-3884-9df0-a8fe3201a3e5",
"first_name": "Mango",
"last_name": "F1",
"job_title": null,
"email": "mangoF1#gmail.com",
"phone_number": null,
"phone_country_calling_code": null,
"country_id": null,
"company_name": null,
"is_subcontractor": 0,
"current_jobtitle": "Deck Foreman",
"department_jobtitle": "Rigging Engineers",
"language": "en",
"email_verified_at": "2022-04-12T12:47:55.000000Z",
"gender": null,
"state": null,
"postal_code": null,
"house_number": null,
"street": null,
"city": null,
"date_of_birth": null,
"city_of_birth": null,
"emergency_number": null,
"opt_in": null,
"created_at": "2022-04-12T12:46:34.000000Z",
"updated_at": "2022-04-12T12:47:55.000000Z",
"emergency_number_country_calling_code": null,
"deleted_at": null,
"has_ip_restriction": 0,
"address": null,
"latitude": null,
"longitude": null,
"place_id": null,
"bouwpas_worker_id": null,
"bouwpas_certificate_id": null,
"certificate_matrix": [
{
"id": 463,
.....
}
]
},
Following is my controller function,
public function index($locale, Company $company, Department $department, Request $request)
{
$this->authorize('viewUsers', [Department::class, $company, $department]);
$users = $department->usersWithSubcontractors()
// ->notApiUser()
->select(
'users.id',
'unique_id',
'first_name',
'last_name',
'job_title',
'email',
'phone_number',
'unique_id',
'phone_country_calling_code',
'country_id',
'company_name',
'is_subcontractor',
)->addSelect([
'current_jobtitle' => JobTitle::selectRaw('IFNULL(project_specific_job_titles.title, job_titles.title)')
->join('project_track_records', 'project_track_records.user_id', 'users.id', 'inner')
->join('project_specific_job_titles', 'project_track_records.project_specific_job_title_id', 'project_specific_job_titles.id', 'inner')
->whereColumn('job_titles.id', 'project_specific_job_titles.job_title_id')
->whereDate('project_track_records.start_date', '<=', Carbon::now())
->whereDate('project_track_records.end_date', '>=', Carbon::now())
->limit(1),
'department_jobtitle' => JobTitle::selectRaw('title')->whereColumn('job_titles.id', 'department_user.job_title_id'),
])->when(request('q'), function ($q) {
$q->where(function ($q) {
$q->whereRaw("CONCAT_WS(' ', `first_name`, `last_name`) like '%" . request('q') . "%'");
});
})->when($request->get('employeeType'), function ($q) use ($request) {
$q->whereIn('users.is_subcontractor', $request->get('employeeType'));
})->paginate(\request('per_page', config('repository.pagination.limit')));
$users->load('country');
$users->append(['profile_image']);
$users->makeVisible(['unique_id']);
$users->map(fn ($item) => $item->certificate_matrix = (new GetCertificationMatrixQueryAction())->execute($item->id, $department->id));
if ($request->filled('certificateStatus')) {
$valid = in_array('valid', $request->get('certificateStatus'));
$expire_soon = in_array('expire soon', $request->get('certificateStatus'));
$expired = in_array('expired', $request->get('certificateStatus'));
if ($valid) $users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status && !$certificate->expire_soon && !$certificate->expired)->count() === $item->certificate_matrix->count())->values());
if ($expire_soon && !$expired) $users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status && $certificate->expire_soon)->count() > 0)->values());
if ($expired && !$expire_soon) $users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status && $certificate->expired)->count() > 0)->values());
}
if ($request->filled('matrixStatus')) {
$compliant = in_array('compliant', $request->get('matrixStatus'));
$non_compliant = in_array('non-compliant', $request->get('matrixStatus'));
if ($non_compliant && !$compliant)
$users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status === 0 || $certificate->expire_soon || !$certificate->expired)->count() > 0)->values());
if ($compliant && !$non_compliant)
$users->setCollection($users->filter(fn ($item) => $item->certificate_matrix->filter(fn ($certificate) => $certificate->matrix_status && !$certificate->expire_soon && !$certificate->expired)->count() == $item->certificate_matrix->count())->values());
}
return response()->json($users);
}
The sample data for single user has duplicate id values in certificate_matrix array (2nd and 11th record both have id of 443)
This would probably be causing an issue in the v-for loop at (where you do not have any :key actually)
<!--Certificates-->
<div class="text-certstyle-titles">
<div v-for="certificate in selectedEmployee.certificate_matrix"
v-if="selectedEmployee.certificate_matrix.length > 0"
class="table--row flex items-center justify-between border-certstyle-border last:border-b-0 border-b px-10 py-4">
Or at any other place where certificate_matrix records are being looped over
Using composite key combining value of two fields to generate unique key
One way to try to ensure unique keys on frontend is to have a combination (kind of like composite key index) as ${id}-${matrix_status} if that combination is to remain unique.
Using loop iteration index as composite suffix to generate unique key
If there is no other field value which can be combined to ensure unique keys for child nodes within v-for then the ${id}-${index} can be used to generate unique keys - eg:
<div v-for="(certificate, index) in selectedEmployee.certificate_matrix"
:key="`${certificate.id}-${index}`" //To ensure uniqueness of key
v-if="selectedEmployee.certificate_matrix.length > 0"
class="table--row flex items-center justify-between border-certstyle-border last:border-b-0 border-b px-10 py-4">
Using uuid as unique key
Another way is to add a uuid column on the database tables and then use the uuid as key in the <DataTable /> component markup including subcomponents where :key or :key-id are used.
Laravel makes it very easy to generate Uuid v4 - uuids via (string) Str::orderedUuid(); And also has $table->uuid('uid')->index() column type for migration files.
Can even have uid stored when a new model record is being created/persisted
// In any model class which has a uuid column
public static function booted()
{
static::creating(function ($model) {
$model->uid = Str::orderedUuid()->toString();
});
}
Laravel Docs - Helpers - Str Ordered Uuid
Laravel Docs - Migrations - Uuid Column Type
Laravel Docs - Uuid - Validation Rule
Update
Based on feedback via comment
Thank you for your answer, I have updated the question with the correct backend controller. The reason for id duplication is one user can have multiple departments. Because of that my eloquent gives me results with multiple user id's for some results. When I tried to group the results by user.id, it gives me an 500 error, and I need to pass the user.id as the key rather than passing uuid since the id has used for other functions, like user deletion and all... Any way to group the result from the backend to get only unique user ids?
To ensure only unique user records being sent via response from the controller, filter out duplicate records for the $users collection via unique() method available on Collection
//...method code
$users->setCollection($users->unique('id')->values());
return response()->json($users);
The error message tells you that you have duplicates and you are confused about the reason of getting such an error message, when the id of the user is unique (probably a primary key). However, it is important to note that you load your users with departments and if a user has multiple departments, then you will get that user multiple times. You can group the results to solve the issue.