last message gone when selecting a chat - laravel

We are currently building a chat for the application with livewire. There is an issue, when no chats are selected the last message shows but when selecting a chat. The last messages are gone. And I have no idea why. SQL queries look the same...
code, livewire component:
class Inbox extends Component
{
public ?Collection $chats = null;
public ?User $selectedUser = null;
public ?Collection $selectedChat = null;
public string $newMessage = '';
public function mount()
{
$this->chats = Auth::user()->getChats();
}
public function selectChat(int $userId)
{
$this->selectedUser = User::find($userId);
$this->selectedChat = Auth::user()->getChatWith($this->selectedUser);
$this->newMessage = '';
$this->dispatchBrowserEvent('scroll-to-bottom');
}
public function unselectChat()
{
$this->selectedUser = null;
$this->selectedChat = null;
$this->newMessage = '';
}
public function send()
{
if (empty($this->newMessage)) {
return;
}
$this->selectedChat->push(
Message::from(Auth::user())
->to($this->selectedUser)
->send($this->newMessage)
);
$this->newMessage = '';
$this->dispatchBrowserEvent('scroll-to-bottom');
}
public function render()
{
return view('livewire.inbox', [
'chats' => Auth::user()->getChats(),
]);
}
}
traits
trait HasMessages
{
public function lastMessage()
{
return $this->belongsTo(Message::class);
}
public function scopeWithLastMessage(Builder $query, int $id)
{
return $query->addSelect([
'last_message_id' => Message::select('id')
->where(
fn ($q) => $q->where('sender_id', $id)->whereColumn('receiver_id', 'users.id')
)->orWhere(
fn ($q) => $q->where('receiver_id', $id)->whereColumn('sender_id', 'users.id')
)
->latest('created_at')->latest('id')
->take(1)
])->whereExists(
fn ($q) => $q->select('id')
->from('messages')
->where(
fn ($q) => $q->where('sender_id', $id)->whereColumn('receiver_id', 'users.id')
)->orWhere(
fn ($q) => $q->where('receiver_id', $id)->whereColumn('sender_id', 'users.id')
)
)->with('lastMessage');
}
public function getChats()
{
return User::query()
->withLastMessage($this->id)
->where('id', '<>', $this->id)
->get()
->sortBy([
['lastMessage.created_at', 'desc'],
['lastMessage.id', 'desc'],
]);
}
public function getChatWith(User $user)
{
return Message::where(
fn ($q) => $q->where('sender_id', $this->id)->where('receiver_id', $user->id)
)->orWhere(
fn ($q) => $q->where('receiver_id', $this->id)->where('sender_id', $user->id)
)->oldest('created_at')->oldest('id')
->with(['sender', 'receiver'])
->get();
}
}
the view, inbox chat blade:
<div {{ $attributes->merge(['class' => 'flex items-center px-4 sm:px-6 py-3 bg-white hover:bg-gray-100 cursor-pointer transition']) }}>
<img src="{{ $chat->profilePhotoUrl }}"
class="flex-shirink-0 w-10 h-10 rounded-full" />
<div class="flex-1 ml-4 mr-2 min-w-0">
<h5 class="text-gray-700 font-bold truncate">
{{ $chat->name }}
</h5>
<p class="text-gray-500 text-sm truncate---">
{{-- {{ $chat->lastMessage->body }} --}}
lastMessage: {{ $chat->lastMessage }}
</p>
</div>
{{-- <time class="text-gray-500 text-xs"
datetime="{{ $chat->lastMessage->created_at }}">
{{ $chat->lastMessage->created_at->diffForHumans() }}
</time> --}}
</div>
inbox chat header
#props(['user', 'clickHandler'])
<div class="flex items-center px-4 sm:px-6 py-3 bg-white border-t border-gray-200 shadow-lg">
<button class="md:hidden -ml-2 p-2 rounded-lg text-gray-500 hover:bg-gray-100 transition"
wire:click.prevent="{{ $clickHandler }}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
</svg>
</button>
<img src="{{ $user->profilePhotoUrl }}"
class="ml-2 flex-shirink-0 w-10 h-10 rounded-full" />
<div class="flex-1 ml-4 mr-2 min-w-0">
<h5 class="text-gray-700 font-bold">
{{ $user->name }}
</h5>
</div>
</div>

Related

Adding captions or descriptions to multiple image upload in Laravel

So I am trying to upload images for a project where each image can have its own description or caption. I am trying to do this through the project creation form. Now, everything works except that I am getting duplicate entries in the project_images table because of the nested foreach loop, take a look at the summarised structure of the code Please, specifically the logic in the storProject method if anyone can help I will be forever grateful. Thank you.
I am using Laravel with Livewire.
Models
Project Model
public function up()
{
Schema::create('projects', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->string('title');
$table->integer('budget');
$table->string('proposed_by');
$table->string('executed_by');
$table->string('status')->nullable();
$table->integer('progress_indicator')->nullable();
$table->date('starting_date');
$table->date('completion_date')->nullable();
$table->string('cover_image')->nullable();
$table->mediumText('body')->nullable();
$table->timestamps();
});
}
Project_images Model
public function up()
{
Schema::create('project_images', function (Blueprint $table) {
$table->id();
$table->string('images');
$table->string('caption')->nullable();
$table->UnsignedBigInteger('project_id');
$table->foreign('project_id')->references('id')->on('projects')->onDelete('cascade');
$table->timestamps();
});
}
The Form
<x-slot name="content">
<x-errors />
<div class="space-y-8 divide-y divide-gray-200 mt-10">
<form method="#" enctype="multipart/form-data">
#if ($projectImages)
Images Preview:
<div class="flex flex-row m-8 ">
#foreach ($projectImages as $key => $projectImage)
<div class="flex flex-col gap-3">
<img width="100%" src="{{ $projectImage->temporaryUrl() }}">
<x-input placeholder="Caption"
wire:model.defer="captions.{{ $key }}" />
</div>
#endforeach
</div>
#endif
</div>
<label class="inline-block mb-2 text-gray-500">Upload
Projects Images</label>
<div class="flex items-center justify-center w-full">
<label
class="flex flex-col w-full h-32 border-2 border-dashed hover:bg-gray-500 hover:border-gray-300">
<div class="flex flex-col items-center justify-center pt-7">
<p class="pt-1 text-sm tracking-wider text-gray-400 group-hover:text-gray-600">
Select a photo</p>
</div>
<input wire:model="projectImages" type="file" multiple class="opacity-0" />
</label>
</div>
<x-textarea wire:model.lazy="body" label="Body" placeholder="write your article" />
</form>
</div>
</x-slot>
<x-slot name="footer">
#if ($projectId)
<div class="flex items-center gap-x-3 justify-end">
<x-button wire:click="CancelConfirmation" label="Cancel" flat />
<x-button type="submit" wire:click="updateProject" label="Update" wire:loading.attr="disabled"
primary />
</div>
#else
<div class="flex items-center gap-x-3 justify-end">
<x-button wire:click="CancelConfirmation" label="Cancel" flat />
<x-button type="submit" wire:click="storeProject" label="Create" wire:loading.attr="disabled"
primary />
</div>
#endif
</x-slot>
</x-jet-dialog-modal>
The Project Component
public function storeProject()
{
$this->validate([
'title' => 'required',
'status' => 'required',
'budget' => 'required',
'proposedBy' => 'required',
'executedBy' => 'required',
'startingDate' => 'required',
'completionDate' => 'required',
'progressIndicator' => 'required',
'body' => 'required',
'image' => 'image|max:1024|nullable'
]);
$image_name = $this->image->getClientOriginalName();
$this->image->storeAs('public/photos', $image_name);
$project = new Project();
$project->user_id = auth()->user()->id;
$project->title = $this->title;
$project->status = $this->status;
$project->budget = $this->budget;
$project->proposed_by = $this->proposedBy;
$project->executed_by = $this->executedBy;
$project->starting_date = Carbon::create($this->startingDate);
$project->completion_date = Carbon::create($this->completionDate);
$project->progress_indicator = $this->progressIndicator;
$project->body = $this->body;
$project->cover_image = $image_name;
$project->save();
foreach ($this->projectImages as $projectImage) {
$image_name = $projectImage->getClientOriginalName();
$projectImage->storeAs('public/photos', $image_name);
foreach ($this->captions as $caption) {
$projectImages = new ProjectImages();
$projectImages->project_id = $project->id;
$projectImages->images = $image_name;
$projectImages->caption = $caption;
$projectImages->save();
}
}
$this->notification()->success(
$title = 'Success',
$description = 'Project Created Successfully'
);
$this->reset();
}
so just so you can see visually what am trying to do here is a screenshot of the form
I assume that you only want each project image to have one caption. If that's the case then the nested foreach loops are unnecessary and changing it to one foreach like this should solve the problem.
foreach ($this->projectImages as $key => $value) {
$image_name = $value->getClientOriginalName();
$value->storeAs('public/photos', $image_name);
$projectImages = new ProjectImages();
$projectImages->project_id = $project->id;
$projectImages->images = $image_name;
$projectImages->caption = $this->captions[$key];
$projectImages->save();
}

AlpineJS x-for not running after x-data update

I am running into a very strange issue where I have a chatroom that should update whenever a new message is posted. Instead when I add a new message the x-data is getting updated and I can see the message there but the x-for doesnt seem to run and it never appears on the frontend. Then when I enter another message the previous message shows up on the frontend but the latest message does not show even though the x-data has been updated to reflect it.
Here is the code below:
<div
x-data="{{ json_encode(['messages' => $messages, 'messageBody' => '']) }}"
x-init="
Echo.join('chat.2')
.listen('ChatSent', (e) => {
#this.call('incomingMessage', e)
console.log(e)
})
">
<div
class="wow fadeInUp mb-10 rounded-md bg-light-bg dark:bg-dark"
data-wow-delay="0s"
>
<h3
class="border-b border-body-color border-opacity-10 py-4 px-8 text-lg font-semibold text-black dark:border-white dark:border-opacity-10 dark:text-white"
>
Chat
</h3>
<div id="chat-window" class="relative w-full p-3 overflow-y-auto h-[14rem]">
<ul class="space-y-2">
<template
x-for="message in messages"
:key="message.id"
>
<div>
<template x-if="message.user_id == {{ Auth::user()->id }}">
<li class="flex justify-end">
<div class="relative max-w-xl px-4 py-2 text-black dark:text-white bg-gray-100 dark:bg-black rounded shadow">
<span class="block" x-text="message.body"></span>
</div>
</li>
</template>
<template x-if="message.user_id != {{ Auth::user()->id }}">
<li class="flex justify-start">
<div class="relative max-w-xl px-4 py-2 text-black dark:text-white bg-blue-100 dark:bg-[#1e2a78] rounded shadow">
<span class="block" x-text="message.body"></span>
</div>
</li>
</template>
</div>
</template>
</ul>
</div>
<div class="flex items-center justify-between w-full p-3 border-t border-gray-300">
<input
#keydown.enter="
#this.call('sendMessage', messageBody)
messageBody = ''
"
x-model="messageBody"
type="text"
placeholder="Message"
class="block w-full py-2 pl-4 mx-3 bg-gray-100 dark:bg-black rounded-full outline-none focus:text-gray-700 dark:focus:text-white dark:text-white"
name="message" required />
<button
#click="
#this.call('sendMessage', messageBody)
messageBody = ''
">
<svg class="w-5 h-5 text-black dark:text-white origin-center transform rotate-90" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path
d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z" />
</svg>
</button>
</div>
#error('messageBody')
<div class="w-full p-2 text-center">
<span class="text-red-500">Message is required!</span>
</div>
#enderror
</div>
<div
class="wow fadeInUp mb-10 rounded-md bg-light-bg dark:bg-dark"
data-wow-delay="0s">
<h3
class="border-b border-body-color border-opacity-10 py-4 px-8 text-lg font-semibold text-black dark:border-white dark:border-opacity-10 dark:text-white"
>
Active Players
</h3>
<ul class="flex flex-wrap py-6 px-8">
#forelse($here as $authData)
<li>
<a
href="javascript:void(0)"
class="text-body-color-3 mr-3 mb-3 inline-flex items-center justify-center rounded-full border-[.5px] border-body-color bg-body-color bg-opacity-10 py-2 px-4 hover:border-primary hover:bg-primary hover:text-white dark:border-[#363D68] dark:bg-[#272E5C] dark:text-white dark:hover:border-primary dark:hover:bg-primary dark:hover:text-white"
>
{{ $authData['name'] }}
</a>
</li>
#empty
#endforelse
</ul>
</div>
</div>
I figure it has to be something simple I am missing and hopefully someone can point me in the right direction.
EDIT to include Livewire code:
<?php
namespace App\Http\Livewire;
use App\Events\ChatSent;
use App\Models\Chat;
use App\Models\Games;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Chatbox extends Component
{
public $game_id, $game, $chats;
public $here = [];
public $messages = [];
public function getListeners()
{
$game_id = $this->game_id;
return [
"echo-presence:chat.{$game_id},here" => 'here',
"echo-presence:chat.{$game_id},joining" => 'joining',
"echo-presence:chat.{$game_id},leaving" => 'leaving',
];
}
public function render()
{
return view('games.components.chatbox');
}
public function mount()
{
$this->game = Games::find($this->game_id);
$this->messages = Chat::
where('games_id', $this->game_id)
->with('user')
->latest()
->limit(30)
->get()
->reverse()
->values()
->toArray();
}
public function sendMessage($body)
{
if (! $body) {
$this->addError('messageBody', 'Message body is required.');
return;
}
$message = Auth::user()->chats()->create([
'body' => $body,
'games_id' => $this->game_id,
]);
$message->load('user');
broadcast(new ChatSent($message, $this->game))->toOthers();
$myMessage = $message->toArray();
$this->dispatchBrowserEvent('update-chat');
array_push($this->messages, $myMessage);
}
/**
* #param $message
*/
public function incomingMessage($message)
{
// get the hydrated model from incoming json/array.
$message = Chat::with('user')->find($message['id']);
array_push($this->messages, $message);
$this->dispatchBrowserEvent('update-chat');
}
/**
* #param $data
*/
public function here($data)
{
$this->here = $data;
}
/**
* #param $data
*/
public function leaving($data)
{
$here = collect($this->here);
$firstIndex = $here->search(function ($authData) use ($data) {
return $authData['id'] == $data['id'];
});
$here->splice($firstIndex, 1);
$this->here = $here->toArray();
}
/**
* #param $data
*/
public function joining($data)
{
$this->here[] = $data;
}
}

I'm getting an error displaying the user's profile photo on laravel 7

public function index(Request $request)
{
$data = Admin::when($request->keyword, function ($query) use ($request) {
$query->where('nama_lengkap', 'like', "%{$request->keyword}%");
})->paginate(5);
$data->appends($request->only('keyword'));
return view('admin.crud_admin.index', compact('data'));
}
Model Admin.php
protected $fillable = [
'avatar', 'nama_lengkap', 'email', 'password',
];
public function getAvatar()
{
if (!$this->avatar) {
return asset('images/default.png');
}
return asset('images/' . $this->avatar);
}
index.blade.php
<a class="nav-link dropdown-toggle dropdown-user-link" id="dropdown-user" href="javascript:void(0);"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<div class="user-nav d-sm-flex d-none">
<span class="user-name font-weight-bolder">{{ Auth::user()->nama_lengkap }}</span>
<span class="user-status">Admin</span>
</div>
<span class="avatar"
><img class="round" src="{{ $data->getAvatar('avatar') }}"
alt="avatar" height="40" width="40"
/><span class="avatar-status-online"></span>
</span>
</a>
this does not appear even there is an error:
"Method Illuminate\Database\Eloquent\Collection::getAvatar does not exist. (View: C:\xampp\htdocs\Helpdesk\resources\views\admin\crud_admin\index.blade.php)"
Can you help me? please this is important to me
because $data is a Collection, since you assign to it this:
$data = ...->paginate(5);
Thus, you have to loop over it:
#foreach($data as $user)
...
{{ $user->getAvatar('avatar') }}
...
#endforeach

Edit function can't work properly in vue js and laravel

I build a Task Management project using laravel & vue js which is worked very nicely.But edit function can't work properly in this application.I tried a lot but failed.Even when clicked edit option this would work to same as create/add function.How can I solve this problem.I will share my code below:
resource/js/app.js
require("./bootstrap");
window.Vue = require("vue");
/ / Register our components
Vue.component("kanban-board", require("./components/KanbanBoard.vue").default);
const app = new Vue({
el: "#app"
});
resource/js/components/KanbanBoard.vue
<template>
<div class="relative p-2 flex overflow-x-auto h-full">
<!-- Columns (Statuses) -->
<div
v-for="status in statuses"
:key="status.slug"
class="mr-6 w-4/5 max-w-xs flex-shrink-0"
>
<div class="rounded-md shadow-md overflow-hidden">
<div class="p-1 flex justify-between items-baseline bg-green-500 ">
<h4 class="font-medium text-white">
{{ status.title }}
</h4>
</div>
<div class="p-2 bg-green-300">
<!-- AddTaskForm -->
<AddTaskForm
v-if="newTaskForStatus === status.id"
:status-id="status.id"
v-on:task-added="handleTaskAdded"
v-on:task-canceled="closeAddTaskForm"
/>
<!-- ./AddTaskForm -->
<!-- EditTaskForm -->
<AddTaskForm
v-if="editTaskForStatus === status.id"
:status-id="status.id"
v-on:task-edit="handleTaskEdit"
v-on:task-canceled="closeEditTaskForm"
/>
<!-- ./EditTaskForm -->
<!-- Tasks -->
<draggable
class="flex-1 overflow-hidden"
v-model="status.tasks"
v-bind="taskDragOptions"
#end="handleTaskMoved"
>
<transition-group
class="flex-1 flex flex-col h-full overflow-x-hidden overflow-y-auto rounded shadow-xs"
tag="div"
>
<div
v-for="task in status.tasks"
:key="task.id"
class="mb-3 p-4 flex flex-col bg-white rounded-md shadow transform hover:shadow-md cursor-
pointer"
>
<button
type="submit"
#click="openEditTaskForm(status.id)"
class="py-1 px-2 text-sm text-black-500 hover:underline"
>
Edit Task
</button>
<span class="block mb-2 text-xl text-gray-900">
Task-100{{ task.id }}
</span>
<span class="block mb-2 text-xl text-gray-900">
{{ task.title }}
</span>
<p class="text-gray-700">
{{ task.description }}
</p>
</div>
<!-- ./Tasks -->
</transition-group>
</draggable>
<!-- No Tasks -->
<div
v-show="!status.tasks.length && newTaskForStatus !== status.id"
class="flex-1 p-4 flex flex-col items-center justify-center"
>
<!-- <span class="text-gray-600">No tasks yet</span> -->
<button
class="mt-1 text-sm text-orange-600 hover:underline"
#click="openAddTaskForm(status.id)"
>
Add one
</button>
</div>
<!-- ./No Tasks -->
</div>
</div>
<button
type="submit"
#click="openAddTaskForm(status.id)"
class="px-12 py-1 leading-5 text-white bg-orange-600 hover:bg-orange-500 rounded"
>
Add
</button>
</div>
<!-- ./Columns -->
</div>
</template>
<script>
import draggable from "vuedraggable";
import AddTaskForm from "./AddTaskForm";
import EditTaskForm from "./EditTaskForm";
export default {
components: { draggable, AddTaskForm,EditTaskForm },
props: {
initialData: Array
},
data() {
return {
statuses: [],
newTaskForStatus: 0,
editTaskForStatus: 0
};
},
computed: {
taskDragOptions() {
return {
animation: 200,
group: "task-list",
dragClass: "status-drag"
};
}
},
mounted() {
// 'clone' the statuses so we don't alter the prop when making changes
this.statuses = JSON.parse(JSON.stringify(this.initialData));
},
methods: {
openAddTaskForm(statusId) {
this.newTaskForStatus = statusId;
},
closeAddTaskForm() {
this.newTaskForStatus = 0;
},
handleTaskAdded(newTask) {
// Find the index of the status where we should add the task
const statusIndex = this.statuses.findIndex(
status => status.id === newTask.status_id
);
// Add newly created task to our column
this.statuses[statusIndex].tasks.push(newTask);
// Reset and close the AddTaskForm
this.closeAddTaskForm();
},
handleTaskMoved(evt) {
axios.put("/tasks/sync", { columns: this.statuses }).catch(err => {
console.log(err.response);
});
},
openEditTaskForm(statusId) {
this.editTaskForStatus = statusId;
},
closeEditTaskForm() {
this.editTaskForStatus = 0;
},
handleTaskEdit(editTask) {
// Find the index of the status where we should add the task
const statusIndex = this.statuses.findIndex(
status => status.id === editTask.status_id
);
// Add newly created task to our column
this.statuses[statusIndex].tasks.push(editTask);
// Reset and close the AddTaskForm
this.closeEditTaskForm();
}
}
};
</script>
<style scoped>
.status-drag {
transition: transform 0.5s;
transition-property: all;
}
</style>
resource/js/components/AddTask.vue
<template>
<form
class="relative mb-3 flex flex-col justify-between bg-white rounded-md shadow overflow-
hidden"
#submit.prevent="handleAddNewTask"
>
<div class="p-3 flex-1">
<input
class="block w-full px-2 py-1 text-lg border-b border-blue-800 rounded"
type="text"
placeholder="Enter a title"
v-model.trim="newTask.title"
/>
<textarea
class="mt-3 p-2 block w-full p-1 border text-sm rounded"
rows="2"
placeholder="Add a description"
v-model.trim="newTask.description"
></textarea>
<div v-show="errorMessage">
<span class="text-xs text-red-500">
{{ errorMessage }}
</span>
</div>
</div>
<div class="p-3 flex justify-between items-end text-sm bg-gray-100">
<button
#click="$emit('task-canceled')"
type="reset"
class="py-1 leading-5 text-gray-600 hover:text-gray-700"
>
cancel
</button>
<button
type="submit"
class="px-3 py-1 leading-5 text-white bg-orange-600 hover:bg-orange-500 rounded"
>
Add
</button>
</div>
</form>
</template>
<script>
export default {
props: {
statusId: Number
},
data() {
return {
newTask: {
title: "",
description: "",
status_id: null
},
errorMessage: ""
};
},
mounted() {
this.newTask.status_id = this.statusId;
},
methods: {
handleAddNewTask() {
// Basic validation so we don't send an empty task to the server
if (!this.newTask.title) {
this.errorMessage = "The title field is required";
return;
}
// Send new task to server
axios
.post("/tasks", this.newTask)
.then(res => {
// Tell the parent component we've added a new task and include it
this.$emit("task-added", res.data);
})
.catch(err => {
// Handle the error returned from our request
this.handleErrors(err);
});
},
handleErrors(err) {
if (err.response && err.response.status === 422) {
// We have a validation error
const errorBag = err.response.data.errors;
if (errorBag.title) {
this.errorMessage = errorBag.title[0];
} else if (errorBag.description) {
this.errorMessage = errorBag.description[0];
} else {
this.errorMessage = err.response.message;
}
} else {
// We have bigger problems
console.log(err.response);
}
}
}
};
</script>
resource/js/components/EditTask.vue
<template>
<form
class="relative mb-3 flex flex-col justify-between bg-white rounded-md shadow overflow-
hidden"
#submit.prevent="handleEditTask"
>
<div class="p-3 flex-1">
<input
class="block w-full px-2 py-1 text-lg border-b border-blue-800 rounded"
type="text"
#blur="handleEditTask(task)"
v-model="editTask.title"
/>
<textarea
class="mt-3 p-2 block w-full p-1 border text-sm rounded"
rows="2"
p
#blur="handleEditTask(task)"
v-model="editTask.description"
></textarea>
<div v-show="errorMessage">
<span class="text-xs text-red-500">
{{ errorMessage }}
</span>
</div>
</div>
<div class="p-3 flex justify-between items-end text-sm bg-gray-100">
<button
#click="$emit('task-canceled')"
type="reset"
class="py-1 leading-5 text-gray-600 hover:text-gray-700"
>
cancel
</button>
<button
type="submit"
class="px-3 py-1 leading-5 text-white bg-orange-600 hover:bg-orange-500 rounded"
>
Add
</button>
</div>
</form>
</template>
<script>
export default {
props: {
statusId: Number
},
data() {
return {
editTask: {
title:task.title,
description:task.description,
status_id: null
},
errorMessage: ""
};
},
mounted() {
this.editTask.status_id = this.statusId;
},
methods: {
handleEditTask() {
// Basic validation so we don't send an empty task to the server
if (!this.editTask.title) {
this.errorMessage = "The title field is required";
return;
}
// Send edit task to server
axios
.patch("tasks/{task}", this.editTask)
.then(res => {
// Tell the parent component we've added a new task and include it
this.$emit("task-edit", res.data);
})
.catch(err => {
// Handle the error returned from our request
this.handleErrors(err);
});
},
handleErrors(err) {
if (err.response && err.response.status === 422) {
// We have a validation error
const errorBag = err.response.data.errors;
if (errorBag.title) {
this.errorMessage = errorBag.title[0];
} else if (errorBag.description) {
this.errorMessage = errorBag.description[0];
} else {
this.errorMessage = err.response.message;
}
} else {
// We have bigger problems
console.log(err.response);
}
}
}
};
</script>
app/Http/Controllers/TaskController
<?php
namespace App\Http\Controllers;
use App\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
$tasks = auth()->user()->statuses()->with('tasks')->get();
return view('tasks.index', compact('tasks'));
}
public function create()
{
//
}
public function store(Request $request)
{
$this->validate($request, [
'title' => ['required', 'string', 'max:56'],
'description' => ['nullable', 'string'],
'status_id' => ['required', 'exists:statuses,id']
]);
return $request->user()
->tasks()
->create($request->only('title', 'description', 'status_id'));
}
public function sync(Request $request)
{
$this->validate(request(), [
'title' => ['required', 'string', 'max:56'],
'description' => ['nullable', 'string'],
'columns' => ['required', 'array']
]);
foreach ($request->columns as $status) {
foreach ($status['tasks'] as $i => $task) {
$order = $i + 1;
if ($task['status_id'] !== $status['id'] || $task['order'] !== $order) {
request()->user()->tasks()
->find($task['id'])
->update(['status_id' => $status['id'], 'order' => $order],$request-
>only('title', 'description',
'status_id'));
}
}
}
return $request->user()->statuses()->with('tasks')->get();
}
public function show(Task $task)
{
//
}
public function edit(Request $request)
{
}
public function update(Request $request, Task $task)
{
$this->validate($request, [
'title' => ['required', 'string', 'max:56'],
'description' => ['nullable', 'string'],
'status_id' => ['required', 'exists:statuses,id']
]);
return $request->user()
->tasks()
->update($request->only('title', 'description', 'status_id'));
}
public function destroy(Task $task)
{
//
}
}
resource/views/tasks/index.blade.php
#extends('layouts.app')
#section('content')
<div class="md:mx-4 relative overflow-hidden">
<main class="h-full flex flex-col overflow-auto">
<kanban-board :initial-data="{{ $tasks }}"></kanban-board>
</main>
</div>
#endsection
web.php
Route::group(['middleware' => 'auth'], function () {
Route::get('tasks', 'TaskController#index')->name('tasks.index');
Route::post('tasks', 'TaskController#store')->name('tasks.store');
Route::put('tasks/sync', 'TaskController#sync')->name('tasks.sync');
Route::put('tasks/{task}', 'TaskController#update')->name('tasks.update');
});
Route::group(['middleware' => 'auth'], function () {
Route::post('statuses', 'StatusController#store')->name('statuses.store');
Route::put('statuses', 'StatusController#update')->name('statuses.update');
});

Laravel - How to use a single textbox for global search

I have this Laravel-5.8 code whereby users can search using different control fields to search for employee_code, designation and department.
class HrEmployee extends Model
{
protected $table = 'hr_employees';
protected $primaryKey = 'id';
protected $fillable = [
'id',
'employee_code',
'address',
'email',
'employee_designation_id',
'first_name',
'emp_image',
'last_name',
'other_name',
'department_id',
];
protected $appends = ['full_name'];
public function user()
{
return $this->belongsTo('App\User');
}
public function designation()
{
return $this->belongsTo('App\Models\Hr\HrDesignation','employee_designation_id');
}
public function department()
{
return $this->belongsTo('App\Models\Hr\HrDepartment','department_id');
}
}
Controller
public function index(Request $request)
{
$userCompany = Auth::user()->company_id;
$render=[];
$employees = HrEmployee::where('company_id', $userCompany);
$employees=$employees->with('department','designation');
if(isset($request->employee_code))
{
$employees=$employees->where('employee_code','like','%'.$request->employee_code.'%');
$render['employee_code']=$request->employee_code;
}
if(isset($request->employee_designation_id))
{
$employees=$employees->where('employee_designation_id',$request->employee_designation_id);
$render['employee_designation_id']=$request->employee_designation_id;
}
if(isset($request->department_id))
{
$employees=$employees->where('department_id',$request->department_id);
$render['department_id']=$request->department_id;
}
$employees= $employees->paginate(6);
$employees= $employees->appends($render);
$data['employees'] = $employees;
$data['departments']= HrDepartment::where('company_id', $userCompany)->pluck('dept_name','id');
$data['designations']= HrDesignation::where('company_id', $userCompany)->pluck('designation_name','id');
return view('hr.employees.index',$data);
}
view
{{ Form::model(request(),['method'=>'get']) }}
<div class="row" style="margin-bottom: 10px">
<div orm-group class="col-sm-3">
{{Form::label('employee_code', 'Employee No.')}}
{{ Form::text('employee_code',null,['class'=>'form-control','placeholder'=>'Employee Code']) }}
</div>
<div class="col-sm-4">
{{Form::label('department_id', 'Department')}}
{{ Form::select('department_id',$departments,null,['class'=>'form-control select2bs4','placeholder'=>'Select Department']) }}
</div>
<div class="col-sm-4">
{{Form::label('employee_designation_id', 'Designation')}}
{{ Form::select('employee_designation_id',$designations,null,['class'=>'form-control select2bs4','placeholder'=>'Select Designation']) }}
</div>
<div class="col-xs-3">
<br>
{{ Form::submit('Search',['class'=>'btn btn-warning']) }}
</div>
{{ Form::close() }}
</div>
<br>
<!-- Default box -->
<div class="card card-solid">
<div class="card-body pb-0">
<div class="row d-flex align-items-stretch">
#if (count($employees))
#foreach($employees as $key => $employee)
<div class="col-12 col-sm-6 col-md-4 d-flex align-items-stretch">
<div class="card bg-light">
<div class="card-header text-muted border-bottom-0">
{{isset($employee->designation) ? $employee->designation->designation_name : ''}}
</div>
<div class="card-body pt-0">
<div class="row">
<div class="col-7">
<h2 class="lead"><b>Staff ID: {{$employee->employee_code}}</b></h2>
<h2 class="lead"><b>{{$employee->first_name}} {{$employee->last_name}}</b></h2>
<h6 class="lead"><b>Employee Department: </b>{{isset($employee->department) ? $employee->department->dept_name : ''}}</h6>
<h6 class="lead"><b>Employment Date: </b>{{$employee->employment_date ? Carbon\Carbon::parse($employee->employment_date)->format('d-m-Y') : 'N/A' }}</h6>
<ul class="ml-4 mb-0 fa-ul text-muted">
<li class="small"><span class="fa-li"><i class="fas fa-lg fa-envelope"></i></span> Email: {{$employee->email}}</li>
<li class="small"><span class="fa-li"><i class="fas fa-lg fa-phone"></i></span> Phone #: {{isset($employee->phone) ? $employee->phone : 'N/A'}}</li>
</ul>
</div>
<div class="col-5 text-center">
#if($employee->emp_image != '')
<img src="{{ URL::to('/') }}/public/storage/employees/image/{{ $employee->emp_image }}" class="img-circle img-fluid" />
#else
<img class="profile-user-img img-fluid img-circle" src="{{asset('theme/adminlte3/dist/img/default.png')}}" alt="" class="img-circle img-fluid">
#endif
</div>
</div>
</div>
<div class="card-footer">
<div class="text-right">
#can('employee_show')
<a href="{{ route('hr.employees.show', ['id'=>$employee->id]) }}" class="btn btn-sm btn-primary">
<i class="fas fa-user"></i> View Detail
</a>
#endcan
</div>
</div>
</div>
</div>
#endforeach
#else
<h4 style="text-align:center;">No matching records found</h4>
#endif
</div>
</div>
<!-- /.card-body -->
<div class="card-footer align-items-center d-flex justify-content-center">
{{ $employees->links() }}
<!-- /.card-footer -->
</div>
<!-- /.card -->
</div>
Instead of searching for each field one by one using different control fields. How do I use a sing textbox to search for employee_code, first_name, last_name, department and designation?
Thank you
You can do it with below code:
$searchText = $request->searchText;
$employees = HrEmployee::with(['department','designation'])->where('company_id', $userCompany);
if($searchText)
{
$employees->where(function ($query) use($searchText) {
$query->where('employee_code','like','%'.$searchText.'%')
->orWhere('first_name','like','%'.$searchText.'%')
->orWhere('last_name','like','%'.$searchText.'%')
->orWhereHas('designation', function($q) use ($searchText) {
$q->where('designation_name', 'like','%'.$searchText.'%');
})
->orWhereHas('department', function($q) use ($searchText) {
$q->where('department_name', 'like','%'.$searchText.'%');
});
});
}
$employees = $employees->get();
// For pagination
// $employees = $employees->paginate(6);
Here designation_name is name field in designation table and department_name is name field in designation table.
{{ Form::model(request(),['method'=>'get']) }}
<div class="row" style="margin-bottom: 10px">
<div orm-group class="col-sm-3">
{{Form::label('search', 'Search')}}
{{ Form::text('search',null,['class'=>'form-control','placeholder'=>'search']) }}
</div>
<div class="col-xs-3">
<br>
{{ Form::submit('Search',['class'=>'btn btn-warning']) }}
</div>
{{ Form::close() }}
then your controller function use orwhere()
public function index(Request $request)
{
$search = $request->search;
$userCompany = Auth::user()->company_id;
$render=[];
$employees = HrEmployee::where('company_id', $userCompany);
$employees=$employees->with('department','designation');
$employees=$employees->where('employee_code','like','%'.$search.'%')
->orWhere('employee_designation_id',$search)
->orwhere('department_id',$search)
->paginate(6);
$data['employees'] = $employees;
$data['search'] = $search;
$data['departments']= HrDepartment::where('company_id', $userCompany)->pluck('dept_name','id');
$data['designations']= HrDesignation::where('company_id', $userCompany)->pluck('designation_name','id');
return view('hr.employees.index',$data);
}

Resources