New report system and views
This commit is contained in:
@@ -827,6 +827,6 @@ public function destroy(Request $request, Import $import)
|
||||
|
||||
$import->delete();
|
||||
|
||||
return back()->with(['ok' => true]);
|
||||
return back()->with('success', 'Import deleted successfully');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Reports\ReportRegistry;
|
||||
use App\Models\Report;
|
||||
use App\Services\ReportQueryBuilder;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
@@ -10,15 +11,19 @@
|
||||
|
||||
class ReportController extends Controller
|
||||
{
|
||||
public function __construct(protected ReportRegistry $registry) {}
|
||||
public function __construct(protected ReportQueryBuilder $queryBuilder) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$reports = collect($this->registry->all())
|
||||
$reports = Report::where('enabled', true)
|
||||
->orderBy('order')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn ($r) => [
|
||||
'slug' => $r->slug(),
|
||||
'name' => $r->name(),
|
||||
'description' => $r->description(),
|
||||
'slug' => $r->slug,
|
||||
'name' => $r->name,
|
||||
'description' => $r->description,
|
||||
'category' => $r->category,
|
||||
])
|
||||
->values();
|
||||
|
||||
@@ -29,26 +34,30 @@ public function index(Request $request)
|
||||
|
||||
public function show(string $slug, Request $request)
|
||||
{
|
||||
$report = $this->registry->findBySlug($slug);
|
||||
abort_if(! $report, 404);
|
||||
$report->authorize($request);
|
||||
$report = Report::with(['filters', 'columns'])
|
||||
->where('slug', $slug)
|
||||
->where('enabled', true)
|
||||
->firstOrFail();
|
||||
|
||||
// Accept filters & pagination from query and return initial data for server-driven table
|
||||
$filters = $this->validateFilters($report->inputs(), $request);
|
||||
$inputs = $this->buildInputsArray($report);
|
||||
$filters = $this->validateFilters($inputs, $request);
|
||||
\Log::info('Report filters', ['filters' => $filters, 'request' => $request->all()]);
|
||||
|
||||
$perPage = (int) ($request->integer('per_page') ?: 25);
|
||||
$paginator = $report->paginate($filters, $perPage);
|
||||
$query = $this->queryBuilder->build($report, $filters);
|
||||
$paginator = $query->paginate($perPage);
|
||||
|
||||
$rows = collect($paginator->items())
|
||||
->map(fn ($row) => $this->normalizeRow($row))
|
||||
->values();
|
||||
|
||||
return Inertia::render('Reports/Show', [
|
||||
'slug' => $report->slug(),
|
||||
'name' => $report->name(),
|
||||
'description' => $report->description(),
|
||||
'inputs' => $report->inputs(),
|
||||
'columns' => $report->columns(),
|
||||
'slug' => $report->slug,
|
||||
'name' => $report->name,
|
||||
'description' => $report->description,
|
||||
'inputs' => $inputs,
|
||||
'columns' => $this->buildColumnsArray($report),
|
||||
'rows' => $rows,
|
||||
'meta' => [
|
||||
'total' => $paginator->total(),
|
||||
@@ -62,14 +71,17 @@ public function show(string $slug, Request $request)
|
||||
|
||||
public function data(string $slug, Request $request)
|
||||
{
|
||||
$report = $this->registry->findBySlug($slug);
|
||||
abort_if(! $report, 404);
|
||||
$report->authorize($request);
|
||||
$report = Report::with(['filters', 'columns'])
|
||||
->where('slug', $slug)
|
||||
->where('enabled', true)
|
||||
->firstOrFail();
|
||||
|
||||
$filters = $this->validateFilters($report->inputs(), $request);
|
||||
$inputs = $this->buildInputsArray($report);
|
||||
$filters = $this->validateFilters($inputs, $request);
|
||||
$perPage = (int) ($request->integer('per_page') ?: 25);
|
||||
|
||||
$paginator = $report->paginate($filters, $perPage);
|
||||
$query = $this->queryBuilder->build($report, $filters);
|
||||
$paginator = $query->paginate($perPage);
|
||||
|
||||
$rows = collect($paginator->items())
|
||||
->map(fn ($row) => $this->normalizeRow($row))
|
||||
@@ -85,20 +97,23 @@ public function data(string $slug, Request $request)
|
||||
|
||||
public function export(string $slug, Request $request)
|
||||
{
|
||||
$report = $this->registry->findBySlug($slug);
|
||||
abort_if(! $report, 404);
|
||||
$report->authorize($request);
|
||||
$report = Report::with(['filters', 'columns'])
|
||||
->where('slug', $slug)
|
||||
->where('enabled', true)
|
||||
->firstOrFail();
|
||||
|
||||
$filters = $this->validateFilters($report->inputs(), $request);
|
||||
$inputs = $this->buildInputsArray($report);
|
||||
$filters = $this->validateFilters($inputs, $request);
|
||||
$format = strtolower((string) $request->get('format', 'csv'));
|
||||
|
||||
$rows = $report->query($filters)->get()->map(fn ($row) => $this->normalizeRow($row));
|
||||
$columns = $report->columns();
|
||||
$filename = $report->slug().'-'.now()->format('Ymd_His');
|
||||
$query = $this->queryBuilder->build($report, $filters);
|
||||
$rows = $query->get()->map(fn ($row) => $this->normalizeRow($row));
|
||||
$columns = $this->buildColumnsArray($report);
|
||||
$filename = $report->slug.'-'.now()->format('Ymd_His');
|
||||
|
||||
if ($format === 'pdf') {
|
||||
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('reports.pdf.table', [
|
||||
'name' => $report->name(),
|
||||
'name' => $report->name,
|
||||
'columns' => $columns,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
@@ -299,6 +314,35 @@ protected function validateFilters(array $inputs, Request $request): array
|
||||
return $request->validate($rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build inputs array from report filters.
|
||||
*/
|
||||
protected function buildInputsArray(Report $report): array
|
||||
{
|
||||
return $report->filters->map(fn($filter) => [
|
||||
'key' => $filter->key,
|
||||
'type' => $filter->type,
|
||||
'label' => $filter->label,
|
||||
'nullable' => $filter->nullable,
|
||||
'default' => $filter->default_value,
|
||||
'options' => $filter->options,
|
||||
])->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build columns array from report columns.
|
||||
*/
|
||||
protected function buildColumnsArray(Report $report): array
|
||||
{
|
||||
return $report->columns
|
||||
->where('visible', true)
|
||||
->map(fn($col) => [
|
||||
'key' => $col->key,
|
||||
'label' => $col->label,
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure derived export/display fields exist on row objects.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Report;
|
||||
use App\Models\ReportEntity;
|
||||
use App\Models\ReportColumn;
|
||||
use App\Models\ReportFilter;
|
||||
use App\Models\ReportCondition;
|
||||
use App\Models\ReportOrder;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ReportSettingsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$reports = Report::orderBy('order')->orderBy('name')->get();
|
||||
|
||||
return Inertia::render('Settings/Reports/Index', [
|
||||
'reports' => $reports,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Report $report)
|
||||
{
|
||||
$report->load(['entities', 'columns', 'filters', 'conditions', 'orders']);
|
||||
|
||||
return Inertia::render('Settings/Reports/Edit', [
|
||||
'report' => $report,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'slug' => 'required|string|unique:reports,slug|max:255',
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'category' => 'nullable|string|max:100',
|
||||
'enabled' => 'boolean',
|
||||
'order' => 'integer',
|
||||
]);
|
||||
|
||||
$report = Report::create($validated);
|
||||
|
||||
return redirect()->route('settings.reports.index')
|
||||
->with('success', 'Report created successfully.');
|
||||
}
|
||||
|
||||
public function update(Request $request, Report $report)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'slug' => 'required|string|unique:reports,slug,' . $report->id . '|max:255',
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'category' => 'nullable|string|max:100',
|
||||
'enabled' => 'boolean',
|
||||
'order' => 'integer',
|
||||
]);
|
||||
|
||||
$report->update($validated);
|
||||
|
||||
return redirect()->route('settings.reports.index')
|
||||
->with('success', 'Report updated successfully.');
|
||||
}
|
||||
|
||||
public function destroy(Report $report)
|
||||
{
|
||||
$report->delete();
|
||||
|
||||
return redirect()->route('settings.reports.index')
|
||||
->with('success', 'Report deleted successfully.');
|
||||
}
|
||||
|
||||
public function toggleEnabled(Report $report)
|
||||
{
|
||||
$report->update(['enabled' => !$report->enabled]);
|
||||
|
||||
return back()->with('success', 'Report status updated.');
|
||||
}
|
||||
|
||||
// Entity CRUD
|
||||
public function storeEntity(Request $request, Report $report)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'model_class' => 'required|string|max:255',
|
||||
'alias' => 'nullable|string|max:50',
|
||||
'join_type' => 'required|in:base,join,leftJoin,rightJoin',
|
||||
'join_first' => 'nullable|string|max:100',
|
||||
'join_operator' => 'nullable|string|max:10',
|
||||
'join_second' => 'nullable|string|max:100',
|
||||
'order' => 'integer',
|
||||
]);
|
||||
|
||||
$report->entities()->create($validated);
|
||||
|
||||
return back()->with('success', 'Entity added successfully.');
|
||||
}
|
||||
|
||||
public function updateEntity(Request $request, ReportEntity $entity)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'model_class' => 'required|string|max:255',
|
||||
'alias' => 'nullable|string|max:50',
|
||||
'join_type' => 'required|in:base,join,leftJoin,rightJoin',
|
||||
'join_first' => 'nullable|string|max:100',
|
||||
'join_operator' => 'nullable|string|max:10',
|
||||
'join_second' => 'nullable|string|max:100',
|
||||
'order' => 'integer',
|
||||
]);
|
||||
|
||||
$entity->update($validated);
|
||||
|
||||
return back()->with('success', 'Entity updated successfully.');
|
||||
}
|
||||
|
||||
public function destroyEntity(ReportEntity $entity)
|
||||
{
|
||||
$entity->delete();
|
||||
|
||||
return back()->with('success', 'Entity deleted successfully.');
|
||||
}
|
||||
|
||||
// Column CRUD
|
||||
public function storeColumn(Request $request, Report $report)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'key' => 'required|string|max:100',
|
||||
'label' => 'required|string|max:255',
|
||||
'type' => 'required|string|max:50',
|
||||
'expression' => 'required|string',
|
||||
'sortable' => 'boolean',
|
||||
'visible' => 'boolean',
|
||||
'order' => 'integer',
|
||||
'format_options' => 'nullable|array',
|
||||
]);
|
||||
|
||||
$report->columns()->create($validated);
|
||||
|
||||
return back()->with('success', 'Column added successfully.');
|
||||
}
|
||||
|
||||
public function updateColumn(Request $request, ReportColumn $column)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'key' => 'required|string|max:100',
|
||||
'label' => 'required|string|max:255',
|
||||
'type' => 'required|string|max:50',
|
||||
'expression' => 'required|string',
|
||||
'sortable' => 'boolean',
|
||||
'visible' => 'boolean',
|
||||
'order' => 'integer',
|
||||
'format_options' => 'nullable|array',
|
||||
]);
|
||||
|
||||
$column->update($validated);
|
||||
|
||||
return back()->with('success', 'Column updated successfully.');
|
||||
}
|
||||
|
||||
public function destroyColumn(ReportColumn $column)
|
||||
{
|
||||
$column->delete();
|
||||
|
||||
return back()->with('success', 'Column deleted successfully.');
|
||||
}
|
||||
|
||||
// Filter CRUD
|
||||
public function storeFilter(Request $request, Report $report)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'key' => 'required|string|max:100',
|
||||
'label' => 'required|string|max:255',
|
||||
'type' => 'required|string|max:50',
|
||||
'nullable' => 'boolean',
|
||||
'default_value' => 'nullable|string',
|
||||
'options' => 'nullable|array',
|
||||
'data_source' => 'nullable|string|max:255',
|
||||
'order' => 'integer',
|
||||
]);
|
||||
|
||||
$report->filters()->create($validated);
|
||||
|
||||
return back()->with('success', 'Filter added successfully.');
|
||||
}
|
||||
|
||||
public function updateFilter(Request $request, ReportFilter $filter)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'key' => 'required|string|max:100',
|
||||
'label' => 'required|string|max:255',
|
||||
'type' => 'required|string|max:50',
|
||||
'nullable' => 'boolean',
|
||||
'default_value' => 'nullable|string',
|
||||
'options' => 'nullable|array',
|
||||
'data_source' => 'nullable|string|max:255',
|
||||
'order' => 'integer',
|
||||
]);
|
||||
|
||||
$filter->update($validated);
|
||||
|
||||
return back()->with('success', 'Filter updated successfully.');
|
||||
}
|
||||
|
||||
public function destroyFilter(ReportFilter $filter)
|
||||
{
|
||||
$filter->delete();
|
||||
|
||||
return back()->with('success', 'Filter deleted successfully.');
|
||||
}
|
||||
|
||||
// Condition CRUD
|
||||
public function storeCondition(Request $request, Report $report)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'column' => 'required|string|max:255',
|
||||
'operator' => 'required|string|max:50',
|
||||
'value_type' => 'required|in:static,filter,expression',
|
||||
'value' => 'nullable|string',
|
||||
'filter_key' => 'nullable|string|max:100',
|
||||
'logical_operator' => 'required|in:AND,OR',
|
||||
'group_id' => 'nullable|integer',
|
||||
'order' => 'integer',
|
||||
'enabled' => 'boolean',
|
||||
]);
|
||||
|
||||
$report->conditions()->create($validated);
|
||||
|
||||
return back()->with('success', 'Condition added successfully.');
|
||||
}
|
||||
|
||||
public function updateCondition(Request $request, ReportCondition $condition)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'column' => 'required|string|max:255',
|
||||
'operator' => 'required|string|max:50',
|
||||
'value_type' => 'required|in:static,filter,expression',
|
||||
'value' => 'nullable|string',
|
||||
'filter_key' => 'nullable|string|max:100',
|
||||
'logical_operator' => 'required|in:AND,OR',
|
||||
'group_id' => 'nullable|integer',
|
||||
'order' => 'integer',
|
||||
'enabled' => 'boolean',
|
||||
]);
|
||||
|
||||
$condition->update($validated);
|
||||
|
||||
return back()->with('success', 'Condition updated successfully.');
|
||||
}
|
||||
|
||||
public function destroyCondition(ReportCondition $condition)
|
||||
{
|
||||
$condition->delete();
|
||||
|
||||
return back()->with('success', 'Condition deleted successfully.');
|
||||
}
|
||||
|
||||
// Order CRUD
|
||||
public function storeOrder(Request $request, Report $report)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'column' => 'required|string|max:255',
|
||||
'direction' => 'required|in:ASC,DESC',
|
||||
'order' => 'integer',
|
||||
]);
|
||||
|
||||
$report->orders()->create($validated);
|
||||
|
||||
return back()->with('success', 'Order clause added successfully.');
|
||||
}
|
||||
|
||||
public function updateOrder(Request $request, ReportOrder $order)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'column' => 'required|string|max:255',
|
||||
'direction' => 'required|in:ASC,DESC',
|
||||
'order' => 'integer',
|
||||
]);
|
||||
|
||||
$order->update($validated);
|
||||
|
||||
return back()->with('success', 'Order clause updated successfully.');
|
||||
}
|
||||
|
||||
public function destroyOrder(ReportOrder $order)
|
||||
{
|
||||
$order->delete();
|
||||
|
||||
return back()->with('success', 'Order clause deleted successfully.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user