Skip to content

Commit

Permalink
feat: activity transcript view
Browse files Browse the repository at this point in the history
  • Loading branch information
keenthekeen committed Jul 29, 2024
1 parent dd1ac45 commit 0f411d4
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 14 deletions.
26 changes: 26 additions & 0 deletions app/Http/Controllers/TranscriptController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Inertia\Inertia;

class TranscriptController extends Controller {
public function index(Request $request) {
$this->authorize('faculty-action');
$keyword = $request->input('search');

return Inertia::render('TranscriptView', [
'user' => User::searchQuery($keyword)?->with(['participants', 'participants.project', 'participants.project.department'])->first(),
'keyword' => $keyword,
]);
}

public function print(User $user) {
$this->authorize('faculty-action');

return response()->view('base64-pdf-viewer', ['encoded' => base64_encode(Pdf::loadView('my-projects', ['user' => $user])->output())]);
}
}
2 changes: 1 addition & 1 deletion app/Models/ProjectParticipant.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ProjectParticipant extends Model {
use HasFactory;

protected $fillable = ['user_id', 'type', 'title'];
protected $visible = ['id', 'type', 'title', 'user', 'project'];
protected $visible = ['id', 'type', 'title', 'user', 'project', 'approve_status'];
protected $casts = [
'reject_participants' => AsArrayObject::class,
];
Expand Down
18 changes: 17 additions & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
Expand Down Expand Up @@ -100,8 +101,23 @@ public function participantAndProjects(): Collection
return $this->participants->whereNotNull('project.approvalDocument')->sortByDesc('id')->values();
}


public function projects(): \Illuminate\Database\Eloquent\Relations\HasMany {
return $this->hasMany(Project::class);
}

public static function searchQuery(string $keyword = null): ?Builder {
$query = self::query();
if (empty($keyword) or strlen($keyword) < 3) {
return null;
}
if (is_numeric($keyword)) {
$query->where('student_id', $keyword);
} elseif (str_contains($keyword, '@')) {
$query->where('email', $keyword);
} else {
$query->where('name', 'like', "%$keyword%");
}

return $query;
}
}
36 changes: 24 additions & 12 deletions resources/js/Pages/ProjectApprovalIndex.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
<template>
<AppLayout>
<template #header>
<Link :href="route('projects.index')" class="mb-4 block flex items-center text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="inline h-3 mr-2">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16l-4-4m0 0l4-4m-4 4h18" class="text-gray-500"/>
</svg>
<p>โครงการ</p>
</Link>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
สถานะการอนุมัติรายงานผลโครงการ
</h2>
<p class="mt-2 text-gray-500">
เพื่อบันทึกใน Student Profile/Activity Transcript
</p>
<div class="flex flex-wrap gap-y-4 items-center">
<div class="flex-grow">
<Link :href="route('projects.index')" class="mb-4 block flex items-center text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="inline h-3 mr-2">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16l-4-4m0 0l4-4m-4 4h18"
class="text-gray-500"/>
</svg>
<p>โครงการ</p>
</Link>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
สถานะการอนุมัติรายงานผลโครงการ
</h2>
<p class="mt-2 text-gray-500">
เพื่อบันทึกใน Student Profile/Activity Transcript
</p>
</div>
<div class="flex-auto flex items-center justify-end gap-2">
<a :href="route('transcript.index')"
class="inline-flex py-2 px-4 justify-center items-center text-center text-base font-semibold transition ease-in duration-200 text-purple-500 border-purple-500 border rounded-lg shadow hover:shadow-md focus:ring-purple-500 focus:ring-offset-purple-200 focus:outline-none focus:ring-2 focus:ring-offset-2"
>
ดู <span class="hidden sm:inline px-1">Activity </span> Transcript ของนิสิต
</a>
</div>
</div>
</template>

<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
Expand Down
125 changes: 125 additions & 0 deletions resources/js/Pages/TranscriptView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Activity Transcript
</h2>
</template>

<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<Label>ชื่อ/อีเมล Docchula/เลขประจำตัวนิสิต</Label>
<search-input v-model="searchKeyword" :status="searchMessage"/>

<div v-if="user" class="my-4 p-4 flex gap-4 items-center shadow overflow-hidden border-b border-gray-200 sm:rounded-lg bg-white">
<div class="items-center">
<UserIcon class="h-10 w-10 text-gray-400"/>
</div>
<div class="items-center">
<p class="">{{ user.name }}</p>
<p class="mt-1 text-sm text-gray-500">เลขประจำตัวนิสิต {{ user.student_id }}</p>
</div>
<div class="flex-auto items-center text-right">
<Link :href="route('transcript.print', {user: user.id})" class="text-green-600 ml-2 inline-block">
<PrinterIcon class="inline-block h-5 w-5"/>
พิมพ์
</Link>
</div>
</div>
<div v-if="participantSorted" class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-2 py-2 md:px-4 md:py-3 text-left text-xs font-medium text-gray-500 tracking-wider">
เลขที่
</th>
<th scope="col" class="px-2 py-2 md:px-4 md:py-3 text-left text-xs font-medium text-gray-500 tracking-wider">
โครงการ
</th>
<th scope="col" class="px-2 py-2 md:px-4 md:py-3 text-left text-xs font-medium text-gray-500 tracking-wider">
หน่วยงาน
</th>
<th scope="col" class="px-2 py-2 md:px-4 md:py-3 text-left text-xs font-medium text-gray-500 tracking-wider">
วันที่จัดกิจกรรม
</th>
<th scope="col" class="px-2 py-2 md:px-4 md:py-3 text-left text-xs font-medium text-gray-500 tracking-wider">
ระยะเวลา (ชม.)
</th>
<th scope="col" class="px-2 py-2 md:px-4 md:py-3 text-left text-xs font-medium text-gray-500 tracking-wider">
บทบาท
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="participant in participantSorted" :key="participant.id"
:class="{'text-gray-400 bg-gray-100': participant.approve_status !== 1}">
<td class="px-2 py-2 md:px-4 text-xs">
<Link :href="route('projects.show', {project: participant.project.id})">
{{ participant.project.year }}-{{ participant.project.number }}
</Link>
</td>
<td class="px-2 py-2 md:px-4">
<Link :href="route('projects.show', {project: participant.project.id})">
{{ participant.project.name }}
</Link>
</td>
<td class="px-2 py-2 md:px-4 text-sm">{{ participant.project.department.name }}</td>
<td class="px-2 py-2 md:px-4 text-xs">
{{ participant.project.period_start }}
<span v-if="participant.project.period_start !== participant.project.period_end">- {{
participant.project.period_end
}}</span>
</td>
<td class="px-2 py-2 md:px-4 text-sm">{{ participant.project.duration }}</td>
<td class="px-2 py-2 md:px-4">
{{ PROJECT_PARTICIPANT_ROLES[participant.type] }}
<CheckCircleIcon v-if="participant.approve_status === 1" class="inline-block ml-1 h-4 w-4 text-green-500"/>
<XCircleIcon v-if="participant.approve_status === -1" class="inline-block ml-1 h-4 w-4 text-red-500"/>
<p v-if="participant.title" class="text-xs">{{ participant.title }}</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</app-layout>
</template>

<script setup>
import AppLayout from '@/Layouts/AppLayout.vue'
import SearchInput from '@/Components/SearchInput.vue';
import Label from "@/Jetstream/Label.vue";
import {Link, router} from '@inertiajs/vue3';
import {CheckCircleIcon, UserIcon, XCircleIcon} from '@heroicons/vue/20/solid';
import {computed, ref, watch} from 'vue';
import {debounce} from 'lodash';
import {PROJECT_PARTICIPANT_ROLES} from '@/static';
import {PrinterIcon} from "@heroicons/vue/24/solid";
const props = defineProps({
keyword: String,
user: Object,
static_departments: Array,
});
const searchKeyword = ref(props.keyword ?? '');
const searchMessage = ref('');
const participantSorted = computed(() => {
if (!props.user || !props.user.participants) {
return null;
}
return props.user.participants.sort((a, b) => {
return a.project.id - b.project.id;
});
});
const search = () => {
router.get(route('transcript.index'), {search: searchKeyword.value}, {
preserveState: true
});
searchMessage.value = "";
};
const debouncedSearch = debounce(search, 500);
watch(searchKeyword, () => {
searchMessage.value = 'Typing...';
debouncedSearch();
});
</script>
4 changes: 4 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Http\Controllers\PlanController;
use App\Http\Controllers\ProjectClosureController;
use App\Http\Controllers\ProjectController;
use App\Http\Controllers\TranscriptController;
use App\Http\Controllers\UserProfileController;
use App\Models\Personnel;
use Illuminate\Support\Facades\Route;
Expand Down Expand Up @@ -89,5 +90,8 @@
Route::get('/user/profile', [UserProfileController::class, 'show'])->name('profile.show');
Route::get('/user/profile/printMyProjects', [UserProfileController::class, 'printMyProjects'])->name('profile.printMyProjects');

Route::get('transcript', [TranscriptController::class, 'index'])->name('transcript.index');
Route::get('transcript/{user}/print', [TranscriptController::class, 'print'])->name('transcript.print');

Route::get('health/board', HealthCheckResultsController::class);
});

0 comments on commit 0f411d4

Please sign in to comment.