Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve admin tools #71

Merged
merged 9 commits into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 47 additions & 15 deletions app/Http/Controllers/CRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,38 @@ abstract class CRUDController extends Controller
/**
* The validation rules.
*
* @var array<int, string>
* @var array<string, string | array<int|string>>
*/
protected array $rules = [];

/**
* The columns to search in.
*
* @var array<int, string>
*/
protected array $search = [];

/**
* The validation rules for the store method.
*
* @var array<int, string>|null
* @param T $old The old model.
* @return array<string, string | array<int|string>>
*/
protected ?array $storeRules;
protected function storeRules(): array
{
return $this->rules;
}

/**
* The validation rules for the update method.
*
* @var array<int, string>|null
* @param T $old The old model.
* @return array<string, string | array<int|string>>
*/
protected ?array $updateRules;
protected function updateRules($old): array
{
return $this->rules;
}

/**
* The array to include with the views.
Expand All @@ -53,9 +68,26 @@ protected function with(): array
return [];
}

public function index()
public function index(Request $request)
{
$items = $this->model::paginate(10);
$sort_by = $request->query('sort_by', 'id');
$sort_dir = $request->query('sort_dir', 'asc');
$query = $this->model::orderBy($sort_by, $sort_dir);

$search = $request->query('query');

if ($search) {
$search = explode(' ', $search);
foreach ($search as $searchTerm) {
$query->where(function ($query) use ($searchTerm) {
foreach ($this->search as $column) {
$query->orWhere($column, 'ILIKE', "%{$searchTerm}%");
}
});
}
}

$items = $query->paginate()->withQueryString();

$with = $this->with();

Expand Down Expand Up @@ -113,7 +145,7 @@ protected function created(array $new): ?array

public function store(Request $request)
{
$validated = $request->validate($this->storeRules ?? $this->rules);
$validated = $request->validate($this->storeRules());

$newValues = $this->created($validated);

Expand All @@ -129,22 +161,22 @@ public function store(Request $request)
* The returned array will be used to update the model, unless
* null is returned, in which case the model will not be updated.
*
* @param array<string, mixed> $old The old values of the model.
* @param T $old The old model.
* @param array<string, mixed> $new The validated values.
* @return array<string, mixed>|null
*/
protected function updated(array $old, array $new): ?array
protected function updated($old, array $new): ?array
{
return $new;
}

public function update(Request $request, $id)
{
$validated = $request->validate($this->updateRules ?? $this->rules);

$model = $this->model::find($id);

$newValues = $this->updated($model->toArray(), $validated);
$validated = $request->validate($this->updateRules($model));

$newValues = $this->updated($model, $validated);

if ($newValues !== null) {
$model->update($newValues);
Expand All @@ -158,9 +190,9 @@ public function update(Request $request, $id)
* If true is returned, the model will be deleted.
* If false is returned, the model will not be deleted.
*
* @param array<string, mixed> $old The old values of the model.
* @param T $old The old model.
*/
protected function destroyed(array $old): bool
protected function destroyed($old): bool
{
return true;
}
Expand Down
2 changes: 2 additions & 0 deletions app/Http/Controllers/EditionCRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ class EditionCRUDController extends CRUDController
'name' => 'required|string',
'year' => 'required|integer',
];

protected array $search = ['name', 'year'];
}
2 changes: 2 additions & 0 deletions app/Http/Controllers/EventCRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class EventCRUDController extends CRUDController
'edition_id' => 'required|exists:editions,id',
];

protected array $search = ['name', 'topic', 'capacity'];

protected function with(): array
{
return [
Expand Down
2 changes: 2 additions & 0 deletions app/Http/Controllers/ProductCRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class ProductCRUDController extends CRUDController
'edition_id' => 'required|exists:editions,id',
];

protected array $search = ['name', 'price', 'stock'];

protected function with(): array
{
return [
Expand Down
4 changes: 3 additions & 1 deletion app/Http/Controllers/QuestCRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ protected function with(): array
];
}

protected array $search = ['name', 'points', 'category'];

protected function created(array $new): ?array
{
$requirement = explode(';', $new['requirement']);
Expand All @@ -48,7 +50,7 @@ protected function created(array $new): ?array
];
}

protected function updated(array $old, array $new): ?array
protected function updated($old, array $new): ?array
{
$requirement = explode(';', $new['requirement']);

Expand Down
6 changes: 4 additions & 2 deletions app/Http/Controllers/SpeakerCRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class SpeakerCRUDController extends CRUDController
'event_id' => 'required|exists:events,id',
];

protected array $search = ['name', 'title', 'description', 'organization'];

protected function with(): array
{
return [
Expand Down Expand Up @@ -56,10 +58,10 @@ protected function created(array $new): ?array
return null;
}

protected function updated(array $old, array $new): ?array
protected function updated($old, array $new): ?array
{
if (isset($new['social_media'])) {
$socialMedia = SocialMedia::find($old['social_media_id']);
$socialMedia = $old->socialMedia;
if ($socialMedia === null) {
$socialMedia = SocialMedia::create($new['social_media']);
} else {
Expand Down
4 changes: 3 additions & 1 deletion app/Http/Controllers/SponsorCRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class SponsorCRUDController extends CRUDController
'company_id' => 'required|exists:companies,id',
];

protected array $search = ['tier'];

protected function created(array $new): ?array
{
return [
Expand All @@ -27,7 +29,7 @@ protected function created(array $new): ?array
];
}

protected function updated(array $old, array $new): ?array
protected function updated($old, array $new): ?array
{
return [
'edition_id' => $new['edition_id'],
Expand Down
24 changes: 21 additions & 3 deletions app/Http/Controllers/UserCRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,18 @@ class UserCRUDController extends CRUDController
'social_media.linkedin' => 'sometimes|nullable|string',
'social_media.twitter' => 'sometimes|nullable|string',
'social_media.website' => 'sometimes|nullable|string|url',
'photo' => 'nullable|mimes:jpg,jpeg,png|max:1024',
];

protected array $search = ['name', 'email'];

protected function updateRules($old): array
{
return array_merge($this->rules, [
'email' => 'required|string|email|max:255|unique:users,email,'.$old->id,
]);
}

protected function created(array $new): ?array
{
$type = match ($new['type']) {
Expand Down Expand Up @@ -56,31 +66,39 @@ protected function created(array $new): ?array
}
DB::commit();

if (isset($new['photo'])) {
$user->updateProfilePhoto($new['photo']);
}

return null;
}

protected function updated(array $old, array $new): ?array
protected function updated($old, array $new): ?array
{
$type = match ($new['type']) {
'student' => Student::class,
'company' => Company::class,
'admin' => Admin::class,
};

if ($old['usertype_type'] !== $type) {
if ($old->usertype_type !== $type) {
// This should never happen
throw new \Exception('Cannot change user type');
}

if ($new['type'] !== 'admin' && isset($new['social_media'])) {
$socialMedia = SocialMedia::find($old['social_media_id']);
$socialMedia = $old->social_media;
ttoino marked this conversation as resolved.
Show resolved Hide resolved
if ($socialMedia === null) {
$socialMedia = SocialMedia::create($new['social_media']);
} else {
$socialMedia->update($new['social_media']);
}
}

if (isset($new['photo'])) {
$old->updateProfilePhoto($new['photo']);
}

return [
'name' => $new['name'],
'email' => $new['email'],
Expand Down
41 changes: 39 additions & 2 deletions resources/js/Components/CRUD/Header.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,51 @@
<script setup lang="ts">
import { Link } from "@inertiajs/vue3";
import { computed } from "vue";
import route from "ziggy-js";

interface Props {
sortBy?: string;
}

defineProps<Props>();
const props = defineProps<Props>();

const sort = computed(() => {
if (!props.sortBy) return { url: "#", icon: "io-swap-vertical" };

const searchParams = new URLSearchParams(window.location.search);

const currentColumn = searchParams.get("sort_by");
const currentDirection = searchParams.get("sort_dir");

searchParams.set("sort_by", props.sortBy);
searchParams.set(
"sort_dir",
currentColumn === props.sortBy && currentDirection === "asc"
? "desc"
: "asc",
);

const router = route();
return {
url: route(router.current() ?? "", {
...router.params,
...Object.fromEntries(searchParams.entries()),
}),
icon:
currentColumn === props.sortBy
? currentDirection === "asc"
? "io-arrow-down"
: "io-arrow-up"
: "io-swap-vertical",
};
});
</script>

<template>
<th class="px-4 py-2 text-start last:text-right">
<slot></slot>
<v-icon v-if="sortBy" name="io-swap-vertical" class="ml-2" />
<Link v-if="sortBy" :href="sort.url" preserve-scroll>
<v-icon :name="sort.icon" class="ml-2" />
</Link>
</th>
</template>
2 changes: 1 addition & 1 deletion resources/js/Components/CRUD/HeaderRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Header from "./Header.vue";

<template>
<tr class="border border-black bg-2023-teal-dark text-white">
<Header>ID</Header>
<Header sort-by="id">ID</Header>
<slot></slot>
<Header>Ações</Header>
</tr>
Expand Down
59 changes: 59 additions & 0 deletions resources/js/Components/ImageInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script setup lang="ts">
import { ref } from "vue";

interface Props {
modelValue: File | null;
initialPreview?: string;
errorMessage?: string;
}

interface Emits {
(event: "update:modelValue", value: File | null): void;
}

const props = defineProps<Props>();
const emit = defineEmits<Emits>();

const input = ref<HTMLInputElement | null>(null);
const preview = ref(props.initialPreview);

const updatePreview = () => {
const photo = input.value?.files?.[0];

if (!photo) return;

const reader = new FileReader();

reader.onload = (e) => {
preview.value = (e.target?.result as string) ?? null;
};

reader.readAsDataURL(photo);

emit("update:modelValue", photo);
};
</script>

<template>
<label>
<input
ref="input"
type="file"
class="sr-only"
@change="updatePreview"
/>

<img
v-if="preview"
:src="preview"
alt=""
class="h-auto max-h-20 w-full object-contain"
/>
<div
v-else
class="flex h-20 w-full flex-col items-center justify-center border-2 border-dashed border-black"
>
<span class="text-2023-teal">Selecionar imagem</span>
</div>
</label>
</template>
Loading
Loading