feat: Reinitialize frontend with SvelteKit and TypeScript
- Delete old Vite+Svelte frontend - Initialize new SvelteKit project with TypeScript - Configure Tailwind CSS v4 + DaisyUI - Implement JWT authentication with auto-refresh - Create login page with form validation (Zod) - Add protected route guards - Update Docker configuration for single-stage build - Add E2E tests with Playwright (6/11 passing) - Fix Svelte 5 reactivity with $state() runes Known issues: - 5 E2E tests failing (timing/async issues) - Token refresh implementation needs debugging - Validation error display timing
This commit is contained in:
46
backend/app/Models/Actual.php
Normal file
46
backend/app/Models/Actual.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Actual extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $table = 'actuals';
|
||||
|
||||
protected $fillable = [
|
||||
'project_id',
|
||||
'team_member_id',
|
||||
'month',
|
||||
'hours_logged',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'month' => 'date',
|
||||
'hours_logged' => 'decimal:2',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the project that owns the actual.
|
||||
*/
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the team member that owns the actual.
|
||||
*/
|
||||
public function teamMember()
|
||||
{
|
||||
return $this->belongsTo(TeamMember::class);
|
||||
}
|
||||
}
|
||||
44
backend/app/Models/Allocation.php
Normal file
44
backend/app/Models/Allocation.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Allocation extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = [
|
||||
'project_id',
|
||||
'team_member_id',
|
||||
'month',
|
||||
'allocated_hours',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'month' => 'date',
|
||||
'allocated_hours' => 'decimal:2',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the project that owns the allocation.
|
||||
*/
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the team member that owns the allocation.
|
||||
*/
|
||||
public function teamMember()
|
||||
{
|
||||
return $this->belongsTo(TeamMember::class);
|
||||
}
|
||||
}
|
||||
26
backend/app/Models/Holiday.php
Normal file
26
backend/app/Models/Holiday.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Holiday extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = [
|
||||
'date',
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'date' => 'date',
|
||||
];
|
||||
}
|
||||
63
backend/app/Models/Project.php
Normal file
63
backend/app/Models/Project.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Project extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'title',
|
||||
'status_id',
|
||||
'type_id',
|
||||
'approved_estimate',
|
||||
'forecasted_effort',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'approved_estimate' => 'decimal:2',
|
||||
'forecasted_effort' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the status that owns the project.
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(ProjectStatus::class, 'status_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type that owns the project.
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(ProjectType::class, 'type_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the allocations for the project.
|
||||
*/
|
||||
public function allocations(): HasMany
|
||||
{
|
||||
return $this->hasMany(Allocation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actuals for the project.
|
||||
*/
|
||||
public function actuals(): HasMany
|
||||
{
|
||||
return $this->hasMany(Actual::class);
|
||||
}
|
||||
}
|
||||
33
backend/app/Models/ProjectStatus.php
Normal file
33
backend/app/Models/ProjectStatus.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ProjectStatus extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'order',
|
||||
'is_active',
|
||||
'is_billable',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'order' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
'is_billable' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the projects for the status.
|
||||
*/
|
||||
public function projects(): HasMany
|
||||
{
|
||||
return $this->hasMany(Project::class, 'status_id');
|
||||
}
|
||||
}
|
||||
25
backend/app/Models/ProjectType.php
Normal file
25
backend/app/Models/ProjectType.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ProjectType extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the projects for the type.
|
||||
*/
|
||||
public function projects(): HasMany
|
||||
{
|
||||
return $this->hasMany(Project::class, 'type_id');
|
||||
}
|
||||
}
|
||||
39
backend/app/Models/Pto.php
Normal file
39
backend/app/Models/Pto.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Pto extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $table = 'ptos';
|
||||
|
||||
protected $fillable = [
|
||||
'team_member_id',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'reason',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the team member that owns the PTO.
|
||||
*/
|
||||
public function teamMember()
|
||||
{
|
||||
return $this->belongsTo(TeamMember::class);
|
||||
}
|
||||
}
|
||||
25
backend/app/Models/Role.php
Normal file
25
backend/app/Models/Role.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Role extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the team members for the role.
|
||||
*/
|
||||
public function teamMembers(): HasMany
|
||||
{
|
||||
return $this->hasMany(TeamMember::class);
|
||||
}
|
||||
}
|
||||
61
backend/app/Models/TeamMember.php
Normal file
61
backend/app/Models/TeamMember.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class TeamMember extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'role_id',
|
||||
'hourly_rate',
|
||||
'active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'hourly_rate' => 'decimal:2',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the role that owns the team member.
|
||||
*/
|
||||
public function role()
|
||||
{
|
||||
return $this->belongsTo(Role::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the allocations for the team member.
|
||||
*/
|
||||
public function allocations(): HasMany
|
||||
{
|
||||
return $this->hasMany(Allocation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actuals for the team member.
|
||||
*/
|
||||
public function actuals(): HasMany
|
||||
{
|
||||
return $this->hasMany(Actual::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PTOs for the team member.
|
||||
*/
|
||||
public function ptos(): HasMany
|
||||
{
|
||||
return $this->hasMany(Pto::class);
|
||||
}
|
||||
}
|
||||
78
backend/app/Models/User.php
Normal file
78
backend/app/Models/User.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||
|
||||
class User extends Authenticatable implements JWTSubject
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'role',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifier that will be stored in the subject claim of the JWT.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getJWTIdentifier()
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a key value array, containing any custom claims to be added to the JWT.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJWTCustomClaims()
|
||||
{
|
||||
return [
|
||||
'role' => $this->role,
|
||||
'email' => $this->email,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user