adds roles
This commit is contained in:
86
app/Console/Commands/DevMenuCommand.php
Normal file
86
app/Console/Commands/DevMenuCommand.php
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
final class DevMenuCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'menu';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Developer tools menu';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
if (! in_array(app()->environment(), ['local', 'testing'])) {
|
||||||
|
$this->error('This command can only be run in local or testing environments.');
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('');
|
||||||
|
$this->info(' ╔═══════════════════════════════╗');
|
||||||
|
$this->info(' ║ Go No Go — Dev Tools ║');
|
||||||
|
$this->info(' ╚═══════════════════════════════╝');
|
||||||
|
$this->info('');
|
||||||
|
|
||||||
|
$choice = $this->choice('Select an action', [
|
||||||
|
0 => 'Exit',
|
||||||
|
1 => 'Fresh migrate, seed & build',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($choice === 'Exit') {
|
||||||
|
$this->info('Bye!');
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($choice === 'Fresh migrate, seed & build') {
|
||||||
|
$this->freshMigrateAndBuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs migrate:fresh with seeding, then runs npm build.
|
||||||
|
*
|
||||||
|
* Displays output from both processes and confirms success or failure.
|
||||||
|
*/
|
||||||
|
private function freshMigrateAndBuild(): void
|
||||||
|
{
|
||||||
|
$this->info('');
|
||||||
|
$this->comment('Running migrate:fresh --seed...');
|
||||||
|
$this->call('migrate:fresh', ['--seed' => true]);
|
||||||
|
|
||||||
|
$this->info('');
|
||||||
|
$this->comment('Running npm run build...');
|
||||||
|
|
||||||
|
$process = new Process(['npm', 'run', 'build']);
|
||||||
|
$process->setWorkingDirectory(base_path());
|
||||||
|
$process->setTimeout(120);
|
||||||
|
$process->run(function (string $type, string $output): void {
|
||||||
|
$this->output->write($output);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
$this->info('');
|
||||||
|
$this->info('Environment rebuilt successfully.');
|
||||||
|
} else {
|
||||||
|
$this->error('Build failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Role;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\ActivityLogger;
|
use App\Services\ActivityLogger;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
@@ -29,17 +30,25 @@ public function callback(): RedirectResponse
|
|||||||
{
|
{
|
||||||
$azureUser = Socialite::driver('azure')->user();
|
$azureUser = Socialite::driver('azure')->user();
|
||||||
|
|
||||||
$user = User::query()->firstOrCreate(
|
$user = User::query()->updateOrCreate(
|
||||||
['email' => $azureUser->getEmail()],
|
['email' => $azureUser->getEmail()],
|
||||||
[
|
[
|
||||||
'name' => $azureUser->getName(),
|
'name' => $azureUser->getName(),
|
||||||
'password' => null,
|
'azure_id' => $azureUser->getId(),
|
||||||
|
'photo' => $azureUser->getAvatar(),
|
||||||
|
'job_title' => Arr::get($azureUser->user, 'jobTitle'),
|
||||||
|
'department' => Arr::get($azureUser->user, 'department'),
|
||||||
|
'phone' => Arr::get($azureUser->user, 'mobilePhone', Arr::get($azureUser->user, 'businessPhones.0')),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($user->role_id === null) {
|
||||||
|
$user->update(['role_id' => Role::where('name', 'user')->first()->id]);
|
||||||
|
}
|
||||||
|
|
||||||
auth()->login($user);
|
auth()->login($user);
|
||||||
|
|
||||||
ActivityLogger::log('login', $user->id, metadata: ['email' => $user->email, 'firm_name' => Arr::get($azureUser, 'companyName')]);
|
ActivityLogger::log('login', $user->id, metadata: ['email' => $user->email, 'firm_name' => Arr::get($azureUser->user, 'companyName')]);
|
||||||
|
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,10 +68,6 @@ public function update(UpdateSessionRequest $request, Session $session): Redirec
|
|||||||
{
|
{
|
||||||
$validated = $request->validated();
|
$validated = $request->validated();
|
||||||
|
|
||||||
if (Arr::has($validated, 'basic_info')) {
|
|
||||||
$session->update(['basic_info' => Arr::get($validated, 'basic_info')]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Arr::has($validated, 'answers')) {
|
if (Arr::has($validated, 'answers')) {
|
||||||
$this->saveAnswers($session, Arr::get($validated, 'answers'));
|
$this->saveAnswers($session, Arr::get($validated, 'answers'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Inertia\Middleware;
|
use Inertia\Middleware;
|
||||||
|
use Laravel\Nova\Nova;
|
||||||
|
|
||||||
final class HandleInertiaRequests extends Middleware
|
final class HandleInertiaRequests extends Middleware
|
||||||
{
|
{
|
||||||
@@ -32,6 +34,7 @@ public function share(Request $request): array
|
|||||||
...parent::share($request),
|
...parent::share($request),
|
||||||
'auth' => [
|
'auth' => [
|
||||||
'user' => $this->getAuthenticatedUser(),
|
'user' => $this->getAuthenticatedUser(),
|
||||||
|
'logo_href' => $this->getLogoHref(),
|
||||||
],
|
],
|
||||||
'flash' => [
|
'flash' => [
|
||||||
'success' => fn () => Arr::get($request->session()->all(), 'success'),
|
'success' => fn () => Arr::get($request->session()->all(), 'success'),
|
||||||
@@ -57,4 +60,18 @@ private function getAuthenticatedUser(): ?array
|
|||||||
'email' => $user->email,
|
'email' => $user->email,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine logo href based on user Nova access.
|
||||||
|
*/
|
||||||
|
private function getLogoHref(): string
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
if ($user !== null && Gate::allows('viewNova', $user)) {
|
||||||
|
return Nova::path();
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,6 @@ public function authorize(): bool
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'basic_info' => ['sometimes', 'required', 'array'],
|
|
||||||
'basic_info.client_name' => ['required_with:basic_info', 'string', 'max:255'],
|
|
||||||
'basic_info.client_contact' => ['required_with:basic_info', 'string', 'max:255'],
|
|
||||||
'basic_info.lead_firm_name' => ['required_with:basic_info', 'string', 'max:255'],
|
|
||||||
'basic_info.lead_firm_contact' => ['required_with:basic_info', 'string', 'max:255'],
|
|
||||||
'answers' => ['sometimes', 'array'],
|
'answers' => ['sometimes', 'array'],
|
||||||
'answers.*.value' => ['nullable', 'string', 'in:yes,no,not_applicable'],
|
'answers.*.value' => ['nullable', 'string', 'in:yes,no,not_applicable'],
|
||||||
'answers.*.text_value' => ['nullable', 'string', 'max:10000'],
|
'answers.*.text_value' => ['nullable', 'string', 'max:10000'],
|
||||||
@@ -41,20 +36,6 @@ public function rules(): array
|
|||||||
public function messages(): array
|
public function messages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'basic_info.required' => 'Basic information is required.',
|
|
||||||
'basic_info.array' => 'Basic information must be a valid data structure.',
|
|
||||||
'basic_info.client_name.required_with' => 'The client name is required.',
|
|
||||||
'basic_info.client_name.string' => 'The client name must be text.',
|
|
||||||
'basic_info.client_name.max' => 'The client name cannot exceed 255 characters.',
|
|
||||||
'basic_info.client_contact.required_with' => 'The client contact is required.',
|
|
||||||
'basic_info.client_contact.string' => 'The client contact must be text.',
|
|
||||||
'basic_info.client_contact.max' => 'The client contact cannot exceed 255 characters.',
|
|
||||||
'basic_info.lead_firm_name.required_with' => 'The lead firm name is required.',
|
|
||||||
'basic_info.lead_firm_name.string' => 'The lead firm name must be text.',
|
|
||||||
'basic_info.lead_firm_name.max' => 'The lead firm name cannot exceed 255 characters.',
|
|
||||||
'basic_info.lead_firm_contact.required_with' => 'The lead firm contact is required.',
|
|
||||||
'basic_info.lead_firm_contact.string' => 'The lead firm contact must be text.',
|
|
||||||
'basic_info.lead_firm_contact.max' => 'The lead firm contact cannot exceed 255 characters.',
|
|
||||||
'answers.array' => 'Answers must be a valid data structure.',
|
'answers.array' => 'Answers must be a valid data structure.',
|
||||||
'answers.*.value.in' => 'Answer value must be yes, no, or not_applicable.',
|
'answers.*.value.in' => 'Answer value must be yes, no, or not_applicable.',
|
||||||
'answers.*.text_value.string' => 'Answer text must be text.',
|
'answers.*.text_value.string' => 'Answer text must be text.',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
final class Question extends Model
|
final class Question extends Model
|
||||||
{
|
{
|
||||||
@@ -48,4 +49,12 @@ public function questionGroup(): BelongsTo
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(QuestionGroup::class);
|
return $this->belongsTo(QuestionGroup::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all answers for this question.
|
||||||
|
*/
|
||||||
|
public function answers(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Answer::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
app/Models/Role.php
Normal file
23
app/Models/Role.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
final class Role extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all users with this role.
|
||||||
|
*/
|
||||||
|
public function users(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@ final class Session extends Model
|
|||||||
'status',
|
'status',
|
||||||
'score',
|
'score',
|
||||||
'result',
|
'result',
|
||||||
'basic_info',
|
|
||||||
'additional_comments',
|
'additional_comments',
|
||||||
'completed_at',
|
'completed_at',
|
||||||
];
|
];
|
||||||
@@ -40,7 +39,6 @@ protected function casts(): array
|
|||||||
'category_id' => 'integer',
|
'category_id' => 'integer',
|
||||||
'screening_id' => 'integer',
|
'screening_id' => 'integer',
|
||||||
'score' => 'integer',
|
'score' => 'integer',
|
||||||
'basic_info' => 'array',
|
|
||||||
'completed_at' => 'datetime',
|
'completed_at' => 'datetime',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
@@ -23,6 +24,12 @@ final class User extends Authenticatable
|
|||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
|
'azure_id',
|
||||||
|
'photo',
|
||||||
|
'job_title',
|
||||||
|
'department',
|
||||||
|
'phone',
|
||||||
|
'role_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,6 +40,7 @@ final class User extends Authenticatable
|
|||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password',
|
'password',
|
||||||
'remember_token',
|
'remember_token',
|
||||||
|
'azure_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,9 +53,18 @@ protected function casts(): array
|
|||||||
return [
|
return [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
|
'role_id' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the role assigned to this user.
|
||||||
|
*/
|
||||||
|
public function role(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Role::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all sessions for this user.
|
* Get all sessions for this user.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,6 +49,22 @@ final class AnswerResource extends Resource
|
|||||||
*/
|
*/
|
||||||
public static $with = ['session', 'question'];
|
public static $with = ['session', 'question'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return 'Answers';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return 'Answer';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fields displayed by the resource.
|
* Get the fields displayed by the resource.
|
||||||
*
|
*
|
||||||
@@ -76,6 +92,7 @@ public function fields(NovaRequest $request): array
|
|||||||
->rules('nullable', 'max:255'),
|
->rules('nullable', 'max:255'),
|
||||||
|
|
||||||
Textarea::make('Text Value')
|
Textarea::make('Text Value')
|
||||||
|
->alwaysShow()
|
||||||
->rules('nullable'),
|
->rules('nullable'),
|
||||||
|
|
||||||
DateTime::make('Created At')
|
DateTime::make('Created At')
|
||||||
|
|||||||
@@ -42,6 +42,22 @@ final class CategoryResource extends Resource
|
|||||||
*/
|
*/
|
||||||
public static $displayInNavigation = false;
|
public static $displayInNavigation = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return 'Categories';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return 'Category';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fields displayed by the resource.
|
* Get the fields displayed by the resource.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -56,6 +56,22 @@ final class LogResource extends Resource
|
|||||||
*/
|
*/
|
||||||
public static $with = ['user', 'session', 'category'];
|
public static $with = ['user', 'session', 'category'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return 'Logs';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return 'Log';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fields displayed by the resource.
|
* Get the fields displayed by the resource.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -44,6 +44,22 @@ final class QuestionGroupResource extends Resource
|
|||||||
*/
|
*/
|
||||||
public static $displayInNavigation = false;
|
public static $displayInNavigation = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return 'Question Groups';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return 'Question Group';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fields displayed by the resource.
|
* Get the fields displayed by the resource.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
use Laravel\Nova\Fields\BelongsTo;
|
use Laravel\Nova\Fields\BelongsTo;
|
||||||
use Laravel\Nova\Fields\Boolean;
|
use Laravel\Nova\Fields\Boolean;
|
||||||
use Laravel\Nova\Fields\DateTime;
|
use Laravel\Nova\Fields\DateTime;
|
||||||
|
use Laravel\Nova\Fields\HasMany;
|
||||||
use Laravel\Nova\Fields\ID;
|
use Laravel\Nova\Fields\ID;
|
||||||
use Laravel\Nova\Fields\Number;
|
use Laravel\Nova\Fields\Number;
|
||||||
use Laravel\Nova\Fields\Text;
|
use Laravel\Nova\Fields\Text;
|
||||||
@@ -51,6 +52,22 @@ final class QuestionResource extends Resource
|
|||||||
*/
|
*/
|
||||||
public static $group = 'Questionnaire';
|
public static $group = 'Questionnaire';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return 'Questions';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return 'Question';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fields displayed by the resource.
|
* Get the fields displayed by the resource.
|
||||||
*
|
*
|
||||||
@@ -111,6 +128,8 @@ public function fields(NovaRequest $request): array
|
|||||||
->exceptOnForms()
|
->exceptOnForms()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable(),
|
->filterable(),
|
||||||
|
|
||||||
|
HasMany::make('Answers', 'answers', AnswerResource::class),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
75
app/Nova/RoleResource.php
Normal file
75
app/Nova/RoleResource.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Nova;
|
||||||
|
|
||||||
|
use Laravel\Nova\Fields\DateTime;
|
||||||
|
use Laravel\Nova\Fields\HasMany;
|
||||||
|
use Laravel\Nova\Fields\ID;
|
||||||
|
use Laravel\Nova\Fields\Text;
|
||||||
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
|
|
||||||
|
final class RoleResource extends Resource
|
||||||
|
{
|
||||||
|
public static string $model = \App\Models\Role::class;
|
||||||
|
|
||||||
|
public static $title = 'name';
|
||||||
|
|
||||||
|
public static $search = ['id', 'name'];
|
||||||
|
|
||||||
|
public static $displayInNavigation = false;
|
||||||
|
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return 'Roles';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return 'Role';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fields(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
ID::make()->sortable(),
|
||||||
|
|
||||||
|
Text::make('Name')
|
||||||
|
->sortable()
|
||||||
|
->filterable()
|
||||||
|
->copyable()
|
||||||
|
->rules('required', 'max:255'),
|
||||||
|
|
||||||
|
DateTime::make('Created At')
|
||||||
|
->exceptOnForms()
|
||||||
|
->sortable(),
|
||||||
|
|
||||||
|
DateTime::make('Updated At')
|
||||||
|
->exceptOnForms()
|
||||||
|
->sortable(),
|
||||||
|
|
||||||
|
HasMany::make('Users', 'users', User::class),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cards(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filters(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lenses(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actions(NovaRequest $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,22 @@ final class ScreeningResource extends Resource
|
|||||||
*/
|
*/
|
||||||
public static $with = ['user'];
|
public static $with = ['user'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return 'Screenings';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return 'Screening';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fields displayed by the resource.
|
* Get the fields displayed by the resource.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,12 +5,11 @@
|
|||||||
namespace App\Nova;
|
namespace App\Nova;
|
||||||
|
|
||||||
use Laravel\Nova\Fields\BelongsTo;
|
use Laravel\Nova\Fields\BelongsTo;
|
||||||
use Laravel\Nova\Fields\Code;
|
|
||||||
use Laravel\Nova\Fields\DateTime;
|
use Laravel\Nova\Fields\DateTime;
|
||||||
use Laravel\Nova\Fields\HasMany;
|
use Laravel\Nova\Fields\HasMany;
|
||||||
use Laravel\Nova\Fields\ID;
|
use Laravel\Nova\Fields\ID;
|
||||||
use Laravel\Nova\Fields\Number;
|
use Laravel\Nova\Fields\Number;
|
||||||
use Laravel\Nova\Fields\Text;
|
use Laravel\Nova\Fields\Select;
|
||||||
use Laravel\Nova\Fields\Textarea;
|
use Laravel\Nova\Fields\Textarea;
|
||||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||||
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
use Maatwebsite\LaravelNovaExcel\Actions\DownloadExcel;
|
||||||
@@ -59,6 +58,22 @@ final class SessionResource extends Resource
|
|||||||
*/
|
*/
|
||||||
public static $with = ['user', 'category', 'screening'];
|
public static $with = ['user', 'category', 'screening'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable label of the resource.
|
||||||
|
*/
|
||||||
|
public static function label(): string
|
||||||
|
{
|
||||||
|
return 'Sessions';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the displayable singular label of the resource.
|
||||||
|
*/
|
||||||
|
public static function singularLabel(): string
|
||||||
|
{
|
||||||
|
return 'Session';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fields displayed by the resource.
|
* Get the fields displayed by the resource.
|
||||||
*
|
*
|
||||||
@@ -85,11 +100,16 @@ public function fields(NovaRequest $request): array
|
|||||||
->filterable()
|
->filterable()
|
||||||
->rules('nullable'),
|
->rules('nullable'),
|
||||||
|
|
||||||
Text::make('Status')
|
Select::make('Status')
|
||||||
|
->options([
|
||||||
|
'in_progress' => 'In Progress',
|
||||||
|
'completed' => 'Completed',
|
||||||
|
'abandoned' => 'Abandoned',
|
||||||
|
])
|
||||||
|
->displayUsingLabels()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->readonly(),
|
||||||
->rules('required', 'max:255'),
|
|
||||||
|
|
||||||
Number::make('Score')
|
Number::make('Score')
|
||||||
->sortable()
|
->sortable()
|
||||||
@@ -97,15 +117,16 @@ public function fields(NovaRequest $request): array
|
|||||||
->copyable()
|
->copyable()
|
||||||
->rules('nullable', 'integer'),
|
->rules('nullable', 'integer'),
|
||||||
|
|
||||||
Text::make('Result')
|
Select::make('Result')
|
||||||
|
->options([
|
||||||
|
'go' => 'Go',
|
||||||
|
'no_go' => 'No Go',
|
||||||
|
'consult_leadership' => 'Consult Leadership',
|
||||||
|
])
|
||||||
|
->displayUsingLabels()
|
||||||
->sortable()
|
->sortable()
|
||||||
->filterable()
|
->filterable()
|
||||||
->copyable()
|
->readonly(),
|
||||||
->rules('nullable', 'max:255'),
|
|
||||||
|
|
||||||
Code::make('Basic Info', 'basic_info')
|
|
||||||
->json()
|
|
||||||
->rules('nullable'),
|
|
||||||
|
|
||||||
Textarea::make('Additional Comments')
|
Textarea::make('Additional Comments')
|
||||||
->rules('nullable'),
|
->rules('nullable'),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Laravel\Nova\Auth\PasswordValidationRules;
|
use Laravel\Nova\Auth\PasswordValidationRules;
|
||||||
|
use Laravel\Nova\Fields\BelongsTo;
|
||||||
use Laravel\Nova\Fields\ID;
|
use Laravel\Nova\Fields\ID;
|
||||||
use Laravel\Nova\Fields\Password;
|
use Laravel\Nova\Fields\Password;
|
||||||
use Laravel\Nova\Fields\Text;
|
use Laravel\Nova\Fields\Text;
|
||||||
@@ -35,7 +36,7 @@ final class User extends Resource
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public static $search = [
|
public static $search = [
|
||||||
'id', 'name', 'email',
|
'id', 'name', 'email', 'department', 'job_title',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,6 +49,10 @@ public function fields(NovaRequest $request): array
|
|||||||
return [
|
return [
|
||||||
ID::make()->sortable(),
|
ID::make()->sortable(),
|
||||||
|
|
||||||
|
BelongsTo::make('Role', 'role', RoleResource::class)
|
||||||
|
->sortable()
|
||||||
|
->filterable(),
|
||||||
|
|
||||||
Text::make('Name')
|
Text::make('Name')
|
||||||
->sortable()
|
->sortable()
|
||||||
->rules('required', 'max:255'),
|
->rules('required', 'max:255'),
|
||||||
@@ -58,6 +63,31 @@ public function fields(NovaRequest $request): array
|
|||||||
->creationRules('unique:users,email')
|
->creationRules('unique:users,email')
|
||||||
->updateRules('unique:users,email,{{resourceId}}'),
|
->updateRules('unique:users,email,{{resourceId}}'),
|
||||||
|
|
||||||
|
Text::make('Azure ID', 'azure_id')
|
||||||
|
->onlyOnDetail()
|
||||||
|
->copyable(),
|
||||||
|
|
||||||
|
Text::make('Photo', 'photo')
|
||||||
|
->onlyOnDetail()
|
||||||
|
->copyable(),
|
||||||
|
|
||||||
|
Text::make('Job Title', 'job_title')
|
||||||
|
->sortable()
|
||||||
|
->filterable()
|
||||||
|
->copyable()
|
||||||
|
->readonly(),
|
||||||
|
|
||||||
|
Text::make('Department')
|
||||||
|
->sortable()
|
||||||
|
->filterable()
|
||||||
|
->copyable()
|
||||||
|
->readonly(),
|
||||||
|
|
||||||
|
Text::make('Phone')
|
||||||
|
->sortable()
|
||||||
|
->copyable()
|
||||||
|
->readonly(),
|
||||||
|
|
||||||
Password::make('Password')
|
Password::make('Password')
|
||||||
->onlyOnForms()
|
->onlyOnForms()
|
||||||
->creationRules($this->passwordRules())
|
->creationRules($this->passwordRules())
|
||||||
|
|||||||
@@ -3,8 +3,16 @@
|
|||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Nova\Dashboards\Main;
|
||||||
|
use App\Nova\LogResource;
|
||||||
|
use App\Nova\QuestionResource;
|
||||||
|
use App\Nova\ScreeningResource;
|
||||||
|
use App\Nova\SessionResource;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Laravel\Fortify\Features;
|
use Laravel\Fortify\Features;
|
||||||
|
use Laravel\Nova\Menu\MenuItem;
|
||||||
|
use Laravel\Nova\Menu\MenuSection;
|
||||||
use Laravel\Nova\Nova;
|
use Laravel\Nova\Nova;
|
||||||
use Laravel\Nova\NovaApplicationServiceProvider;
|
use Laravel\Nova\NovaApplicationServiceProvider;
|
||||||
|
|
||||||
@@ -17,7 +25,25 @@ public function boot(): void
|
|||||||
{
|
{
|
||||||
parent::boot();
|
parent::boot();
|
||||||
|
|
||||||
//
|
Nova::mainMenu(function (Request $request) {
|
||||||
|
return [
|
||||||
|
MenuSection::dashboard(Main::class)->icon('home'),
|
||||||
|
|
||||||
|
MenuSection::make('Questionnaire', [
|
||||||
|
MenuItem::resource(QuestionResource::class),
|
||||||
|
MenuItem::resource(ScreeningResource::class),
|
||||||
|
MenuItem::resource(SessionResource::class),
|
||||||
|
])->icon('clipboard-document-list')->collapsible(),
|
||||||
|
|
||||||
|
MenuSection::make('Logs', [
|
||||||
|
MenuItem::resource(LogResource::class),
|
||||||
|
])->icon('chart-bar')->collapsible(),
|
||||||
|
|
||||||
|
MenuSection::make('Users', [
|
||||||
|
MenuItem::resource(\App\Nova\User::class),
|
||||||
|
])->icon('users')->collapsible(),
|
||||||
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ public function definition(): array
|
|||||||
'status' => 'in_progress',
|
'status' => 'in_progress',
|
||||||
'score' => null,
|
'score' => null,
|
||||||
'result' => null,
|
'result' => null,
|
||||||
'basic_info' => null,
|
|
||||||
'additional_comments' => null,
|
'additional_comments' => null,
|
||||||
'completed_at' => null,
|
'completed_at' => null,
|
||||||
];
|
];
|
||||||
|
|||||||
32
database/migrations/0000_00_00_000000_create_roles_table.php
Normal file
32
database/migrations/0000_00_00_000000_create_roles_table.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('roles', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name')->unique();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
$now = now();
|
||||||
|
|
||||||
|
DB::table('roles')->insert([
|
||||||
|
['name' => 'user', 'created_at' => $now, 'updated_at' => $now],
|
||||||
|
['name' => 'admin', 'created_at' => $now, 'updated_at' => $now],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('roles');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -13,10 +13,16 @@ public function up(): void
|
|||||||
{
|
{
|
||||||
Schema::create('users', function (Blueprint $table) {
|
Schema::create('users', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->foreignId('role_id')->default(1)->constrained();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->string('email')->unique();
|
$table->string('email')->unique();
|
||||||
|
$table->string('azure_id')->nullable()->unique();
|
||||||
|
$table->string('photo')->nullable();
|
||||||
|
$table->string('job_title')->nullable();
|
||||||
|
$table->string('department')->nullable();
|
||||||
|
$table->string('phone')->nullable();
|
||||||
$table->timestamp('email_verified_at')->nullable();
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
$table->string('password');
|
$table->string('password')->nullable();
|
||||||
$table->rememberToken();
|
$table->rememberToken();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ public function up(): void
|
|||||||
$table->string('status', 50)->default('in_progress');
|
$table->string('status', 50)->default('in_progress');
|
||||||
$table->integer('score')->nullable();
|
$table->integer('score')->nullable();
|
||||||
$table->string('result', 50)->nullable();
|
$table->string('result', 50)->nullable();
|
||||||
$table->json('basic_info')->nullable();
|
|
||||||
$table->text('additional_comments')->nullable();
|
$table->text('additional_comments')->nullable();
|
||||||
$table->timestamp('completed_at')->nullable();
|
$table->timestamp('completed_at')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\Role;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
class JonathanSeeder extends Seeder
|
final class JonathanSeeder extends Seeder
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Seed the application's database.
|
* Seed the application's database with admin user Jonathan.
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
|
$adminRole = Role::where('name', 'admin')->first();
|
||||||
|
|
||||||
User::factory()->create([
|
User::factory()->create([
|
||||||
'name' => 'Jonathan',
|
'name' => 'Jonathan',
|
||||||
'email' => 'jonathan@blijnder.nl',
|
'email' => 'jonathan@blijnder.nl',
|
||||||
'password' => bcrypt('secret'),
|
'password' => bcrypt('secret'),
|
||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
|
'role_id' => $adminRole->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ const buttonClasses = computed(() => {
|
|||||||
|
|
||||||
// Size classes
|
// Size classes
|
||||||
if (props.size === 'sm') {
|
if (props.size === 'sm') {
|
||||||
classes.push('px-3 py-1.5 text-sm')
|
classes.push('px-4 py-2 text-sm')
|
||||||
} else if (props.size === 'md') {
|
} else if (props.size === 'md') {
|
||||||
classes.push('px-5 py-2.5 text-base')
|
classes.push('px-6 py-3 text-base')
|
||||||
} else if (props.size === 'lg') {
|
} else if (props.size === 'lg') {
|
||||||
classes.push('px-7 py-3 text-lg')
|
classes.push('px-8 py-3.5 text-lg')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variant classes
|
// Variant classes
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { usePage } from '@inertiajs/vue3'
|
||||||
import AppLogo from '@/Components/AppLogo.vue'
|
import AppLogo from '@/Components/AppLogo.vue'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
@@ -7,12 +9,17 @@ defineProps({
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const page = usePage()
|
||||||
|
const logoHref = computed(() => page.props.auth?.logo_href ?? '/')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="sticky top-0 z-50 bg-surface border-b border-gray-700">
|
<header class="sticky top-0 z-50 bg-surface border-b border-gray-700">
|
||||||
<div class="px-6 py-4 flex items-center gap-6">
|
<div class="px-6 py-4 flex items-center gap-6">
|
||||||
|
<a :href="logoHref" class="flex items-center">
|
||||||
<AppLogo class="text-2xl" />
|
<AppLogo class="text-2xl" />
|
||||||
|
</a>
|
||||||
<h1 v-if="title" class="text-xl font-semibold text-white">
|
<h1 v-if="title" class="text-xl font-semibold text-white">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -28,9 +28,12 @@ const handleChange = (value) => {
|
|||||||
|
|
||||||
const getSegmentClasses = (index) => {
|
const getSegmentClasses = (index) => {
|
||||||
const classes = [
|
const classes = [
|
||||||
'px-6',
|
'inline-flex',
|
||||||
'py-2.5',
|
'items-center',
|
||||||
'text-sm',
|
'justify-center',
|
||||||
|
'px-8',
|
||||||
|
'py-3',
|
||||||
|
'text-base',
|
||||||
'font-medium',
|
'font-medium',
|
||||||
'select-none',
|
'select-none',
|
||||||
'transition-all',
|
'transition-all',
|
||||||
@@ -64,7 +67,7 @@ const getSegmentClasses = (index) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div role="radiogroup" :aria-label="name" class="inline-flex rounded-lg overflow-hidden border border-white/10">
|
<div role="radiogroup" :aria-label="name" class="inline-flex rounded-lg overflow-hidden border border-white/10 min-h-[44px]">
|
||||||
<label
|
<label
|
||||||
v-for="(option, index) in options"
|
v-for="(option, index) in options"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
|
|||||||
@@ -91,14 +91,6 @@ const resultDisplay = computed(() => {
|
|||||||
<dt class="text-gray-400">Category</dt>
|
<dt class="text-gray-400">Category</dt>
|
||||||
<dd class="text-white font-medium">{{ categoryName }}</dd>
|
<dd class="text-white font-medium">{{ categoryName }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<dt class="text-gray-400">Client</dt>
|
|
||||||
<dd class="text-white font-medium">{{ session.basic_info?.client_name ?? 'N/A' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt class="text-gray-400">Lead Firm</dt>
|
|
||||||
<dd class="text-white font-medium">{{ session.basic_info?.lead_firm_name ?? 'N/A' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-400">Completed</dt>
|
<dt class="text-gray-400">Completed</dt>
|
||||||
<dd class="text-white font-medium">{{ new Date(session.completed_at).toLocaleDateString() }}</dd>
|
<dd class="text-white font-medium">{{ new Date(session.completed_at).toLocaleDateString() }}</dd>
|
||||||
|
|||||||
@@ -78,32 +78,6 @@ public function test_show_returns_inertia_props(): void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_can_save_basic_info(): void
|
|
||||||
{
|
|
||||||
$user = $this->createAuthenticatedUser();
|
|
||||||
$category = Category::factory()->create();
|
|
||||||
|
|
||||||
$session = Session::factory()->create([
|
|
||||||
'user_id' => $user->id,
|
|
||||||
'category_id' => $category->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$basicInfo = [
|
|
||||||
'client_name' => 'Test Client',
|
|
||||||
'client_contact' => 'client@example.com',
|
|
||||||
'lead_firm_name' => 'Test Firm',
|
|
||||||
'lead_firm_contact' => 'firm@example.com',
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->put("/sessions/{$session->id}", [
|
|
||||||
'basic_info' => $basicInfo,
|
|
||||||
])->assertRedirect();
|
|
||||||
|
|
||||||
$session->refresh();
|
|
||||||
|
|
||||||
$this->assertEquals($basicInfo, $session->basic_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_can_save_answers(): void
|
public function test_can_save_answers(): void
|
||||||
{
|
{
|
||||||
$user = $this->createAuthenticatedUser();
|
$user = $this->createAuthenticatedUser();
|
||||||
|
|||||||
Reference in New Issue
Block a user