Skip to content

Commit 89991c5

Browse files
committed
Implement category CRUD in admin panel
1 parent dc68108 commit 89991c5

File tree

16 files changed

+703
-3
lines changed

16 files changed

+703
-3
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Http\Requests\StoreCategoryRequest;
7+
use App\Http\Requests\UpdateCategoryRequest;
8+
use App\Http\Resources\CategoryResource;
9+
use App\Models\Category;
10+
11+
class CategoryController extends Controller
12+
{
13+
/**
14+
* Display a listing of the resource.
15+
*/
16+
public function index()
17+
{
18+
$sortField = request('sort_field', 'updated_at');
19+
$sortDirection = request('sort_direction', 'desc');
20+
21+
$categories = Category::query()
22+
->orderBy($sortField, $sortDirection)
23+
->latest()
24+
->get();
25+
26+
return CategoryResource::collection($categories);
27+
}
28+
29+
/**
30+
* Store a newly created resource in storage.
31+
*/
32+
public function store(StoreCategoryRequest $request)
33+
{
34+
$data = $request->validated();
35+
$data['created_by'] = $request->user()->id;
36+
$data['updated_by'] = $request->user()->id;
37+
$category = Category::create($data);
38+
39+
return new CategoryResource($category);
40+
}
41+
42+
/**
43+
* Update the specified resource in storage.
44+
*/
45+
public function update(UpdateCategoryRequest $request, Category $category)
46+
{
47+
$data = $request->validated();
48+
$data['updated_by'] = $request->user()->id;
49+
$category->update($data);
50+
51+
return new CategoryResource($category);
52+
}
53+
54+
/**
55+
* Remove the specified resource from storage.
56+
*/
57+
public function destroy(Category $category)
58+
{
59+
$category->delete();
60+
61+
return response()->noContent();
62+
}
63+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace App\Http\Requests;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class StoreCategoryRequest extends FormRequest
8+
{
9+
/**
10+
* Determine if the user is authorized to make this request.
11+
*/
12+
public function authorize(): bool
13+
{
14+
return true;
15+
}
16+
17+
/**
18+
* Get the validation rules that apply to the request.
19+
*
20+
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
21+
*/
22+
public function rules(): array
23+
{
24+
return [
25+
'name' => ['required', 'string'],
26+
'parent_id' => ['nullable', 'exists:categories,id'],
27+
'active' => ['required', 'boolean']
28+
];
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace App\Http\Requests;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class UpdateCategoryRequest extends FormRequest
8+
{
9+
/**
10+
* Determine if the user is authorized to make this request.
11+
*/
12+
public function authorize(): bool
13+
{
14+
return true;
15+
}
16+
17+
/**
18+
* Get the validation rules that apply to the request.
19+
*
20+
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
21+
*/
22+
public function rules(): array
23+
{
24+
return [
25+
'name' => ['required', 'string'],
26+
'parent_id' => ['nullable', 'exists:categories,id'],
27+
'active' => ['required', 'boolean']
28+
];
29+
}
30+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Http\Resources\Json\JsonResource;
7+
8+
class CategoryResource extends JsonResource
9+
{
10+
/**
11+
* Transform the resource into an array.
12+
*
13+
* @return array<string, mixed>
14+
*/
15+
public function toArray(Request $request): array
16+
{
17+
return [
18+
'id' => $this->id,
19+
'name' => $this->name,
20+
'slug' => $this->slug,
21+
'active' => $this->active,
22+
'parent_id' => $this->parent_id,
23+
'parent' => $this->parent ? new CategoryResource($this->parent) : null,
24+
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
25+
'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
26+
];
27+
}
28+
}

app/Models/Category.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Database\Eloquent\Factories\HasFactory;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\SoftDeletes;
8+
use Spatie\Sluggable\HasSlug;
9+
use Spatie\Sluggable\SlugOptions;
10+
11+
class Category extends Model
12+
{
13+
use HasFactory;
14+
use HasSlug;
15+
use SoftDeletes;
16+
17+
protected $fillable = ['name', 'slug', 'active', 'parent_id', 'created_by', 'updated_by'];
18+
19+
public function getSlugOptions(): SlugOptions
20+
{
21+
return SlugOptions::create()
22+
->generateSlugsFrom('name')
23+
->saveSlugsTo('slug');
24+
}
25+
26+
public function parent()
27+
{
28+
return $this->belongsTo(Category::class);
29+
}
30+
}

backend/src/components/Sidebar.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99
Dashboard
1010
</span>
1111
</router-link>
12+
<router-link :to="{name: 'app.categories'}"
13+
class="flex items-center p-2 rounded transition-colors hover:bg-black/30">
14+
<span class="mr-2 text-gray-300">
15+
<ViewListIcon class="w-5"/>
16+
</span>
17+
<span class="text-xs">
18+
Categories
19+
</span>
20+
</router-link>
1221
<router-link :to="{name: 'app.products'}"
1322
class="flex items-center p-2 rounded transition-colors hover:bg-black/30">
1423
<span class="mr-2 text-gray-300">

backend/src/components/core/CustomInput.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div>
33
<label class="sr-only">{{ label }}</label>
4-
<div class="mt-1 flex rounded-md shadow-sm">
4+
<div class="mt-1 flex rounded-md" :class="type === 'checkbox' ? 'items-center' : 'shadow-sm'">
55
<span v-if="prepend"
66
class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 text-sm">
77
{{ prepend }}

backend/src/router/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Report from "../views/Reports/Report.vue";
1616
import OrdersReport from "../views/Reports/OrdersReport.vue";
1717
import CustomersReport from "../views/Reports/CustomersReport.vue";
1818
import ProductForm from "../views/Products/ProductForm.vue";
19+
import Categories from "../views/Categories/Categories.vue";
1920

2021
const routes = [
2122
{
@@ -41,6 +42,11 @@ const routes = [
4142
name: 'app.products',
4243
component: Products
4344
},
45+
{
46+
path: 'categories',
47+
name: 'app.categories',
48+
component: Categories
49+
},
4450
{
4551
path: 'products/create',
4652
name: 'app.products.create',

backend/src/store/actions.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,29 @@ export function deleteCustomer({commit}, customer) {
186186
return axiosClient.delete(`/customers/${customer.id}`)
187187
}
188188

189+
export function getCategories({commit, state}, {sort_field, sort_direction} = {}) {
190+
commit('setCategories', [true])
191+
return axiosClient.get('/categories', {
192+
params: {
193+
sort_field, sort_direction
194+
}
195+
})
196+
.then((response) => {
197+
commit('setCategories', [false, response.data])
198+
})
199+
.catch(() => {
200+
commit('setCategories', [false])
201+
})
202+
}
203+
204+
export function createCategory({commit}, category) {
205+
return axiosClient.post('/categories', category)
206+
}
189207

208+
export function updateCategory({commit}, category) {
209+
return axiosClient.put(`/categories/${category.id}`, category)
210+
}
211+
212+
export function deleteCategory({commit}, category) {
213+
return axiosClient.delete(`/categories/${category.id}`)
214+
}

backend/src/store/mutations.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function setUsers(state, [loading, data = null]) {
4343
total: data.meta.total,
4444
}
4545
}
46-
state.products.loading = loading;
46+
state.users.loading = loading;
4747
}
4848

4949
export function setCustomers(state, [loading, data = null]) {
@@ -93,3 +93,15 @@ export function hideToast(state) {
9393
export function setCountries(state, countries) {
9494
state.countries = countries.data;
9595
}
96+
97+
export function setCategories(state, [loading, data = null]) {
98+
99+
if (data) {
100+
state.categories = {
101+
...state.categories,
102+
data: data.data,
103+
}
104+
}
105+
106+
state.categories.loading = loading;
107+
}

0 commit comments

Comments
 (0)