From d452b40fef7989bcba5c25899a3b7f8bc6aab3c5 Mon Sep 17 00:00:00 2001 From: Denis40-prog Date: Sat, 16 Aug 2025 16:14:48 +0200 Subject: [PATCH 1/3] =?UTF-8?q?[UPD]=20modifications=20des=20diff=C3=A9ren?= =?UTF-8?q?ts=20type=20de=20roles=20+=20[ADD]=20page=20de=20gestion=20des?= =?UTF-8?q?=20utilisateurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Livewire/Admin/UserManagement.php | 146 ++++++++++ app/Livewire/Dashboard.php | 11 +- app/Livewire/ProjectComponent.php | 10 +- app/Livewire/TaskComponent.php | 5 +- app/Livewire/TeamComponent.php | 9 +- app/Livewire/Wellness/TeamDashboard.php | 16 +- app/Livewire/Wellness/TeamsList.php | 31 +- app/Models/Comment.php | 40 +++ app/Models/Team.php | 35 ++- app/Models/TeamUser.php | 63 ++++ app/Models/User.php | 72 ++++- app/Providers/AuthServiceProvider.php | 32 +- app/Traits/HasTeamPermissions.php | 104 +++++++ database/factories/CommentFactory.php | 29 +- database/factories/UserFactory.php | 2 +- .../0001_01_01_000000_create_users_table.php | 2 +- ...025_05_06_154419_create_comments_table.php | 2 +- ...25_05_06_155943_create_team_user_table.php | 2 +- ...25_08_16_123748_update_team_user_roles.php | 41 +++ database/seeders/DatabaseSeeder.php | 5 - database/seeders/TeamUserSeeder.php | 35 ++- database/seeders/UserSeeder.php | 11 + .../components/layouts/app/sidebar.blade.php | 8 + .../livewire/admin/user-management.blade.php | 273 ++++++++++++++++++ routes/web.php | 5 + 25 files changed, 935 insertions(+), 54 deletions(-) create mode 100644 app/Livewire/Admin/UserManagement.php create mode 100644 app/Traits/HasTeamPermissions.php create mode 100644 database/migrations/2025_08_16_123748_update_team_user_roles.php create mode 100644 resources/views/livewire/admin/user-management.blade.php diff --git a/app/Livewire/Admin/UserManagement.php b/app/Livewire/Admin/UserManagement.php new file mode 100644 index 0000000..ec62e0a --- /dev/null +++ b/app/Livewire/Admin/UserManagement.php @@ -0,0 +1,146 @@ + ['except' => ''], + 'emailFilter' => ['except' => ''], + 'teamFilter' => ['except' => ''], + 'roleFilter' => ['except' => ''], + 'sortBy' => ['except' => 'name'], + 'sortDirection' => ['except' => 'asc'], + ]; + + public function mount() + { + // Vérifier que l'utilisateur est admin du site + if (Auth::user()->role !== User::ROLE_ADMIN) { + abort(403, 'Accès refusé. Réservé aux administrateurs du site.'); + } + } + + public function updatingSearch() + { + $this->resetPage(); + } + + public function updatingEmailFilter() + { + $this->resetPage(); + } + + public function updatingTeamFilter() + { + $this->resetPage(); + } + + public function updatingRoleFilter() + { + $this->resetPage(); + } + + public function sortBy($field) + { + if ($this->sortBy === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortBy = $field; + $this->sortDirection = 'asc'; + } + $this->resetPage(); + } + + public function clearFilters() + { + $this->reset(['search', 'emailFilter', 'teamFilter', 'roleFilter']); + $this->resetPage(); + } + + public function confirmPromoteUser($userId) + { + $this->userToPromote = User::find($userId); + $this->showPromoteModal = true; + } + + public function promoteToAdmin() + { + if ($this->userToPromote && $this->userToPromote->role !== User::ROLE_ADMIN) { + $this->userToPromote->update(['role' => User::ROLE_ADMIN]); + + $this->dispatch('flash', [ + 'type' => 'success', + 'text' => "L'utilisateur {$this->userToPromote->name} a été promu administrateur du site." + ]); + } + + $this->reset(['showPromoteModal', 'userToPromote']); + } + + public function demoteFromAdmin($userId) + { + $user = User::find($userId); + if ($user && $user->role === User::ROLE_ADMIN && $user->id !== Auth::id()) { + $user->update(['role' => User::ROLE_USER]); + + $this->dispatch('flash', [ + 'type' => 'success', + 'text' => "L'utilisateur {$user->name} n'est plus administrateur du site." + ]); + } + } + + public function render() + { + $users = User::query() + ->when($this->search, function ($query) { + $query->where('name', 'like', "%{$this->search}%"); + }) + ->when($this->emailFilter, function ($query) { + $query->where('email', 'like', "%{$this->emailFilter}%"); + }) + ->when($this->roleFilter, function ($query) { + $query->where('role', $this->roleFilter); + }) + ->when($this->teamFilter, function ($query) { + $query->whereHas('teams', function ($q) { + $q->where('teams.id', $this->teamFilter); + }); + }) + ->with(['teams' => function ($query) { + $query->select('teams.id', 'teams.name'); + }]) + ->orderBy($this->sortBy, $this->sortDirection) + ->paginate(20); + + $teams = Team::select('id', 'name')->orderBy('name')->get(); + + return view('livewire.admin.user-management', [ + 'users' => $users, + 'teams' => $teams, + ]); + } +} diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php index 4cdc771..1bdbbd1 100644 --- a/app/Livewire/Dashboard.php +++ b/app/Livewire/Dashboard.php @@ -4,6 +4,7 @@ use App\Models\Team; use App\Models\TeamUser; +use App\Models\User; use Livewire\Component; use Illuminate\Support\Facades\Auth; @@ -46,7 +47,15 @@ public function createTeam() public function render() { - $teams = Auth::user()->teams()->get(); + $user = Auth::user(); + + // Si c'est l'admin du site, il voit toutes les équipes + if ($user->role === User::ROLE_ADMIN) { + $teams = Team::all(); + } else { + // Sinon, seulement ses équipes + $teams = $user->teams()->get(); + } return view('livewire.dashboard', [ 'teams' => $teams diff --git a/app/Livewire/ProjectComponent.php b/app/Livewire/ProjectComponent.php index 9154658..5a7fada 100644 --- a/app/Livewire/ProjectComponent.php +++ b/app/Livewire/ProjectComponent.php @@ -29,7 +29,9 @@ public function mount($teamId) $query->withPivot('role'); }])->findOrFail($teamId); - if (! $this->team->users->pluck('id')->contains(Auth::id())) { + // Vérifier l'accès : admin du site OU membre de l'équipe + $user = Auth::user(); + if ($user->role !== 'admin' && !$this->team->users->pluck('id')->contains(Auth::id())) { abort(403, 'Vous n\'êtes pas membre de cette équipe.'); } @@ -83,6 +85,12 @@ private function checkIfTeamAdmin() { $currentUser = Auth::user(); + // Admin du site a tous les droits + if ($currentUser->role === 'admin') { + $this->isTeamAdmin = true; + return; + } + if ($this->team->owner_id == $currentUser->id) { $this->isTeamAdmin = true; return; diff --git a/app/Livewire/TaskComponent.php b/app/Livewire/TaskComponent.php index dcf8e96..f09bd4f 100644 --- a/app/Livewire/TaskComponent.php +++ b/app/Livewire/TaskComponent.php @@ -43,8 +43,9 @@ public function mount($projectId) $this->projectId = $projectId; $this->project = Project::with('team')->findOrFail($projectId); - // Vérifier que l'utilisateur fait partie de l'équipe - if (!$this->project->team->users->contains(Auth::id())) { + // Vérifier l'accès : admin du site OU membre de l'équipe + $user = Auth::user(); + if ($user->role !== 'admin' && !$this->project->team->users->contains(Auth::id())) { abort(403, 'Vous n\'avez pas accès à ce projet.'); } } diff --git a/app/Livewire/TeamComponent.php b/app/Livewire/TeamComponent.php index 967ae98..d3b1b59 100644 --- a/app/Livewire/TeamComponent.php +++ b/app/Livewire/TeamComponent.php @@ -45,9 +45,14 @@ public function sortBy($field) public function render() { + $user = Auth::user(); + $teams = Team::query() - ->whereHas('users', function ($query) { - $query->where('users.id', Auth::id()); + ->when($user->role !== 'admin', function ($query) { + // Si pas admin du site, filtrer par appartenance à l'équipe + $query->whereHas('users', function ($q) { + $q->where('users.id', Auth::id()); + }); }) ->when($this->search, function ($query) { $search = "%{$this->search}%"; diff --git a/app/Livewire/Wellness/TeamDashboard.php b/app/Livewire/Wellness/TeamDashboard.php index 4d394fb..0d20e2e 100644 --- a/app/Livewire/Wellness/TeamDashboard.php +++ b/app/Livewire/Wellness/TeamDashboard.php @@ -19,16 +19,12 @@ public function mount(Team $team): void { $this->team = $team; - // membres visibles = membres de l'équipe + // Récupérer tous les membres réels de l'équipe (ceux dans team_user) $this->members = $team->users() - ->select( - 'users.id as id', - 'users.name', - 'users.email' - ) - ->orderBy('users.name') - ->get() - ->toArray(); + ->select('users.id as id', 'users.name', 'users.email') + ->orderBy('users.name') + ->get() + ->toArray(); // plage par défaut : 30 derniers jours $from = Carbon::now()->subDays(30)->startOfDay(); @@ -56,6 +52,6 @@ public function mount(Team $team): void public function render() { - return view('livewire.wellness.team-dashboard')->title("Suivi météo — {$this->team->name}"); + return view('livewire.wellness.team-dashboard'); } } diff --git a/app/Livewire/Wellness/TeamsList.php b/app/Livewire/Wellness/TeamsList.php index 64fd18f..b69dc05 100644 --- a/app/Livewire/Wellness/TeamsList.php +++ b/app/Livewire/Wellness/TeamsList.php @@ -4,6 +4,9 @@ use Illuminate\Support\Facades\Auth; use Livewire\Component; +use App\Models\User; +use App\Models\Team; +use App\Models\TeamUser; class TeamsList extends Component { @@ -11,16 +14,30 @@ class TeamsList extends Component public function mount(): void { - $this->teams = Auth::user() - ->teams() - ->select('teams.id', 'teams.name', 'teams.created_at') - ->orderBy('teams.name') - ->get() - ->toArray(); + $user = Auth::user(); + + // Si l'utilisateur est admin du site, il peut voir toutes les équipes + if ($user->role === User::ROLE_ADMIN) { + $this->teams = Team::select('id', 'name', 'created_at') + ->orderBy('name') + ->get() + ->toArray(); + } else { + // Sinon, seulement les équipes où il a accès au wellness (admin ou RH) + $teamIds = TeamUser::where('user_id', $user->id) + ->whereIn('role', [TeamUser::TEAM_ROLE_ADMIN, TeamUser::TEAM_ROLE_RH]) + ->pluck('team_id'); + + $this->teams = Team::whereIn('id', $teamIds) + ->select('id', 'name', 'created_at') + ->orderBy('name') + ->get() + ->toArray(); + } } public function render() { - return view('livewire.wellness.teams-list')->title('Suivi météo — Mes équipes'); + return view('livewire.wellness.teams-list'); } } diff --git a/app/Models/Comment.php b/app/Models/Comment.php index 170e783..0b62cd9 100644 --- a/app/Models/Comment.php +++ b/app/Models/Comment.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Validation\ValidationException; class Comment extends Model { @@ -16,6 +17,45 @@ class Comment extends Model 'user_id', ]; + /** + * Boot the model + */ + protected static function boot() + { + parent::boot(); + + // S'assurer qu'un commentaire a soit task_id soit project_id mais pas les deux + static::creating(function ($comment) { + if (empty($comment->task_id) && empty($comment->project_id)) { + throw ValidationException::withMessages([ + 'comment' => 'Un commentaire doit être associé soit à une tâche soit à un projet.' + ]); + } + + if (!empty($comment->task_id) && !empty($comment->project_id)) { + throw ValidationException::withMessages([ + 'comment' => 'Un commentaire ne peut pas être associé à la fois à une tâche et à un projet.' + ]); + } + }); + } + + /** + * Check if this comment belongs to a task + */ + public function isTaskComment(): bool + { + return !empty($this->task_id); + } + + /** + * Check if this comment belongs to a project + */ + public function isProjectComment(): bool + { + return !empty($this->project_id); + } + public function task() { return $this->belongsTo(Task::class); diff --git a/app/Models/Team.php b/app/Models/Team.php index a6e4cd2..04feb05 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -18,7 +18,40 @@ public function owner() public function users() { - return $this->belongsToMany(User::class)->withTimestamps(); + return $this->belongsToMany(User::class)->withPivot('role')->withTimestamps(); + } + + /** + * Get team admins + */ + public function admins() + { + return $this->belongsToMany(User::class) + ->withPivot('role') + ->withTimestamps() + ->wherePivot('role', TeamUser::TEAM_ROLE_ADMIN); + } + + /** + * Get team RH users + */ + public function rhUsers() + { + return $this->belongsToMany(User::class) + ->withPivot('role') + ->withTimestamps() + ->wherePivot('role', TeamUser::TEAM_ROLE_RH); + } + + /** + * Get users who can access wellness surveys (admin or RH) + */ + public function wellnessAccessUsers() + { + return $this->belongsToMany(User::class) + ->withPivot('role') + ->withTimestamps() + ->whereIn('team_user.role', [TeamUser::TEAM_ROLE_ADMIN, TeamUser::TEAM_ROLE_RH]); } public function projects() diff --git a/app/Models/TeamUser.php b/app/Models/TeamUser.php index fac1eba..c20001c 100644 --- a/app/Models/TeamUser.php +++ b/app/Models/TeamUser.php @@ -9,7 +9,70 @@ class TeamUser extends Model { use HasFactory; + /** + * The available team roles. + */ + public const TEAM_ROLE_USER = 'user'; + public const TEAM_ROLE_ADMIN = 'admin'; + public const TEAM_ROLE_RH = 'rh'; + protected $table = 'team_user'; protected $fillable = ['user_id', 'team_id', 'role']; + + /** + * Check if the team user is a team admin. + */ + public function isTeamAdmin(): bool + { + return $this->role === self::TEAM_ROLE_ADMIN; + } + + /** + * Check if the team user is RH. + */ + public function isTeamRH(): bool + { + return $this->role === self::TEAM_ROLE_RH; + } + + /** + * Check if the team user is a regular user. + */ + public function isTeamUser(): bool + { + return $this->role === self::TEAM_ROLE_USER; + } + + /** + * Check if the team user can access wellness survey (admin or RH). + */ + public function canAccessWellnessSurvey(): bool + { + return in_array($this->role, [self::TEAM_ROLE_ADMIN, self::TEAM_ROLE_RH]); + } + + /** + * Check if the team user can manage team members (only admin). + */ + public function canManageTeamMembers(): bool + { + return $this->role === self::TEAM_ROLE_ADMIN; + } + + /** + * Get the user relationship + */ + public function user() + { + return $this->belongsTo(User::class); + } + + /** + * Get the team relationship + */ + public function team() + { + return $this->belongsTo(Team::class); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 769dcc7..dddf575 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,17 +7,17 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Str; +use App\Traits\HasTeamPermissions; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable; + use HasFactory, Notifiable, HasTeamPermissions; /** * The available user roles. */ public const ROLE_ADMIN = 'admin'; public const ROLE_USER = 'user'; - public const ROLE_MANAGER = 'manager'; /** * Check if the user is an admin. @@ -35,13 +35,6 @@ public function isUser(): bool return $this->role === self::ROLE_USER; } - /** - * Check if the user is a manager user. - */ - public function isManager(): bool { - return $this->role === self::ROLE_MANAGER; - } - /** * The attributes that are mass assignable. * @@ -79,7 +72,66 @@ protected function casts(): array public function teams() { - return $this->belongsToMany(Team::class)->withTimestamps(); + return $this->belongsToMany(Team::class)->withPivot('role')->withTimestamps(); + } + + /** + * Get teams where user is admin + */ + public function adminTeams() + { + return $this->belongsToMany(Team::class) + ->withPivot('role') + ->withTimestamps() + ->wherePivot('role', TeamUser::TEAM_ROLE_ADMIN); + } + + /** + * Get teams where user is RH + */ + public function rhTeams() + { + return $this->belongsToMany(Team::class) + ->withPivot('role') + ->withTimestamps() + ->wherePivot('role', TeamUser::TEAM_ROLE_RH); + } + + /** + * Get teams where user can access wellness surveys (admin or RH) + */ + public function wellnessAccessTeams() + { + return $this->belongsToMany(Team::class) + ->withPivot('role') + ->withTimestamps() + ->whereIn('team_user.role', [TeamUser::TEAM_ROLE_ADMIN, TeamUser::TEAM_ROLE_RH]); + } + + /** + * Check if user can access wellness survey for a specific team + */ + public function canAccessWellnessForTeam(Team $team): bool + { + if ($this->isAdmin()) { + return true; // Site admin has access to everything + } + + $teamUser = $this->teams()->where('teams.id', $team->id)->first(); + return $teamUser && in_array($teamUser->pivot->role, [TeamUser::TEAM_ROLE_ADMIN, TeamUser::TEAM_ROLE_RH]); + } + + /** + * Check if user can manage members of a specific team + */ + public function canManageTeamMembers(Team $team): bool + { + if ($this->isAdmin()) { + return true; // Site admin can manage all teams + } + + $teamUser = $this->teams()->where('teams.id', $team->id)->first(); + return $teamUser && $teamUser->pivot->role === TeamUser::TEAM_ROLE_ADMIN; } public function ownedTeams() diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 3964388..4bb01cf 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use App\Models\Team; +use App\Models\TeamUser; use App\Models\User; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Gate; @@ -11,11 +12,36 @@ class AuthServiceProvider extends ServiceProvider { public function boot(): void { - Gate::define('viewWellness', fn (User $user) => in_array($user->role, ['admin', 'manager'])); + // Permission pour voir la liste des équipes avec accès wellness + Gate::define('viewWellness', function (User $user) { + // Admin du site peut tout voir + if ($user->role === User::ROLE_ADMIN) { + return true; + } + // Utilisateur normal : doit avoir au moins une équipe avec accès wellness + return TeamUser::where('user_id', $user->id) + ->whereIn('role', [TeamUser::TEAM_ROLE_ADMIN, TeamUser::TEAM_ROLE_RH]) + ->exists(); + }); + + // Permission pour voir le wellness d'une équipe spécifique Gate::define('viewWellnessForTeam', function (User $user, Team $team) { - if (!in_array($user->role, ['admin', 'manager'])) return false; - return $user->teams()->whereKey($team->id)->exists(); + // Admin du site peut tout voir + if ($user->role === User::ROLE_ADMIN) { + return true; + } + + // Utilisateur normal : doit être admin ou RH de cette équipe + return TeamUser::where('user_id', $user->id) + ->where('team_id', $team->id) + ->whereIn('role', [TeamUser::TEAM_ROLE_ADMIN, TeamUser::TEAM_ROLE_RH]) + ->exists(); + }); + + // Permission pour gérer les utilisateurs (réservé aux admins du site) + Gate::define('manageUsers', function (User $user) { + return $user->role === User::ROLE_ADMIN; }); } } diff --git a/app/Traits/HasTeamPermissions.php b/app/Traits/HasTeamPermissions.php new file mode 100644 index 0000000..b506bc2 --- /dev/null +++ b/app/Traits/HasTeamPermissions.php @@ -0,0 +1,104 @@ +teams()->where('teams.id', $team->id); + + if ($role) { + $query->wherePivot('role', $role); + } + + return $query->exists(); + } + + /** + * Get user's role in a specific team + */ + public function getRoleInTeam(Team $team): ?string + { + $teamUser = $this->teams()->where('teams.id', $team->id)->first(); + return $teamUser ? $teamUser->pivot->role : null; + } + + /** + * Check if user is team admin for a specific team + */ + public function isTeamAdminFor(Team $team): bool + { + if ($this->isAdmin()) { + return true; // Site admin has all permissions + } + + return $this->hasRoleInTeam($team, TeamUser::TEAM_ROLE_ADMIN); + } + + /** + * Check if user is RH for a specific team + */ + public function isTeamRHFor(Team $team): bool + { + return $this->hasRoleInTeam($team, TeamUser::TEAM_ROLE_RH); + } + + /** + * Check if user has wellness access for a specific team + */ + public function hasWellnessAccessFor(Team $team): bool + { + if ($this->isAdmin()) { + return true; // Site admin has all permissions + } + + $role = $this->getRoleInTeam($team); + return in_array($role, [TeamUser::TEAM_ROLE_ADMIN, TeamUser::TEAM_ROLE_RH]); + } + + /** + * Check if user can manage team members for a specific team + */ + public function canManageTeamMembersFor(Team $team): bool + { + if ($this->isAdmin()) { + return true; // Site admin can manage all teams + } + + return $this->isTeamAdminFor($team); + } + + /** + * Check if user can access team content (projects, tasks, comments) + */ + public function canAccessTeamContent(Team $team): bool + { + if ($this->isAdmin()) { + return true; // Site admin has access to everything + } + + return $this->hasRoleInTeam($team); + } + + /** + * Get all teams where user has a specific permission + */ + public function getTeamsWithPermission(string $permission): \Illuminate\Database\Eloquent\Collection + { + $permissionMap = [ + 'wellness_access' => $this->wellnessAccessTeams, + 'manage_members' => $this->adminTeams, + 'rh_access' => $this->rhTeams, + ]; + + return $permissionMap[$permission] ?? collect(); + } +} diff --git a/database/factories/CommentFactory.php b/database/factories/CommentFactory.php index 7b2cd0b..18ccc7e 100644 --- a/database/factories/CommentFactory.php +++ b/database/factories/CommentFactory.php @@ -16,12 +16,27 @@ class CommentFactory extends Factory */ public function definition(): array { - return [ - 'content' => $this->faker->paragraph(), - 'task_id' => \App\Models\Task::factory(), - 'user_id' => \App\Models\User::factory(), - 'created_at' => now(), - 'updated_at' => now(), - ]; + // Créer aléatoirement soit un commentaire de tâche soit un commentaire de projet + $isTaskComment = $this->faker->boolean(70); // 70% de chance d'être un commentaire de tâche + + if ($isTaskComment) { + return [ + 'content' => $this->faker->paragraph(), + 'task_id' => \App\Models\Task::inRandomOrder()->first()?->id ?? \App\Models\Task::factory(), + 'project_id' => null, + 'user_id' => \App\Models\User::inRandomOrder()->first()?->id ?? \App\Models\User::factory(), + 'created_at' => now(), + 'updated_at' => now(), + ]; + } else { + return [ + 'content' => $this->faker->paragraph(), + 'task_id' => null, + 'project_id' => \App\Models\Project::inRandomOrder()->first()?->id ?? \App\Models\Project::factory(), + 'user_id' => \App\Models\User::inRandomOrder()->first()?->id ?? \App\Models\User::factory(), + 'created_at' => now(), + 'updated_at' => now(), + ]; + } } } diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index b340da5..e8809c2 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -28,7 +28,7 @@ public function definition(): array 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), - 'role' => $this->faker->randomElement(['admin', 'user', 'manager']), + 'role' => $this->faker->randomElement(['admin', 'user']), 'remember_token' => Str::random(10), 'created_at' => now(), 'updated_at' => now(), diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index f019163..94d3bbb 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -17,7 +17,7 @@ public function up(): void $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); - $table->enum('role', ['admin', 'user','manager']); + $table->enum('role', ['admin', 'user']); $table->rememberToken(); $table->timestamps(); }); diff --git a/database/migrations/2025_05_06_154419_create_comments_table.php b/database/migrations/2025_05_06_154419_create_comments_table.php index f1c7a93..f76678d 100644 --- a/database/migrations/2025_05_06_154419_create_comments_table.php +++ b/database/migrations/2025_05_06_154419_create_comments_table.php @@ -15,7 +15,7 @@ public function up(): void $table->id(); $table->text('content'); $table->foreignId('task_id')->nullable()->constrained('tasks')->onDelete('cascade'); - $table->foreignId('project_id')->constrained('projects')->onDelete('cascade'); + $table->foreignId('project_id')->nullable()->constrained('projects')->onDelete('cascade'); $table->foreignId('user_id')->constrained('users')->onDelete('cascade'); $table->timestamps(); }); diff --git a/database/migrations/2025_05_06_155943_create_team_user_table.php b/database/migrations/2025_05_06_155943_create_team_user_table.php index fc68a5f..b934d82 100644 --- a/database/migrations/2025_05_06_155943_create_team_user_table.php +++ b/database/migrations/2025_05_06_155943_create_team_user_table.php @@ -15,7 +15,7 @@ public function up(): void $table->id(); $table->foreignId('user_id')->constrained('users')->onDelete('cascade'); $table->foreignId('team_id')->constrained('teams')->onDelete('cascade'); - $table->string('role')->default('member'); + $table->enum('role', ['user', 'admin', 'rh'])->default('user'); $table->timestamps(); $table->unique(['user_id', 'team_id']); diff --git a/database/migrations/2025_08_16_123748_update_team_user_roles.php b/database/migrations/2025_08_16_123748_update_team_user_roles.php new file mode 100644 index 0000000..e7519a6 --- /dev/null +++ b/database/migrations/2025_08_16_123748_update_team_user_roles.php @@ -0,0 +1,41 @@ +where('role', 'member')->update(['role' => 'user']); + + // Changer la colonne pour utiliser ENUM au lieu de string + $table->dropColumn('role'); + }); + + Schema::table('team_user', function (Blueprint $table) { + $table->enum('role', ['user', 'admin', 'rh'])->default('user')->after('team_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('team_user', function (Blueprint $table) { + $table->dropColumn('role'); + }); + + Schema::table('team_user', function (Blueprint $table) { + $table->string('role')->default('member')->after('team_id'); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d220609..f2246e9 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -29,10 +29,5 @@ public function run(): void ChallengeParticipantSeeder::class, WellnessSurveySeeder::class, ]); - - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', - ]); } } diff --git a/database/seeders/TeamUserSeeder.php b/database/seeders/TeamUserSeeder.php index 7ac5b10..ea4d1cb 100644 --- a/database/seeders/TeamUserSeeder.php +++ b/database/seeders/TeamUserSeeder.php @@ -3,6 +3,8 @@ namespace Database\Seeders; use App\Models\TeamUser; +use App\Models\User; +use App\Models\Team; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; @@ -13,6 +15,37 @@ class TeamUserSeeder extends Seeder */ public function run(): void { - TeamUser::factory()->count(10)->create(); + // Exclure l'admin du site du seeding automatique + $users = User::where('role', '!=', User::ROLE_ADMIN)->get(); + $teams = Team::all(); + + // Assigner des rôles variés aux utilisateurs dans les équipes + foreach ($teams as $team) { + $teamUsers = $users->random(rand(3, 6)); + + foreach ($teamUsers as $index => $user) { + // Éviter les doublons + if (TeamUser::where('user_id', $user->id)->where('team_id', $team->id)->exists()) { + continue; + } + + if ($index === 0) { + // Premier utilisateur = admin de l'équipe + $role = TeamUser::TEAM_ROLE_ADMIN; + } elseif ($index === 1 && rand(0, 1)) { + // Deuxième utilisateur = parfois RH + $role = TeamUser::TEAM_ROLE_RH; + } else { + // Les autres = utilisateurs normaux + $role = TeamUser::TEAM_ROLE_USER; + } + + TeamUser::create([ + 'user_id' => $user->id, + 'team_id' => $team->id, + 'role' => $role, + ]); + } + } } } diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index 7555956..5fe957f 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -5,6 +5,7 @@ use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\User; +use Illuminate\Support\Facades\Hash; class UserSeeder extends Seeder { @@ -13,6 +14,16 @@ class UserSeeder extends Seeder */ public function run(): void { + // Créer un admin par défaut + User::create([ + 'name' => 'Administrateur Root', + 'email' => 'root@teamtask.com', + 'password' => Hash::make('root123456'), // À changer après la première connexion + 'role' => User::ROLE_ADMIN, + 'email_verified_at' => now(), + ]); + + // Créer quelques utilisateurs de test User::factory()->count(10)->create(); } } diff --git a/resources/views/components/layouts/app/sidebar.blade.php b/resources/views/components/layouts/app/sidebar.blade.php index ca2519c..9c476e4 100644 --- a/resources/views/components/layouts/app/sidebar.blade.php +++ b/resources/views/components/layouts/app/sidebar.blade.php @@ -30,6 +30,14 @@ class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover @endcan + @can('manageUsers') + + 👥 + Gestion des utilisateurs + + @endcan + {{ __('Dashboard') }} diff --git a/resources/views/livewire/admin/user-management.blade.php b/resources/views/livewire/admin/user-management.blade.php new file mode 100644 index 0000000..0a95d2c --- /dev/null +++ b/resources/views/livewire/admin/user-management.blade.php @@ -0,0 +1,273 @@ +
+
+
+
+

Gestion des utilisateurs

+

+ Gérez tous les utilisateurs inscrits sur la plateforme +

+
+ + +
+
+

Filtres

+ +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ +
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + @forelse($users as $user) + + + + + + + + + @empty + + + + @endforelse + +
+ + + + + Rôle + + Équipes + + + + Actions +
+
+
+
+ {{ $user->initials() }} +
+
+
+
+ {{ $user->name }} +
+
+
+
+ {{ $user->email }} + + @if($user->role === 'admin') + + Administrateur + + @else + + Utilisateur + + @endif + +
+ @if($user->teams->count() > 0) +
+ @foreach($user->teams->take(3) as $team) + + {{ $team->name }} + + @endforeach + @if($user->teams->count() > 3) + + +{{ $user->teams->count() - 3 }} + + @endif +
+ @else + Aucune équipe + @endif +
+
+ {{ $user->created_at->format('d/m/Y') }} + + @if($user->role === 'user') + + @elseif($user->id !== auth()->id()) + + @else + Vous + @endif +
+ Aucun utilisateur trouvé +
+
+ + +
+ {{ $users->links() }} +
+
+
+
+ + + @if($showPromoteModal && $userToPromote) +
+
+
+
+
+
+
+ + + +
+
+

+ Promouvoir en administrateur +

+
+

+ Êtes-vous sûr de vouloir promouvoir {{ $userToPromote->name }} en tant qu'administrateur du site ? + Cette action lui donnera accès à toutes les fonctionnalités d'administration. +

+
+
+
+
+
+ + +
+
+
+
+
+ @endif +
diff --git a/routes/web.php b/routes/web.php index 9667296..23f0774 100644 --- a/routes/web.php +++ b/routes/web.php @@ -90,6 +90,11 @@ Route::get('/wellness/suivi/team/{team}/member/{user}', MemberDetail::class) ->middleware('can:viewWellnessForTeam,team') ->name('wellness.followup.team.member'); + + // Administration des utilisateurs (réservé aux admins du site) + Route::get('/admin/users', \App\Livewire\Admin\UserManagement::class) + ->middleware('can:manageUsers') + ->name('admin.users'); }); require __DIR__.'/auth.php'; From edcb21487dfec8104478d38656ffd8c3f1be8f57 Mon Sep 17 00:00:00 2001 From: Denis40-prog Date: Sat, 16 Aug 2025 16:47:14 +0200 Subject: [PATCH 2/3] =?UTF-8?q?[ADD]=20suppression=20sur=20les=20diff?= =?UTF-8?q?=C3=A9rens=20objets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PERMISSIONS_SUPPRESSION.md | 96 +++++++ app/Livewire/CommentComponent.php | 32 +++ app/Livewire/Dashboard.php | 20 +- app/Livewire/ProjectComponent.php | 18 ++ app/Livewire/TaskComponent.php | 36 ++- app/Livewire/TeamComponent.php | 18 ++ app/Providers/AuthServiceProvider.php | 24 ++ app/Traits/HasTeamPermissions.php | 66 +++++ ...mail', '!=', 'root@teamtask.com')->first() | 12 + ...eleteTeam($team) ? 'OUI' : 'NON') . \"n\"" | 258 ++++++++++++++++++ resources/views/livewire/dashboard.blade.php | 58 ++-- .../livewire/project-component.blade.php | 92 ++++--- .../views/livewire/task-component.blade.php | 22 +- .../views/livewire/team-component.blade.php | 10 +- test_permissions.php | 83 ++++++ ...eleteTeam($team) ? 'OUI' : 'NON') . \"n\"" | 258 ++++++++++++++++++ 16 files changed, 1044 insertions(+), 59 deletions(-) create mode 100644 PERMISSIONS_SUPPRESSION.md create mode 100644 er = AppModelsUser::where('email', '!=', 'root@teamtask.com')->first() create mode 100644 "er peut supprimer \303\251quipe: \" . ($user->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" create mode 100644 test_permissions.php create mode 100644 "upprimer \303\251quipe: \" . ($admin->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" diff --git a/PERMISSIONS_SUPPRESSION.md b/PERMISSIONS_SUPPRESSION.md new file mode 100644 index 0000000..4929d51 --- /dev/null +++ b/PERMISSIONS_SUPPRESSION.md @@ -0,0 +1,96 @@ +# 🗑️ Système de Permissions de Suppression + +## ✅ Fonctionnalités Implémentées + +### 🏢 **Équipes** +- **Admin du Site** : Peut supprimer n'importe quelle équipe +- **Admin d'Équipe** : Peut supprimer uniquement son équipe +- **Utilisateur Normal** : Ne peut pas supprimer d'équipes + +**Localisation des boutons :** +- Dashboard : Icône poubelle sur chaque carte d'équipe +- Page équipes dédiée : Bouton "Supprimer" dans la liste + +### 📂 **Projets** +- **Admin du Site** : Peut supprimer n'importe quel projet +- **Admin d'Équipe** : Peut supprimer les projets de son équipe +- **Utilisateur Normal** : Ne peut pas supprimer de projets + +**Localisation des boutons :** +- Page projets : Icône poubelle à côté du titre du projet + +### 📝 **Tâches** +- **Admin du Site** : Peut supprimer n'importe quelle tâche +- **Admin d'Équipe** : Peut supprimer les tâches de son équipe +- **Utilisateur Normal** : Ne peut pas supprimer de tâches + +**Localisation des boutons :** +- Page tâches : Bouton "Supprimer" à côté du bouton "Modifier" + +### 💬 **Commentaires** +- **Admin du Site** : Peut supprimer n'importe quel commentaire +- **Admin d'Équipe** : Peut supprimer les commentaires dans son équipe +- **Utilisateur Normal** : Peut supprimer UNIQUEMENT ses propres commentaires + +**Localisation des boutons :** +- Page tâches : Icône 🗑️ à côté de la date du commentaire + +## 🔧 Architecture Technique + +### Permissions (Trait HasTeamPermissions) +```php +- canDeleteTeam(Team $team) // Admin site OU Admin équipe +- canDeleteProject(Project $project) // Admin site OU Admin équipe du projet +- canDeleteTask(Task $task) // Admin site OU Admin équipe du projet de la tâche +- canDeleteComment(Comment $comment) // Auteur OU Admin site OU Admin équipe +``` + +### Gates (AuthServiceProvider) +```php +- deleteTeam +- deleteProject +- deleteTask +- deleteComment +``` + +### Méthodes Livewire +```php +// Dashboard.php & TeamComponent.php +- deleteTeam(Team $team) + +// ProjectComponent.php +- deleteProject(Project $project) + +// TaskComponent.php +- deleteTask(Task $task) +- deleteComment(Comment $comment) +``` + +## 🛡️ Sécurité + +- ✅ Vérification des permissions via Gates +- ✅ Confirmations JavaScript avant suppression +- ✅ Messages d'erreur si permissions insuffisantes +- ✅ Suppression en cascade via migrations (CASCADE) +- ✅ Gestion des exceptions + +## 🎯 Tests Recommandés + +1. **Connectez-vous en tant qu'admin** (root@teamtask.com) + - Testez la suppression d'équipes, projets, tâches, commentaires + +2. **Créez un admin d'équipe** + - Testez qu'il peut supprimer dans son équipe seulement + +3. **Connectez-vous en utilisateur normal** + - Vérifiez qu'il ne peut supprimer que ses commentaires + +4. **Testez la suppression en cascade** + - Supprimez une équipe → tous ses projets/tâches/commentaires disparaissent + +## 🚨 Messages de Confirmation + +- **Équipe** : "Êtes-vous sûr de vouloir supprimer cette équipe ? Cette action est irréversible et supprimera tous les projets, tâches et commentaires associés." +- **Projet** : "Êtes-vous sûr de vouloir supprimer ce projet ? Cette action est irréversible." +- **Tâche** : "Êtes-vous sûr de vouloir supprimer cette tâche ? Cette action est irréversible." +- **Commentaire** : "Êtes-vous sûr de vouloir supprimer ce commentaire ?" diff --git a/app/Livewire/CommentComponent.php b/app/Livewire/CommentComponent.php index 185b4bf..f86dd4c 100644 --- a/app/Livewire/CommentComponent.php +++ b/app/Livewire/CommentComponent.php @@ -2,10 +2,42 @@ namespace App\Livewire; +use App\Models\Comment; use Livewire\Component; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; class CommentComponent extends Component { + public $comment; + + public function mount(Comment $comment) + { + $this->comment = $comment; + } + + public function deleteComment() + { + // Vérifier les permissions + if (!Gate::allows('deleteComment', $this->comment)) { + $this->dispatch('flash', type: 'error', text: 'Vous n\'avez pas les permissions pour supprimer ce commentaire.'); + return; + } + + try { + $this->comment->delete(); + $this->dispatch('flash', type: 'success', text: 'Le commentaire a été supprimé avec succès.'); + $this->dispatch('commentDeleted'); + } catch (\Exception $e) { + $this->dispatch('flash', type: 'error', text: 'Une erreur est survenue lors de la suppression du commentaire.'); + } + } + + public function canDelete() + { + return Gate::allows('deleteComment', $this->comment); + } + public function render() { return view('livewire.comment-component'); diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php index 1bdbbd1..0392979 100644 --- a/app/Livewire/Dashboard.php +++ b/app/Livewire/Dashboard.php @@ -7,6 +7,7 @@ use App\Models\User; use Livewire\Component; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; class Dashboard extends Component { @@ -45,6 +46,23 @@ public function createTeam() $this->dispatch('flash', type: 'success', text: 'Équipe créée avec succès !'); } + public function deleteTeam(Team $team) + { + // Vérifier les permissions + if (!Gate::allows('deleteTeam', $team)) { + $this->dispatch('flash', type: 'error', text: 'Vous n\'avez pas les permissions pour supprimer cette équipe.'); + return; + } + + try { + $teamName = $team->name; + $team->delete(); + $this->dispatch('flash', type: 'success', text: 'L\'équipe "' . $teamName . '" a été supprimée avec succès.'); + } catch (\Exception $e) { + $this->dispatch('flash', type: 'error', text: 'Une erreur est survenue lors de la suppression de l\'équipe.'); + } + } + public function render() { $user = Auth::user(); @@ -54,7 +72,7 @@ public function render() $teams = Team::all(); } else { // Sinon, seulement ses équipes - $teams = $user->teams()->get(); + $teams = $user->teams; } return view('livewire.dashboard', [ diff --git a/app/Livewire/ProjectComponent.php b/app/Livewire/ProjectComponent.php index 5a7fada..89598ff 100644 --- a/app/Livewire/ProjectComponent.php +++ b/app/Livewire/ProjectComponent.php @@ -7,6 +7,7 @@ use App\Models\User; use Livewire\Component; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; class ProjectComponent extends Component { @@ -172,6 +173,23 @@ public function demoteFromAdmin($userId) $this->dispatch('flash', type: 'success', text: $user->name . ' n\'est plus administrateur de l\'équipe !'); } + public function deleteProject(Project $project) + { + // Vérifier les permissions + if (!Gate::allows('deleteProject', $project)) { + $this->dispatch('flash', type: 'error', text: 'Vous n\'avez pas les permissions pour supprimer ce projet.'); + return; + } + + try { + $projectName = $project->name; + $project->delete(); + $this->dispatch('flash', type: 'success', text: 'Le projet "' . $projectName . '" a été supprimé avec succès.'); + } catch (\Exception $e) { + $this->dispatch('flash', type: 'error', text: 'Une erreur est survenue lors de la suppression du projet.'); + } + } + public function render() { $projects = Project::where('team_id', $this->teamId)->get(); diff --git a/app/Livewire/TaskComponent.php b/app/Livewire/TaskComponent.php index f09bd4f..18909f4 100644 --- a/app/Livewire/TaskComponent.php +++ b/app/Livewire/TaskComponent.php @@ -7,6 +7,7 @@ use App\Models\Comment; use Livewire\Component; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; class TaskComponent extends Component { @@ -146,7 +147,7 @@ public function setSort($field) $this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc'; } else { $this->sortField = $field; - $this->sortDir = $field === 'priority' ? 'desc' : 'desc'; + $this->sortDir = 'desc'; } } @@ -160,6 +161,39 @@ public function resetFilters() $this->sortDir = 'desc'; } + public function deleteTask(Task $task) + { + // Vérifier les permissions + if (!Gate::allows('deleteTask', $task)) { + $this->dispatch('flash', type: 'error', text: 'Vous n\'avez pas les permissions pour supprimer cette tâche.'); + return; + } + + try { + $taskTitle = $task->title; + $task->delete(); + $this->dispatch('flash', type: 'success', text: 'La tâche "' . $taskTitle . '" a été supprimée avec succès.'); + } catch (\Exception $e) { + $this->dispatch('flash', type: 'error', text: 'Une erreur est survenue lors de la suppression de la tâche.'); + } + } + + public function deleteComment(Comment $comment) + { + // Vérifier les permissions + if (!Gate::allows('deleteComment', $comment)) { + $this->dispatch('flash', type: 'error', text: 'Vous n\'avez pas les permissions pour supprimer ce commentaire.'); + return; + } + + try { + $comment->delete(); + $this->dispatch('flash', type: 'success', text: 'Le commentaire a été supprimé avec succès.'); + } catch (\Exception $e) { + $this->dispatch('flash', type: 'error', text: 'Une erreur est survenue lors de la suppression du commentaire.'); + } + } + public function render() { $teamMembers = $this->project->team->users; diff --git a/app/Livewire/TeamComponent.php b/app/Livewire/TeamComponent.php index d3b1b59..a1d2744 100644 --- a/app/Livewire/TeamComponent.php +++ b/app/Livewire/TeamComponent.php @@ -6,6 +6,7 @@ use Livewire\Component; use Livewire\WithPagination; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; class TeamComponent extends Component { @@ -43,6 +44,23 @@ public function sortBy($field) $this->resetPage(); } + public function deleteTeam(Team $team) + { + // Vérifier les permissions + if (!Gate::allows('deleteTeam', $team)) { + session()->flash('error', 'Vous n\'avez pas les permissions pour supprimer cette équipe.'); + return; + } + + try { + // Supprimer l'équipe et toutes ses relations + $team->delete(); + session()->flash('success', 'L\'équipe "' . $team->name . '" a été supprimée avec succès.'); + } catch (\Exception $e) { + session()->flash('error', 'Une erreur est survenue lors de la suppression de l\'équipe.'); + } + } + public function render() { $user = Auth::user(); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 4bb01cf..3bacbae 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -43,5 +43,29 @@ public function boot(): void Gate::define('manageUsers', function (User $user) { return $user->role === User::ROLE_ADMIN; }); + + // ===================================== + // PERMISSIONS DE SUPPRESSION + // ===================================== + + // Permission pour supprimer une équipe + Gate::define('deleteTeam', function (User $user, Team $team) { + return $user->canDeleteTeam($team); + }); + + // Permission pour supprimer un projet + Gate::define('deleteProject', function (User $user, $project) { + return $user->canDeleteProject($project); + }); + + // Permission pour supprimer une tâche + Gate::define('deleteTask', function (User $user, $task) { + return $user->canDeleteTask($task); + }); + + // Permission pour supprimer un commentaire + Gate::define('deleteComment', function (User $user, $comment) { + return $user->canDeleteComment($comment); + }); } } diff --git a/app/Traits/HasTeamPermissions.php b/app/Traits/HasTeamPermissions.php index b506bc2..5ed0674 100644 --- a/app/Traits/HasTeamPermissions.php +++ b/app/Traits/HasTeamPermissions.php @@ -101,4 +101,70 @@ public function getTeamsWithPermission(string $permission): \Illuminate\Database return $permissionMap[$permission] ?? collect(); } + + // ===================================== + // PERMISSIONS DE SUPPRESSION + // ===================================== + + /** + * Check if user can delete a team + */ + public function canDeleteTeam(Team $team): bool + { + if ($this->isAdmin()) { + return true; // Site admin can delete any team + } + + return $this->isTeamAdminFor($team); + } + + /** + * Check if user can delete a project + */ + public function canDeleteProject(\App\Models\Project $project): bool + { + if ($this->isAdmin()) { + return true; // Site admin can delete any project + } + + return $this->isTeamAdminFor($project->team); + } + + /** + * Check if user can delete a task + */ + public function canDeleteTask(\App\Models\Task $task): bool + { + if ($this->isAdmin()) { + return true; // Site admin can delete any task + } + + return $this->isTeamAdminFor($task->project->team); + } + + /** + * Check if user can delete a comment + */ + public function canDeleteComment(\App\Models\Comment $comment): bool + { + // Si l'utilisateur est l'auteur du commentaire, il peut le supprimer + if ($comment->user_id === $this->id) { + return true; + } + + // Site admin peut supprimer n'importe quel commentaire + if ($this->isAdmin()) { + return true; + } + + // Team admin peut supprimer les commentaires dans son équipe + $team = null; + if ($comment->isTaskComment()) { + $team = $comment->task->project->team; + } elseif ($comment->isProjectComment()) { + $team = $comment->project->team; + } + + return $team ? $this->isTeamAdminFor($team) : false; + } } diff --git a/er = AppModelsUser::where('email', '!=', 'root@teamtask.com')->first() b/er = AppModelsUser::where('email', '!=', 'root@teamtask.com')->first() new file mode 100644 index 0000000..fbd916d --- /dev/null +++ b/er = AppModelsUser::where('email', '!=', 'root@teamtask.com')->first() @@ -0,0 +1,12 @@ += App\Models\User {#6643 + id: 1, + name: "Administrateur Root", + email: "root@teamtask.com", + email_verified_at: "2025-08-16 13:05:14", + #password: "$2y$12$ikGoYBOQqG9sWccPTHT0G.wfdtzfgJxZedW3jmlvtnlLbIvBuu1ey", + role: "admin", + #remember_token: null, + created_at: "2025-08-16 13:05:14", + updated_at: "2025-08-16 13:05:14", + } + diff --git "a/er peut supprimer \303\251quipe: \" . ($user->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" "b/er peut supprimer \303\251quipe: \" . ($user->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" new file mode 100644 index 0000000..333a0b5 --- /dev/null +++ "b/er peut supprimer \303\251quipe: \" . ($user->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" @@ -0,0 +1,258 @@ + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^W WRAP search if no match found. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-M_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k [_f_i_l_e] . --lesskey-file=[_f_i_l_e] + Use a lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n -N .... --line-numbers --LINE-NUMBERS + Don't use line numbers. + -o [_f_i_l_e] . --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t [_t_a_g] .. --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --incsearch + Search file as each pattern character is typed in. + --line-num-width=N + Set the width of the -N line number field to N characters. + --mouse + Enable mouse input. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --rscroll=C + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --status-col-width=N + Set the width of the -J status column to N characters. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=N + Each click of the mouse wheel moves N lines. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all. diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index a39e6c2..8f04152 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -69,27 +69,47 @@ class="bg-slate-400 dark:bg-gray-600 hover:bg-slate-500 dark:hover:bg-gray-700 t
@forelse($teams as $team) -
+
-

{{ $team->name }}

- - - +

+ {{ $team->name }} +

+
+ @can('deleteTeam', $team) + + @endcan + + + +
- @if($team->description) -

{{ Str::limit($team->description, 100) }}

- @endif -
-

- Membres: {{ $team->users()->count() }} -

-

- Projets: {{ $team->projects()->count() }} -

-

- Créée le {{ $team->created_at->format('d/m/Y') }} -

+
+ @if($team->description) +

{{ Str::limit($team->description, 100) }}

+ @endif +
+

+ Membres: {{ $team->users()->count() }} +

+

+ Projets: {{ $team->projects()->count() }} +

+

+ Créée le {{ $team->created_at->format('d/m/Y') }} +

+
@empty diff --git a/resources/views/livewire/project-component.blade.php b/resources/views/livewire/project-component.blade.php index 09f00b4..5e3e8e2 100644 --- a/resources/views/livewire/project-component.blade.php +++ b/resources/views/livewire/project-component.blade.php @@ -211,46 +211,66 @@ class="bg-slate-400 dark:bg-gray-600 hover:bg-slate-500 dark:hover:bg-gray-700 t
@forelse($projects as $project) -
+
-

{{ $project->name }}

- - - +

+ {{ $project->name }} +

+
+ @can('deleteProject', $project) + + @endcan + + + +
- @if($project->description) -

{{ Str::limit($project->description, 100) }}

- @endif -
- @if($project->start_date || $project->end_date) +
+ @if($project->description) +

{{ Str::limit($project->description, 100) }}

+ @endif +
+ @if($project->start_date || $project->end_date) +

+ Période: + @if($project->start_date) + {{ $project->start_date->format('d/m/Y') }} + @else + Non définie + @endif + @if($project->end_date) + - {{ $project->end_date->format('d/m/Y') }} + @endif +

+ @endif

- Période: - @if($project->start_date) - {{ $project->start_date->format('d/m/Y') }} - @else - Non définie - @endif - @if($project->end_date) - - {{ $project->end_date->format('d/m/Y') }} - @endif + Statut: + + {{ $project->status === 'active' ? 'Actif' : 'Archivé' }} +

- @endif -

- Statut: - - {{ $project->status === 'active' ? 'Actif' : 'Archivé' }} - -

-

- Tâches: {{ $project->tasks()->count() }} -

-

- Propriétaire: {{ $project->owner->name }} -

-

- Créé le {{ $project->created_at->format('d/m/Y') }} -

+

+ Tâches: {{ $project->tasks()->count() }} +

+

+ Propriétaire: {{ $project->owner->name }} +

+

+ Créé le {{ $project->created_at->format('d/m/Y') }} +

+
@empty diff --git a/resources/views/livewire/task-component.blade.php b/resources/views/livewire/task-component.blade.php index 6df1bc4..7a774e4 100644 --- a/resources/views/livewire/task-component.blade.php +++ b/resources/views/livewire/task-component.blade.php @@ -301,6 +301,14 @@ class="bg-slate-400 hover:bg-slate-500 dark:bg-gray-600 dark:hover:bg-gray-700 t class="bg-emerald-600 hover:bg-emerald-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white px-2 py-1 rounded text-xs transition-colors cursor-pointer"> Modifier + @can('deleteTask', $task) + + @endcan
@if($task->description) @@ -390,7 +398,19 @@ class="bg-emerald-600 hover:bg-emerald-700 dark:bg-purple-600 dark:hover:bg-purp
{{ $comment->user->name }} - {{ $comment->created_at->format('d/m/Y H:i') }} +
+ {{ $comment->created_at->format('d/m/Y H:i') }} + @can('deleteComment', $comment) + + @endcan +

{{ $comment->content }}

diff --git a/resources/views/livewire/team-component.blade.php b/resources/views/livewire/team-component.blade.php index bcc8f18..96e14ac 100644 --- a/resources/views/livewire/team-component.blade.php +++ b/resources/views/livewire/team-component.blade.php @@ -42,7 +42,15 @@ class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg f
- + @can('deleteTeam', $team) + + @endcan
diff --git a/test_permissions.php b/test_permissions.php new file mode 100644 index 0000000..bd50417 --- /dev/null +++ b/test_permissions.php @@ -0,0 +1,83 @@ +make(Illuminate\Contracts\Console\Kernel::class); +$kernel->bootstrap(); + +echo "=== TEST DES PERMISSIONS DE SUPPRESSION ===\n\n"; + +try { + $admin = \App\Models\User::where('email', 'root@teamtask.com')->first(); + $user = \App\Models\User::where('email', '!=', 'root@teamtask.com')->first(); + $team = \App\Models\Team::first(); + $project = \App\Models\Project::first(); + $task = \App\Models\Task::first(); + $comment = \App\Models\Comment::first(); + + if (!$admin) { + echo "❌ Admin non trouvé\n"; + exit(1); + } + + echo "👤 Admin: " . $admin->name . " (" . $admin->email . ")\n"; + echo "👤 User: " . ($user ? $user->name . " (" . $user->email . ")" : "Aucun autre utilisateur") . "\n\n"; + + echo "=== PERMISSIONS ADMIN DU SITE ===\n"; + if ($team) { + echo "🏢 Peut supprimer équipe: " . ($admin->canDeleteTeam($team) ? "✅ OUI" : "❌ NON") . "\n"; + } + if ($project) { + echo "📂 Peut supprimer projet: " . ($admin->canDeleteProject($project) ? "✅ OUI" : "❌ NON") . "\n"; + } + if ($task) { + echo "📝 Peut supprimer tâche: " . ($admin->canDeleteTask($task) ? "✅ OUI" : "❌ NON") . "\n"; + } + if ($comment) { + echo "💬 Peut supprimer commentaire: " . ($admin->canDeleteComment($comment) ? "✅ OUI" : "❌ NON") . "\n"; + } + + if ($user) { + echo "\n=== PERMISSIONS UTILISATEUR NORMAL ===\n"; + if ($team) { + echo "🏢 Peut supprimer équipe: " . ($user->canDeleteTeam($team) ? "✅ OUI" : "❌ NON") . "\n"; + } + if ($project) { + echo "📂 Peut supprimer projet: " . ($user->canDeleteProject($project) ? "✅ OUI" : "❌ NON") . "\n"; + } + if ($task) { + echo "📝 Peut supprimer tâche: " . ($user->canDeleteTask($task) ? "✅ OUI" : "❌ NON") . "\n"; + } + if ($comment) { + echo "💬 Peut supprimer commentaire: " . ($user->canDeleteComment($comment) ? "✅ OUI" : "❌ NON") . "\n"; + } + } + + echo "\n=== TEST TEAM ADMIN ===\n"; + // Créer un admin d'équipe pour les tests + $teamAdmin = \App\Models\User::where('email', '!=', 'root@teamtask.com')->skip(1)->first(); + if ($teamAdmin && $team) { + // S'assurer qu'il soit admin de l'équipe + $team->users()->syncWithoutDetaching([$teamAdmin->id => ['role' => 'admin']]); + $teamAdmin->refresh(); + + echo "👤 Team Admin: " . $teamAdmin->name . "\n"; + echo "🏢 Peut supprimer équipe: " . ($teamAdmin->canDeleteTeam($team) ? "✅ OUI" : "❌ NON") . "\n"; + if ($project) { + echo "📂 Peut supprimer projet: " . ($teamAdmin->canDeleteProject($project) ? "✅ OUI" : "❌ NON") . "\n"; + } + if ($task) { + echo "📝 Peut supprimer tâche: " . ($teamAdmin->canDeleteTask($task) ? "✅ OUI" : "❌ NON") . "\n"; + } + if ($comment) { + echo "💬 Peut supprimer commentaire: " . ($teamAdmin->canDeleteComment($comment) ? "✅ OUI" : "❌ NON") . "\n"; + } + } + + echo "\n✅ Tests terminés avec succès !\n"; + +} catch (Exception $e) { + echo "❌ Erreur: " . $e->getMessage() . "\n"; + echo "📍 Fichier: " . $e->getFile() . ":" . $e->getLine() . "\n"; +} diff --git "a/upprimer \303\251quipe: \" . ($admin->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" "b/upprimer \303\251quipe: \" . ($admin->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" new file mode 100644 index 0000000..333a0b5 --- /dev/null +++ "b/upprimer \303\251quipe: \" . ($admin->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" @@ -0,0 +1,258 @@ + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^W WRAP search if no match found. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-M_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k [_f_i_l_e] . --lesskey-file=[_f_i_l_e] + Use a lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n -N .... --line-numbers --LINE-NUMBERS + Don't use line numbers. + -o [_f_i_l_e] . --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t [_t_a_g] .. --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --incsearch + Search file as each pattern character is typed in. + --line-num-width=N + Set the width of the -N line number field to N characters. + --mouse + Enable mouse input. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --rscroll=C + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --status-col-width=N + Set the width of the -J status column to N characters. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=N + Each click of the mouse wheel moves N lines. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all. From 14ad575b2c7186491237ba9ca433e4b736f95ed1 Mon Sep 17 00:00:00 2001 From: Denis40-prog Date: Sat, 16 Aug 2025 17:28:16 +0200 Subject: [PATCH 3/3] [DEL] suppression de fichiers inutiles + [UPD] update readme --- PERMISSIONS_SUPPRESSION.md | 96 ------- README.md | 10 +- ...mail', '!=', 'root@teamtask.com')->first() | 12 - ...eleteTeam($team) ? 'OUI' : 'NON') . \"n\"" | 258 ------------------ .../views/components/hexagon-grid.blade.php | 50 ---- test_permissions.php | 83 ------ ...eleteTeam($team) ? 'OUI' : 'NON') . \"n\"" | 258 ------------------ 7 files changed, 6 insertions(+), 761 deletions(-) delete mode 100644 PERMISSIONS_SUPPRESSION.md delete mode 100644 er = AppModelsUser::where('email', '!=', 'root@teamtask.com')->first() delete mode 100644 "er peut supprimer \303\251quipe: \" . ($user->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" delete mode 100644 resources/views/components/hexagon-grid.blade.php delete mode 100644 test_permissions.php delete mode 100644 "upprimer \303\251quipe: \" . ($admin->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" diff --git a/PERMISSIONS_SUPPRESSION.md b/PERMISSIONS_SUPPRESSION.md deleted file mode 100644 index 4929d51..0000000 --- a/PERMISSIONS_SUPPRESSION.md +++ /dev/null @@ -1,96 +0,0 @@ -# 🗑️ Système de Permissions de Suppression - -## ✅ Fonctionnalités Implémentées - -### 🏢 **Équipes** -- **Admin du Site** : Peut supprimer n'importe quelle équipe -- **Admin d'Équipe** : Peut supprimer uniquement son équipe -- **Utilisateur Normal** : Ne peut pas supprimer d'équipes - -**Localisation des boutons :** -- Dashboard : Icône poubelle sur chaque carte d'équipe -- Page équipes dédiée : Bouton "Supprimer" dans la liste - -### 📂 **Projets** -- **Admin du Site** : Peut supprimer n'importe quel projet -- **Admin d'Équipe** : Peut supprimer les projets de son équipe -- **Utilisateur Normal** : Ne peut pas supprimer de projets - -**Localisation des boutons :** -- Page projets : Icône poubelle à côté du titre du projet - -### 📝 **Tâches** -- **Admin du Site** : Peut supprimer n'importe quelle tâche -- **Admin d'Équipe** : Peut supprimer les tâches de son équipe -- **Utilisateur Normal** : Ne peut pas supprimer de tâches - -**Localisation des boutons :** -- Page tâches : Bouton "Supprimer" à côté du bouton "Modifier" - -### 💬 **Commentaires** -- **Admin du Site** : Peut supprimer n'importe quel commentaire -- **Admin d'Équipe** : Peut supprimer les commentaires dans son équipe -- **Utilisateur Normal** : Peut supprimer UNIQUEMENT ses propres commentaires - -**Localisation des boutons :** -- Page tâches : Icône 🗑️ à côté de la date du commentaire - -## 🔧 Architecture Technique - -### Permissions (Trait HasTeamPermissions) -```php -- canDeleteTeam(Team $team) // Admin site OU Admin équipe -- canDeleteProject(Project $project) // Admin site OU Admin équipe du projet -- canDeleteTask(Task $task) // Admin site OU Admin équipe du projet de la tâche -- canDeleteComment(Comment $comment) // Auteur OU Admin site OU Admin équipe -``` - -### Gates (AuthServiceProvider) -```php -- deleteTeam -- deleteProject -- deleteTask -- deleteComment -``` - -### Méthodes Livewire -```php -// Dashboard.php & TeamComponent.php -- deleteTeam(Team $team) - -// ProjectComponent.php -- deleteProject(Project $project) - -// TaskComponent.php -- deleteTask(Task $task) -- deleteComment(Comment $comment) -``` - -## 🛡️ Sécurité - -- ✅ Vérification des permissions via Gates -- ✅ Confirmations JavaScript avant suppression -- ✅ Messages d'erreur si permissions insuffisantes -- ✅ Suppression en cascade via migrations (CASCADE) -- ✅ Gestion des exceptions - -## 🎯 Tests Recommandés - -1. **Connectez-vous en tant qu'admin** (root@teamtask.com) - - Testez la suppression d'équipes, projets, tâches, commentaires - -2. **Créez un admin d'équipe** - - Testez qu'il peut supprimer dans son équipe seulement - -3. **Connectez-vous en utilisateur normal** - - Vérifiez qu'il ne peut supprimer que ses commentaires - -4. **Testez la suppression en cascade** - - Supprimez une équipe → tous ses projets/tâches/commentaires disparaissent - -## 🚨 Messages de Confirmation - -- **Équipe** : "Êtes-vous sûr de vouloir supprimer cette équipe ? Cette action est irréversible et supprimera tous les projets, tâches et commentaires associés." -- **Projet** : "Êtes-vous sûr de vouloir supprimer ce projet ? Cette action est irréversible." -- **Tâche** : "Êtes-vous sûr de vouloir supprimer cette tâche ? Cette action est irréversible." -- **Commentaire** : "Êtes-vous sûr de vouloir supprimer ce commentaire ?" diff --git a/README.md b/README.md index 1dd4d0d..90b8ee0 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,12 @@ - Ajout de commentaires sur un projet (associés à une tâche système si nécessaire) - Affichage des commentaires récents avec auteur et date -### 🎨 Interface +### �️ Météo des émotions +- Suivi du bien-être de l'équipe via des questionnaires +- Visualisation de l'état émotionnel des membres +- Historique des réponses pour analyse des tendances + +### �🎨 Interface - UI responsive avec **Tailwind CSS** - Composants dynamiques Livewire pour une interaction fluide - Affichage clair des priorités et statuts par couleurs @@ -118,9 +123,6 @@ sail npm run dev - Documentation technique & utilisateur complète - Mise en production -## Suivi de la météo des émotions -- Questionnaire - ## Mise en concurrence hebdomadaire - Challenge sous forme de mission hebdomadaire diff --git a/er = AppModelsUser::where('email', '!=', 'root@teamtask.com')->first() b/er = AppModelsUser::where('email', '!=', 'root@teamtask.com')->first() deleted file mode 100644 index fbd916d..0000000 --- a/er = AppModelsUser::where('email', '!=', 'root@teamtask.com')->first() +++ /dev/null @@ -1,12 +0,0 @@ -= App\Models\User {#6643 - id: 1, - name: "Administrateur Root", - email: "root@teamtask.com", - email_verified_at: "2025-08-16 13:05:14", - #password: "$2y$12$ikGoYBOQqG9sWccPTHT0G.wfdtzfgJxZedW3jmlvtnlLbIvBuu1ey", - role: "admin", - #remember_token: null, - created_at: "2025-08-16 13:05:14", - updated_at: "2025-08-16 13:05:14", - } - diff --git "a/er peut supprimer \303\251quipe: \" . ($user->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" "b/er peut supprimer \303\251quipe: \" . ($user->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" deleted file mode 100644 index 333a0b5..0000000 --- "a/er peut supprimer \303\251quipe: \" . ($user->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" +++ /dev/null @@ -1,258 +0,0 @@ - - SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS - - Commands marked with * may be preceded by a number, _N. - Notes in parentheses indicate the behavior if _N is given. - A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. - - h H Display this help. - q :q Q :Q ZZ Exit. - --------------------------------------------------------------------------- - - MMOOVVIINNGG - - e ^E j ^N CR * Forward one line (or _N lines). - y ^Y k ^K ^P * Backward one line (or _N lines). - f ^F ^V SPACE * Forward one window (or _N lines). - b ^B ESC-v * Backward one window (or _N lines). - z * Forward one window (and set window to _N). - w * Backward one window (and set window to _N). - ESC-SPACE * Forward one window, but don't stop at end-of-file. - d ^D * Forward one half-window (and set half-window to _N). - u ^U * Backward one half-window (and set half-window to _N). - ESC-) RightArrow * Right one half screen width (or _N positions). - ESC-( LeftArrow * Left one half screen width (or _N positions). - ESC-} ^RightArrow Right to last column displayed. - ESC-{ ^LeftArrow Left to first column. - F Forward forever; like "tail -f". - ESC-F Like F but stop when search pattern is found. - r ^R ^L Repaint screen. - R Repaint screen, discarding buffered input. - --------------------------------------------------- - Default "window" is the screen height. - Default "half-window" is half of the screen height. - --------------------------------------------------------------------------- - - SSEEAARRCCHHIINNGG - - /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. - ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. - n * Repeat previous search (for _N-th occurrence). - N * Repeat previous search in reverse direction. - ESC-n * Repeat previous search, spanning files. - ESC-N * Repeat previous search, reverse dir. & spanning files. - ESC-u Undo (toggle) search highlighting. - ESC-U Clear search highlighting. - &_p_a_t_t_e_r_n * Display only matching lines. - --------------------------------------------------- - A search pattern may begin with one or more of: - ^N or ! Search for NON-matching lines. - ^E or * Search multiple files (pass thru END OF FILE). - ^F or @ Start search at FIRST file (for /) or last file (for ?). - ^K Highlight matches, but don't move (KEEP position). - ^R Don't use REGULAR EXPRESSIONS. - ^W WRAP search if no match found. - --------------------------------------------------------------------------- - - JJUUMMPPIINNGG - - g < ESC-< * Go to first line in file (or line _N). - G > ESC-> * Go to last line in file (or line _N). - p % * Go to beginning of file (or _N percent into file). - t * Go to the (_N-th) next tag. - T * Go to the (_N-th) previous tag. - { ( [ * Find close bracket } ) ]. - } ) ] * Find open bracket { ( [. - ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. - ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. - --------------------------------------------------- - Each "find close bracket" command goes forward to the close bracket - matching the (_N-th) open bracket in the top line. - Each "find open bracket" command goes backward to the open bracket - matching the (_N-th) close bracket in the bottom line. - - m_<_l_e_t_t_e_r_> Mark the current top line with . - M_<_l_e_t_t_e_r_> Mark the current bottom line with . - '_<_l_e_t_t_e_r_> Go to a previously marked position. - '' Go to the previous position. - ^X^X Same as '. - ESC-M_<_l_e_t_t_e_r_> Clear a mark. - --------------------------------------------------- - A mark is any upper-case or lower-case letter. - Certain marks are predefined: - ^ means beginning of the file - $ means end of the file - --------------------------------------------------------------------------- - - CCHHAANNGGIINNGG FFIILLEESS - - :e [_f_i_l_e] Examine a new file. - ^X^V Same as :e. - :n * Examine the (_N-th) next file from the command line. - :p * Examine the (_N-th) previous file from the command line. - :x * Examine the first (or _N-th) file from the command line. - :d Delete the current file from the command line list. - = ^G :f Print current file name. - --------------------------------------------------------------------------- - - MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS - - -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. - --_<_n_a_m_e_> Toggle a command line option, by name. - __<_f_l_a_g_> Display the setting of a command line option. - ___<_n_a_m_e_> Display the setting of an option, by name. - +_c_m_d Execute the less cmd each time a new file is examined. - - !_c_o_m_m_a_n_d Execute the shell command with $SHELL. - |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. - s _f_i_l_e Save input to a file. - v Edit the current file with $VISUAL or $EDITOR. - V Print version number of "less". - --------------------------------------------------------------------------- - - OOPPTTIIOONNSS - - Most options may be changed either on the command line, - or from within less by using the - or -- command. - Options may be given in one of two forms: either a single - character preceded by a -, or a name preceded by --. - - -? ........ --help - Display help (from command line). - -a ........ --search-skip-screen - Search skips current screen. - -A ........ --SEARCH-SKIP-SCREEN - Search starts just after target line. - -b [_N] .... --buffers=[_N] - Number of buffers. - -B ........ --auto-buffers - Don't automatically allocate buffers for pipes. - -c ........ --clear-screen - Repaint by clearing rather than scrolling. - -d ........ --dumb - Dumb terminal. - -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r - Set screen colors. - -e -E .... --quit-at-eof --QUIT-AT-EOF - Quit at end of file. - -f ........ --force - Force open non-regular files. - -F ........ --quit-if-one-screen - Quit if entire file fits on first screen. - -g ........ --hilite-search - Highlight only last match for searches. - -G ........ --HILITE-SEARCH - Don't highlight any matches for searches. - -h [_N] .... --max-back-scroll=[_N] - Backward scroll limit. - -i ........ --ignore-case - Ignore case in searches that do not contain uppercase. - -I ........ --IGNORE-CASE - Ignore case in all searches. - -j [_N] .... --jump-target=[_N] - Screen position of target lines. - -J ........ --status-column - Display a status column at left edge of screen. - -k [_f_i_l_e] . --lesskey-file=[_f_i_l_e] - Use a lesskey file. - -K ........ --quit-on-intr - Exit less in response to ctrl-C. - -L ........ --no-lessopen - Ignore the LESSOPEN environment variable. - -m -M .... --long-prompt --LONG-PROMPT - Set prompt style. - -n -N .... --line-numbers --LINE-NUMBERS - Don't use line numbers. - -o [_f_i_l_e] . --log-file=[_f_i_l_e] - Copy to log file (standard input only). - -O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e] - Copy to log file (unconditionally overwrite). - -p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n] - Start at pattern (from command line). - -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] - Define new prompt. - -q -Q .... --quiet --QUIET --silent --SILENT - Quiet the terminal bell. - -r -R .... --raw-control-chars --RAW-CONTROL-CHARS - Output "raw" control characters. - -s ........ --squeeze-blank-lines - Squeeze multiple blank lines. - -S ........ --chop-long-lines - Chop (truncate) long lines rather than wrapping. - -t [_t_a_g] .. --tag=[_t_a_g] - Find a tag. - -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] - Use an alternate tags file. - -u -U .... --underline-special --UNDERLINE-SPECIAL - Change handling of backspaces. - -V ........ --version - Display the version number of "less". - -w ........ --hilite-unread - Highlight first new line after forward-screen. - -W ........ --HILITE-UNREAD - Highlight first new line after any forward movement. - -x [_N[,...]] --tabs=[_N[,...]] - Set tab stops. - -X ........ --no-init - Don't use termcap init/deinit strings. - -y [_N] .... --max-forw-scroll=[_N] - Forward scroll limit. - -z [_N] .... --window=[_N] - Set size of window. - -" [_c[_c]] . --quotes=[_c[_c]] - Set shell quote characters. - -~ ........ --tilde - Don't display tildes after end of file. - -# [_N] .... --shift=[_N] - Set horizontal scroll amount (0 = one half screen width). - --file-size - Automatically determine the size of the input file. - --follow-name - The F command changes files if the input file is renamed. - --incsearch - Search file as each pattern character is typed in. - --line-num-width=N - Set the width of the -N line number field to N characters. - --mouse - Enable mouse input. - --no-keypad - Don't send termcap keypad init/deinit strings. - --no-histdups - Remove duplicates from command history. - --rscroll=C - Set the character used to mark truncated lines. - --save-marks - Retain marks across invocations of less. - --status-col-width=N - Set the width of the -J status column to N characters. - --use-backslash - Subsequent options use backslash as escape char. - --use-color - Enables colored text. - --wheel-lines=N - Each click of the mouse wheel moves N lines. - - - --------------------------------------------------------------------------- - - LLIINNEE EEDDIITTIINNGG - - These keys can be used to edit text being entered - on the "command line" at the bottom of the screen. - - RightArrow ..................... ESC-l ... Move cursor right one character. - LeftArrow ...................... ESC-h ... Move cursor left one character. - ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. - ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. - HOME ........................... ESC-0 ... Move cursor to start of line. - END ............................ ESC-$ ... Move cursor to end of line. - BACKSPACE ................................ Delete char to left of cursor. - DELETE ......................... ESC-x ... Delete char under cursor. - ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. - ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. - ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. - UpArrow ........................ ESC-k ... Retrieve previous command line. - DownArrow ...................... ESC-j ... Retrieve next command line. - TAB ...................................... Complete filename & cycle. - SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. - ctrl-L ................................... Complete filename, list all. diff --git a/resources/views/components/hexagon-grid.blade.php b/resources/views/components/hexagon-grid.blade.php deleted file mode 100644 index 6ac88fd..0000000 --- a/resources/views/components/hexagon-grid.blade.php +++ /dev/null @@ -1,50 +0,0 @@ -@props(['teams']) - - - -
- @foreach ($teams->chunk(6) as $rowIndex => $chunk) -
- @foreach ($chunk as $team) - - {{ $team->name }} - - @endforeach -
- @endforeach -
diff --git a/test_permissions.php b/test_permissions.php deleted file mode 100644 index bd50417..0000000 --- a/test_permissions.php +++ /dev/null @@ -1,83 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -echo "=== TEST DES PERMISSIONS DE SUPPRESSION ===\n\n"; - -try { - $admin = \App\Models\User::where('email', 'root@teamtask.com')->first(); - $user = \App\Models\User::where('email', '!=', 'root@teamtask.com')->first(); - $team = \App\Models\Team::first(); - $project = \App\Models\Project::first(); - $task = \App\Models\Task::first(); - $comment = \App\Models\Comment::first(); - - if (!$admin) { - echo "❌ Admin non trouvé\n"; - exit(1); - } - - echo "👤 Admin: " . $admin->name . " (" . $admin->email . ")\n"; - echo "👤 User: " . ($user ? $user->name . " (" . $user->email . ")" : "Aucun autre utilisateur") . "\n\n"; - - echo "=== PERMISSIONS ADMIN DU SITE ===\n"; - if ($team) { - echo "🏢 Peut supprimer équipe: " . ($admin->canDeleteTeam($team) ? "✅ OUI" : "❌ NON") . "\n"; - } - if ($project) { - echo "📂 Peut supprimer projet: " . ($admin->canDeleteProject($project) ? "✅ OUI" : "❌ NON") . "\n"; - } - if ($task) { - echo "📝 Peut supprimer tâche: " . ($admin->canDeleteTask($task) ? "✅ OUI" : "❌ NON") . "\n"; - } - if ($comment) { - echo "💬 Peut supprimer commentaire: " . ($admin->canDeleteComment($comment) ? "✅ OUI" : "❌ NON") . "\n"; - } - - if ($user) { - echo "\n=== PERMISSIONS UTILISATEUR NORMAL ===\n"; - if ($team) { - echo "🏢 Peut supprimer équipe: " . ($user->canDeleteTeam($team) ? "✅ OUI" : "❌ NON") . "\n"; - } - if ($project) { - echo "📂 Peut supprimer projet: " . ($user->canDeleteProject($project) ? "✅ OUI" : "❌ NON") . "\n"; - } - if ($task) { - echo "📝 Peut supprimer tâche: " . ($user->canDeleteTask($task) ? "✅ OUI" : "❌ NON") . "\n"; - } - if ($comment) { - echo "💬 Peut supprimer commentaire: " . ($user->canDeleteComment($comment) ? "✅ OUI" : "❌ NON") . "\n"; - } - } - - echo "\n=== TEST TEAM ADMIN ===\n"; - // Créer un admin d'équipe pour les tests - $teamAdmin = \App\Models\User::where('email', '!=', 'root@teamtask.com')->skip(1)->first(); - if ($teamAdmin && $team) { - // S'assurer qu'il soit admin de l'équipe - $team->users()->syncWithoutDetaching([$teamAdmin->id => ['role' => 'admin']]); - $teamAdmin->refresh(); - - echo "👤 Team Admin: " . $teamAdmin->name . "\n"; - echo "🏢 Peut supprimer équipe: " . ($teamAdmin->canDeleteTeam($team) ? "✅ OUI" : "❌ NON") . "\n"; - if ($project) { - echo "📂 Peut supprimer projet: " . ($teamAdmin->canDeleteProject($project) ? "✅ OUI" : "❌ NON") . "\n"; - } - if ($task) { - echo "📝 Peut supprimer tâche: " . ($teamAdmin->canDeleteTask($task) ? "✅ OUI" : "❌ NON") . "\n"; - } - if ($comment) { - echo "💬 Peut supprimer commentaire: " . ($teamAdmin->canDeleteComment($comment) ? "✅ OUI" : "❌ NON") . "\n"; - } - } - - echo "\n✅ Tests terminés avec succès !\n"; - -} catch (Exception $e) { - echo "❌ Erreur: " . $e->getMessage() . "\n"; - echo "📍 Fichier: " . $e->getFile() . ":" . $e->getLine() . "\n"; -} diff --git "a/upprimer \303\251quipe: \" . ($admin->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" "b/upprimer \303\251quipe: \" . ($admin->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" deleted file mode 100644 index 333a0b5..0000000 --- "a/upprimer \303\251quipe: \" . ($admin->canDeleteTeam($team) ? 'OUI' : 'NON') . \"n\"" +++ /dev/null @@ -1,258 +0,0 @@ - - SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS - - Commands marked with * may be preceded by a number, _N. - Notes in parentheses indicate the behavior if _N is given. - A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. - - h H Display this help. - q :q Q :Q ZZ Exit. - --------------------------------------------------------------------------- - - MMOOVVIINNGG - - e ^E j ^N CR * Forward one line (or _N lines). - y ^Y k ^K ^P * Backward one line (or _N lines). - f ^F ^V SPACE * Forward one window (or _N lines). - b ^B ESC-v * Backward one window (or _N lines). - z * Forward one window (and set window to _N). - w * Backward one window (and set window to _N). - ESC-SPACE * Forward one window, but don't stop at end-of-file. - d ^D * Forward one half-window (and set half-window to _N). - u ^U * Backward one half-window (and set half-window to _N). - ESC-) RightArrow * Right one half screen width (or _N positions). - ESC-( LeftArrow * Left one half screen width (or _N positions). - ESC-} ^RightArrow Right to last column displayed. - ESC-{ ^LeftArrow Left to first column. - F Forward forever; like "tail -f". - ESC-F Like F but stop when search pattern is found. - r ^R ^L Repaint screen. - R Repaint screen, discarding buffered input. - --------------------------------------------------- - Default "window" is the screen height. - Default "half-window" is half of the screen height. - --------------------------------------------------------------------------- - - SSEEAARRCCHHIINNGG - - /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. - ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. - n * Repeat previous search (for _N-th occurrence). - N * Repeat previous search in reverse direction. - ESC-n * Repeat previous search, spanning files. - ESC-N * Repeat previous search, reverse dir. & spanning files. - ESC-u Undo (toggle) search highlighting. - ESC-U Clear search highlighting. - &_p_a_t_t_e_r_n * Display only matching lines. - --------------------------------------------------- - A search pattern may begin with one or more of: - ^N or ! Search for NON-matching lines. - ^E or * Search multiple files (pass thru END OF FILE). - ^F or @ Start search at FIRST file (for /) or last file (for ?). - ^K Highlight matches, but don't move (KEEP position). - ^R Don't use REGULAR EXPRESSIONS. - ^W WRAP search if no match found. - --------------------------------------------------------------------------- - - JJUUMMPPIINNGG - - g < ESC-< * Go to first line in file (or line _N). - G > ESC-> * Go to last line in file (or line _N). - p % * Go to beginning of file (or _N percent into file). - t * Go to the (_N-th) next tag. - T * Go to the (_N-th) previous tag. - { ( [ * Find close bracket } ) ]. - } ) ] * Find open bracket { ( [. - ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. - ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. - --------------------------------------------------- - Each "find close bracket" command goes forward to the close bracket - matching the (_N-th) open bracket in the top line. - Each "find open bracket" command goes backward to the open bracket - matching the (_N-th) close bracket in the bottom line. - - m_<_l_e_t_t_e_r_> Mark the current top line with . - M_<_l_e_t_t_e_r_> Mark the current bottom line with . - '_<_l_e_t_t_e_r_> Go to a previously marked position. - '' Go to the previous position. - ^X^X Same as '. - ESC-M_<_l_e_t_t_e_r_> Clear a mark. - --------------------------------------------------- - A mark is any upper-case or lower-case letter. - Certain marks are predefined: - ^ means beginning of the file - $ means end of the file - --------------------------------------------------------------------------- - - CCHHAANNGGIINNGG FFIILLEESS - - :e [_f_i_l_e] Examine a new file. - ^X^V Same as :e. - :n * Examine the (_N-th) next file from the command line. - :p * Examine the (_N-th) previous file from the command line. - :x * Examine the first (or _N-th) file from the command line. - :d Delete the current file from the command line list. - = ^G :f Print current file name. - --------------------------------------------------------------------------- - - MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS - - -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. - --_<_n_a_m_e_> Toggle a command line option, by name. - __<_f_l_a_g_> Display the setting of a command line option. - ___<_n_a_m_e_> Display the setting of an option, by name. - +_c_m_d Execute the less cmd each time a new file is examined. - - !_c_o_m_m_a_n_d Execute the shell command with $SHELL. - |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. - s _f_i_l_e Save input to a file. - v Edit the current file with $VISUAL or $EDITOR. - V Print version number of "less". - --------------------------------------------------------------------------- - - OOPPTTIIOONNSS - - Most options may be changed either on the command line, - or from within less by using the - or -- command. - Options may be given in one of two forms: either a single - character preceded by a -, or a name preceded by --. - - -? ........ --help - Display help (from command line). - -a ........ --search-skip-screen - Search skips current screen. - -A ........ --SEARCH-SKIP-SCREEN - Search starts just after target line. - -b [_N] .... --buffers=[_N] - Number of buffers. - -B ........ --auto-buffers - Don't automatically allocate buffers for pipes. - -c ........ --clear-screen - Repaint by clearing rather than scrolling. - -d ........ --dumb - Dumb terminal. - -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r - Set screen colors. - -e -E .... --quit-at-eof --QUIT-AT-EOF - Quit at end of file. - -f ........ --force - Force open non-regular files. - -F ........ --quit-if-one-screen - Quit if entire file fits on first screen. - -g ........ --hilite-search - Highlight only last match for searches. - -G ........ --HILITE-SEARCH - Don't highlight any matches for searches. - -h [_N] .... --max-back-scroll=[_N] - Backward scroll limit. - -i ........ --ignore-case - Ignore case in searches that do not contain uppercase. - -I ........ --IGNORE-CASE - Ignore case in all searches. - -j [_N] .... --jump-target=[_N] - Screen position of target lines. - -J ........ --status-column - Display a status column at left edge of screen. - -k [_f_i_l_e] . --lesskey-file=[_f_i_l_e] - Use a lesskey file. - -K ........ --quit-on-intr - Exit less in response to ctrl-C. - -L ........ --no-lessopen - Ignore the LESSOPEN environment variable. - -m -M .... --long-prompt --LONG-PROMPT - Set prompt style. - -n -N .... --line-numbers --LINE-NUMBERS - Don't use line numbers. - -o [_f_i_l_e] . --log-file=[_f_i_l_e] - Copy to log file (standard input only). - -O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e] - Copy to log file (unconditionally overwrite). - -p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n] - Start at pattern (from command line). - -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] - Define new prompt. - -q -Q .... --quiet --QUIET --silent --SILENT - Quiet the terminal bell. - -r -R .... --raw-control-chars --RAW-CONTROL-CHARS - Output "raw" control characters. - -s ........ --squeeze-blank-lines - Squeeze multiple blank lines. - -S ........ --chop-long-lines - Chop (truncate) long lines rather than wrapping. - -t [_t_a_g] .. --tag=[_t_a_g] - Find a tag. - -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] - Use an alternate tags file. - -u -U .... --underline-special --UNDERLINE-SPECIAL - Change handling of backspaces. - -V ........ --version - Display the version number of "less". - -w ........ --hilite-unread - Highlight first new line after forward-screen. - -W ........ --HILITE-UNREAD - Highlight first new line after any forward movement. - -x [_N[,...]] --tabs=[_N[,...]] - Set tab stops. - -X ........ --no-init - Don't use termcap init/deinit strings. - -y [_N] .... --max-forw-scroll=[_N] - Forward scroll limit. - -z [_N] .... --window=[_N] - Set size of window. - -" [_c[_c]] . --quotes=[_c[_c]] - Set shell quote characters. - -~ ........ --tilde - Don't display tildes after end of file. - -# [_N] .... --shift=[_N] - Set horizontal scroll amount (0 = one half screen width). - --file-size - Automatically determine the size of the input file. - --follow-name - The F command changes files if the input file is renamed. - --incsearch - Search file as each pattern character is typed in. - --line-num-width=N - Set the width of the -N line number field to N characters. - --mouse - Enable mouse input. - --no-keypad - Don't send termcap keypad init/deinit strings. - --no-histdups - Remove duplicates from command history. - --rscroll=C - Set the character used to mark truncated lines. - --save-marks - Retain marks across invocations of less. - --status-col-width=N - Set the width of the -J status column to N characters. - --use-backslash - Subsequent options use backslash as escape char. - --use-color - Enables colored text. - --wheel-lines=N - Each click of the mouse wheel moves N lines. - - - --------------------------------------------------------------------------- - - LLIINNEE EEDDIITTIINNGG - - These keys can be used to edit text being entered - on the "command line" at the bottom of the screen. - - RightArrow ..................... ESC-l ... Move cursor right one character. - LeftArrow ...................... ESC-h ... Move cursor left one character. - ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. - ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. - HOME ........................... ESC-0 ... Move cursor to start of line. - END ............................ ESC-$ ... Move cursor to end of line. - BACKSPACE ................................ Delete char to left of cursor. - DELETE ......................... ESC-x ... Delete char under cursor. - ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. - ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. - ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. - UpArrow ........................ ESC-k ... Retrieve previous command line. - DownArrow ...................... ESC-j ... Retrieve next command line. - TAB ...................................... Complete filename & cycle. - SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. - ctrl-L ................................... Complete filename, list all.