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

Feat/department edit #90

Merged
merged 11 commits into from
May 27, 2024
1 change: 0 additions & 1 deletion .prettierrc.json

This file was deleted.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ If you discover any security related issues, please email me@thiritin.com instea
## Credits

- [Thiritin](https://github.com/thiritin)
- [Invisi](https://github.com/invisi)
- [All Contributors](../../contributors)
44 changes: 42 additions & 2 deletions app/Http/Controllers/Staff/DepartmentMemberController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
use App\Http\Controllers\Controller;
use App\Models\Group;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;

Expand Down Expand Up @@ -39,12 +41,50 @@ public function store(Group $department, Request $request)
return redirect()->route('staff.departments.show', $department);
}

public function edit(Group $department, User $user)
public function edit(Group $department, User $member)
{
return Inertia::render('Staff/DepartmentMember/DepartmentMemberEdit', [
'department' => $department,
// Load pivot data
'member' => $department->users()->where('user_id', $user->id)->first()
'member' => $department->users()->where('user_id', $member->id)->select(['id', 'name'])->first()
]);
}

/**
* @throws AuthorizationException
* @throws ValidationException
*/
public function update(Group $department, User $member, Request $request)
{
if ($member->id == $request->user()->id) {
throw ValidationException::withMessages(["You cannot update your own level."]);
}

$data = $request->validate([
'level' => new Enum(GroupUserLevel::class),
]);

$requestMember = $department->users()->find($request->user())->pivot;
$this->authorize("update", $requestMember);

$pivot = $department->users()->find($member->id)->pivot;
$pivot->update($data);

return to_route("staff.departments.show", ['department' => $department->hashid()]);
}

/**
* @throws AuthorizationException
* @throws ValidationException
*/
public function destroy(Group $department, User $member, Request $request)
{
if ($member->id === $request->user()->id) {
throw ValidationException::withMessages(["You cannot remove yourself."]);
}

$requestMember = $department->users()->find($member)->pivot;
$this->authorize('delete', $requestMember);
$department->users()->detach($member);
}
}
11 changes: 8 additions & 3 deletions app/Http/Controllers/Staff/DepartmentsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ class DepartmentsController extends Controller
{
public function index(Request $request)
{
$myDepartmentIds = $request->user()->groups()->where('type', GroupTypeEnum::Department)->pluck('id');
$myDepartments = $request->user()->groups()
->where('type', GroupTypeEnum::Department)->select('id', 'level')->get()
->mapWithKeys(fn($role) => [$role->id => ucwords($role->level)]);

$departments = Group::where('type', GroupTypeEnum::Department)
->withCount('users')->get();
$departmentsSortedByMembershipAndUserCount = $departments->sortByDesc(fn($department) => [
$myDepartmentIds->contains($department->id),
$myDepartments->contains($department->id),
$department->users_count
]);

return Inertia::render('Staff/Departments/DepartmentsIndex', [
'groups' => $departmentsSortedByMembershipAndUserCount,
'myGroups' => $myDepartments,
]);
}

Expand All @@ -37,7 +41,8 @@ public function show(Group $department, Request $request)
'profile_photo_path' => (is_null($user->profile_photo_path)) ? null : Storage::drive('s3-avatars')->url($user->profile_photo_path),
'level' => $user->pivot->level,
'title' => $user->pivot->title,
])
]),
'canEdit' => $department->isAdmin($request->user())
]);
}

Expand Down
15 changes: 13 additions & 2 deletions app/Models/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Models;

use App\Enums\GroupTypeEnum;
use App\Enums\GroupUserLevel;
use Carbon\Carbon;
use Eloquent;
use Illuminate\Database\Eloquent\Builder;
Expand Down Expand Up @@ -60,6 +61,13 @@ public function users()
);
}

public function owner()
{
return $this->hasOneThrough(User::class, GroupUser::class, "group_id", "id", "id", "user_id")
->where('level', GroupUserLevel::Owner)
->select(["name"]);
}

public function apps()
{
return $this->belongsToMany(App::class);
Expand Down Expand Up @@ -88,7 +96,10 @@ public function isMember(User $user): bool

public function isAdmin(User $user)
{
return $this->users->contains($user,
fn($user) => $user->pivot->level === 'admin' || $user->pivot->level === 'owner');
$member = $this->users->find($user);
if (!$member) {
return false;
}
return $member->pivot->level == GroupUserLevel::Admin || $member->pivot->level == GroupUserLevel::Owner;
}
}
2 changes: 1 addition & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public function permCheck(string $ability)

public function scopeCheck(string $ability)
{
if (Auth::guard('web')->check()) {
if (Auth::guard('web')->check() || Auth::guard('staff')->check()) {
return true;
}
$sanctumCheck = $this->tokenCan($ability);
Expand Down
9 changes: 7 additions & 2 deletions app/Policies/GroupUserPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public function view(User $user, GroupUser $groupUser): bool
return ($user->scopeCheck('groups.read') && $groupUser->isMember());
}

public function update(User $user, GroupUser $groupUser): bool
public function update(User $user, GroupUser $groupUserInitiator): bool
{
return $user->scopeCheck('groups.update') && $groupUser->isAdmin();
return $user->scopeCheck('groups.update') && $groupUserInitiator->isAdmin();
}

public function create(User $user, GroupUser $groupUserInitiator): bool
Expand All @@ -35,6 +35,11 @@ public function delete(User $user, GroupUser $groupUser): Response
if ($user->scopeCheck('groups.update') && $groupUser->isAdmin()) {
return Response::allow();
}

// check if user is of type admin
if ($groupUser->group->isAdmin($user)) {
return Response::allow();
}
return Response::deny('Insufficient permissions, you cannot delete users.');
}
}
2 changes: 1 addition & 1 deletion resources/css/app.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
@import 'tailwindcss/base';
@import 'tailwindcss/utilities';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
6 changes: 3 additions & 3 deletions resources/js/Layouts/AppLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<li>
<StaffMainMenu :navigation="navigation"></StaffMainMenu>
</li>
<li>
<li v-if="teams.length">
<StaffTeamMenu :teams="teams"></StaffTeamMenu>
</li>
</ul>
Expand Down Expand Up @@ -73,7 +73,7 @@
<StaffMainMenu :navigation="navigation"></StaffMainMenu>
</li>
<li>
<StaffTeamMenu :teams="teams"></StaffTeamMenu>
<StaffTeamMenu v-if="teams.length" :teams="teams"></StaffTeamMenu>
</li>
<li class="-mx-6 mt-auto">
<Menu as="div">
Expand Down Expand Up @@ -228,7 +228,7 @@ const profileNavMenu = [
]

const teams = [
{id: 1, name: 'Registration', href: '#', initial: 'R', current: false},
// {id: 1, name: 'Registration', href: '#', initial: 'R', current: false},
]

const sidebarOpen = ref(false)
Expand Down
52 changes: 29 additions & 23 deletions resources/js/Pages/Staff/DepartmentMember/DepartmentMemberEdit.vue
Original file line number Diff line number Diff line change
@@ -1,37 +1,43 @@
<script setup>
import { defineProps } from 'vue'
import AppLayout from '../../../Layouts/AppLayout.vue'
import SiteHeader from '../../../Components/Staff/SiteHeader.vue'
import { Head, useForm } from '@inertiajs/vue3'
import Dropdown from 'primevue/dropdown'
import PrimaryButton from '../../../Components/PrimaryButton.vue'

import {defineProps} from 'vue';
import AppLayout from "../../../Layouts/AppLayout.vue";
import SiteHeader from "../../../Components/Staff/SiteHeader.vue";
import {Head, useForm} from '@inertiajs/vue3'
import Dropdown from 'primevue/dropdown';

defineOptions({layout: AppLayout})
const props = defineProps({
department: Object,
user: Object,
})
const form = useForm({
level: 1,
})
const levels = [
{name: 'Member', value: 'member'},
{name: 'Moderator', value: 'moderator'},
{name: 'Admin', value: 'admin'},
]
defineOptions({ layout: AppLayout })
const props = defineProps({
department: Object,
user: Object,
member: Object,
})
const form = useForm({
level: props.member.pivot.level,
})
const levels = [
{ name: 'Member', value: 'member' },
{ name: 'Moderator', value: 'moderator' },
{ name: 'Admin', value: 'admin' },
]
</script>

<template>
<Head title="Edit a member"></Head>
<SiteHeader :title="department.name + ' - Edit a member'"></SiteHeader>
<SiteHeader :title="`${department.name} - Edit a member - ${member.name}`"></SiteHeader>

<div>
<div class="max-w-sm mx-auto mt-12">
<form action="#" method="post"
@submit.prevent="form.post(route('staff.departments.members.store',{department: department.hashid}))">
<form action="#" method="post" @submit.prevent="form.patch(route('staff.departments.members.update', {
department: props.department.hashid,
member: props.member.hashid,
}))">
<!-- Prime Vue Edit Member form with one select input of level -->
<Dropdown v-model="form.level" :options="levels" option-value="value" optionLabel="name"
class="w-full md:w-[14rem]"/>
class="w-full md:w-[14rem]" />
<div class="flex pt-2 justify-start">
<PrimaryButton type="submit" class="btn btn-primary w-full md:w-[14rem]">Update</PrimaryButton>
</div>
</form>
</div>
</div>
Expand Down
46 changes: 19 additions & 27 deletions resources/js/Pages/Staff/Departments/DepartmentsIndex.vue
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
<script setup>
import AppLayout from "../../../Layouts/AppLayout.vue";
import SiteHeader from "../../../Components/Staff/SiteHeader.vue";
import ChevronRightIcon from "../../../Components/Icons/ChevronRightIcon.vue";
import {Link} from "@inertiajs/vue3";
import AppLayout from '../../../Layouts/AppLayout.vue'
import SiteHeader from '../../../Components/Staff/SiteHeader.vue'
import ChevronRightIcon from '../../../Components/Icons/ChevronRightIcon.vue'
import {Link} from '@inertiajs/vue3'

defineOptions({layout: AppLayout})
const props = defineProps({
groups: Array,
myGroups: Array,
})

</script>

<template>
<SiteHeader class="mb-4" title="Departments"></SiteHeader>
<!--
<div class="xl:grid grid-cols-2 gap-4">
<div>
<Head title="Departments"></Head>
<PageHeadline>My Department(s)</PageHeadline>
<ListDepartments :departments="myGroups"></ListDepartments>
</div>
<div>
<PageHeadline>All Departments</PageHeadline>
<ListDepartments :departments="groups"></ListDepartments>
</div>
</div>-->
<!-- List of Groups -->
<!-- Deployment list -->
<!-- Department list -->
<ul role="list" class="divide-y divide-gray-900/5">
<li v-for="department in groups" :key="department.id"
class="relative flex items-center space-x-4 px-4 py-4 sm:px-6 lg:px-8">
Expand All @@ -40,17 +28,21 @@ const props = defineProps({
</div>
<div class="mt-3 flex items-center gap-x-2.5 text-xs leading-5 text-gray-400">
<p class="truncate">{{ department.users_count }} Members</p>
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-300">
<circle cx="1" cy="1" r="1"/>
</svg>
<p class="whitespace-nowrap">Lead by xxx</p>

<!--<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-300">
<circle cx="1" cy="1" r="1"/>
</svg>
<p class="whitespace-nowrap">Lead by xxx</p> -->
</div>
</div>
<div
:class="'rounded-full flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset'">
Member
</div>
<ChevronRightIcon class="h-5 w-5 flex-none text-gray-400" aria-hidden="true"/>
<Link :href="route('staff.departments.show',{department: department.hashid})" class="flex items-center ">
<div
v-if="myGroups[department.id]"
class="rounded-full flex-none py-1 px-2 mr-5 text-xs font-medium ring-1 ring-inset">
{{ myGroups[department.id] }}
</div>
<ChevronRightIcon class="h-5 w-5 flex-none text-gray-400" aria-hidden="true"/>
</Link>
</li>
</ul>
</template>
Expand Down
Loading
Loading