From 90f4c6fcc1f60359e4066efc9ae1f9697fb03b30 Mon Sep 17 00:00:00 2001 From: Tareq Hasan Date: Thu, 22 Feb 2024 12:15:00 +0600 Subject: [PATCH 1/2] feat: post on behalf of an user Fixes: #2 --- .../Controllers/Admin/PostsController.php | 19 +- .../Admin/UserSearchController.php | 23 +++ app/Models/Post.php | 2 +- package.json | 1 + resources/js/Pages/Admin/Feedbacks/Create.tsx | 178 +++++++++++++++++- routes/web.php | 7 +- yarn.lock | 5 + 7 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 app/Http/Controllers/Admin/UserSearchController.php diff --git a/app/Http/Controllers/Admin/PostsController.php b/app/Http/Controllers/Admin/PostsController.php index 2fcaf67..6bd1b50 100644 --- a/app/Http/Controllers/Admin/PostsController.php +++ b/app/Http/Controllers/Admin/PostsController.php @@ -15,14 +15,29 @@ public function store(Request $request) 'body' => 'required', 'status_id' => 'required|exists:statuses,id', 'board_id' => 'required|exists:boards,id', + 'behalf_id' => 'sometimes|exists:users,id', ]); - $post = Post::create([ + $args = [ 'title' => $request->title, 'body' => $request->body, 'status_id' => $request->status_id, 'board_id' => $request->board_id, - ]); + ]; + + if ($request->behalf_id) { + $args['created_by'] = $request->behalf_id; + $args['by'] = auth()->id(); + } + + $post = Post::create($args); + + if ($post) { + $post->votes()->create([ + 'user_id' => $post->created_by, + 'board_id' => $post->board_id, + ]); + } return redirect()->route('admin.feedbacks.show', $post)->with('success', 'Post created successfully'); } diff --git a/app/Http/Controllers/Admin/UserSearchController.php b/app/Http/Controllers/Admin/UserSearchController.php new file mode 100644 index 0000000..80e760a --- /dev/null +++ b/app/Http/Controllers/Admin/UserSearchController.php @@ -0,0 +1,23 @@ +input('query'); + + $users = User::where('name', 'LIKE', "%$query%") + ->orWhere('email', 'LIKE', "%$query%") + ->get() + ->take(10) + ->makeVisible('email'); + + return response()->json($users); + } +} diff --git a/app/Models/Post.php b/app/Models/Post.php index 2ab5c17..681fe83 100644 --- a/app/Models/Post.php +++ b/app/Models/Post.php @@ -37,7 +37,7 @@ protected static function boot() $post->slug = $count ? "{$slug}-{$count}" : $slug; - if (auth()->id()) { + if (auth()->id() && ! $post->created_by) { $post->created_by = auth()->id(); } }); diff --git a/package.json b/package.json index 0bc521c..4aa182f 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@radix-ui/react-popover": "^1.0.7", "@wedevs/tail-react": "^0.3.3", "classnames": "^2.5.1", + "lodash.debounce": "^4.0.8", "react-color": "^2.19.3" }, "config": { diff --git a/resources/js/Pages/Admin/Feedbacks/Create.tsx b/resources/js/Pages/Admin/Feedbacks/Create.tsx index c95b347..b8ceac3 100644 --- a/resources/js/Pages/Admin/Feedbacks/Create.tsx +++ b/resources/js/Pages/Admin/Feedbacks/Create.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import { useState, Fragment, useEffect } from 'react'; import { useForm } from '@inertiajs/react'; +import { Combobox, Transition } from '@headlessui/react'; import { Button, Modal, @@ -10,6 +11,10 @@ import { TextField, Textarea, } from '@wedevs/tail-react'; +import classNames from 'classnames'; +import { CheckIcon, PlusIcon, XMarkIcon } from '@heroicons/react/24/outline'; +import { debounce } from 'lodash'; +import { User } from '@/types'; type Option = { value: string; @@ -24,6 +29,12 @@ type Props = { boards: Option[]; }; +type SearchProps = { + onSelect: (user: any) => void; + onCreate: () => void; + onClear: () => void; +}; + const CreateModal = ({ showModal, setShowModal, @@ -31,11 +42,13 @@ const CreateModal = ({ statuses, boards, }: Props) => { + const [showUserForm, setShowUserForm] = useState(false); const form = useForm({ title: '', body: '', board_id: '', status_id: '', + behalf_id: 0, }); const statusOptions = statuses.map((status) => ({ @@ -69,6 +82,17 @@ const CreateModal = ({
Create Feedback +
+
+ Post on behalf of a user (optional) +
+ setShowUserForm(true)} + onSelect={(user) => form.setData('behalf_id', user.id)} + onClear={() => form.setData('behalf_id', 0)} + /> +
+ + + setShowUserForm(false)}> +
+ Create User + + + + + + + + +
+
); }; export default CreateModal; + +const UserSearchDropdown = ({ onSelect, onCreate, onClear }: SearchProps) => { + const [selectedUser, setSelectedUser] = useState(null); + const [users, setUsers] = useState([]); + const [query, setQuery] = useState(''); + + const fetchUsers = async (searchTerm: string) => { + const response = await fetch( + route('admin.users.search', { query: searchTerm }) + ); + + return response.json(); + }; + + const handleSelect = (user: any) => { + setSelectedUser(user); + onSelect(user); + }; + + const handleClear = () => { + setSelectedUser(null); + onClear(); + }; + + const handleSearch = debounce((e: React.KeyboardEvent) => { + setQuery((e.target as HTMLInputElement).value); + fetchUsers((e.target as HTMLInputElement).value).then((fetchedUsers) => + setUsers(fetchedUsers) + ); + }, 300); + + return ( +
+ {selectedUser === null && ( + + + + {users.length === 0 && query !== '' ? ( +
+ +
+ ) : ( + users.map((user) => ( + + `relative cursor-pointer select-none py-2 pl-10 pr-4 ${ + active ? 'bg-indigo-100 text-gray-700' : 'text-gray-900' + }` + } + > + {({ selected, active }) => ( + <> +
+ {user.name} + + {user.name} ({user.email}) + +
+ + {selected ? ( + + + ) : null} + + )} +
+ )) + )} +
+
+ )} + + {selectedUser && ( +
+
+ {selectedUser.name} + + {selectedUser.name} + +
+ + +
+ )} +
+ ); +}; diff --git a/routes/web.php b/routes/web.php index 95a91ca..21603ab 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,16 +4,17 @@ use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Application; use App\Http\Controllers\ProfileController; +use App\Http\Controllers\Admin\UserController; +use App\Http\Controllers\Admin\PostsController; use App\Http\Controllers\Admin\StatusController; use App\Http\Controllers\Frontend\HomeController; use App\Http\Controllers\Frontend\PostController; use App\Http\Controllers\Admin\FeedbackController; use App\Http\Controllers\Frontend\BoardController; use App\Http\Controllers\Admin\DashboardController; +use App\Http\Controllers\Admin\UserSearchController; use App\Http\Controllers\Frontend\CommentController; use App\Http\Controllers\Admin\BoardController as AdminBoardController; -use App\Http\Controllers\Admin\PostsController; -use App\Http\Controllers\Admin\UserController; /* |-------------------------------------------------------------------------- @@ -58,6 +59,8 @@ Route::post('/users', [UserController::class, 'store'])->name('admin.users.store'); Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('admin.users.destroy'); Route::put('/users/{user}', [UserController::class, 'update'])->name('admin.users.update'); + + Route::get('/search-users', [UserSearchController::class, 'search'])->name('admin.users.search'); }); Route::middleware(['auth', 'verified'])->group(function () { diff --git a/yarn.lock b/yarn.lock index 5daefbc..55abda9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1892,6 +1892,11 @@ lodash-es@^4.17.15: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" From 3675e478ce111048d5a03cc87742e5a73144c35e Mon Sep 17 00:00:00 2001 From: Tareq Hasan Date: Thu, 22 Feb 2024 12:16:48 +0600 Subject: [PATCH 2/2] Add @types/lodash package --- package.json | 1 + resources/js/Pages/Admin/Feedbacks/Create.tsx | 4 ++-- yarn.lock | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4aa182f..b71ee73 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@headlessui/react": "^1.4.2", "@inertiajs/react": "^1.0.0", "@tailwindcss/forms": "^0.5.3", + "@types/lodash": "^4.14.202", "@types/node": "^18.13.0", "@types/react": "^18.0.28", "@types/react-color": "^3.0.11", diff --git a/resources/js/Pages/Admin/Feedbacks/Create.tsx b/resources/js/Pages/Admin/Feedbacks/Create.tsx index b8ceac3..9ee9936 100644 --- a/resources/js/Pages/Admin/Feedbacks/Create.tsx +++ b/resources/js/Pages/Admin/Feedbacks/Create.tsx @@ -1,6 +1,6 @@ -import { useState, Fragment, useEffect } from 'react'; +import { useState } from 'react'; import { useForm } from '@inertiajs/react'; -import { Combobox, Transition } from '@headlessui/react'; +import { Combobox } from '@headlessui/react'; import { Button, Modal, diff --git a/yarn.lock b/yarn.lock index 55abda9..140d321 100644 --- a/yarn.lock +++ b/yarn.lock @@ -817,6 +817,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/lodash@^4.14.202": + version "4.14.202" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + "@types/node@^18.13.0": version "18.19.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.5.tgz#4b23a9ab8ab7dafebb57bcbaf5c3d8d04f9d8cac"