fixing bugs

This commit is contained in:
2026-02-16 15:09:41 +01:00
parent c39b8085af
commit fb1c28a0ba
14 changed files with 238 additions and 19 deletions

View File

@@ -191,7 +191,7 @@ private function validateDetailsAnswer($question, $answer, array &$errors): void
} }
} }
if (! $hasRadioButtons && $details !== null && $details !== '') { if (! $hasRadioButtons && $details !== null && $details !== '' && $details !== 'optional') {
if (! $answer || empty(trim(Arr::get($answer->toArray(), 'text_value', '')))) { if (! $answer || empty(trim(Arr::get($answer->toArray(), 'text_value', '')))) {
$errors[] = "Question '{$question->text}' requires a text response."; $errors[] = "Question '{$question->text}' requires a text response.";
} }

View File

@@ -14,6 +14,7 @@
final class AnswerResource extends Resource final class AnswerResource extends Resource
{ {
/** /**
* The model the resource corresponds to. * The model the resource corresponds to.
* *

View File

@@ -1,11 +1,16 @@
<?php <?php
declare(strict_types=1);
namespace App\Nova\Dashboards; namespace App\Nova\Dashboards;
use Laravel\Nova\Cards\Help; use App\Nova\Metrics\ScreeningsTrend;
use App\Nova\Metrics\SessionsTrend;
use App\Nova\Metrics\TotalScreenings;
use App\Nova\Metrics\TotalSessions;
use Laravel\Nova\Dashboards\Main as Dashboard; use Laravel\Nova\Dashboards\Main as Dashboard;
class Main extends Dashboard final class Main extends Dashboard
{ {
/** /**
* Get the cards for the dashboard. * Get the cards for the dashboard.
@@ -15,7 +20,10 @@ class Main extends Dashboard
public function cards(): array public function cards(): array
{ {
return [ return [
new Help, new TotalSessions,
new TotalScreenings,
new SessionsTrend,
new ScreeningsTrend,
]; ];
} }
} }

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Nova\Metrics;
use App\Models\Screening;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Metrics\Trend;
final class ScreeningsTrend extends Trend
{
public ?int $cacheFor = null;
public function calculate(NovaRequest $request): mixed
{
return $this->countByDays($request, Screening::class);
}
public function name(): string
{
return 'Screenings';
}
public function ranges(): array
{
return [
30 => '30 Days',
60 => '60 Days',
90 => '90 Days',
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Nova\Metrics;
use App\Models\Session;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Metrics\Trend;
final class SessionsTrend extends Trend
{
public function calculate(NovaRequest $request): mixed
{
return $this->countByDays($request, Session::class);
}
public function name(): string
{
return 'Sessions';
}
public function ranges(): array
{
return [
30 => '30 Days',
60 => '60 Days',
90 => '90 Days',
];
}
public $cacheFor = null;
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Nova\Metrics;
use App\Models\Screening;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Metrics\Value;
final class TotalScreenings extends Value
{
/**
* Calculate the value of the metric.
*/
public function calculate(NovaRequest $request): mixed
{
return $this->count($request, Screening::class);
}
/**
* Get the displayable name of the metric.
*/
public function name(): string
{
return 'Total Screenings';
}
/**
* Get the ranges available for the metric.
*/
public function ranges(): array
{
return [
30 => '30 Days',
60 => '60 Days',
365 => '365 Days',
'TODAY' => 'Today',
'ALL' => 'All Time',
];
}
/**
* Determine the amount of time the results should be cached.
*/
public $cacheFor = null;
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Nova\Metrics;
use App\Models\Session;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Metrics\Value;
final class TotalSessions extends Value
{
public function calculate(NovaRequest $request): mixed
{
return $this->count($request, Session::class);
}
public function name(): string
{
return 'Total Sessions';
}
public function ranges(): array
{
return [
30 => '30 Days',
60 => '60 Days',
365 => '365 Days',
'TODAY' => 'Today',
'ALL' => 'All Time',
];
}
public $cacheFor = null;
}

View File

@@ -42,7 +42,7 @@ final class QuestionGroupResource extends Resource
* *
* @var bool * @var bool
*/ */
public static $displayInNavigation = false; public static $displayInNavigation = true;
/** /**
* Get the displayable label of the resource. * Get the displayable label of the resource.

View File

@@ -9,6 +9,17 @@
abstract class Resource extends NovaResource abstract class Resource extends NovaResource
{ {
public static function perPageOptions()
{
return [50, 100, 150];
}
public static function perPageViaRelationshipOptions()
{
return [10, 25, 50];
}
/** /**
* Build an "index" query for the given resource. * Build an "index" query for the given resource.
*/ */

View File

@@ -108,8 +108,7 @@ public function fields(NovaRequest $request): array
]) ])
->displayUsingLabels() ->displayUsingLabels()
->sortable() ->sortable()
->filterable() ->filterable(),
->readonly(),
Number::make('Score') Number::make('Score')
->sortable() ->sortable()
@@ -125,8 +124,7 @@ public function fields(NovaRequest $request): array
]) ])
->displayUsingLabels() ->displayUsingLabels()
->sortable() ->sortable()
->filterable() ->filterable(),
->readonly(),
Textarea::make('Additional Comments') Textarea::make('Additional Comments')
->rules('nullable'), ->rules('nullable'),

View File

@@ -74,19 +74,16 @@ public function fields(NovaRequest $request): array
Text::make('Job Title', 'job_title') Text::make('Job Title', 'job_title')
->sortable() ->sortable()
->filterable() ->filterable()
->copyable() ->copyable(),
->readonly(),
Text::make('Department') Text::make('Department')
->sortable() ->sortable()
->filterable() ->filterable()
->copyable() ->copyable(),
->readonly(),
Text::make('Phone') Text::make('Phone')
->sortable() ->sortable()
->copyable() ->copyable(),
->readonly(),
Password::make('Password') Password::make('Password')
->onlyOnForms() ->onlyOnForms()

View File

@@ -6,6 +6,7 @@
use App\Nova\CategoryResource; use App\Nova\CategoryResource;
use App\Nova\Dashboards\Main; use App\Nova\Dashboards\Main;
use App\Nova\LogResource; use App\Nova\LogResource;
use App\Nova\QuestionGroupResource;
use App\Nova\QuestionResource; use App\Nova\QuestionResource;
use App\Nova\ScreeningResource; use App\Nova\ScreeningResource;
use App\Nova\SessionResource; use App\Nova\SessionResource;
@@ -31,10 +32,11 @@ public function boot(): void
MenuSection::dashboard(Main::class)->icon('home'), MenuSection::dashboard(Main::class)->icon('home'),
MenuSection::make('Questionnaire', [ MenuSection::make('Questionnaire', [
MenuItem::resource(CategoryResource::class),
MenuItem::resource(QuestionResource::class), MenuItem::resource(QuestionResource::class),
MenuItem::resource(ScreeningResource::class), MenuItem::resource(QuestionGroupResource::class),
MenuItem::resource(CategoryResource::class),
MenuItem::resource(SessionResource::class), MenuItem::resource(SessionResource::class),
MenuItem::resource(ScreeningResource::class),
])->icon('clipboard-document-list')->collapsible(), ])->icon('clipboard-document-list')->collapsible(),
MenuSection::make('Logs', [ MenuSection::make('Logs', [

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
final class TestCategorySeeder extends Seeder
{
/**
* Seed a minimal test category with one question group and five scored questions for quick testing.
*/
public function run(): void
{
$categoryId = DB::table('categories')->insertGetId([
'name' => 'Test',
'sort_order' => 99,
'created_at' => now(),
'updated_at' => now(),
]);
$groupId = DB::table('question_groups')->insertGetId([
'category_id' => $categoryId,
'name' => 'Test Group',
'sort_order' => 1,
'description' => null,
'scoring_instructions' => null,
'created_at' => now(),
'updated_at' => now(),
]);
$questions = [];
for ($i = 1; $i <= 5; $i++) {
$questions[] = [
'question_group_id' => $groupId,
'text' => "Test question {$i}",
'has_yes' => true,
'has_no' => true,
'has_na' => true,
'details' => 'optional',
'sort_order' => $i,
'is_scored' => true,
'created_at' => now(),
'updated_at' => now(),
];
}
DB::table('questions')->insert($questions);
}
}

View File

@@ -118,8 +118,8 @@ const validate = () => {
return return
} }
// Rule 3: Text-only questions (no radio buttons, has details) // Rule 3: Text-only questions (no radio buttons, has required details)
if (!hasRadioButtons && question.details && !answer.text_value?.trim()) { if (!hasRadioButtons && question.details && question.details !== 'optional' && !answer.text_value?.trim()) {
errors[question.id] = 'Please enter a response' errors[question.id] = 'Please enter a response'
return return
} }
@@ -161,7 +161,9 @@ const completeSession = async () => {
} }
completing = true completing = true
clearTimeout(saveTimeout)
router.put(`/sessions/${props.session.id}`, { router.put(`/sessions/${props.session.id}`, {
answers: { ...answerData },
complete: true, complete: true,
}) })
} }