diff --git a/app/Http/Controllers/Admin/AdminController.php b/app/Http/Controllers/Admin/AdminController.php index 9f5722eb..d4301845 100644 --- a/app/Http/Controllers/Admin/AdminController.php +++ b/app/Http/Controllers/Admin/AdminController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use App\Models\Order; use App\Models\Product; +use App\Models\CustomerQuestion; use Illuminate\Contracts\View\View; use Illuminate\Support\Facades\View as ViewFactory; @@ -16,6 +17,7 @@ public function dashboard(): View 'productsCount' => Product::count(), 'ordersCount' => Order::where('status', 'PENDING')->count(), 'readyToShipCount' => Order::where('status', 'READY TO SHIP')->count(), + 'customerQueryCount' => CustomerQuestion::whereNUll('reply')->count(), ]); } } diff --git a/app/Http/Controllers/Admin/CustomerQuestion/MessDelete.php b/app/Http/Controllers/Admin/CustomerQuestion/MessDelete.php new file mode 100644 index 00000000..99fb3232 --- /dev/null +++ b/app/Http/Controllers/Admin/CustomerQuestion/MessDelete.php @@ -0,0 +1,27 @@ +get('ids'); + + $questions = CustomerQuestion::whereIn('id', $ids)->get(['id']); + + foreach ($questions as $order) { + $order->delete(); + } + + Alert::toast('Removed', 'success'); + + return Redirect::route('customerQuestion.adminView'); + } +} diff --git a/app/Http/Controllers/Admin/CustomerQuestion/Reply.php b/app/Http/Controllers/Admin/CustomerQuestion/Reply.php new file mode 100644 index 00000000..6f414890 --- /dev/null +++ b/app/Http/Controllers/Admin/CustomerQuestion/Reply.php @@ -0,0 +1,24 @@ +update([ + 'reply' => $request->reply, + ]); + + return Redirect::route('customerQuestion.adminView'); + } +} diff --git a/app/Http/Controllers/Admin/CustomerQuestion/ReplyView.php b/app/Http/Controllers/Admin/CustomerQuestion/ReplyView.php new file mode 100644 index 00000000..1018ff73 --- /dev/null +++ b/app/Http/Controllers/Admin/CustomerQuestion/ReplyView.php @@ -0,0 +1,19 @@ +with('user', 'product.productImage') + ->first(); + + return ViewFactory::make('admin.customer.reply', compact('question')); + } +} diff --git a/app/Http/Controllers/Api/CustomerQuestionApi.php b/app/Http/Controllers/Api/CustomerQuestionApi.php new file mode 100644 index 00000000..738f6ac9 --- /dev/null +++ b/app/Http/Controllers/Api/CustomerQuestionApi.php @@ -0,0 +1,38 @@ +get(); + + return DataTables::of($questions) + ->addColumn('select', function ($row) { + $item = ' + '; + return $item; + }) + ->addColumn('created_at', function ($row) { + return date('d/m/Y h:i A', strtotime($row->created_at)); + }) + ->addColumn('reply', function ($row) { + $class = $row->reply ? 'text-danger' : 'text-danger'; + $val = $row->reply ?? '

pending

'; + return $val; + }) + ->addColumn('action', function ($row) { + $item = 'Reply + '; + return $item; + }) + + ->rawColumns(['select', 'action', 'reply']) + ->make(true); + } +} diff --git a/app/Http/Controllers/User/CustomerQuestion/Destroy.php b/app/Http/Controllers/User/CustomerQuestion/Destroy.php new file mode 100644 index 00000000..1e5d46e3 --- /dev/null +++ b/app/Http/Controllers/User/CustomerQuestion/Destroy.php @@ -0,0 +1,28 @@ +questions() + ->where('id', $id) + ->delete(); + + Alert::toast('Deleted!', 'success'); + + return Redirect::route('customerQuestion.index'); + } +} diff --git a/app/Http/Controllers/User/CustomerQuestion/Index.php b/app/Http/Controllers/User/CustomerQuestion/Index.php new file mode 100644 index 00000000..fae9fc69 --- /dev/null +++ b/app/Http/Controllers/User/CustomerQuestion/Index.php @@ -0,0 +1,26 @@ +questions() + ->with('product') + ->paginate(20); + + return ViewFactory::make('customer-question.index')->with([ + 'questions' => $questions, + ]); + } +} diff --git a/app/Http/Controllers/User/CustomerQuestion/Store.php b/app/Http/Controllers/User/CustomerQuestion/Store.php new file mode 100644 index 00000000..690a6aa7 --- /dev/null +++ b/app/Http/Controllers/User/CustomerQuestion/Store.php @@ -0,0 +1,32 @@ +validate([ + 'question' => 'required|max:255', + ]); + //product imported to redirect to its path + $product = Product::findOrFail($request->product_id); + + $question = new CustomerQuestion(); + $question->user_id = auth()->user()->id; + $question->product_id = $request->product_id; + $question->question = $request->question; + $question->save(); + + return Redirect::to($product->path()); + } +} diff --git a/app/Http/Controllers/User/ShowProduct.php b/app/Http/Controllers/User/ShowProduct.php index eebe41d5..bbe0a4a2 100644 --- a/app/Http/Controllers/User/ShowProduct.php +++ b/app/Http/Controllers/User/ShowProduct.php @@ -20,6 +20,11 @@ public function __invoke($id): View ->with('productImage') ->first(); + $questions = $product + ->getQuestions() + ->with('user') + ->paginate(6); + $mightAlsoLike = Product::where('id', '!=', $product->id) ->inRandomOrder() ->with('productImage') @@ -28,6 +33,7 @@ public function __invoke($id): View return ViewFactory::make('shop.show')->with([ 'product' => $product, + 'questions' => $questions, 'mightAlsoLike' => $mightAlsoLike ]); } diff --git a/app/Http/Requests/Admin/ReplyCustomerQuestionRequest.php b/app/Http/Requests/Admin/ReplyCustomerQuestionRequest.php new file mode 100644 index 00000000..6e204e25 --- /dev/null +++ b/app/Http/Requests/Admin/ReplyCustomerQuestionRequest.php @@ -0,0 +1,23 @@ + 'required|max:255', + ]; + } +} diff --git a/app/Models/CustomerQuestion.php b/app/Models/CustomerQuestion.php new file mode 100644 index 00000000..d06c189f --- /dev/null +++ b/app/Models/CustomerQuestion.php @@ -0,0 +1,36 @@ +belongsTo(Product::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/Product.php b/app/Models/Product.php index c3c2aa74..1a6db01b 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -3,9 +3,9 @@ namespace App\Models; use Carbon\Carbon; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; use Illuminate\Support\Facades\URL; @@ -54,6 +54,11 @@ public function getImage(): HasMany return $this->productImage(); } + public function getQuestions(): HasMany + { + return $this->hasMany(CustomerQuestion::class); + } + public function scopeMightAlsoLike($query) { return $query->inRandomOrder()->with('first_image')->take(4); diff --git a/app/Models/User.php b/app/Models/User.php index 7805db35..283cffc3 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -74,4 +74,9 @@ public function orders(): HasMany { return $this->hasMany(Order::class); } + + public function questions(): HasMany + { + return $this->hasMany(CustomerQuestion::class); + } } diff --git a/database/migrations/2023_07_01_000000_create_customer_questions_table.php b/database/migrations/2023_07_01_000000_create_customer_questions_table.php new file mode 100644 index 00000000..dd0a973d --- /dev/null +++ b/database/migrations/2023_07_01_000000_create_customer_questions_table.php @@ -0,0 +1,25 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('product_id'); + $table->string('question'); + $table->string('reply')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('customer_questions'); + } +} diff --git a/resources/views/admin/customer/customer-question.blade.php b/resources/views/admin/customer/customer-question.blade.php new file mode 100644 index 00000000..49f58ea1 --- /dev/null +++ b/resources/views/admin/customer/customer-question.blade.php @@ -0,0 +1,84 @@ +@extends('layouts.admin') + +@section('content') +
+ +
+
+
Customer Questions/Queries
+
+
+ +
+
+ + +
+ +
+
+ @csrf +
+ + + + + + + + + + + +
SelectQuestionReplyCreated_atAction
+
+
+
+
+ +
+@endsection + +@push('js') + +@endpush \ No newline at end of file diff --git a/resources/views/admin/customer/reply.blade.php b/resources/views/admin/customer/reply.blade.php new file mode 100644 index 00000000..69d589d3 --- /dev/null +++ b/resources/views/admin/customer/reply.blade.php @@ -0,0 +1,73 @@ +@extends('layouts.admin') + +@section('content') +
+ +
+
+
Customer Query Reply
+
+
+ +
+
+ +
+
+
+ +
+
+

{{$question->product->title}}

+ @if($question->product->onSale) +

Price : Rs.{{number_format($question->product->price)}}

+

Sale Price : Rs.{{number_format($question->product->sale_price)}}

+ @else +

Price : Rs.{{number_format($question->product->price)}}

+ @endif +
+
+
+ + + @if($errors->any()) +
+ {{ implode('', $errors->all(':message')) }} +
+ @endif +
+ @csrf + @method('PUT') +
+
+
+ +

{{$question->question}}

+

Asked by {{$question->user->name}} on {{$question->created_at->diffForHumans()}}

+
+
+ + +
+
+ +
+
+
+
+
+
+ @csrf + + +
+
+
+ +
+ +
+
+ +
+@endsection diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 3cf363d7..24c4c15c 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -10,9 +10,9 @@
- +
- +
- +
- +
@@ -48,7 +48,7 @@
- +
@@ -319,4 +337,4 @@ function number_format(number, decimals, dec_point, thousands_sep) { } -@endpush +@endpush \ No newline at end of file diff --git a/resources/views/customer-question/index.blade.php b/resources/views/customer-question/index.blade.php new file mode 100644 index 00000000..e509416a --- /dev/null +++ b/resources/views/customer-question/index.blade.php @@ -0,0 +1,72 @@ +@extends('layouts.app') + +@section('page-title','Product queries') + +@section('content') +
+
+ +
+ @include('inc.app.user-sidebar') +
+ +
+
+
+

Product queries

+
+ +
+ +
+ + + + + + + + + + + + @if($questions->count()) + @foreach($questions as $question) + + + + + + + + @endforeach + @else + + + + + + + + + Go to products page + @endif + +
Product titleQuestionReplyCreated_atAction
{{$question->product->title}}{{$question->question}}{{$question->reply ?? 'pending' }}{{$question->created_at->diffForHumans()}} +
+ @csrf + @method('delete') +
No questions made on the products yet.
+
+
+ {{$questions->links()}} +
+
+ +
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/inc/admin/sidebar.blade.php b/resources/views/inc/admin/sidebar.blade.php index d56c6186..783e32f1 100644 --- a/resources/views/inc/admin/sidebar.blade.php +++ b/resources/views/inc/admin/sidebar.blade.php @@ -14,11 +14,11 @@ Dashboard - + @endrole - + @role('shipper') @@ -85,6 +85,25 @@
+ + + + + + + @endrole diff --git a/resources/views/inc/admin/topbar.blade.php b/resources/views/inc/admin/topbar.blade.php index 446a8b48..b5a73026 100644 --- a/resources/views/inc/admin/topbar.blade.php +++ b/resources/views/inc/admin/topbar.blade.php @@ -5,7 +5,7 @@ - +
- +
+ +
+ + @endrole + @auth
  • - My Account + My Account
  • @@ -22,9 +22,14 @@ Cancellations
  • +
  • + + My queries + +
  • Logout
  • - + \ No newline at end of file diff --git a/resources/views/shop/show.blade.php b/resources/views/shop/show.blade.php index 17f280ee..a19db67d 100644 --- a/resources/views/shop/show.blade.php +++ b/resources/views/shop/show.blade.php @@ -125,6 +125,77 @@ class="pl-3 border" disabled style="width: 50px">
    +
    +
    +
    +
    +
    +
    Questions about the product
    +
    +
    +
    +
    +
    + @auth +
    + @csrf +
    + + + +
    +
    + @endauth + @guest +

    Login or Register to ask the seller now

    + @endguest +
    +
    + @if($questions->count()) + @foreach($questions as $question) +
    +
    +
    [{{$question->user->name}} + ] >> {{$question->question}} + - {{$question->created_at->diffForHumans()}}
    +
    +
    + @if($question->reply) +
    + + {{$question->reply}} +
    + @else +
    Waiting for the seller to reply.
    + @endif +
    +
    + @endforeach + @else +
    +
    +

    There are not questions yet.

    +
    + @endif +
    + {{$questions->links()}} +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/routes/admin.php b/routes/admin.php index 2b65bda0..07c179e7 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -3,6 +3,9 @@ use App\Http\Controllers\Admin\AdminController; use App\Http\Controllers\Admin\AdminLoginController; use App\Http\Controllers\Admin\CategoryController; +use App\Http\Controllers\Admin\CustomerQuestion\MessDelete; +use App\Http\Controllers\Admin\CustomerQuestion\Reply; +use App\Http\Controllers\Admin\CustomerQuestion\ReplyView; use App\Http\Controllers\Admin\DeliveredController; use App\Http\Controllers\Admin\InvoiceController; use App\Http\Controllers\Admin\OrderController; @@ -32,6 +35,16 @@ Route::delete('/product/{id}/image', [ProductImageController::class, 'destroy']) ->name('productImage.destroy'); + // Customer Question functions + Route::view('/admin/customer-question', 'admin.customer.customer-question') + ->name('customerQuestion.adminView'); + Route::get('/admin/customer-question/{id}/reply', ReplyView::class) + ->name('customerQuestion.adminReply'); + Route::post('/admin/customer-question', MessDelete::class) + ->name('customerQuestion.massDelete'); + Route::put('/admin/customer-question/{question}/reply', Reply::class) + ->name('customerQuestion.reply'); + Route::get('/user-management', [UserManagementController::class, 'index']) ->name('userManagement.index'); Route::post('/user-management', [UserManagementController::class, 'store']) diff --git a/routes/api.php b/routes/api.php index 4a269e85..1eff3ed6 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,6 +1,7 @@ name('product.onSaleInvert'); Route::get('/product/live/invert/{id}', [ProductApiController::class, 'liveInvert'])->name('product.liveInvert'); +//customer question +Route::get('/customer-question/all', CustomerQuestionApi::class)->name('customerQuestion.all'); + //orders api Route::get('/order/all', [OrderApiController::class, 'all'])->name('order.all'); diff --git a/routes/web.php b/routes/web.php index 2c866e50..da5d56a6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,6 +6,9 @@ use App\Http\Controllers\HomeController; use App\Http\Controllers\MyOrderController; use App\Http\Controllers\User\Catalog; +use App\Http\Controllers\User\CustomerQuestion\Destroy as DestroyCustomerQuestion; +use App\Http\Controllers\User\CustomerQuestion\Index as IndexCustomerQuestion; +use App\Http\Controllers\User\CustomerQuestion\Store as StoreCustomerQuestion; use App\Http\Controllers\User\Order; use App\Http\Controllers\User\Shop; use App\Http\Controllers\User\ShowProduct; @@ -55,4 +58,12 @@ Route::get('/user', [UserController::class, 'index'])->name('user.index'); Route::post('/user/address', [UserController::class, 'address'])->name('user.address'); + + // Customer Question functions + Route::get('/customer-question', IndexCustomerQuestion::class) + ->name('customerQuestion.index'); + Route::post('/customer-question', StoreCustomerQuestion::class) + ->name('customerQuestion.store'); + Route::delete('/customer-question/{id}', DestroyCustomerQuestion::class) + ->name('customerQuestion.destroy'); }); diff --git a/tests/Feature/Http/Controllers/CustomerQuestionTest.php b/tests/Feature/Http/Controllers/CustomerQuestionTest.php new file mode 100644 index 00000000..3785bbca --- /dev/null +++ b/tests/Feature/Http/Controllers/CustomerQuestionTest.php @@ -0,0 +1,50 @@ +get('/admin/customer-question'); + $response->assertStatus(200); + } + + #[Test] + public function usersAreRestrictedToGoToAdminView() + { + Artisan::call('db:seed'); + Auth::loginUsingId(2); //user according to the seeder + + $response = $this->get('/admin/customer-question'); + $response->assertForbidden(); + } + + #[Test] + public function userCanStoreQuestionToProduct() + { + Artisan::call('db:seed'); + Auth::loginUsingId(3); //user according to the seeder + + $response = $this->post('/customer-question', [ + 'question' => 'Is is good?', + 'product_id' => 1 + ]); + $response->assertStatus(302); + $this->assertCount(1, CustomerQuestion::all()); + } +}