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

#147 - Google CSV import to grades #155

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions .phpstorm.meta.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Illuminate\Database\Eloquent {
use Illuminate\Contracts\Database\Eloquent\Builder as BuilderContract;

class Builder implements BuilderContract {
public function whereLikeUnaccentInsensitive(string $column, mixed $value): static {
return $this;
}

public function orWhereLikeUnaccentInsensitive(string $column, mixed $value): static {
return $this;
}
}
}
12 changes: 6 additions & 6 deletions app/Http/Controllers/Dashboard/GradeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
use Illuminate\Http\Request;
use Inertia\Response;
use Keating\DTOs\CourseSemesterData;
use Keating\Http\Requests\UpdateGrade;
use Keating\Http\Requests\UpdateGradeColumn;
use Keating\Http\Requests\UpdateGradeColumnRequest;
use Keating\Http\Requests\UpdateGradeRequest;
use Keating\Models\CourseSemester;
use Keating\Models\GradeColumn;
use Keating\Models\Group;
Expand Down Expand Up @@ -40,31 +40,31 @@ public function index(Request $request, CourseSemester $course, Group $group): R
]);
}

public function store(UpdateGradeColumn $request, CourseSemester $course, Group $group): RedirectResponse
public function store(UpdateGradeColumnRequest $request, CourseSemester $course, Group $group): RedirectResponse
{
$group->gradeColumns()->create($request->getData());

return redirect()->back()
->with("success", "Dodano kolumnę");
}

public function update(UpdateGradeColumn $request, CourseSemester $course, Group $group, GradeColumn $gradeColumn): RedirectResponse
public function update(UpdateGradeColumnRequest $request, CourseSemester $course, Group $group, GradeColumn $gradeColumn): RedirectResponse
{
$gradeColumn->update($request->getData());

return redirect()->back()
->with("success", "Zaktualizowano kolumnę");
}

public function storeGrade(UpdateGrade $request, CourseSemester $course, Group $group, GradeColumn $gradeColumn): RedirectResponse
public function storeGrade(UpdateGradeRequest $request, CourseSemester $course, Group $group, GradeColumn $gradeColumn): RedirectResponse
{
$gradeColumn->grades()
->create($request->getData());

return redirect()->back();
}

public function updateGrade(UpdateGrade $request, CourseSemester $course, Group $group, GradeColumn $gradeColumn): RedirectResponse
public function updateGrade(UpdateGradeRequest $request, CourseSemester $course, Group $group, GradeColumn $gradeColumn): RedirectResponse
{
$grade = $gradeColumn->grades()
->where("student_id", $request->get("student_id"))
Expand Down
50 changes: 50 additions & 0 deletions app/Http/Controllers/Dashboard/GradeImportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Keating\Http\Controllers\Dashboard;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\ResponseFactory;
use Inertia\Response;
use Keating\DTOs\CourseSemesterData;
use Keating\Models\CourseSemester;
use Keating\Models\Grade;
use Keating\Models\GradeColumn;
use Keating\Models\Group;

class GradeImportController
{
public function index(CourseSemester $course, Group $group, GradeColumn $column): Response
{
return inertia("Dashboard/CourseSemester/Grade/Import", [
"course" => CourseSemesterData::fromModel($course),
"group" => $group,
"column" => $column,
"csrfToken" => csrf_token(),
]);
}

public function prepare(Request $request, CourseSemester $course, Group $group, GradeColumn $column, ResponseFactory $response): JsonResponse
{
$students = $request;

return $response->json([
"students" => $column->refresh()->grades->map(fn(Grade $grade): array => [
"id" => $grade->id,
"column" => [
"id" => $column->id,
],
"student" => [
"id" => $grade->student->id,
"name" => $grade->student->fullName,
"indexNumber" => $grade->student->index_number,
],
"status" => $grade->status,
"value" => $grade->value,
"imported" => rand(0, 1) > .5,
]),
]);
}
}
14 changes: 10 additions & 4 deletions app/Http/Controllers/Dashboard/GroupStudentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,22 @@ public function index(Request $request, CourseSemester $course, Group $group): R
$students = $group->students()
->paginate()
->withQueryString();
$availableStudents = $searchText
? Student::query()

$availableStudents = match(true) {
$searchText === null => [],
preg_match("/^(\d+\s?)+$/", $searchText) === 1 => Student::query()
->whereNotIn("id", $group->students->pluck("id"))
->whereIn("index_number", explode(" ", $searchText))
->get(),
default => Student::query()
->whereNotIn("id", $group->students->pluck("id"))
->where(
fn(Builder $query): Builder => $query
->where("first_name", "ILIKE", "%$searchText%")
->orWhere("surname", "ILIKE", "%$searchText%")
->orWhere("index_number", "LIKE", "%$searchText%"),
)->get()
: [];
)->get(),
};

return inertia("Dashboard/CourseSemester/Student/Index", [
"course" => CourseSemesterData::fromModel($course),
Expand Down
1 change: 1 addition & 0 deletions app/Http/Controllers/Dashboard/StudentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public function index(Request $request): Response
->orWhereLikeUnaccentInsensitive("surname", $searchText)
->orWhere("index_number", "LIKE", "%$searchText%"),
)
->orderBy("surname")
->paginate()
->withQueryString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Illuminate\Foundation\Http\FormRequest;

class UpdateGradeColumn extends FormRequest
class UpdateGradeColumnRequest extends FormRequest
{
public function rules(): array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Illuminate\Foundation\Http\FormRequest;

class UpdateGrade extends FormRequest
class UpdateGradeRequest extends FormRequest
{
public function rules(): array
{
Expand Down
5 changes: 5 additions & 0 deletions app/Models/Grade.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
* @property string $id
* @property ?boolean $status
* @property ?string $value
* @property string $student_id
* @property Student $student
* @property string $grade_column_id
* @property GradeColumn $gradeColumn
* @property Carbon $created_at
* @property Carbon $updated_at
*/
Expand All @@ -27,6 +31,7 @@ class Grade extends Model
"status",
"value",
"student_id",
"grade_column_id",
];
protected $casts = [
"status" => "boolean",
Expand Down
7 changes: 7 additions & 0 deletions app/Models/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Keating\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
Expand All @@ -18,8 +19,14 @@
* @property string $name
* @property string $course_semester_id
* @property StudyForm $form
* @property CourseSemester course
* @property Collection<Student> students
* @property Collection<GradeColumn> gradeColumns
* @property Carbon $created_at
* @property Carbon $updated_at
* @property-read CourseSemester $course
* @property-read Collection<Student> $students
* @property-read Collection<GradeColumn> $gradeColumns
*/
class Group extends Model
{
Expand Down
9 changes: 9 additions & 0 deletions app/Models/Student.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Keating\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
Expand All @@ -16,6 +17,7 @@
* @property string $first_name
* @property string $surname
* @property string $index_number
* @property-read string $fullName
* @property Carbon $created_at
* @property Carbon $updated_at
*/
Expand All @@ -30,6 +32,13 @@ class Student extends Model
"index_number",
];

public function fullName(): Attribute
{
return new Attribute(
get: fn(): string => $this->first_name . " " . $this->surname,
);
}

public function groups(): BelongsToMany
{
return $this->belongsToMany(Group::class, "student_group");
Expand Down
14 changes: 12 additions & 2 deletions app/Providers/BuilderServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@

class BuilderServiceProvider extends ServiceProvider
{
/**
* all macros are also registered in .phpstorm.meta.php file
*/
public function boot(): void
{
Builder::macro("whereLikeUnaccentInsensitive", fn($column, $search): Builder => $this->whereRaw("unaccent($column) ILIKE unaccent(?)", ["%{$search}%"]));
Builder::macro("orWhereLikeUnaccentInsensitive", fn($column, $search): Builder => $this->orWhereRaw("unaccent($column) ILIKE unaccent(?)", ["%{$search}%"]));
Builder::macro(
"whereLikeUnaccentInsensitive",
fn($column, $search): Builder => $this->whereRaw("unaccent($column) ILIKE unaccent(?)", ["%{$search}%"]),
);

Builder::macro(
"orWhereLikeUnaccentInsensitive",
fn($column, $search): Builder => $this->orWhereRaw("unaccent($column) ILIKE unaccent(?)", ["%{$search}%"]),
);
}
}
2 changes: 0 additions & 2 deletions codestyle.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
use Blumilk\Codestyle\Config;
use Blumilk\Codestyle\Configuration\Defaults\CommonRules;
use Blumilk\Codestyle\Configuration\Defaults\LaravelPaths;
use PhpCsFixer\Fixer\LanguageConstruct\ClassKeywordFixer;

$paths = new LaravelPaths();
$rules = new CommonRules();

$config = new Config(
paths: $paths->add("codestyle.php"),
rules: $rules->filter(ClassKeywordFixer::class),
);

return $config->config();
2 changes: 1 addition & 1 deletion database/factories/GradeColumnFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class GradeColumnFactory extends Factory
public function definition(): array
{
return [
"name" => fake()->asciify("***"),
"name" => fake()->numerify("W#"),
"active" => fake()->boolean,
"priority" => fake()->numberBetween(1, 100),
"group_id" => Group::factory(),
Expand Down
106 changes: 106 additions & 0 deletions resources/js/Pages/Dashboard/CourseSemester/Grade/Import.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<script setup>
import DashboardLayout from '@/Layouts/DashboardLayout.vue'
import ManagementHeader from '@/Shared/Components/ManagementHeader.vue'
import Section from '@/Shared/Components/Section.vue'
import SubmitButton from '@/Shared/Components/Buttons/SubmitButton.vue'
import FormGroup from '@/Shared/Forms/FormGroup.vue'
import FormLabel from '@/Shared/Forms/FormLabel.vue'
import { useForm } from '@inertiajs/inertia-vue3'
import FormError from '@/Shared/Forms/FormError.vue'
import TableWrapper from '@/Shared/Components/Table/Public/TableWrapper.vue'
import TableRow from '@/Shared/Components/Table/Public/TableRow.vue'
import TableCell from '@/Shared/Components/Table/Public/TableCell.vue'
import GradeCell from '@/Shared/Components/GradeCell.vue'
import TableHeader from "@/Shared/Components/Table/TableHeader.vue";

const props = defineProps({
course: Object,
group: Object,
column: Object,
csrfToken: String,
})

const preparationForm = useForm({
content: '',
})

const gradesForm = useForm({
grades: '',
})

function prepareStudentsList() {
fetch(`/dashboard/semester-courses/${props.course.id}/groups/${props.group.id}/grades/${props.column.id}/prepare`)
.then(response => response.json())
.then(data => {
gradesForm.grades = data['students']
})
}
</script>

<template>
<DashboardLayout>
<div class="flex flex-col gap-8">
<ManagementHeader>
<template #header>
Import aktywności studentów
<span class="text-gray-500">{{ group.name }}</span>
<br> dla kursu
<span class="text-gray-500">{{ course.course }}</span>
dla oceny
<span class="text-gray-500">{{ column.name }}</span>
</template>
</ManagementHeader>
<div class="grid grid-cols-2 gap-8">
<Section>
<form class="flex flex-col justify-between gap-4" @submit.prevent="prepareStudentsList()">
<FormGroup>
<FormLabel for="content">
Lista studentów do zaimportowania
<span class="text-gray-500">(z pliku CSV przysłanego z Google Workspace)</span>
</FormLabel>
<textarea v-model="preparationForm.content" class="block h-[320px] w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 sm:text-sm sm:leading-6" />
<FormError :error="preparationForm.errors.content" />
</FormGroup>
<div class="mt-4 flex justify-end">
<SubmitButton>
przygotuj
</SubmitButton>
</div>
</form>
</Section>
<Section>
<FormLabel for="content">
Lista ocen
</FormLabel>
<TableWrapper class="mt-2">
<template #header>
<TableHeader>
Numer indeksu
</TableHeader>
<TableHeader>
Obecna ocena
</TableHeader>
<TableHeader>
Proponowana
</TableHeader>
</template>
<template #body>
<TableRow v-for="data in gradesForm.grades" :key="data.student.id">
<TableCell class="h-[70px] w-1 cursor-pointer flex-row border-2">
<div class="text-nowrap font-bold">
{{ data.student.name }}
</div>
<div>
({{ data.student.indexNumber }})
</div>
</TableCell>
<GradeCell :grade="data" :grade-column="data.column" :student="data.student" class="cursor-pointer border-2"/>
<GradeCell :grade="data" :grade-column="data.column" :student="data.student" class="cursor-pointer border-2"/>
</TableRow>
</template>
</TableWrapper>
</Section>
</div>
</div>
</DashboardLayout>
</template>
Loading
Loading