Skip to content

Commit

Permalink
feat: allow faculty to edit participant list before approval
Browse files Browse the repository at this point in the history
  • Loading branch information
keenthekeen committed Sep 21, 2024
1 parent 162f98a commit 7fa68d6
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 37 deletions.
17 changes: 17 additions & 0 deletions app/Http/Controllers/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,23 @@ public function removeParticipant(ProjectParticipant $participant) {
return back()->with('flash.banner', 'ลบ ' . $participant->user->name . ' แล้ว')->with('flash.bannerStyle', 'success');
}

public function editParticipant(Request $request, ProjectParticipant $participant) {
$this->validate($request, [
'type' => 'required|string|in:organizer,staff,attendee',
'title' => 'nullable|string|max:255',
]);
$participant->load(['project', 'user']);
$this->authorize('update-project', $participant->project);
if ($participant->project->hasSubmittedClosure() and $request->user()->cannot('faculty-action')) {
return back()->with('flash.banner', 'ไม่อนุญาตให้แก้ไขหลังส่งรายงานผลโครงการ')->with('flash.bannerStyle', 'danger');
}
$participant->type = $request->input('type');
$participant->title = $request->input('title');
$participant->save();

return back()->with('flash.banner', 'แก้ไข '.$participant->user->name.' แล้ว')->with('flash.bannerStyle', 'success');
}

public function importParticipantUpload(Request $request, Project $project) {
$this->validate($request, [
'import' => 'required|file|mimes:csv,xlsx,xls'
Expand Down
78 changes: 78 additions & 0 deletions resources/js/Components/ParticipantEditDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<template>
<DialogModal :show="showModal" @close="$emit('close')">
<template #title>
แก้ไขข้อมูลนิสิตผู้เกี่ยวข้อง
</template>

<template #content v-if="participant">
<div class="grid sm:grid-cols-2 gap-4">
<div class="space-y-2">
<Label>นิสิต</Label>
{{ participant.user.name }}
</div>
<div class="space-y-2">
<Label>เลขประจำตัว</Label>
{{ participant.user.student_id }}
</div>
</div>
<div class="mt-4">
<label for="type" class="block text-sm font-medium text-gray-700">บทบาท</label>
<select id="type" v-model="form.type" required
class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option v-for="(name, value) in PROJECT_PARTICIPANT_ROLES"
:key="value" :value="value">
{{ name }}
</option>
</select>
<InputError :message="form.errors.department_id" class="mt-2"/>
</div>
<div class="mt-4">
<Label for="title" value="ตำแหน่ง"/>
<Input id="title" type="text" class="mt-2 block w-full" v-model.trim="form.title"/>
</div>
</template>

<template #footer>
<SecondaryButton @click.native="$emit('close')">
ปิด
</SecondaryButton>
<Button @click="submit" class="ml-4">
บันทึก
</Button>
</template>
</DialogModal>
</template>

<script setup lang="ts">
import {useForm} from '@inertiajs/vue3';
import Button from "@/Jetstream/Button.vue";
import DialogModal from "@/Jetstream/DialogModal.vue";
import Input from "@/Jetstream/Input.vue";
import InputError from '@/Jetstream/InputError.vue';
import Label from "@/Jetstream/Label.vue";
import SecondaryButton from "@/Jetstream/SecondaryButton.vue";
import {ProjectParticipant} from '@/types';
import {PROJECT_PARTICIPANT_ROLES} from '@/static';
import {watch} from 'vue';
const props = defineProps<{
showModal: Boolean,
participant: ProjectParticipant | null,
}>();
const emit = defineEmits(['close']);
const form = useForm({
title: props.participant?.title,
type: props.participant?.type,
});
watch(() => props.participant, (participant) => {
form.title = participant?.title;
form.type = participant?.type;
});
const submit = () => {
form.post(route('projects.editParticipant', {participant: props.participant.id}), {
onSuccess: () => {
emit('close');
},
});
};
</script>
90 changes: 53 additions & 37 deletions resources/js/Pages/ProjectApproval.vue
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,16 @@
</h3>
</div>
<div class="border-t border-gray-200">
<div class="px-4 py-4 sm:px-6">
<div class="py-4 sm:px-6">
<table class="w-full divide-y divide-gray-200">
<thead>
<tr>
<th scope="col" class="px-2 pb-1 text-left text-xs font-medium text-gray-500 tracking-wider">
ชื่อ
</th>
<th scope="col" class="px-2 pb-1 text-left text-xs font-medium text-gray-500 tracking-wider">
เลขประจำตัวนิสิต
<th scope="col"
class="px-2 pb-1 text-left text-xs font-medium text-gray-500 tracking-wider hidden sm:block">
เลขประจำตัว
</th>
<th scope="col" class="px-2 pb-1 text-left text-xs font-medium text-gray-500 tracking-wider">
ตำแหน่ง
Expand All @@ -127,23 +128,32 @@
scope="col" class="px-2 pb-1 text-left text-xs font-medium text-gray-500 tracking-wider">
อนุมัติ
</th>
<th v-if="item.closure_status < 10" scope="col"
class="px-2 pb-1 text-left text-xs font-medium text-gray-500 tracking-wider"></th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<template v-for="(name, type) in PROJECT_PARTICIPANT_ROLES">
<tr class="text-sm bg-gray-300 text-gray-800">
<td :colspan="showCheckbox ? 3 : 2" class="px-2 py-0.5">{{ name }}</td>
<td class="px-2 py-0.5">{{ name }}</td>
<td class="px-2 py-0.5 hidden sm:block"></td>
<td class="px-2 py-0.5"></td>
<td colspan="2" class="px-2 py-0.5 text-right">
<span v-if="participantsGrouped[type]">
ตรวจสอบแล้ว
{{ participantsGrouped[type].filter(e => e.verify_status).length }}
จาก {{ participantsGrouped[type].length }} คน
</span>
</td>
<td></td>
</tr>
<tr v-for="(e, i) in participantsGrouped[type]">
<td class="px-2 py-1"><span class="text-gray-400">{{ i + 1 }}.</span>&ensp;{{ e.user.name }}</td>
<td class="px-2 py-1">{{ e.user.student_id }}</td>
<td class="px-2 py-1">
<span class="text-gray-400">{{ i + 1 }}.</span>&ensp;
{{ e.user.name }}
<p class="text-xs text-gray-400 sm:hidden">{{ e.user.student_id }}</p>
</td>
<td class="px-2 py-1 hidden sm:block">{{ e.user.student_id }}</td>
<td class="px-2 py-1" :class="{'text-gray-300': !e.title}">{{ e.title ?? '-' }}</td>
<td class="px-2 py-1 text-center">
<span v-if="e.verify_status === 1">รับรอง</span>
Expand All @@ -158,33 +168,34 @@
<Checkbox v-else :checked="selectedParticipants.includes(e.id)" @click="selectParticipant(e.id)"
class="text-blue-600 focus:border-blue-300 focus:ring-blue-200"/>
</td>
<td v-if="item.closure_status < 10" class="px-1 py-1 text-center text-gray-400">
<PencilIcon @click="showParticipantEditDialog=e" class="cursor-pointer w-4"/>
</td>
</tr>
</template>
</tbody>
</table>
<div class="mt-4">
<div class="mt-4 text-gray-500">
<h6 class="font-semibold">เงื่อนไขจำนวนนิสิตผู้เกี่ยวข้อง เพื่อบันทึกใน Activity Transcript</h6>
<p v-if="!organizerCountCompliance || !staffCountCompliance"
class="mt-2 mb-1 text-orange-500 border-orange-500 border p-2 w-full rounded-md">
<span class="font-semibold">ไม่ตรงตามเงื่อนไข</span>
เมื่อยืนยันและส่งเอกสารแล้ว ให้ติดต่อชี้แจงกับผู้ช่วยคณบดี/รองคณบดีที่ได้รับมอบหมาย
</p>
<ul class="mt-1 space-y-1 text-sm text-gray-500 list-inside list-disc">
<li class="font-bold"
:class="{'text-green-600': organizerCountCompliance, 'text-red-500': !organizerCountCompliance}">
ผู้รับผิดชอบ พึงมีจำนวนไม่เกินร้อยละ 20 ของจำนวนผู้ปฏิบัติงาน ยกเว้นโครงการที่ไม่มีนิสิตเป็นผู้ปฏิบัติงาน
</li>
<li class="font-bold" :class="{'text-green-600': staffCountCompliance, 'text-red-500': !staffCountCompliance}">
ผู้ปฏิบัติงาน พึงมีจำนวนไม่เกิน 2 ใน 3 ของผู้มีส่วนร่วมในกิจกรรมทั้งหมด ทั้งผู้รับผิดชอบ ผู้ปฏิบัติงาน
และผู้เข้าร่วม ทั้งนิสิตและบุคคลภายนอก
</li>
<li>ผู้เข้าร่วม ต้องลงชื่อเข้าร่วมกิจกรรมทุกวัน ทุกครึ่งวัน หรือตามความเหมาะสมต่อลักษณะกิจกรรม
โดยมีหลักฐานว่าเข้าร่วมกิจกรรมไม่น้อยกว่า 2 ใน 3 ของระยะเวลากิจกรรมทั้งหมด
ให้ผู้รับผิดชอบโครงการเก็บรักษาหลักฐานดังกล่าวไว้
</li>
</ul>
</div>
<div class="mt-4 px-4 sm:px-0 text-gray-500">
<h6 class="font-semibold">เงื่อนไขจำนวนนิสิตผู้เกี่ยวข้อง เพื่อบันทึกใน Activity Transcript</h6>
<p v-if="!organizerCountCompliance || !staffCountCompliance"
class="mt-2 mb-1 text-orange-500 border-orange-500 border p-2 w-full rounded-md">
<span class="font-semibold">ไม่ตรงตามเงื่อนไข</span>
เมื่อยืนยันและส่งเอกสารแล้ว ให้ติดต่อชี้แจงกับผู้ช่วยคณบดี/รองคณบดีที่ได้รับมอบหมาย
</p>
<ul class="mt-1 space-y-1 text-sm text-gray-500 list-inside list-disc">
<li class="font-bold"
:class="{'text-green-600': organizerCountCompliance, 'text-red-500': !organizerCountCompliance}">
ผู้รับผิดชอบ พึงมีจำนวนไม่เกินร้อยละ 20 ของจำนวนผู้ปฏิบัติงาน ยกเว้นโครงการที่ไม่มีนิสิตเป็นผู้ปฏิบัติงาน
</li>
<li class="font-bold" :class="{'text-green-600': staffCountCompliance, 'text-red-500': !staffCountCompliance}">
ผู้ปฏิบัติงาน พึงมีจำนวนไม่เกิน 2 ใน 3 ของผู้มีส่วนร่วมในกิจกรรมทั้งหมด ทั้งผู้รับผิดชอบ ผู้ปฏิบัติงาน
และผู้เข้าร่วม ทั้งนิสิตและบุคคลภายนอก
</li>
<li>ผู้เข้าร่วม ต้องลงชื่อเข้าร่วมกิจกรรมทุกวัน ทุกครึ่งวัน หรือตามความเหมาะสมต่อลักษณะกิจกรรม
โดยมีหลักฐานว่าเข้าร่วมกิจกรรมไม่น้อยกว่า 2 ใน 3 ของระยะเวลากิจกรรมทั้งหมด
ให้ผู้รับผิดชอบโครงการเก็บรักษาหลักฐานดังกล่าวไว้
</li>
</ul>
</div>
</div>
</div>
Expand Down Expand Up @@ -328,16 +339,18 @@
</div>
<ClosureLogDialog :show-modal="showLogDialog" :project="item" @close="showLogDialog = false"/>
<ClosureMessageDialog :show-modal="showMessageDialog" :project="item" @close="showMessageDialog = false"/>
<ParticipantEditDialog :show-modal="showParticipantEditDialog" @close="showParticipantEditDialog = null"
:participant="showParticipantEditDialog"/>
</AppLayout>
</template>

<script setup>
<script setup lang="ts">
import {CheckIcon, PencilIcon, XMarkIcon} from "@heroicons/vue/20/solid";
import AppLayout from '@/Layouts/AppLayout.vue'
import InputError from '@/Jetstream/InputError.vue'
import Label from '@/Jetstream/Label.vue'
import AppLayout from '@/Layouts/AppLayout.vue';
import InputError from '@/Jetstream/InputError.vue';
import Label from '@/Jetstream/Label.vue';
import {computed, ref} from 'vue';
import {Link, useForm} from '@inertiajs/vue3'
import {Link, useForm} from '@inertiajs/vue3';
import {groupBy} from "lodash";
import {PROJECT_PARTICIPANT_ROLES} from "@/static";
import Checkbox from "@/Jetstream/Checkbox.vue";
Expand All @@ -347,10 +360,12 @@ import ProjectClosureStatus from "@/Components/ProjectClosureStatus.vue";
import ClosureLogDialog from "@/Components/ClosureLogDialog.vue";
import ClosureStatusText from "@/Components/ClosureStatusText.vue";
import ClosureMessageDialog from "@/Components/ClosureMessageDialog.vue";
import {Project, ProjectParticipant} from '@/types';
import ParticipantEditDialog from '@/Components/ParticipantEditDialog.vue';

const props = defineProps({
item: Object,
});
const props = defineProps<{
item: Project,
}>();

const form = useForm({
_method: 'POST',
Expand All @@ -363,6 +378,7 @@ const oldSelectedParticipants = props.item.participants.filter(p => p.approve_st
const selectedParticipants = ref(oldSelectedParticipants.length > 0 ? oldSelectedParticipants : props.item.participants.map(e => e.id));
const showLogDialog = ref(false);
const showMessageDialog = ref(false);
const showParticipantEditDialog = ref<ProjectParticipant | null>(null);
const forceShowApproveBox = ref(false);

// Computed
Expand Down
67 changes: 67 additions & 0 deletions resources/js/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
export interface Project {
id: number;
created_at: string | null;
updated_at: string | null;
year: number;
number: number;
name: string;
advisor: string | null;
type: string | null;
recurrence: string | null;
period_start: string | null;
period_end: string | null;
background: string | null;
aims: string | null;
outcomes: string | null;
objectives: string | null;
expense: string | null;
user_id: number | null;
department_id: number | null;
approval_document_id: number | null;
duration: number | null;
estimated_attendees: string | null;
closure_reminded_at: string | null;
closure_submitted_at: string | null;
closure_submitted_by: number | null;
closure_approved_at: string | null;
closure_approved_message: string | null;
closure_approved_by: number | null;
closure_approved_status: number;
// Relationship
participants: ProjectParticipant[];
user: User;
}

export interface ProjectParticipant {
id: number;
created_at: string | null;
updated_at: string | null;
user_id: number;
project_id: number;
type: 'organizer' | 'staff' | 'attendee';
title: string | null;
verify_status: number;
reject_reason: string | null;
reject_participants: number[];
approve_status: number;
// Relationship
user: User;
}

export interface User {
id: number;
name: string;
email: string;
email_verified_at: string | null;
password: string;
remember_token: string | null;
current_team_id: number | null;
profile_photo_path: string | null;
google_id: string | null;
student_id: string | null;
created_at: string;
updated_at: string;
two_factor_secret: string | null;
two_factor_recovery_codes: string | null;
roles: string;
}
Loading

0 comments on commit 7fa68d6

Please sign in to comment.