Merge pull request 'task-1 (Grades)' (#1) from feature/task-1 into develop

Reviewed-on: #1
This commit is contained in:
klllst 2024-04-16 13:53:33 +04:00
commit ad825f534f
21 changed files with 1607 additions and 3 deletions

View File

@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\GradePostRequest;
use App\Models\Grade;
use Illuminate\Http\Request;
class GradeController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('grades.index', [
'grades' => Grade::filter()->paginate(10)->withQueryString(),
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('grades.create');
}
/**
* Store a newly created resource in storage.
*/
public function store(GradePostRequest $request)
{
$grade = Grade::create($request->validated());
return redirect()->route('grades.show', $grade);
}
/**
* Display the specified resource.
*/
public function show(Grade $grade)
{
return view('grades.show', [
'grade' => $grade,
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Grade $grade)
{
return view('grades.edit', [
'grade' => $grade,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(GradePostRequest $request, Grade $grade)
{
$grade->update($request->validated());
return redirect()->route('grades.show', $grade);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Grade $grade)
{
$grade->delete();
return redirect()->route('grades.index');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class GradePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required|unique:grades|max:255',
];
}
}

25
app/Models/Grade.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Grade extends Model
{
use HasFactory;
protected $fillable = [
'name',
];
public function scopeFilter(Builder $query)
{
$name = request('name');
$query->when($name, function (Builder $query, $name) {
$query->whereRaw('name ilike ?', ["$name%"]);
});
}
}

View File

@ -7,7 +7,8 @@
"require": {
"php": "^8.2",
"laravel/framework": "^11.0",
"laravel/tinker": "^2.9"
"laravel/tinker": "^2.9",
"laravel/ui": "^4.5"
},
"require-dev": {
"fakerphp/faker": "^1.23",

65
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": "da9b7a1dc1dd923105198c572cc7df8d",
"content-hash": "f67fa4f47f4251e2e6b9b94e684924ab",
"packages": [
{
"name": "brick/math",
@ -1433,6 +1433,69 @@
},
"time": "2024-01-04T16:10:04+00:00"
},
{
"name": "laravel/ui",
"version": "v4.5.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/ui.git",
"reference": "a3562953123946996a503159199d6742d5534e61"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/ui/zipball/a3562953123946996a503159199d6742d5534e61",
"reference": "a3562953123946996a503159199d6742d5534e61",
"shasum": ""
},
"require": {
"illuminate/console": "^9.21|^10.0|^11.0",
"illuminate/filesystem": "^9.21|^10.0|^11.0",
"illuminate/support": "^9.21|^10.0|^11.0",
"illuminate/validation": "^9.21|^10.0|^11.0",
"php": "^8.0",
"symfony/console": "^6.0|^7.0"
},
"require-dev": {
"orchestra/testbench": "^7.35|^8.15|^9.0",
"phpunit/phpunit": "^9.3|^10.4|^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.x-dev"
},
"laravel": {
"providers": [
"Laravel\\Ui\\UiServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Ui\\": "src/",
"Illuminate\\Foundation\\Auth\\": "auth-backend/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel UI utilities and presets.",
"keywords": [
"laravel",
"ui"
],
"support": {
"source": "https://github.com/laravel/ui/tree/v4.5.1"
},
"time": "2024-03-21T18:12:29+00:00"
},
{
"name": "league/commonmark",
"version": "2.4.2",

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('grades', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('grades');
}
};

1154
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,11 @@
"build": "vite build"
},
"devDependencies": {
"@popperjs/core": "^2.11.6",
"axios": "^1.6.4",
"bootstrap": "^5.2.3",
"laravel-vite-plugin": "^1.0",
"sass": "^1.56.1",
"vite": "^5.0"
}
}

View File

@ -1,4 +1,34 @@
import 'bootstrap';
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// import Pusher from 'pusher-js';
// window.Pusher = Pusher;
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
// });

View File

@ -0,0 +1,7 @@
// Body
$body-bg: #f8fafc;
// Typography
$font-family-sans-serif: 'Nunito', sans-serif;
$font-size-base: 0.9rem;
$line-height-base: 1.6;

8
resources/sass/app.scss Normal file
View File

@ -0,0 +1,8 @@
// Fonts
@import url('https://fonts.bunny.net/css?family=Nunito');
// Variables
@import 'variables';
// Bootstrap
@import 'bootstrap/scss/bootstrap';

View File

@ -0,0 +1,5 @@
@extends('layouts.app')
@section('content')
@include('grades.form', ['route' => route('grades.store'), 'method' => 'POST'])
@endsection

View File

@ -0,0 +1,5 @@
@extends('layouts.app')
@section('content')
@include('grades.form', ['route' => route('grades.update', $grade), 'method' => 'PUT'])
@endsection

View File

@ -0,0 +1,19 @@
<div class="container col-md-5">
<div class="row justify-content-center">
<div class="card mt-4">
<div class="card-body">
<form action="{{ $route }}" method="POST">
@csrf
@method($method)
<div class="mb-3">
<label for="name" class="form-label">Название:</label>
<input type="text" id="name" name="name" class="form-control" value="{{ isset($grade) ? $grade->name : '' }}" required>
</div>
<div>
<button type="submit" class="btn btn-success">Подтвердить</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,74 @@
@extends('layouts.app')
@section('content')
<div class="container col-md-7">
<div class="row justify-content-center">
<div class="card">
<div class="card-header">{{__('Новый класс')}}</div>
<div class="card-body">
<a href="{{ route('grades.create') }}" class="btn btn-success">Добавить</a>
</div>
</div>
<div class="card mt-4">
<div class="card-header">{{__('Поиск класса')}}</div>
<div class="card-body">
<form action="{{ route('grades.index') }}" method="GET">
<div class="mb-3">
<label for="name" class="form-label">Название:</label>
<input type="text" id="name" name="name" class="form-control"
value="{{ request('name') ?? '' }}">
</div>
<div>
<button type="submit" class="btn btn-primary">Фильтр</button>
</div>
</form>
</div>
</div>
@if (count($grades))
<div class="card mt-4">
<div class="card-header">{{__('Классы')}}</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<th>Название</th>
<th>&nbsp;</th>
</thead>
<tbody>
@foreach ($grades as $grade)
<tr>
<td class="table-text">
<div>
<a href="{{ route('grades.show', $grade) }}"
class="btn btn-block col-8">{{ $grade->name }}</a>
</div>
</td>
<td>
<div>
<a href="{{ route('grades.edit', $grade) }}" class="btn btn-warning">Редактировать</a>
</div>
</td>
{{-- <td>--}}
{{-- <div>--}}
{{-- <a href="{{ route('journals.index', $grade) }}" class="btn btn-primary">Журнал</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>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="card-footer">{{ $grades->links() }}</div>
</div>
@endif
</div>
</div>
@endsection

View File

@ -0,0 +1,20 @@
@extends('layouts.app')
@section('content')
<div class="container col-md-5">
<div class="row justify-content-center">
<div class="card mt-4">
<div class="card-header" {{ __('Класс') }}}>
<div class="card-body">
<div class="mb-3">
<h4><strong>Название:</strong> {{ $grade->name }}</h4>
</div>
<div>
<a href="{{ route('grades.edit', $grade) }}" class="btn btn-warning">Редактировать</a>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,10 @@
<div class="container">
<footer class="footer py-3 my-4">
<ul class="nav justify-content-center pb-3 mb-3">
<li class="nav-item"><a href="{{ route('grades.index') }}" class="nav-link px-2 text-muted">Группы</a></li>
{{-- <li class="nav-item"><a href="{{ route('subjects.index') }}" class="nav-link px-2 text-muted">Предметы</a></li>--}}
{{-- <li class="nav-item"><a href="{{ route('users.index') }}" class="nav-link px-2 text-muted">Студенты</a></li>--}}
</ul>
<p class="text-center text-muted">&copy; 2024 EduDiary</p>
</footer>
</div>

View File

@ -0,0 +1,9 @@
<div class="container">
<header class="d-flex justify-content-center py-3">
<ul class="nav nav-pills">
<li class="nav-item"><a href="{{ route('grades.index') }}" class="nav-link @if(request()->is('grades*')) active @endif" aria-current="page">Группы</a></li>
{{-- <li class="nav-item"><a href="{{ route('subjects.index') }}" class="nav-link @if(request()->is('subjects*')) active @endif">Предметы</a></li>--}}
{{-- <li class="nav-item"><a href="{{ route('users.index') }}" class="nav-link @if(request()->is('users*')) active @endif">Студенты</a></li>--}}
</ul>
</header>
</div>

View File

@ -0,0 +1,31 @@
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Internship') }}</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<!-- Scripts -->
@vite(['resources/sass/app.scss', 'resources/js/app.js'])
</head>
<body>
@include('includes.header')
<div>
@yield('content')
</div>
@include('includes.footer')
</body>
</html>

View File

@ -1,7 +1,10 @@
<?php
use App\Http\Controllers\GradeController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::resource('grades', GradeController::class);

View File

@ -4,7 +4,10 @@ import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
input: [
'resources/sass/app.scss',
'resources/js/app.js',
],
refresh: true,
}),
],