Skip to content

Commit

Permalink
Merge pull request #5 from IronSinew/blake-admin-categories
Browse files Browse the repository at this point in the history
Admin Category Manager
  • Loading branch information
IronSinew authored Mar 19, 2024
2 parents 8ea5c3d + e46d4b8 commit 99fa46c
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 4 deletions.
61 changes: 61 additions & 0 deletions app/Http/Controllers/Admin/CategoryController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Http\Controllers\Admin;

use App\Enums\BannerTypeEnum;
use App\Http\Controllers\Controller;
use App\Models\Category;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;

class CategoryController extends Controller
{
public function index()
{
return Inertia::render('Admin/Category/CategoryIndex')->with([
'categories' => fn () => Category::orderBy('order_column')
->withCount('recipes')
->withTrashed()
->get()
->makeVisible('id'),
]);
}

/** @codeCoverageIgnore */
public function setNewOrder(Request $request)
{
Category::setNewOrder($request->all());

return response()->noContent();
}

public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'name' => ['required', 'max:40', 'unique:categories'],
]);

$category = Category::create($validated);

return redirect()->route('admin.categories.index')->withBanner("Successfully created {$category->name}");
}

public function destroy(Category $category)
{
$name = $category->name;
$category->delete();

return redirect()->route('admin.categories.index')
->withBanner("Deleted {$name}", BannerTypeEnum::danger);
}

public function restore(Category $category)
{
$name = $category->name;
$category->restore();

return redirect()->route('admin.categories.index')
->withBanner("Restored {$name}");
}
}
14 changes: 13 additions & 1 deletion app/Http/Controllers/Admin/LabelController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ class LabelController extends Controller
public function index()
{
return Inertia::render('Admin/Label/LabelIndex')->with([
'labels' => fn () => Label::orderBy('order_column')->withCount('recipes')->get()->makeVisible('id'),
'labels' => fn () => Label::orderBy('order_column')->withCount('recipes')
->withTrashed()
->get()
->makeVisible('id'),
]);
}

Expand Down Expand Up @@ -45,4 +48,13 @@ public function destroy(Label $label)
return redirect()->route('admin.labels.index')
->withBanner("Deleted {$name}", BannerTypeEnum::danger);
}

public function restore(Label $label)
{
$name = $label->name;
$label->restore();

return redirect()->route('admin.labels.index')
->withBanner("Restored {$name}");
}
}
2 changes: 1 addition & 1 deletion app/Http/Controllers/CategoryListController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ class CategoryListController extends Controller
{
public function __invoke(): JsonResponse
{
return response()->json(Category::select('slug', 'name')->get());
return response()->json(Category::select('slug', 'name')->orderBy('order_column')->get());
}
}
2 changes: 1 addition & 1 deletion app/Models/Recipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function toSearchableArray(): array

public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class);
return $this->belongsToMany(Category::class)->orderBy((new Category)->determineOrderColumnName());
}

public function user(): BelongsTo
Expand Down
4 changes: 4 additions & 0 deletions resources/js/Layouts/AppLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ onMounted(() => {
{
label: 'Labels',
route: 'admin.labels.index'
},
{
label: 'Categories',
route: 'admin.categories.index'
}
],
})
Expand Down
151 changes: 151 additions & 0 deletions resources/js/Pages/Admin/Category/CategoryIndex.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<script setup>
import AppLayout from '@/Layouts/AppLayout.vue';
import Card from "primevue/card";
import Column from "primevue/column";
import ConfirmDialog from "primevue/confirmdialog";
import DataTable from "primevue/datatable";
import InputText from "primevue/inputtext";
import Button from "primevue/button";
import { useConfirm } from "primevue/useconfirm";
import {router, useForm} from "@inertiajs/vue3";
import {onMounted, ref, watch} from "vue";
const props = defineProps({
categories: {
type: [Array, Object],
required: false,
default() {
return [];
},
}
});
watch(() => props.categories, (data, prevData) => {
tableData.value = data;
});
onMounted(() => {
tableData.value = props.categories;
});
const tableData = ref();
const columns = [
{ field: 'name', header: 'Name' },
{ field: 'order_column', header: 'Order' },
{ field: 'recipes_count', header: '# Recipes' },
];
const reloadTableData = () => {
router.reload({ only: ['categories'], preserveScroll: true, })
}
const onRowReorder = (event) => {
tableData.value = event.value;
axios.post(route("admin.categories.set_order"), tableData.value.map(row => row.id)).then(() => {
reloadTableData();
})
};
const saveNew = () => {
form.post(route("admin.categories.store"), {
preserveScroll: true,
onSuccess: () => {
reloadTableData();
form.reset()
}
});
}
const showNewForm = ref(false);
const form = useForm({
name: null,
});
const confirm = useConfirm();
const deleteRowData = (data) => {
confirm.require({
message: "Are you sure you want to delete this category?",
header: `Category: ${data.name}`,
icon: "pi pi-exclamation-triangle",
accept: () => {
router.delete(route("admin.categories.destroy", {
slug: data.slug,
}), {
preserveScroll: true,
onFinish: () => {
reloadTableData();
}
})
}
});
};
const restoreRowData = (data) => {
router.put(route("admin.categories.restore", {
slug: data.slug,
}), {
preserveScroll: true,
onFinish: () => {
reloadTableData();
}
})
};
</script>

<template>
<AppLayout title="Category Admin">
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="flex flex-row mb-5">
<h1 class="text-5xl font-extrabold dark:text-white grow">Category Admin</h1>
<Button @click="showNewForm = !showNewForm" label="Add New"></Button>
</div>
<form @submit.prevent="saveNew" :class="[showNewForm ? 'scale-1 h-full' : 'scale-0 h-0']" class="transition-all ease-in-out delay-150 duration-500 mb-5">
<Card>
<template #content>
<h5 class="text-xl font-bold text-primary-200 mb-3">New Category</h5>
<InputText v-model="form.name" placeholder="Category Name" class="font-normal"/>
<div :class="[form.errors.name ? 'opacity-100' : 'opacity-0']" class="transition-opacity ease-in-out delay-150 duration-300 pt-4 text-sm text-red-500 font-bold">
{{ form.errors.name }}
</div>
</template>
<template #footer>
<div class="text-right">
<Button type="submit" severity="success" label="Save" class="text-right" :disabled="form.processing"></Button>
</div>
</template>
</Card>
</form>
<DataTable :value="tableData" tableStyle="min-width: 50rem" @rowReorder="onRowReorder" pt:transition="transition-all ease-in-out delay-150 duration-500">
<Column rowReorder headerStyle="width: 3rem" :reorderableColumn="false" />
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header"></Column>
<Column header="" class="text-right">
<template #body="{ data }">
<Button
v-if="! data.deleted_at"
@click="deleteRowData(data)"
severity="danger"
size="small"
class="ml-5"
v-tooltip.top="'Delete'"
>
<i class="pi pi-trash"></i>
</Button>
<Button
v-else
@click="restoreRowData(data)"
severity="success"
size="small"
class="ml-5"
v-tooltip.top="'Restore'"
>
<i class="pi pi-replay"></i>
</Button>
</template>
</Column>
</DataTable>
</div>
<ConfirmDialog/>
</div>
</AppLayout>
</template>
24 changes: 24 additions & 0 deletions resources/js/Pages/Admin/Label/LabelIndex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,25 @@ const deleteRowData = (data) => {
router.delete(route("admin.labels.destroy", {
slug: data.slug,
}), {
preserveScroll: true,
onFinish: () => {
reloadTableData();
}
})
}
});
};
const restoreRowData = (data) => {
router.put(route("admin.labels.restore", {
slug: data.slug,
}), {
preserveScroll: true,
onFinish: () => {
reloadTableData();
}
})
};
</script>

<template>
Expand Down Expand Up @@ -110,13 +122,25 @@ const deleteRowData = (data) => {
<Column header="" class="text-right">
<template #body="{ data }">
<Button
v-if="! data.deleted_at"
@click="deleteRowData(data)"
severity="danger"
size="small"
class="ml-5"
v-tooltip.top="'Delete'"
>
<i class="pi pi-trash"></i>
</Button>
<Button
v-else
@click="restoreRowData(data)"
severity="success"
size="small"
class="ml-5"
v-tooltip.top="'Restore'"
>
<i class="pi pi-replay"></i>
</Button>
</template>
</Column>
</DataTable>
Expand Down
2 changes: 2 additions & 0 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import PrimeVue from 'primevue/config';
import Ripple from 'primevue/ripple';
import BadgeDirective from 'primevue/badgedirective';
import ConfirmationService from "primevue/confirmationservice";
import Tooltip from "primevue/tooltip";

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

Expand All @@ -29,6 +30,7 @@ createInertiaApp({
})
.directive("ripple", Ripple)
.directive("badge", BadgeDirective)
.directive("tooltip", Tooltip)
.use(ConfirmationService)
.mount(el);
},
Expand Down
12 changes: 12 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use App\Http\Controllers\Admin\CategoryController as AdminCategoryController;
use App\Http\Controllers\Admin\LabelController as AdminLabelController;
use App\Http\Controllers\CategoryController;
use App\Http\Controllers\CategoryListController;
Expand Down Expand Up @@ -33,8 +34,19 @@
HasAdminAreaAccess::class])
->group(function () {
Route::middleware(IsAdmin::class)->group(function () {
// labels
Route::post('/labels/order', [AdminLabelController::class, 'setNewOrder'])->name('labels.set_order');
Route::put('/labels/{label}/restore', [AdminLabelController::class, 'restore'])
->withTrashed()
->name('labels.restore');
Route::resource('labels', AdminLabelController::class)->except('show', 'create');

// categories
Route::post('/categories/order', [AdminCategoryController::class, 'setNewOrder'])->name('categories.set_order');
Route::put('/categories/{category}/restore', [AdminCategoryController::class, 'restore'])
->withTrashed()
->name('categories.restore');
Route::resource('categories', AdminCategoryController::class)->except('show', 'create');
});
});

Expand Down
Loading

0 comments on commit 99fa46c

Please sign in to comment.