Clients CRUD
This commit is contained in:
parent
3f4dbbf5d4
commit
282ef63cf2
12
app/Enums/ProjectStatus.php
Normal file
12
app/Enums/ProjectStatus.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ProjectStatus: string
|
||||
{
|
||||
case OPEN = 'open';
|
||||
case IN_PROGRESS = 'in progress';
|
||||
case BLOCKED = 'blocked';
|
||||
case CANCELLED = 'cancelled';
|
||||
case COMPLETED = 'completed';
|
||||
}
|
||||
50
app/Http/Controllers/ClientController.php
Normal file
50
app/Http/Controllers/ClientController.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Client;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use App\Http\Requests\StoreClientRequest;
|
||||
use App\Http\Requests\UpdateClientRequest;
|
||||
|
||||
class ClientController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$clients = Client::paginate(20);
|
||||
|
||||
return view('clients.index', compact('clients'));
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
{
|
||||
return view('clients.create');
|
||||
}
|
||||
|
||||
public function store(StoreClientRequest $request): RedirectResponse
|
||||
{
|
||||
Client::create($request->validated());
|
||||
|
||||
return redirect()->route('clients.index');
|
||||
}
|
||||
|
||||
public function edit(Client $client): View
|
||||
{
|
||||
return view('clients.edit', compact('client'));
|
||||
}
|
||||
|
||||
public function update(UpdateClientRequest $request, Client $client): RedirectResponse
|
||||
{
|
||||
$client->update($request->validated());
|
||||
|
||||
return redirect()->route('clients.index');
|
||||
}
|
||||
|
||||
public function destroy(Client $client): RedirectResponse
|
||||
{
|
||||
$client->delete();
|
||||
|
||||
return redirect()->route('clients.index');
|
||||
}
|
||||
}
|
||||
58
app/Http/Controllers/ProjectController.php
Normal file
58
app/Http/Controllers/ProjectController.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Client;
|
||||
use App\Models\Project;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use App\Http\Requests\StoreProjectRequest;
|
||||
use App\Http\Requests\UpdateProjectRequest;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$projects = Project::with(['user', 'client'])->paginate(10);
|
||||
|
||||
return view('projects.index', compact('projects'));
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
{
|
||||
$users = User::select(['id', 'first_name', 'last_name'])->get();
|
||||
$clients = Client::select(['id', 'company_name'])->get();
|
||||
|
||||
return view('projects.create', compact('users', 'clients'));
|
||||
}
|
||||
|
||||
public function store(StoreProjectRequest $request): RedirectResponse
|
||||
{
|
||||
Project::create($request->validated());
|
||||
|
||||
return redirect()->route('projects.index');
|
||||
}
|
||||
|
||||
public function edit(Project $project): View
|
||||
{
|
||||
$users = User::select(['id', 'first_name', 'last_name'])->get();
|
||||
$clients = Client::select(['id', 'company_name'])->get();
|
||||
|
||||
return view('projects.edit', compact('project', 'users', 'clients'));
|
||||
}
|
||||
|
||||
public function update(UpdateProjectRequest $request, Project $project): RedirectResponse
|
||||
{
|
||||
$project->update($request->validated());
|
||||
|
||||
return redirect()->route('projects.index');
|
||||
}
|
||||
|
||||
public function destroy(Project $project): RedirectResponse
|
||||
{
|
||||
$project->delete();
|
||||
|
||||
return redirect()->route('projects.index');
|
||||
}
|
||||
}
|
||||
36
app/Http/Requests/StoreClientRequest.php
Normal file
36
app/Http/Requests/StoreClientRequest.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreClientRequest 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 [
|
||||
'contact_name' => ['required', 'string', 'max:255'],
|
||||
'contact_email' => ['required', 'string', 'email', 'max:255', Rule::unique('clients')],
|
||||
'contact_phone_number' => ['required'],
|
||||
'company_name' => ['required'],
|
||||
'company_address' => ['required'],
|
||||
'company_city' => ['required', 'string'],
|
||||
'company_zip' => ['required', 'integer'],
|
||||
'company_vat' => ['required', 'numeric'],
|
||||
];
|
||||
}
|
||||
}
|
||||
35
app/Http/Requests/StoreProjectRequest.php
Normal file
35
app/Http/Requests/StoreProjectRequest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Enums\ProjectStatus;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreProjectRequest 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 [
|
||||
'title' => ['required'],
|
||||
'description' => ['required'],
|
||||
'user_id' => ['required', Rule::exists('users', 'id')],
|
||||
'client_id' => ['required', Rule::exists('clients', 'id')],
|
||||
'deadline_at' => ['required', 'date'],
|
||||
'status' => ['required', Rule::enum(ProjectStatus::class)],
|
||||
];
|
||||
}
|
||||
}
|
||||
36
app/Http/Requests/UpdateClientRequest.php
Normal file
36
app/Http/Requests/UpdateClientRequest.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateClientRequest 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 [
|
||||
'contact_name' => ['required', 'string', 'max:255'],
|
||||
'contact_email' => ['required', 'string', 'email', 'max:255', Rule::unique('clients')->ignore($this->client)],
|
||||
'contact_phone_number' => ['required'],
|
||||
'company_name' => ['required'],
|
||||
'company_address' => ['required'],
|
||||
'company_city' => ['required', 'string'],
|
||||
'company_zip' => ['required', 'integer'],
|
||||
'company_vat' => ['required', 'numeric'],
|
||||
];
|
||||
}
|
||||
}
|
||||
35
app/Http/Requests/UpdateProjectRequest.php
Normal file
35
app/Http/Requests/UpdateProjectRequest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Enums\ProjectStatus;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateProjectRequest 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 [
|
||||
'title' => ['required'],
|
||||
'description' => ['required'],
|
||||
'user_id' => ['required', Rule::exists('users', 'id')],
|
||||
'client_id' => ['required', Rule::exists('clients', 'id')],
|
||||
'deadline_at' => ['required', 'date'],
|
||||
'status' => ['required', Rule::enum(ProjectStatus::class)],
|
||||
];
|
||||
}
|
||||
}
|
||||
23
app/Models/Client.php
Normal file
23
app/Models/Client.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Client extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'contact_name',
|
||||
'contact_email',
|
||||
'contact_phone_number',
|
||||
'company_name',
|
||||
'company_address',
|
||||
'company_city',
|
||||
'company_zip',
|
||||
'company_vat',
|
||||
];
|
||||
}
|
||||
40
app/Models/Project.php
Normal file
40
app/Models/Project.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ProjectStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Project extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'description',
|
||||
'user_id',
|
||||
'client_id',
|
||||
'deadline_at',
|
||||
'status',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function client(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'status' => ProjectStatus::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
30
database/factories/ClientFactory.php
Normal file
30
database/factories/ClientFactory.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Client>
|
||||
*/
|
||||
class ClientFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'contact_name' => fake()->name(),
|
||||
'contact_email' => fake()->unique()->safeEmail(),
|
||||
'contact_phone_number' => fake()->phoneNumber(),
|
||||
'company_name' => fake()->company(),
|
||||
'company_address' => fake()->address(),
|
||||
'company_city' => fake()->city(),
|
||||
'company_zip' => fake()->postcode(),
|
||||
'company_vat' => fake()->unique()->numerify(),
|
||||
];
|
||||
}
|
||||
}
|
||||
34
database/factories/ProjectFactory.php
Normal file
34
database/factories/ProjectFactory.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\ProjectStatus;
|
||||
use App\Models\Client;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Project>
|
||||
*/
|
||||
class ProjectFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$users = User::pluck('id');
|
||||
$clients = Client::pluck('id');
|
||||
|
||||
return [
|
||||
'title' => fake()->sentence(3),
|
||||
'description' => fake()->paragraph(),
|
||||
'deadline_at' => now()->addDays(rand(1, 30))->format('Y-m-d'),
|
||||
'status' => fake()->randomElement(ProjectStatus::cases())->value,
|
||||
'user_id' => $users->random(),
|
||||
'client_id' => $clients->random(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?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('clients', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('contact_name');
|
||||
$table->string('contact_email')->unique();
|
||||
$table->string('contact_phone_number');
|
||||
$table->string('company_name');
|
||||
$table->string('company_address');
|
||||
$table->string('company_city');
|
||||
$table->string('company_zip');
|
||||
$table->integer('company_vat');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('clients');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
<?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('projects', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title');
|
||||
$table->text('description');
|
||||
$table->foreignId('user_id')->constrained();
|
||||
$table->foreignId('client_id')->constrained();
|
||||
$table->date('deadline_at');
|
||||
$table->string('status');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('projects');
|
||||
}
|
||||
};
|
||||
18
database/seeders/ClientSeeder.php
Normal file
18
database/seeders/ClientSeeder.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Client;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ClientSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Client::factory(50)->create();
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,7 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use App\RoleEnum;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
@ -14,14 +12,11 @@ class DatabaseSeeder extends Seeder
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->call(RoleSeeder::class);
|
||||
|
||||
User::factory(10)->create();
|
||||
User::factory()->create([
|
||||
'first_name' => 'Admin',
|
||||
'last_name' => 'Admin',
|
||||
'email' => 'admin@admin.com',
|
||||
'password' => 'secret',
|
||||
])->syncRoles([RoleEnum::ADMIN]);
|
||||
$this->call([
|
||||
RoleSeeder::class,
|
||||
UserSeeder::class,
|
||||
ClientSeeder::class,
|
||||
ProjectSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
18
database/seeders/ProjectSeeder.php
Normal file
18
database/seeders/ProjectSeeder.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ProjectSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Project::factory(20)->create();
|
||||
}
|
||||
}
|
||||
25
database/seeders/UserSeeder.php
Normal file
25
database/seeders/UserSeeder.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use App\RoleEnum;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class UserSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
User::factory(10)->create();
|
||||
User::factory()->create([
|
||||
'first_name' => 'Admin',
|
||||
'last_name' => 'Admin',
|
||||
'email' => 'admin@admin.com',
|
||||
'password' => 'secret',
|
||||
])->syncRoles([RoleEnum::ADMIN]);
|
||||
}
|
||||
}
|
||||
100
resources/views/clients/create.blade.php
Normal file
100
resources/views/clients/create.blade.php
Normal file
@ -0,0 +1,100 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Create Client') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="overflow-hidden overflow-x-auto p-6 bg-white border-b border-gray-200">
|
||||
|
||||
<form method="POST" action="{{ route('clients.store') }}">
|
||||
@csrf
|
||||
|
||||
<div class="divide-y-2 space-y-4">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-4">Contact information</h3>
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<x-input-label for="contact_name" :value="__('Name')"/>
|
||||
<x-text-input id="contact_name" class="block mt-1 w-full" type="text"
|
||||
name="contact_name" :value="old('contact_name')" required/>
|
||||
<x-input-error :messages="$errors->get('contact_name')" class="mt-2"/>
|
||||
</div>
|
||||
|
||||
<!-- Email Address -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="contact_email" :value="__('Email')"/>
|
||||
<x-text-input id="contact_email" class="block mt-1 w-full" type="email"
|
||||
name="contact_email" :value="old('contact_email')" required/>
|
||||
<x-input-error :messages="$errors->get('contact_email')" class="mt-2"/>
|
||||
</div>
|
||||
|
||||
<!-- Phone Number -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="contact_phone_number" :value="__('Phone number')"/>
|
||||
<x-text-input id="contact_phone_number" class="block mt-1 w-full" type="text"
|
||||
name="contact_phone_number" :value="old('contact_phone_number')"
|
||||
required/>
|
||||
<x-input-error :messages="$errors->get('contact_phone_number')" class="mt-2"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold my-4 mt-4">Company information</h3>
|
||||
<!-- Company Name -->
|
||||
<div>
|
||||
<x-input-label for="company_name" :value="__('Company Name')"/>
|
||||
<x-text-input id="company_name" class="block mt-1 w-full" type="text"
|
||||
name="company_name" :value="old('company_name')" required/>
|
||||
<x-input-error :messages="$errors->get('company_name')" class="mt-2"/>
|
||||
</div>
|
||||
|
||||
<!-- Company VAT -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="company_vat" :value="__('Company VAT')"/>
|
||||
<x-text-input id="company_vat" class="block mt-1 w-full" type="text"
|
||||
name="company_vat" :value="old('company_vat')" required/>
|
||||
<x-input-error :messages="$errors->get('company_vat')" class="mt-2"/>
|
||||
</div>
|
||||
|
||||
<!-- Company Address -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="company_address" :value="__('Company address')"/>
|
||||
<x-text-input id="company_address" class="block mt-1 w-full" type="text"
|
||||
name="company_address" :value="old('company_address')" required/>
|
||||
<x-input-error :messages="$errors->get('company_address')" class="mt-2"/>
|
||||
</div>
|
||||
|
||||
<!-- Company City -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="company_city" :value="__('Company city')"/>
|
||||
<x-text-input id="company_city" class="block mt-1 w-full" type="text"
|
||||
name="company_city" :value="old('company_city')" required/>
|
||||
<x-input-error :messages="$errors->get('company_city')" class="mt-2"/>
|
||||
</div>
|
||||
|
||||
<!-- Company ZIP -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="company_zip" :value="__('Company zip')"/>
|
||||
<x-text-input id="company_zip" class="block mt-1 w-full" type="text"
|
||||
name="company_zip" :value="old('company_zip')" required/>
|
||||
<x-input-error :messages="$errors->get('company_zip')" class="mt-2"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-primary-button class="mt-4">
|
||||
{{ __('Save') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
92
resources/views/clients/edit.blade.php
Normal file
92
resources/views/clients/edit.blade.php
Normal file
@ -0,0 +1,92 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Edit Client') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="overflow-hidden overflow-x-auto p-6 bg-white border-b border-gray-200">
|
||||
|
||||
<form method="POST" action="{{ route('clients.update', $client) }}">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
|
||||
<div class="divide-y-2 space-y-4">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-4">Contact information</h3>
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<x-input-label for="contact_name" :value="__('Name')" />
|
||||
<x-text-input id="contact_name" class="block mt-1 w-full" type="text" name="contact_name" :value="old('contact_name', $client->contact_name)" required />
|
||||
<x-input-error :messages="$errors->get('contact_name')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Email Address -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="contact_email" :value="__('Email')" />
|
||||
<x-text-input id="contact_email" class="block mt-1 w-full" type="email" name="contact_email" :value="old('contact_email', $client->contact_email)" required />
|
||||
<x-input-error :messages="$errors->get('contact_email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Phone Number -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="contact_phone_number" :value="__('Phone number')" />
|
||||
<x-text-input id="contact_phone_number" class="block mt-1 w-full" type="text" name="contact_phone_number" :value="old('contact_phone_number', $client->contact_phone_number)" required />
|
||||
<x-input-error :messages="$errors->get('contact_phone_number')" class="mt-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold my-4 mt-4">Company information</h3>
|
||||
<!-- Company Name -->
|
||||
<div>
|
||||
<x-input-label for="company_name" :value="__('Company Name')" />
|
||||
<x-text-input id="company_name" class="block mt-1 w-full" type="text" name="company_name" :value="old('company_name', $client->company_name)" required />
|
||||
<x-input-error :messages="$errors->get('company_name')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Company VAT -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="company_vat" :value="__('Company VAT')" />
|
||||
<x-text-input id="company_vat" class="block mt-1 w-full" type="text" name="company_vat" :value="old('company_vat', $client->company_vat)" required />
|
||||
<x-input-error :messages="$errors->get('company_vat')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Company Address -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="company_address" :value="__('Company address')" />
|
||||
<x-text-input id="company_address" class="block mt-1 w-full" type="text" name="company_address" :value="old('company_address', $client->company_address)" required />
|
||||
<x-input-error :messages="$errors->get('company_address')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Company City -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="company_city" :value="__('Company city')" />
|
||||
<x-text-input id="company_city" class="block mt-1 w-full" type="text" name="company_city" :value="old('company_city', $client->company_city)" required />
|
||||
<x-input-error :messages="$errors->get('company_city')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Company ZIP -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="company_zip" :value="__('Company zip')" />
|
||||
<x-text-input id="company_zip" class="block mt-1 w-full" type="text" name="company_zip" :value="old('company_zip', $client->company_zip)" required />
|
||||
<x-input-error :messages="$errors->get('company_zip')" class="mt-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-primary-button class="mt-4">
|
||||
{{ __('Save') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
67
resources/views/clients/index.blade.php
Normal file
67
resources/views/clients/index.blade.php
Normal file
@ -0,0 +1,67 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Clients') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<a href="{{ route('clients.create') }}" class="underline">Add new client</a>
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200 border mt-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Company</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">VAT</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Address</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="bg-white divide-y divide-gray-200 divide-solid">
|
||||
@foreach($clients as $client)
|
||||
<tr class="bg-white">
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
{{ $client->company_name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
{{ $client->company_vat }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
{{ $client->company_address }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
<a href="{{ route('clients.edit', $client) }}" class="underline">Edit</a>
|
||||
|
|
||||
<form action="{{ route('clients.destroy', $client) }}"
|
||||
method="POST"
|
||||
onsubmit="return confirm('Are you sure?');"
|
||||
class="inline-block">
|
||||
@method('DELETE')
|
||||
@csrf
|
||||
<button type="submit" class="text-red-500 underline">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ $clients->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@ -20,6 +20,12 @@
|
||||
{{ __('Users') }}
|
||||
</x-nav-link>
|
||||
@endrole
|
||||
<x-nav-link :href="route('clients.index')" :active="request()->routeIs('clients.*')">
|
||||
{{ __('Clients') }}
|
||||
</x-nav-link>
|
||||
<x-nav-link :href="route('projects.index')" :active="request()->routeIs('projects.*')">
|
||||
{{ __('Projects') }}
|
||||
</x-nav-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -80,6 +86,12 @@
|
||||
{{ __('Users') }}
|
||||
</x-responsive-nav-link>
|
||||
@endrole
|
||||
<x-responsive-nav-link :href="route('clients.index')" :active="request()->routeIs('clients.*')">
|
||||
{{ __('Clients') }}
|
||||
</x-responsive-nav-link>
|
||||
<x-responsive-nav-link :href="route('projects.index')" :active="request()->routeIs('projects.*')">
|
||||
{{ __('Projects') }}
|
||||
</x-responsive-nav-link>
|
||||
</div>
|
||||
|
||||
<!-- Responsive Settings Options -->
|
||||
|
||||
81
resources/views/projects/create.blade.php
Normal file
81
resources/views/projects/create.blade.php
Normal file
@ -0,0 +1,81 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Create Project') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="overflow-hidden overflow-x-auto p-6 bg-white border-b border-gray-200">
|
||||
|
||||
<form method="POST" action="{{ route('projects.store') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<x-input-label for="title" :value="__('Title')" />
|
||||
<x-text-input id="title" class="block mt-1 w-full" type="text" name="title" :value="old('title')" required />
|
||||
<x-input-error :messages="$errors->get('title')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="description" :value="__('Description')" />
|
||||
<textarea id="description" class="block mt-1 w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" name="description" required>{{ old('description') }}</textarea>
|
||||
<x-input-error :messages="$errors->get('description')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Deadline At -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="deadline_at" :value="__('Deadline')" />
|
||||
<x-text-input id="deadline_at" class="block mt-1 w-full" type="date" name="deadline_at" min="{{ today()->format('Y-m-d') }}" :value="old('deadline_at')" required />
|
||||
<x-input-error :messages="$errors->get('deadline_at')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Assigned User -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="user_id" :value="__('Assigned user')" />
|
||||
<select class="block mt-1 w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" name="user_id" id="user_id">
|
||||
@foreach($users as $user)
|
||||
<option value="{{ $user->id }}"
|
||||
@selected(old('user_id') == $user->id)>{{ $user->first_name . ' ' . $user->last_name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<x-input-error :messages="$errors->get('user_id')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Assigned Client -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="client_id" :value="__('Client')" />
|
||||
<select class="block mt-1 w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" name="client_id" id="client_id">
|
||||
@foreach($clients as $client)
|
||||
<option value="{{ $client->id }}"
|
||||
@selected(old('client_id') == $client->id)>{{ $client->company_name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<x-input-error :messages="$errors->get('client_id')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="status" :value="__('Status')" />
|
||||
<select class="block mt-1 w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" name="status" id="status">
|
||||
@foreach(\App\Enums\ProjectStatus::cases() as $status)
|
||||
<option value="{{ $status->value }}" @selected(old('status') == $status->value)>{{ $status->value }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<x-input-error :messages="$errors->get('status')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<x-primary-button class="mt-4">
|
||||
{{ __('Save') }}
|
||||
</x-primary-button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
82
resources/views/projects/edit.blade.php
Normal file
82
resources/views/projects/edit.blade.php
Normal file
@ -0,0 +1,82 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Edit Project') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="overflow-hidden overflow-x-auto p-6 bg-white border-b border-gray-200">
|
||||
|
||||
<form method="POST" action="{{ route('projects.update', $project) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<x-input-label for="title" :value="__('Title')" />
|
||||
<x-text-input id="title" class="block mt-1 w-full" type="text" name="title" :value="old('title', $project->title)" required />
|
||||
<x-input-error :messages="$errors->get('title')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="description" :value="__('Description')" />
|
||||
<textarea id="description" class="block mt-1 w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" name="description" required>{{ old('description', $project->description) }}</textarea>
|
||||
<x-input-error :messages="$errors->get('description')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Deadline At -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="deadline_at" :value="__('Deadline')" />
|
||||
<x-text-input id="deadline_at" class="block mt-1 w-full" type="date" name="deadline_at" min="{{ today()->format('Y-m-d') }}" :value="old('deadline_at', $project->deadline_at)" required />
|
||||
<x-input-error :messages="$errors->get('deadline_at')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Assigned User -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="user_id" :value="__('Assigned user')" />
|
||||
<select class="block mt-1 w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" name="user_id" id="user_id">
|
||||
@foreach($users as $user)
|
||||
<option value="{{ $user->id }}"
|
||||
@selected(old('user_id', $project->user_id) == $user->id)>{{ $user->first_name . ' ' . $user->last_name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<x-input-error :messages="$errors->get('user_id')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Assigned Client -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="client_id" :value="__('Client')" />
|
||||
<select class="block mt-1 w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" name="client_id" id="client_id">
|
||||
@foreach($clients as $client)
|
||||
<option value="{{ $client->id }}"
|
||||
@selected(old('client_id', $project->client_id) == $client->id)>{{ $client->company_name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<x-input-error :messages="$errors->get('client_id')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="status" :value="__('Status')" />
|
||||
<select class="block mt-1 w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" name="status" id="status">
|
||||
@foreach(\App\Enums\ProjectStatus::cases() as $status)
|
||||
<option value="{{ $status->value }}" @selected(old('status', $project->status->value) == $status->value)>{{ $status->value }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<x-input-error :messages="$errors->get('status')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<x-primary-button class="mt-4">
|
||||
{{ __('Save') }}
|
||||
</x-primary-button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
79
resources/views/projects/index.blade.php
Normal file
79
resources/views/projects/index.blade.php
Normal file
@ -0,0 +1,79 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Projects') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
<a href="{{ route('projects.create') }}" class="underline">Add new project</a>
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200 border mt-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Title</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Assigned To</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Client</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Deadline</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Status</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left">
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="bg-white divide-y divide-gray-200 divide-solid">
|
||||
@foreach($projects as $project)
|
||||
<tr class="bg-white">
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
{{ $project->title }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
{{ $project->user->first_name }} {{ $project->user->last_name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
{{ $project->client->company_name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
{{ $project->deadline_at }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
{{ $project->status }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
|
||||
<a href="{{ route('projects.edit', $project) }}" class="underline">Edit</a>
|
||||
|
|
||||
<form action="{{ route('projects.destroy', $project) }}"
|
||||
method="POST"
|
||||
onsubmit="return confirm('Are you sure?');"
|
||||
class="inline-block">
|
||||
@method('DELETE')
|
||||
@csrf
|
||||
<button type="submit" class="text-red-500 underline">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ $projects->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\ClientController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\ProjectController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use App\RoleEnum;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -16,6 +18,8 @@
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::resource('users', UserController::class)
|
||||
->middleware(['role:' . RoleEnum::ADMIN->value]);
|
||||
Route::resource('clients', ClientController::class);
|
||||
Route::resource('projects', ProjectController::class);
|
||||
|
||||
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user