Compare commits

...

21 Commits

Author SHA1 Message Date
d157d40747 Merge pull request 'prod' (#14) from develop into master
Reviewed-on: #14
2024-06-25 21:58:28 +04:00
ac5d318cef Merge pull request 'Add password mail' (#13) from feature/task-fixes into develop
Reviewed-on: #13
2024-06-25 21:40:05 +04:00
ksenianeva
c6676bd6d1 Add password mail 2024-06-25 21:38:21 +04:00
m.zargarov
780a12581f Fix 2024-06-25 10:04:46 +04:00
m.zargarov
733bb76122 Export average scores to PDF 2024-06-25 01:42:23 +04:00
m.zargarov
c7fefd138a Fix 2024-06-25 00:52:48 +04:00
m.zargarov
321ce81ff0 Add export to excel 2024-06-24 22:04:44 +04:00
ecec8b176c Merge pull request 'Task-11' (#12) from feature/task-11 into develop
Reviewed-on: #12
2024-06-24 17:36:54 +04:00
m.zargarov
769760f414 Fix 2024-06-24 01:28:58 +04:00
m.zargarov
d4cabb23b7 Fix 2024-06-24 00:51:05 +04:00
m.zargarov
b088c3d624 Fix 2024-06-23 20:26:00 +04:00
2e3d757a53 Merge pull request 'Task-10 (Journal)' (#11) from feature/task-10 into develop
Reviewed-on: #11
2024-06-23 17:03:04 +04:00
m.zargarov
6fb18f6949 Add mnogo chego 2024-06-23 17:02:18 +04:00
m.zargarov
36cb0c700a Rebase 2024-06-23 01:02:03 +04:00
m.zargarov
5395261084 hz 2024-06-23 00:58:43 +04:00
m.zargarov
bcdec4dcc7 Rebase 2024-06-23 00:58:31 +04:00
m.zargarov
1216f6b81b Rebase 2024-06-23 00:57:29 +04:00
bd0aca1ff5 Merge pull request 'Task-8 (Authorization)' (#10) from feature/task-8 into develop
Reviewed-on: #10
2024-06-23 00:06:35 +04:00
m.zargarov
2255a901be Fix 2024-06-20 15:57:12 +04:00
m.zargarov
24b5683090 Add journal 2024-06-20 12:27:05 +04:00
2214c533d0 Merge pull request 'feature/task-7 (Authentication)' (#7) from feature/task-7 into master
Reviewed-on: #7
2024-06-16 12:25:51 +04:00
57 changed files with 2089 additions and 288 deletions

View File

@ -0,0 +1,43 @@
<?php
namespace App\Console\Commands;
use App\Models\Admin;
use App\Models\User;
use Illuminate\Console\Command;
class AddAdmin extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:add-admin';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create new admin';
/**
* Execute the console command.
*/
public function handle()
{
$admin = Admin::create();
$user = User::create([
'email' => 'admin' . $admin->id . '@mail',
'password' => 'password',
]);
$admin->user()->save($user);
$this->info('Admin created successfully!');
$this->info('email = ' . $user->email);
$this->info('password = password');
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Console\Commands;
use App\Models\Grade;
use Illuminate\Console\Command;
class AddScores extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:add-scores {grade}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Add 4 and 5 scores for students';
/**
* Execute the console command.
*/
public function handle()
{
$grade = Grade::firstWhere('id', $this->argument('grade'));
$grade->students->random(7)->each(function ($student) {
$student
->lessons()
->syncWithPivotValues($student->lessons()->pluck('id'), ['score' => 4]);
});
$grade->students->random(5)->each(function ($student) {
$student
->lessons()
->syncWithPivotValues($student->lessons()->pluck('id'), ['score' => 5]);
});
}
}

View File

@ -5,11 +5,20 @@ namespace App\Enums;
enum ScoreEnum: string
{
case WithoutScore = 'Без оценки';
case One = '1';
case Two = '2';
case Three = '3';
case Four = '4';
case Five = '5';
case Absent = 'Н';
case Sick = 'Б';
public static function getNumScores(): array
{
return [self::Two, self::Three, self::Four, self::Five];
}
public static function getDebtScores(): array
{
return [self::Absent, self::Sick];
}
}

View File

@ -8,4 +8,16 @@ enum TypeLesson: string
case Classwork = "Работа в классе";
case TestClass = "Самостоятельная работа";
case ExamClass = "Контрольная работа";
public static function getShortType($type)
{
return match ($type) {
self::Homework->value => "д/р",
self::Classwork->value => "кл/р",
self::TestClass->value => "с/р",
self::ExamClass->value => "к/р",
default => "-",
};
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Export;
use App\Enums\ScoreEnum;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithTitle;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class JournalExport implements FromCollection, ShouldAutoSize, WithTitle, WithStyles
{
protected $lessons;
protected $students;
public function __construct($lessons, $students)
{
$this->lessons = $lessons;
$this->students = $students;
}
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
$result = collect();
$headRow = collect();
$headRow->push('');
$this->lessons->each(function ($lesson) use ($headRow) {
$headRow->push($lesson->date);
});
$result->push($headRow);
$headRow = collect();
$headRow->push('ФИО');
$this->lessons->each(function ($lesson) use ($headRow) {
$headRow->push($lesson->shortType);
});
$result->push($headRow);
$this->students->each(function ($student) use ($result){
$row = collect();
$row->push($student->fio);
$this->lessons->each(function ($lesson) use ($row, $student) {
$row->push($student->lessons->find($lesson->id)->pivot->score ?? ScoreEnum::WithoutScore);
});
$result->push($row);
});
return $result;
}
public function title(): string
{
return 'Журнал';
}
public function styles(Worksheet $sheet)
{
$sheet->getStyle($sheet->calculateWorksheetDimension())
->getAlignment()
->setHorizontal(Alignment::HORIZONTAL_CENTER);
}
}

View File

@ -4,6 +4,8 @@ namespace App\Http\Controllers;
use App\Http\Requests\GradePostRequest;
use App\Models\Grade;
use App\Services\FileService;
use App\Services\GradeService;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
@ -12,14 +14,14 @@ class GradeController extends Controller
/**
* Display a listing of the resource.
*/
public function index(): View
public function index(GradeService $service): View
{
if(request()->user()->cannot('viewAny', Grade::class)) {
abort(403);
}
return view('grades.index', [
'grades' => Grade::filter()->paginate(5)->withQueryString(),
'grades' => $service->getGrades(),
]);
}
@ -67,7 +69,7 @@ class GradeController extends Controller
*/
public function edit(Grade $grade): View
{
if(request()->user()->cannot('update', Grade::class)) {
if(request()->user()->cannot('update', $grade)) {
abort(403);
}
@ -81,11 +83,13 @@ class GradeController extends Controller
*/
public function update(GradePostRequest $request, Grade $grade): RedirectResponse
{
if(request()->user()->cannot('update', Grade::class)) {
if(request()->user()->cannot('update', $grade)) {
abort(403);
}
return redirect()->route('grades.show', $grade->update($request->validated()));
$grade->update($request->validated());
return redirect()->route('grades.show', $grade);
}
/**
@ -93,7 +97,7 @@ class GradeController extends Controller
*/
public function destroy(Grade $grade): RedirectResponse
{
if(request()->user()->cannot('delete', Grade::class)) {
if(request()->user()->cannot('delete', $grade)) {
abort(403);
}
@ -101,4 +105,9 @@ class GradeController extends Controller
return redirect()->route('grades.index');
}
public function listStudents(Grade $grade, FileService $fileService)
{
return $fileService->exportStudents($grade);
}
}

View File

@ -5,6 +5,8 @@ namespace App\Http\Controllers;
use App\Http\Requests\GradeSubjectPostRequest;
use App\Models\Grade;
use App\Models\Subject;
use App\Services\FileService;
use App\Services\JournalService;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
@ -25,27 +27,25 @@ class GradeSubjectController extends Controller
return redirect()->route('grades.show', $grade);
}
public function edit(Grade $grade, Subject $subject): View
{
return view('grade-subject.edit', [
'grade' => $grade,
'updateSubject' => $subject,
'subjects' => Subject::all(),
]);
}
public function update(GradeSubjectPostRequest $request, Grade $grade, Subject $subject): RedirectResponse
{
$grade->subjects()->detach($subject);
$grade->subjects()->attach($request->subject_id);
return redirect()->route('grades.show', $grade);
}
public function destroy(Grade $grade, Subject $subject): RedirectResponse
{
$grade->subjects()->detach($subject);
return redirect()->route('grades.show', $grade);
}
public function journal(Grade $grade, Subject $subject, JournalService $service): View
{
return view('grade-subject.journal', [
'lessons' => $grade->lessons()->where('subject_id', $subject->id)->with('students')->get(),
'students' => $grade->students()->orderBy('last_name')->get(),
'grade' => $grade,
'subject' => $subject,
]);
}
public function exportToExcel(Grade $grade, Subject $subject, FileService $service)
{
return $service->exportJournal($grade, $subject);
}
}

View File

@ -7,6 +7,7 @@ use App\Enums\TypeLesson;
use App\Http\Requests\LessonPostRequest;
use App\Models\Grade;
use App\Models\Lesson;
use App\Services\LessonService;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
@ -22,14 +23,14 @@ class LessonController extends Controller
/**
* Display a listing of the resource.
*/
public function index(Grade $grade): View
public function index(Grade $grade, LessonService $service): View
{
if(request()->user()->cannot('viewAny', $grade)) {
abort(403);
}
return view('grade-lesson.index', [
'lessons' => $grade->lessons()->filter()->get(),
'lessons' => $service->getLessons($grade),
'grade' => $grade,
'subjects' => $grade->subjects,
]);
@ -112,12 +113,9 @@ class LessonController extends Controller
abort(403);
}
return redirect()->route(
'grades.lessons.show',[
$grade,
$lesson->update($request->validated()),
]
);
$lesson->update($request->validated());
return redirect()->route('grades.lessons.show',[$grade, $lesson,]);
}
/**

View File

@ -18,7 +18,7 @@ class ScoreController extends Controller
public function show(Lesson $lesson)
{
return view('scores.show', [
'students' => $lesson->students,
'students' => $lesson->students()->orderBy('last_name')->get(),
'lesson' => $lesson,
'scores' => ScoreEnum::cases(),
]);

View File

@ -5,6 +5,8 @@ namespace App\Http\Controllers;
use App\Http\Requests\StudentPostRequest;
use App\Models\Grade;
use App\Models\Student;
use App\Models\Subject;
use App\Services\FileService;
use App\Services\StudentService;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
@ -118,4 +120,24 @@ class StudentController extends Controller
return redirect()->route('students.index');
}
public function scores(StudentService $service, Subject $subject): View
{
return view('students.scores', [
'lessons' => $service->getScores($subject),
'avgScore' => $service->getAvgScore($subject),
]);
}
public function debts(StudentService $service): View
{
return view('students.debts', [
'lessons' => $service->getDebts(),
]);
}
public function exportAvgScores(StudentService $service)
{
return $service->exportAvgScores();
}
}

View File

@ -82,10 +82,9 @@ class SubjectController extends Controller
abort(403);
}
return redirect()->route(
'subjects.show',
$subject->update($request->validated())
);
$subject->update($request->validated());
return redirect()->route('subjects.show', $subject);
}
/**

View File

@ -5,11 +5,19 @@ namespace App\Http\Controllers;
use App\Http\Requests\SubjectTeacherPostRequest;
use App\Models\Subject;
use App\Models\Teacher;
use App\Services\TeacherService;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
class SubjectTeacherController extends Controller
{
public function index(Teacher $teacher, TeacherService $service)
{
return view('subject-teacher.index', [
'subjects' => $service->getSubjects($teacher),
]);
}
public function create(Teacher $teacher): View
{
return view('subject-teacher.create', [

View File

@ -18,14 +18,14 @@ class TeacherController extends Controller
/**
* Display a listing of the resource.
*/
public function index(): View
public function index(TeacherService $service): View
{
if(request()->user()->cannot('viewAny', Teacher::class)) {
abort(403);
}
return view('teachers.index', [
'teachers' => Teacher::filter()->paginate(5)->withQueryString(),
'teachers' => $service->getTeachers(),
]);
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use App\Models\Student;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class StudentAction
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::user()->userable_type != Student::class) {
abort(403);
}
return $next($request);
}
}

View File

@ -2,7 +2,7 @@
namespace App\Http\Middleware;
use App\Models\Student;
use App\Models\Teacher;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@ -17,7 +17,7 @@ class TeacherAction
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::user()->userable_type != Student::class) {
if (Auth::user()->userable_type != Teacher::class) {
abort(403);
}

View File

@ -2,7 +2,9 @@
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StudentPostRequest extends FormRequest
{
@ -27,7 +29,7 @@ class StudentPostRequest extends FormRequest
'middle_name' => 'required|max:255',
'birthday' => 'required|date',
'grade_id' => 'required|exists:grades,id',
'email' => 'required|max:255|lowercase|unique:users,email',
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->route('student')?->user->id), 'regex:/^(([^<>()\[\]\\.,;:\s@”]+(\.[^<>()\[\]\\.,;:\s@”]+)*)|(“.+”))@((\[[09]{1,3}\.[09]{1,3}\.[09]{1,3}\.[09]{1,3}])|(([a-zA-Z\-09]+\.)+[a-zA-Z]{2,}))$/'],
'password' => 'required|max:255',
];
}

View File

@ -2,7 +2,9 @@
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class TeacherPostRequest extends FormRequest
{
@ -26,7 +28,7 @@ class TeacherPostRequest extends FormRequest
'last_name' => 'required|max:255',
'middle_name' => 'required|max:255',
'birthday' => 'required|date',
'email' => 'required|max:255|lowercase|unique:users,email',
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->route('teacher')?->user->id), 'regex:/^(([^<>()\[\]\\.,;:\s@”]+(\.[^<>()\[\]\\.,;:\s@”]+)*)|(“.+”))@((\[[09]{1,3}\.[09]{1,3}\.[09]{1,3}\.[09]{1,3}])|(([a-zA-Z\-09]+\.)+[a-zA-Z]{2,}))$/'],
'password' => 'required|max:255',
];
}

52
app/Mail/UserCreated.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class UserCreated extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(public string $password)
{
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Создана учетная запись',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'mails.user_created',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -2,12 +2,14 @@
namespace App\Models;
use App\Enums\TypeLesson;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
class Lesson extends Model
{
@ -51,4 +53,18 @@ class Lesson extends Model
$query->where('subject_id', $subject_id);
});
}
public function shortType(): Attribute
{
return Attribute::make(
get: fn () => TypeLesson::getShortType($this->type),
);
}
protected function date(): Attribute
{
return Attribute::make(
get: fn () => Carbon::parse($this->lesson_date)->format('d-m-Y')
);
}
}

View File

@ -39,7 +39,7 @@ class Student extends Model
public function lessons(): BelongsToMany
{
return $this->belongsToMany(Lesson::class);
return $this->belongsToMany(Lesson::class)->withPivot('score');
}
public function scopeFilter(Builder $query): void

View File

@ -3,11 +3,15 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
#[ObservedBy(UserObserver::class)]
class User extends Authenticatable
{
use HasFactory, Notifiable;

View File

@ -0,0 +1,18 @@
<?php
namespace App\Observers;
use App\Mail\UserCreated;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
class UserObserver
{
/**
* Handle the User "created" event.
*/
public function created(User $user): void
{
Mail::to($user)->send(new UserCreated(request()->all()['password']));
}
}

View File

@ -5,6 +5,7 @@ namespace App\Policies;
use App\Models\Admin;
use App\Models\Grade;
use App\Models\Student;
use App\Models\Teacher;
use App\Models\User;
class GradePolicy
@ -48,4 +49,14 @@ class GradePolicy
{
return $user->userable_type == Admin::class;
}
public function journal(User $user)
{
return $user->userable_type == Teacher::class;
}
public function list(User $user)
{
return $user->userable_type == Teacher::class;
}
}

View File

@ -6,6 +6,7 @@ use App\Models\Admin;
use App\Models\Grade;
use App\Models\Lesson;
use App\Models\Student;
use App\Models\Teacher;
use App\Models\User;
class LessonPolicy
@ -13,9 +14,9 @@ class LessonPolicy
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user, Grade $grade): bool
public function viewAny(User $user): bool
{
return $user->userable_type != Student::class || $user->userable->grade_id == $grade->id;
return $user->userable_type == Teacher::class;
}
/**
@ -23,7 +24,7 @@ class LessonPolicy
*/
public function view(User $user, Lesson $lesson): bool
{
return $user->userable_type != Student::class || $user->userable->grade_id == $lesson->grade_id;
return $user->userable_type == Teacher::class;
}
/**
@ -31,7 +32,7 @@ class LessonPolicy
*/
public function create(User $user): bool
{
return $user->userable_type == Admin::class;
return $user->userable_type == Teacher::class;
}
/**
@ -39,7 +40,7 @@ class LessonPolicy
*/
public function update(User $user, Lesson $lesson): bool
{
return $user->userable_type == Admin::class;
return $user->userable_type == Teacher::class;
}
/**
@ -47,6 +48,6 @@ class LessonPolicy
*/
public function delete(User $user, Lesson $lesson): bool
{
return $user->userable_type == Admin::class;
return $user->userable_type == Teacher::class;
}
}

View File

@ -21,7 +21,7 @@ class StudentPolicy
*/
public function view(User $user, Student $student): bool
{
return $user->userable_type != Student::class;
return $user->userable_type == Admin::class;
}
/**
@ -29,7 +29,7 @@ class StudentPolicy
*/
public function create(User $user): bool
{
return $user->userable_type != Admin::class;
return $user->userable_type == Admin::class;
}
/**
@ -37,7 +37,7 @@ class StudentPolicy
*/
public function update(User $user, Student $student): bool
{
return $user->userable_type != Admin::class;
return $user->userable_type == Admin::class;
}
/**
@ -45,6 +45,16 @@ class StudentPolicy
*/
public function delete(User $user, Student $student): bool
{
return $user->userable_type != Admin::class;
return $user->userable_type == Admin::class;
}
public function debts(User $user): bool
{
return $user->userable_type == Student::class;
}
public function avgScores(User $user): bool
{
return $user->userable_type == Student::class;
}
}

View File

@ -3,11 +3,17 @@
namespace App\Policies;
use App\Models\Admin;
use App\Models\Student;
use App\Models\Subject;
use App\Models\Teacher;
use App\Models\User;
class SubjectPolicy
{
public function viewAny(User $user): bool
{
return $user->userable_type != Teacher::class;
}
/**
* Determine whether the user can create models.
*/
@ -31,4 +37,14 @@ class SubjectPolicy
{
return $user->userable_type == Admin::class;
}
public function scores(User $user, Subject $subject): bool
{
return $user->userable_type == Student::class;
}
public function pdf(User $user): bool
{
return $user->userable_type == Student::class;
}
}

View File

@ -14,7 +14,7 @@ class TeacherPolicy
*/
public function viewAny(User $user): bool
{
return $user->userable_type != Student::class;
return $user->userable_type != Teacher::class;
}
/**
@ -22,7 +22,7 @@ class TeacherPolicy
*/
public function view(User $user, Teacher $teacher): bool
{
return $user->userable_type == Teacher::class;
return $user->userable_type == Admin::class;
}
/**
@ -48,4 +48,9 @@ class TeacherPolicy
{
return $user->userable_type == Admin::class;
}
public function teacherSubjects(User $user, Teacher $teacher): bool
{
return $user->userable_type == Student::class;
}
}

View File

@ -2,8 +2,12 @@
namespace App\Services;
use App\Export\JournalExport;
use App\Models\Grade;
use App\Models\Subject;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Facades\Excel;
class FileService
{
@ -22,6 +26,33 @@ class FileService
});
});
return Pdf::loadView('subjects.pdf', ['subjects' => $listSubjects])->download('subjects.pdf');
return Pdf::loadView('subjects.pdf', ['subjects' => $listSubjects])->download('Предметы.pdf');
}
public function exportStudents(Grade $grade)
{
$excellentStudents = $this->getMinScore($grade, 5);
$goodStudents = $this->getMinScore($grade, 4);
return Pdf::loadView('grades.list-students', [
'excellentStudents' => $excellentStudents,
'goodStudents' => $goodStudents,
])->download('Студенты.pdf');
}
public function getMinScore(Grade $grade, $minScore)
{
return $grade->students->filter(function ($student) use ($minScore) {
return $student->lessons->min('pivot.score') == $minScore;
});
}
public function exportJournal(Grade $grade, Subject $subject)
{
$lessons = $grade->lessons()->where('subject_id', $subject->id)->with('students')->get();
$students = $grade->students()->orderBy('last_name')->get();
$fileName = $subject->name . '(' . $grade->name . ').xlsx';
return Excel::download(new JournalExport($lessons, $students), $fileName);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Services;
use App\Models\Grade;
use App\Models\Teacher;
use Illuminate\Support\Facades\Auth;
class GradeService
{
public function getGrades()
{
if (Auth::user()->userable_type == Teacher::class) {
return Grade::join('grade_teacher', 'grade_teacher.grade_id', '=', 'grades.id')
->where('grade_teacher.teacher_id', Auth::user()->userable_id)
->filter()
->paginate(5)
->withQueryString();
}
return Grade::filter()->paginate(5)->withQueryString();
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Services;
use App\Models\Grade;
use App\Models\Subject;
use Illuminate\Support\Facades\DB;
class JournalService
{
public function getLessons(Grade $grade, Subject $subject)
{
return DB::table('lessons')
->where('grade_id', $grade->id)
->where('subject_id', $subject->id)
->orderBy('lesson_date')
->get();
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Services;
use App\Models\Grade;
use App\Models\Teacher;
use Illuminate\Support\Facades\Auth;
class LessonService
{
public function getLessons(Grade $grade)
{
if (Auth::user()->userable_type == Teacher::class) {
return $grade
->lessons()
->where('teacher_id', Auth::user()->userable_id)
->filter()
->get();
}
return $grade->lessons()->filter()->get();
}
}

View File

@ -4,8 +4,12 @@ namespace App\Services;
use App\Enums\ScoreEnum;
use App\Models\Student;
use App\Models\Subject;
use App\Models\User;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
class StudentService
{
@ -53,4 +57,44 @@ class StudentService
$model->user()->delete();
$model->delete();
}
public function getScores(Subject $subject): Collection
{
$student = Auth::user()->userable;
return $student->lessons()->where('subject_id', $subject->id)->get();
}
public function getAvgScore(Subject $subject)
{
$student = Auth::user()->userable;
$scores = $student
->lessons()
->where('subject_id', $subject->id)
->whereIn('score', ScoreEnum::getNumScores())
->pluck('score');
return round($scores->avg(), 2);
}
public function getDebts(): Collection
{
$student = Auth::user()->userable;
return $student->lessons()->whereIn('score', ScoreEnum::getDebtScores())->get();
}
public function exportAvgScores()
{
$subjects = Auth::user()->userable->grade->subjects;
$avgScores = collect();
$subjects->each(function ($subject) use ($avgScores) {
$avgScores->put($subject->name, $this->getAvgScore($subject));
});
return Pdf::loadView('students.avg-scores', [
'avgScores' => $avgScores,
])->download('Успеваемость.pdf');
}
}

View File

@ -11,9 +11,7 @@ class SubjectService
public function getSubjects()
{
if(Auth::user()->userable_type == Student::class) {
$student = Auth::user()->userable;
return Subject::whereIn('id', $student->grade->subjects->pluck('id'))
return Subject::whereIn('id', Auth::user()->userable->grade->subjects->pluck('id'))
->filter()
->paginate(5)
->withQueryString();

View File

@ -2,9 +2,12 @@
namespace App\Services;
use App\Models\Student;
use App\Models\Subject;
use App\Models\Teacher;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
class TeacherService
{
@ -47,4 +50,30 @@ class TeacherService
$teacher->user()->delete();
$teacher->delete();
}
public function getTeachers()
{
if (Auth::user()->userable_type == Student::class) {
return Teacher::join('grade_teacher', 'teachers.id', '=', 'grade_teacher.teacher_id')
->where('grade_id', Auth::user()->userable->grade_id)
->filter()
->paginate(5)
->withQueryString();
}
return Teacher::filter()->paginate(5)->withQueryString();
}
public function getSubjects(Teacher $teacher)
{
if (Auth::user()->userable_type == Student::class) {
return Subject::join('subject_teacher', 'subject_teacher.subject_id', '=', 'subjects.id')
->join('grade_subject', 'grade_subject.subject_id', '=', 'subjects.id')
->where('grade_subject.grade_id', Auth::user()->userable->grade_id)
->where('subject_teacher.teacher_id', $teacher->id)
->get();
}
return $teacher->subjects;
}
}

View File

@ -10,7 +10,8 @@
"laravel/framework": "^11.0",
"laravel/telescope": "^5.0",
"laravel/tinker": "^2.9",
"laravel/ui": "^4.5"
"laravel/ui": "^4.5",
"maatwebsite/excel": "^3.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",

601
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "dbd9db2b0c45633cee162e5a5eb75a92",
"content-hash": "04b6d198f726939fe72146f62909bd81",
"packages": [
{
"name": "barryvdh/laravel-dompdf",
@ -207,6 +207,87 @@
],
"time": "2024-02-09T16:56:22+00:00"
},
{
"name": "composer/semver",
"version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.4",
"symfony/phpunit-bridge": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.4.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2023-08-31T09:50:34+00:00"
},
{
"name": "dflydev/dot-access-data",
"version": "v3.0.2",
@ -640,6 +721,67 @@
],
"time": "2023-10-06T06:47:41+00:00"
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.17.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c",
"reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c",
"shasum": ""
},
"require": {
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0"
},
"require-dev": {
"cerdic/css-tidy": "^1.7 || ^2.0",
"simpletest/simpletest": "dev-master"
},
"suggest": {
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
"ext-bcmath": "Used for unit conversion and imagecrash protection",
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
"ext-tidy": "Used for pretty-printing HTML"
},
"type": "library",
"autoload": {
"files": [
"library/HTMLPurifier.composer.php"
],
"psr-0": {
"HTMLPurifier": "library/"
},
"exclude-from-classmap": [
"/library/HTMLPurifier/Language/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0"
},
"time": "2023-11-17T15:01:25+00:00"
},
{
"name": "fruitcake/php-cors",
"version": "v1.3.0",
@ -2097,6 +2239,275 @@
],
"time": "2024-01-28T23:22:08+00:00"
},
{
"name": "maatwebsite/excel",
"version": "3.1.55",
"source": {
"type": "git",
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
"reference": "6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260",
"reference": "6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260",
"shasum": ""
},
"require": {
"composer/semver": "^3.3",
"ext-json": "*",
"illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0",
"php": "^7.0||^8.0",
"phpoffice/phpspreadsheet": "^1.18",
"psr/simple-cache": "^1.0||^2.0||^3.0"
},
"require-dev": {
"laravel/scout": "^7.0||^8.0||^9.0||^10.0",
"orchestra/testbench": "^6.0||^7.0||^8.0||^9.0",
"predis/predis": "^1.1"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Maatwebsite\\Excel\\ExcelServiceProvider"
],
"aliases": {
"Excel": "Maatwebsite\\Excel\\Facades\\Excel"
}
}
},
"autoload": {
"psr-4": {
"Maatwebsite\\Excel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Patrick Brouwers",
"email": "patrick@spartner.nl"
}
],
"description": "Supercharged Excel exports and imports in Laravel",
"keywords": [
"PHPExcel",
"batch",
"csv",
"excel",
"export",
"import",
"laravel",
"php",
"phpspreadsheet"
],
"support": {
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.55"
},
"funding": [
{
"url": "https://laravel-excel.com/commercial-support",
"type": "custom"
},
{
"url": "https://github.com/patrickbrouwers",
"type": "github"
}
],
"time": "2024-02-20T08:27:10+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/b8174494eda667f7d13876b4a7bfef0f62a7c0d1",
"reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-zlib": "*",
"php-64bit": "^8.1"
},
"require-dev": {
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^10.0",
"vimeo/psalm": "^5.0"
},
"suggest": {
"guzzlehttp/psr7": "^2.4",
"psr/http-message": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.0"
},
"funding": [
{
"url": "https://github.com/maennchen",
"type": "github"
},
{
"url": "https://opencollective.com/zipstream",
"type": "open_collective"
}
],
"time": "2023-06-21T14:59:35+00:00"
},
{
"name": "markbaker/complex",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
},
"time": "2022-12-06T16:21:08+00:00"
},
{
"name": "markbaker/matrix",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
},
"time": "2022-12-02T22:17:43+00:00"
},
{
"name": "masterminds/html5",
"version": "2.9.0",
@ -2755,6 +3166,111 @@
},
"time": "2024-04-08T12:52:34+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.29.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0",
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"ezyang/htmlpurifier": "^4.15",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^7.4 || ^8.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^1.0 || ^2.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0 || ^10.0",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0"
},
"time": "2023-06-14T22:48:31+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.2",
@ -6225,87 +6741,6 @@
},
"time": "2024-01-28T17:52:47+00:00"
},
{
"name": "composer/semver",
"version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.4",
"symfony/phpunit-bridge": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.4.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2023-08-31T09:50:34+00:00"
},
{
"name": "dragon-code/contracts",
"version": "2.23.0",
@ -9712,5 +10147,5 @@
"php": "^8.2"
},
"platform-dev": [],
"plugin-api-version": "2.2.0"
"plugin-api-version": "2.6.0"
}

379
config/excel.php Normal file
View File

@ -0,0 +1,379 @@
<?php
use Maatwebsite\Excel\Excel;
return [
'exports' => [
/*
|--------------------------------------------------------------------------
| Chunk size
|--------------------------------------------------------------------------
|
| When using FromQuery, the query is automatically chunked.
| Here you can specify how big the chunk should be.
|
*/
'chunk_size' => 1000,
/*
|--------------------------------------------------------------------------
| Pre-calculate formulas during export
|--------------------------------------------------------------------------
*/
'pre_calculate_formulas' => false,
/*
|--------------------------------------------------------------------------
| Enable strict null comparison
|--------------------------------------------------------------------------
|
| When enabling strict null comparison empty cells ('') will
| be added to the sheet.
*/
'strict_null_comparison' => false,
/*
|--------------------------------------------------------------------------
| CSV Settings
|--------------------------------------------------------------------------
|
| Configure e.g. delimiter, enclosure and line ending for CSV exports.
|
*/
'csv' => [
'delimiter' => ',',
'enclosure' => '"',
'line_ending' => PHP_EOL,
'use_bom' => false,
'include_separator_line' => false,
'excel_compatibility' => false,
'output_encoding' => '',
'test_auto_detect' => true,
],
/*
|--------------------------------------------------------------------------
| Worksheet properties
|--------------------------------------------------------------------------
|
| Configure e.g. default title, creator, subject,...
|
*/
'properties' => [
'creator' => '',
'lastModifiedBy' => '',
'title' => '',
'description' => '',
'subject' => '',
'keywords' => '',
'category' => '',
'manager' => '',
'company' => '',
],
],
'imports' => [
/*
|--------------------------------------------------------------------------
| Read Only
|--------------------------------------------------------------------------
|
| When dealing with imports, you might only be interested in the
| data that the sheet exists. By default we ignore all styles,
| however if you want to do some logic based on style data
| you can enable it by setting read_only to false.
|
*/
'read_only' => true,
/*
|--------------------------------------------------------------------------
| Ignore Empty
|--------------------------------------------------------------------------
|
| When dealing with imports, you might be interested in ignoring
| rows that have null values or empty strings. By default rows
| containing empty strings or empty values are not ignored but can be
| ignored by enabling the setting ignore_empty to true.
|
*/
'ignore_empty' => false,
/*
|--------------------------------------------------------------------------
| Heading Row Formatter
|--------------------------------------------------------------------------
|
| Configure the heading row formatter.
| Available options: none|slug|custom
|
*/
'heading_row' => [
'formatter' => 'slug',
],
/*
|--------------------------------------------------------------------------
| CSV Settings
|--------------------------------------------------------------------------
|
| Configure e.g. delimiter, enclosure and line ending for CSV imports.
|
*/
'csv' => [
'delimiter' => null,
'enclosure' => '"',
'escape_character' => '\\',
'contiguous' => false,
'input_encoding' => 'UTF-8',
],
/*
|--------------------------------------------------------------------------
| Worksheet properties
|--------------------------------------------------------------------------
|
| Configure e.g. default title, creator, subject,...
|
*/
'properties' => [
'creator' => '',
'lastModifiedBy' => '',
'title' => '',
'description' => '',
'subject' => '',
'keywords' => '',
'category' => '',
'manager' => '',
'company' => '',
],
/*
|--------------------------------------------------------------------------
| Cell Middleware
|--------------------------------------------------------------------------
|
| Configure middleware that is executed on getting a cell value
|
*/
'cells' => [
'middleware' => [
//\Maatwebsite\Excel\Middleware\TrimCellValue::class,
//\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class,
],
],
],
/*
|--------------------------------------------------------------------------
| Extension detector
|--------------------------------------------------------------------------
|
| Configure here which writer/reader type should be used when the package
| needs to guess the correct type based on the extension alone.
|
*/
'extension_detector' => [
'xlsx' => Excel::XLSX,
'xlsm' => Excel::XLSX,
'xltx' => Excel::XLSX,
'xltm' => Excel::XLSX,
'xls' => Excel::XLS,
'xlt' => Excel::XLS,
'ods' => Excel::ODS,
'ots' => Excel::ODS,
'slk' => Excel::SLK,
'xml' => Excel::XML,
'gnumeric' => Excel::GNUMERIC,
'htm' => Excel::HTML,
'html' => Excel::HTML,
'csv' => Excel::CSV,
'tsv' => Excel::TSV,
/*
|--------------------------------------------------------------------------
| PDF Extension
|--------------------------------------------------------------------------
|
| Configure here which Pdf driver should be used by default.
| Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF
|
*/
'pdf' => Excel::DOMPDF,
],
/*
|--------------------------------------------------------------------------
| Value Binder
|--------------------------------------------------------------------------
|
| PhpSpreadsheet offers a way to hook into the process of a value being
| written to a cell. In there some assumptions are made on how the
| value should be formatted. If you want to change those defaults,
| you can implement your own default value binder.
|
| Possible value binders:
|
| [x] Maatwebsite\Excel\DefaultValueBinder::class
| [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class
| [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class
|
*/
'value_binder' => [
'default' => Maatwebsite\Excel\DefaultValueBinder::class,
],
'cache' => [
/*
|--------------------------------------------------------------------------
| Default cell caching driver
|--------------------------------------------------------------------------
|
| By default PhpSpreadsheet keeps all cell values in memory, however when
| dealing with large files, this might result into memory issues. If you
| want to mitigate that, you can configure a cell caching driver here.
| When using the illuminate driver, it will store each value in the
| cache store. This can slow down the process, because it needs to
| store each value. You can use the "batch" store if you want to
| only persist to the store when the memory limit is reached.
|
| Drivers: memory|illuminate|batch
|
*/
'driver' => 'memory',
/*
|--------------------------------------------------------------------------
| Batch memory caching
|--------------------------------------------------------------------------
|
| When dealing with the "batch" caching driver, it will only
| persist to the store when the memory limit is reached.
| Here you can tweak the memory limit to your liking.
|
*/
'batch' => [
'memory_limit' => 60000,
],
/*
|--------------------------------------------------------------------------
| Illuminate cache
|--------------------------------------------------------------------------
|
| When using the "illuminate" caching driver, it will automatically use
| your default cache store. However if you prefer to have the cell
| cache on a separate store, you can configure the store name here.
| You can use any store defined in your cache config. When leaving
| at "null" it will use the default store.
|
*/
'illuminate' => [
'store' => null,
],
/*
|--------------------------------------------------------------------------
| Cache Time-to-live (TTL)
|--------------------------------------------------------------------------
|
| The TTL of items written to cache. If you want to keep the items cached
| indefinitely, set this to null. Otherwise, set a number of seconds,
| a \DateInterval, or a callable.
|
| Allowable types: callable|\DateInterval|int|null
|
*/
'default_ttl' => 10800,
],
/*
|--------------------------------------------------------------------------
| Transaction Handler
|--------------------------------------------------------------------------
|
| By default the import is wrapped in a transaction. This is useful
| for when an import may fail and you want to retry it. With the
| transactions, the previous import gets rolled-back.
|
| You can disable the transaction handler by setting this to null.
| Or you can choose a custom made transaction handler here.
|
| Supported handlers: null|db
|
*/
'transactions' => [
'handler' => 'db',
'db' => [
'connection' => null,
],
],
'temporary_files' => [
/*
|--------------------------------------------------------------------------
| Local Temporary Path
|--------------------------------------------------------------------------
|
| When exporting and importing files, we use a temporary file, before
| storing reading or downloading. Here you can customize that path.
| permissions is an array with the permission flags for the directory (dir)
| and the create file (file).
|
*/
'local_path' => storage_path('framework/cache/laravel-excel'),
/*
|--------------------------------------------------------------------------
| Local Temporary Path Permissions
|--------------------------------------------------------------------------
|
| Permissions is an array with the permission flags for the directory (dir)
| and the create file (file).
| If omitted the default permissions of the filesystem will be used.
|
*/
'local_permissions' => [
// 'dir' => 0755,
// 'file' => 0644,
],
/*
|--------------------------------------------------------------------------
| Remote Temporary Disk
|--------------------------------------------------------------------------
|
| When dealing with a multi server setup with queues in which you
| cannot rely on having a shared local temporary path, you might
| want to store the temporary file on a shared disk. During the
| queue executing, we'll retrieve the temporary file from that
| location instead. When left to null, it will always use
| the local path. This setting only has effect when using
| in conjunction with queued imports and exports.
|
*/
'remote_disk' => null,
'remote_prefix' => null,
/*
|--------------------------------------------------------------------------
| Force Resync
|--------------------------------------------------------------------------
|
| When dealing with a multi server setup as above, it's possible
| for the clean up that occurs after entire queue has been run to only
| cleanup the server that the last AfterImportJob runs on. The rest of the server
| would still have the local temporary file stored on it. In this case your
| local storage limits can be exceeded and future imports won't be processed.
| To mitigate this you can set this config value to be true, so that after every
| queued chunk is processed the local temporary file is deleted on the server that
| processed it.
|
*/
'force_resync_remote' => null,
],
];

51
config/tinker.php Normal file
View File

@ -0,0 +1,51 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Console Commands
|--------------------------------------------------------------------------
|
| This option allows you to add additional Artisan commands that should
| be available within the Tinker environment. Once the command is in
| this array you may execute the command in Tinker using its name.
|
*/
'commands' => [
App\Console\Commands\AddAdmin::class,
App\Console\Commands\AddScores::class,
],
/*
|--------------------------------------------------------------------------
| Auto Aliased Classes
|--------------------------------------------------------------------------
|
| Tinker will not automatically alias classes in your vendor namespaces
| but you may explicitly allow a subset of classes to get aliased by
| adding the names of each of those classes to the following list.
|
*/
'alias' => [
//
],
/*
|--------------------------------------------------------------------------
| Classes That Should Not Be Aliased
|--------------------------------------------------------------------------
|
| Typically, Tinker automatically aliases classes as you require them in
| Tinker. However, you may wish to never alias certain classes, which
| you may accomplish by listing the classes in the following array.
|
*/
'dont_alias' => [
'App\Nova',
],
];

View File

@ -18,9 +18,39 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
$namesSubjects = [
'Русский язык',
'Математика',
'Английский язык',
'Биология',
'Химия',
'Литература',
'География',
'История',
'Обществознание',
'Информатика',
];
$subjects = collect();
foreach ($namesSubjects as $name) {
$subjects->push(Subject::factory()->create([
'name' => $name,
]));
}
$letterGrades = ['A', 'Б', 'В',];
$numberGrades = 11;
$grades = collect();
while($numberGrades > 0) {
foreach ($letterGrades as $letter) {
$grades->push(Grade::factory()->create([
'name' => $numberGrades . $letter,
]));
}
$numberGrades--;
}
$teachers = Teacher::factory(15)->create();
$grades = Grade::factory(10)->create();
$subjects = Subject::factory(10)->create();
$scores = ScoreEnum::cases();
$teachers->each(function ($teacher) {
@ -28,33 +58,45 @@ class DatabaseSeeder extends Seeder
$teacher->user()->save($user);
});
$grades->each(function ($grade) use ($subjects, $teachers, $scores){
$grade->subjects()->sync($subjects);
$grade->teachers()->sync($teachers);
$teacher = Teacher::factory()->create();
$teacher->user()->save(User::factory()->create(['email' => 'teacher@mail']));
$teacher->grades()->attach($grades->pluck('id'));
$students = Student::factory(10)->create([
$student = Student::factory()->create(['grade_id' => $grades->first()->id]);
$student->user()->save(User::factory()->create(['email' => 'student@mail']));
$grade = $student->grade;
$lessons = collect();
$subjects->each(function ($item) use ($lessons, $teacher, $student, $grade) {
$teacher->subjects()->attach($item);
$grade->subjects()->attach($item);
$lessons->push(Lesson::factory(5)->create([
'description' => 'Выполнение задания №3 на 87 стр. учебника',
'grade_id' => $student->grade_id,
'subject_id' => $item->id,
'teacher_id' => $teacher->id,
]));
});
$grades->each(function ($grade) use ($subjects, $teachers, $scores){
Student::factory(30)->create([
'grade_id' => $grade->id,
])->each(function ($student) use ($scores) {
$user = User::factory()->create();
$student->user()->save($user);
});
$lessons = Lesson::factory(30)->create([
'grade_id' => $grade->id,
'subject_id' => $grade->subjects->random()->id,
'teacher_id' => $grade->teachers->random()->id,
]);
$students->each(function ($student) use ($lessons, $scores) {
$lessons->each(function ($lesson) use ($student, $scores) {
$student->lessons()
->syncWithoutDetaching([$lesson->id => ['score' => $scores[array_rand($scores)]]]);
});
});
});
$subjects->each(function ($subject) use ($teachers) {
$subject->teachers()->sync($teachers->random(2));
$grade->students->each(function ($student) use ($grade, $scores) {
$grade->lessons->each(function ($lesson) use ($student, $scores) {
$student->lessons()
->syncWithoutDetaching([
$lesson->id => ['score' => $scores[array_rand($scores)]]
]);
});
});
}
}

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "course_work",
"name": "CourseWork",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@ -14,7 +14,7 @@
<select id="type" name="type" class="form-select" required>
<option value="">Выберите тип деятельности</option>
@foreach($types as $type)
<option value="{{ $type }}" {{ isset($lesson) && $lesson->type == $type ? 'selected' : old('type') }}>
<option value="{{ $type }}" {{ isset($lesson) && $lesson->type == $type->value ? 'selected' : old('type') }}>
{{ $type }}
</option>
@endforeach
@ -36,7 +36,7 @@
<textarea class="form-control" id="description" name="description" rows="5" >{{ isset($lesson) ? $lesson->description : old('description') }}</textarea>
</div>
<div class="mb-3">
<label for="lesson_date" class="form-label">Дата рождения:</label>
<label for="lesson_date" class="form-label">Дата занятия:</label>
<input type="date" id="lesson_date" name="lesson_date" class="form-control" value="{{ isset($lesson) ? $lesson->lesson_date : old('lesson_date') }}" required>
</div>
<input type="hidden" name="grade_id" value="{{ $grade->id }}">

View File

@ -1,4 +1,4 @@
`@extends('layouts.application')
@extends('layouts.application')
@section('content')
<div class="container col-md-5">
@ -53,5 +53,3 @@
</div>
</div>
@endsection
`

View File

@ -19,10 +19,12 @@
<h5><strong>Описание: </strong>{{ $lesson->description }}</h5>
</div>
</div>
<div class="card-footer">
<a href="{{ route('lessons.scores.show', $lesson) }}" class="btn btn-success">Оценки</a>
<a href="{{ route('grades.lessons.edit', [$grade, $lesson]) }}" class="btn btn-primary">Редактировать</a>
</div>
@can('update', $lesson)
<div class="card-footer">
<a href="{{ route('lessons.scores.show', $lesson) }}" class="btn btn-success">Оценки</a>
<a href="{{ route('grades.lessons.edit', [$grade, $lesson]) }}" class="btn btn-primary">Редактировать</a>
</div>
@endcan
</div>
</div>
</div>

View File

@ -0,0 +1,48 @@
@extends('layouts.application')
@section('content')
<div class="container col-md-8">
<div class="row justify-content-center">
@if (count($students))
<div class="card mt-4">
<div class="card-header d-flex justify-content-between align-items-center">
{{__('Журнал')}}
<a href="{{ route('export-excel', [$grade, $subject]) }}" class="btn btn-info">Excel</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col"></th>
@foreach ($lessons as $lesson)
<th style="text-align: center; vertical-align: middle;" scope="col">{{ $lesson->date }}</th>
@endforeach
</tr>
<tr>
<th style="text-align: center; vertical-align: middle;" scope="col">ФИО</th>
@foreach ($lessons as $lesson)
<th style="text-align: center; vertical-align: middle;" scope="col">{{ $lesson->shortType }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($students as $student)
<tr>
<td style="text-align: center; vertical-align: middle;"> {{ $student->fio }}</td>
@foreach ($lessons as $lesson)
<td style="text-align: center; vertical-align: middle;">
{{ $student->lessons->find($lesson->id)->pivot->score ?? "-" }}
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
</div>
</div>
@endsection

View File

@ -36,24 +36,37 @@
class="btn btn-block col-8">{{ $grade->name }}</a>
</div>
</td>
<td>
<div>
<a href="{{ route('grades.lessons.index', $grade) }}" class="btn btn-primary">Занятия</a>
</div>
</td>
<td>
<div>
<a href="{{ route('grades.edit', $grade) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
<td>
<form action="{{ route('grades.destroy', $grade) }}" method="POST"
style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@can('viewAny', \App\Models\Lesson::class)
<td>
<div>
<a href="{{ route('grades.lessons.index', $grade) }}" class="btn btn-primary">Занятия</a>
</div>
</td>
@endcan
@can('list', \App\Models\Grade::class)
<td>
<div>
<a href="{{ route('list-students', $grade) }}" class="btn btn-info">Списки учеников</a>
</div>
</td>
@endcan
@can('update', $grade)
<td>
<div>
<a href="{{ route('grades.edit', $grade) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
@endcan
@can('delete', $grade)
<td>
<form action="{{ route('grades.destroy', $grade) }}" method="POST"
style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@endcan
</tr>
@endforeach
</tbody>
@ -62,9 +75,11 @@
<p>Классы отсутствуют</p>
@endif
</div>
<div class="card-footer">
<a href="{{ route('grades.create') }}" class="btn btn-success">Добавить</a>
</div>
@can('create', \App\Models\Grade::class)
<div class="card-footer">
<a href="{{ route('grades.create') }}" class="btn btn-success">Добавить</a>
</div>
@endcan
<div class="card-footer">{{ $grades->links() }}</div>
</div>
</div>

View File

@ -0,0 +1,56 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
* { font-family: DejaVu Sans !important; }
table {
width: 100%;
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
th, td {
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
</style>
<title>Списки студентов</title>
</head>
<body>
<h3>Список отличников</h3>
<table>
<thead>
<tr>
<th>ФИО</th>
</tr>
</thead>
<tbody>
@foreach($excellentStudents as $student)
<tr>
<td>{{ $student->fio }}</td>
</tr>
@endforeach
</tbody>
</table>
<h3>Список хорошистов</h3>
<table>
<thead>
<tr>
<th>ФИО</th>
</tr>
</thead>
<tbody>
@foreach($goodStudents as $student)
<tr>
<td>{{ $student->fio }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@ -10,9 +10,11 @@
<h5><strong>Название: </strong>{{ $grade->name }}</h5>
</div>
</div>
<div class="card-footer">
<a href="{{ route('grades.edit', $grade) }}" class="btn btn-primary">Редактировать</a>
</div>
@can('update', $grade)
<div class="card-footer">
<a href="{{ route('grades.edit', $grade) }}" class="btn btn-primary">Редактировать</a>
</div>
@endcan
</div>
<div class="card mt-4">
<div class="card-header">
@ -33,18 +35,22 @@
{{ $subject->name }}
</div>
</td>
<td>
<div>
<a href="{{ route('grades.subjects.edit', [$grade, $subject]) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
<td>
<form action="{{ route('grades.subjects.destroy', [$grade, $subject]) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@can('journal', \App\Models\Grade::class)
<td>
<div>
<a href="{{ route('grades.subjects.journal', [$grade, $subject]) }}" class="btn btn-primary">Журнал</a>
</div>
</td>
@endcan
@can('delete', $grade)
<td>
<form action="{{ route('grades.subjects.destroy', [$grade, $subject]) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@endcan
</tr>
@endforeach
</tbody>
@ -53,9 +59,11 @@
<p>У класса отсутствуют предметы</p>
@endif
</div>
<div class="card-footer">
<a href="{{ route('grades.subjects.create', $grade) }}" class="btn btn-success">Добавить</a>
</div>
@can('create', \App\Models\Grade::class)
<div class="card-footer">
<a href="{{ route('grades.subjects.create', $grade) }}" class="btn btn-success">Добавить</a>
</div>
@endcan
</div>
</div>
</div>

View File

@ -5,35 +5,49 @@
<div class="flex">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('grades.index') }}">
<x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
</a>
<x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('grades.index')" :active="request()->routeIs('dashboard')">
{{ __('Классы') }}
</x-nav-link>
</div>
@can('viewAny', \App\Models\Grade::class)
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('grades.index')" :active="request()->routeIs('grades.index')">
{{ __('Классы') }}
</x-nav-link>
</div>
@endcan
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('subjects.index')" :active="request()->routeIs('dashboard')">
{{ __('Предметы') }}
</x-nav-link>
</div>
@can('viewAny', \App\Models\Subject::class)
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('subjects.index')" :active="request()->routeIs('subjects.index')">
{{ __('Предметы') }}
</x-nav-link>
</div>
@endcan
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('students.index')" :active="request()->routeIs('dashboard')">
{{ __('Студенты') }}
</x-nav-link>
</div>
@can('viewAny', \App\Models\Student::class)
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('students.index')" :active="request()->routeIs('students.index')">
{{ __('Ученики') }}
</x-nav-link>
</div>
@endcan
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('teachers.index')" :active="request()->routeIs('dashboard')">
{{ __('Учителя') }}
</x-nav-link>
</div>
@can('viewAny', \App\Models\Teacher::class)
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('teachers.index')" :active="request()->routeIs('teachers.index')">
{{ __('Учителя') }}
</x-nav-link>
</div>
@endcan
@can('debts', \App\Models\Student::class)
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('student-debts')" :active="request()->routeIs('student-debts')">
{{ __('Задолженности') }}
</x-nav-link>
</div>
@endcan
</div>
<!-- Settings Dropdown -->
@ -41,7 +55,7 @@
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
<div>{{ Auth::user()->name }}</div>
<div>{{ Auth::user()->userable->fio }}</div>
<div class="ms-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
@ -85,9 +99,35 @@
<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
@can('viewAny', \App\Models\Grade::class)
<x-responsive-nav-link :href="route('grades.index')" :active="request()->routeIs('grades.index')">
{{ __('Классы') }}
</x-responsive-nav-link>
@endcan
@can('viewAny', \App\Models\Subject::class)
<x-responsive-nav-link :href="route('subjects.index')" :active="request()->routeIs('subjects.index')">
{{ __('Предметы') }}
</x-responsive-nav-link>
@endcan
@can('viewAny', \App\Models\Student::class)
<x-responsive-nav-link :href="route('students.index')" :active="request()->routeIs('students.index')">
{{ __('Ученики') }}
</x-responsive-nav-link>
@endcan
@can('viewAny', \App\Models\Teacher::class)
<x-responsive-nav-link :href="route('teachers.index')" :active="request()->routeIs('teachers.index')">
{{ __('Учителя') }}
</x-responsive-nav-link>
@endcan
@can('debts', \App\Models\Student::class)
<x-responsive-nav-link :href="route('student-debts')" :active="request()->routeIs('student-debts')">
{{ __('Задолженности') }}
</x-responsive-nav-link>
@endcan
</div>
<!-- Responsive Settings Options -->

View File

@ -0,0 +1,3 @@
<div>
Ваш пароль для входа на {{ url('/') }}: {{ $password }}
</div>

View File

@ -0,0 +1,43 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
* { font-family: DejaVu Sans !important; }
table {
width: 100%;
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
th, td {
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
</style>
<title>Успеваемость ученика</title>
</head>
<body>
<h1>Информация об успеваемости</h1>
<table>
<thead>
<tr>
<th>Предмет</th>
<th>Средняя оценка</th>
</tr>
</thead>
<tbody>
@foreach($avgScores as $key => $item)
<tr>
<td>{{ $key }}</td>
<td>{{ $item }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,28 @@
@extends('layouts.application')
@section('content')
<div class="container col-md-6">
<div class="row justify-content-center">
@if(count($lessons))
@foreach($lessons as $lesson)
<a href="{{ route('grades.lessons.show', [$lesson->grade_id, $lesson]) }}" class="text-decoration-none">
<div class="card mt-4">
<div class="card-header"><strong>{{ $lesson->subject->name }} - {{ $lesson->pivot->score }}</strong></div>
<div class="card-header">{{ $lesson->type }}</div>
<div class="card-body">
{{ $lesson->name }}
</div>
</div>
</a>
@endforeach
@else
<div class="card mt-4">
<div class="card-header"><strong>Задолженности</strong></div>
<div class="card-body">
Задолженности отсутствуют
</div>
</div>
@endif
</div>
</div>
@endsection

View File

@ -24,6 +24,7 @@
<table class="table table-striped">
<thead>
<th>ФИО</th>
<th>Класс</th>
<th>&nbsp;</th>
</thead>
<tbody>
@ -31,21 +32,34 @@
<tr>
<td class="table-text">
<div>
<a href="{{ route('students.show', $student) }}" class="btn btn-block col-8">{{ $student->fio }}</a>
@can('view', $student)
<a href="{{ route('students.show', $student) }}" class="btn btn-block col-8">{{ $student->fio }}</a>
@else
{{ $student->fio }}
@endcan
</div>
</td>
<td>
<td class="table-text">
<div>
<a href="{{ route('students.edit', $student) }}" class="btn btn-warning">Редактировать</a>
{{ $student->grade->name }}
</div>
</td>
<td>
<form action="{{ route('students.destroy', $student) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@can('update', $student)
<td>
<div>
<a href="{{ route('students.edit', $student) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
@endcan
@can('delete', $student)
<td>
<form action="{{ route('students.destroy', $student) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@endcan
</tr>
@endforeach
</tbody>
@ -54,9 +68,11 @@
<p>Ученики остутствуют</p>
@endif
</div>
<div class="card-footer">
<a href="{{ route('students.create') }}" class="btn btn-success">Добавить</a>
</div>
@can('create', \App\Models\Student::class)
<div class="card-footer">
<a href="{{ route('students.create') }}" class="btn btn-success">Добавить</a>
</div>
@endcan
<div class="card-footer">{{ $students->links() }}</div>
</div>
</div>

View File

@ -0,0 +1,24 @@
@extends('layouts.application')
@section('content')
<div class="container col-md-6">
<div class="row justify-content-center">
<div class="card mt-4">
<div class="card-header"><strong>Средняя оценка - {{ $avgScore }}</strong></div>
</div>
@if(count($lessons))
@foreach($lessons as $lesson)
<a href="{{ route('grades.lessons.show', [$lesson->grade_id, $lesson]) }}" class="text-decoration-none">
<div class="card mt-4">
<div class="card-header"><strong>{{ $lesson->subject->name }} - {{ $lesson->pivot->score }}</strong></div>
<div class="card-header">{{ $lesson->type }}</div>
<div class="card-body">
{{ $lesson->name }}
</div>
</div>
</a>
@endforeach
@endif
</div>
</div>
@endsection

View File

@ -0,0 +1,19 @@
@extends('layouts.application')
@section('content')
<div class="container col-md-6">
<div class="row justify-content-center">
<div class="card mt-4">
<div class="card-header">{{__('Предметы')}}</div>
<div class="card-body">
@if(count($subjects))
<ul class="list-group list-group-flush text-center">
@foreach ($subjects as $subject)
<li class="list-group-item">{{ $subject->name }}</li>
@endforeach
</ul>
@endif
</div>
</div>
</div>
@endsection

View File

@ -18,7 +18,12 @@
</div>
</div>
<div class="card mt-4">
<div class="card-header">{{__('Предметы')}}</div>
<div class="card-header d-flex justify-content-between align-items-center">
{{__('Предметы')}}
@can('avgScores', \App\Models\Student::class)
<a href="{{ route('export-avg-scores') }}" class="btn btn-primary">Успеваемость</a>
@endcan
</div>
<div class="card-body">
@if (count($subjects))
<table class="table table-striped">
@ -31,21 +36,32 @@
<tr>
<td class="table-text">
<div>
<a href="{{ route('subjects.show', $subject) }}" class="btn btn-block col-8">{{ $subject->name }}</a>
{{ $subject->name }}
</div>
</td>
<td>
<div>
<a href="{{ route('subjects.edit', $subject) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
<td>
<form action="{{ route('subjects.destroy', $subject) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@can('scores', $subject)
<td>
<div>
<a href="{{ route('student-scores', $subject) }}" class="btn btn-success">Оценки</a>
</div>
</td>
@endcan
@can('update', $subject)
<td>
<div>
<a href="{{ route('subjects.edit', $subject) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
@endcan
@can('delete', $subject)
<td>
<form action="{{ route('subjects.destroy', $subject) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@endcan
</tr>
@endforeach
</tbody>
@ -55,8 +71,12 @@
@endif
</div>
<div class="card-footer">
<a href="{{ route('subjects.create') }}" class="btn btn-success">Добавить</a>
<a href="{{ route('export-pdf') }}" class="btn btn-info">PDF</a>
@can('create', \App\Models\Subject::class)
<a href="{{ route('subjects.create') }}" class="btn btn-success">Добавить</a>
@endcan
@can('pdf', \App\Models\Subject::class)
<a href="{{ route('export-pdf') }}" class="btn btn-info">PDF</a>
@endcan
</div>
<div class="card-footer">{{ $subjects->links() }}</div>
</div>

View File

@ -31,21 +31,36 @@
<tr>
<td class="table-text">
<div>
<a href="{{ route('teachers.show', $teacher) }}" class="btn btn-block col-8">{{ $teacher->last_name }} {{ $teacher->name }} {{ $teacher->middle_name }}</a>
@can('view', $teacher)
<a href="{{ route('teachers.show', $teacher) }}" class="btn btn-block">{{ $teacher->fio }}</a>
@else
{{ $teacher->fio }}
@endcan
</div>
</td>
<td>
<div>
<a href="{{ route('teachers.edit', $teacher) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
<td>
<form action="{{ route('teachers.destroy', $teacher) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@can('teacherSubjects', $teacher)
<td>
<div>
<a href="{{ route('teachers.subjects.index', $teacher) }}" class="btn btn-warning">Предметы</a>
</div>
</td>
@endcan
@can('update', $teacher)
<td>
<div>
<a href="{{ route('teachers.edit', $teacher) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
@endcan
@can('delete', $teacher)
<td>
<form action="{{ route('teachers.destroy', $teacher) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</td>
@endcan
</tr>
@endforeach
</tbody>
@ -54,9 +69,11 @@
<p>Учителя отсутствуют</p>
@endif
</div>
<div class="card-footer">
<a href="{{ route('teachers.create') }}" class="btn btn-success">Добавить</a>
</div>
@can('create', \App\Models\Teacher::class)
<div class="card-footer">
<a href="{{ route('teachers.create') }}" class="btn btn-success">Добавить</a>
</div>
@endcan
<div class="card-footer">{{ $teachers->links() }}</div>
</div>
</div>

View File

@ -2,6 +2,7 @@
use App\Http\Controllers\ProfileController;
use App\Http\Middleware\AdminAction;
use App\Http\Middleware\StudentAction;
use App\Http\Middleware\TeacherAction;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\GradeController;
@ -44,9 +45,18 @@ Route::middleware('auth')->group(function () {
Route::middleware([TeacherAction::class])->group(function () {
Route::get('lessons/{lesson}/scores', [ScoreController::class, 'show'])->name('lessons.scores.show');
Route::put('lessons/{lesson}/scores', [ScoreController::class, 'update'])->name('lessons.scores.update');
Route::get('grades/{grade}/subjects/{subject}/journal', [GradeSubjectController::class, 'journal'])->name('grades.subjects.journal');
Route::get('grades/{grade}/list-students', [GradeController::class, 'listStudents'])->name('list-students');
Route::get('grades/{grade}/subjects/{subject}/journal/export-excel', [GradeSubjectController::class, 'exportToExcel'])->name('export-excel');
});
Route::get('export-pdf', [SubjectController::class, 'exportToPDF'])->name('export-pdf');
Route::middleware([StudentAction::class])->group(function () {
Route::get('export-pdf', [SubjectController::class, 'exportToPDF'])->name('export-pdf');
Route::get('subjects/{subject}/student-scores', [StudentController::class, 'scores'])->name('student-scores');
Route::get('student-debts', [StudentController::class, 'debts'])->name('student-debts');
Route::get('teachers/{teacher}/subjects', [SubjectTeacherController::class, 'index'])->name('teachers.subjects.index');
Route::get('export-avg-scores', [StudentController::class, 'exportAvgScores'])->name('export-avg-scores');
});
});
require __DIR__.'/auth.php';