Two month of work

This commit is contained in:
2026-02-17 10:35:03 +01:00
parent 0ffbeeedff
commit d9fd3d1ccb
158 changed files with 5637 additions and 1512 deletions
+21 -31
View File
@@ -11,14 +11,14 @@ class NoteController extends Controller
{
/**
* Display a listing of the resource.
* @param Request $request
* @param string $modelType The type of the model (e.g., 'Customer', 'Invoice')
* @param string $modelId The ID of the model
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request, $modelId)
public function index(Request $request, string $modelType, int $modelId)
{
$modelType = $request->route()->parameter('modelType');
if (!$modelType) {
$modelType = $request->route()->getAction('modelType');
}
$model = app("App\\Models\\" . ucfirst($modelType))::findOrFail($modelId);
$model = app("App\\Models\\" . $modelType)::findOrFail($modelId);
// Lade alle Notizen des Modells mit der Benutzerbeziehung
$notes = $model->notes()->with('user')->orderBy('created_at', 'desc')->get();
@@ -34,24 +34,30 @@ public function index(Request $request, $modelId)
/**
* Store a newly created resource in storage.
*/
public function store(Request $request, $modelId)
public function store(Request $request)
{
$modelType = $request->route()->parameter('modelType');
if (!$modelType) {
$modelType = $request->route()->getAction('modelType');
}
$validatedData = $request->validate([
'userId' => 'required|integer|exists:users,id',
'text' => 'required|string',
'userId' => 'required|integer'
'noteableId' => 'required|integer',
'noteableType' => 'required|string',
'createdAt' => 'sometimes|date|nullable'
]);
// Convert camelCase to snake_case
$snakeCaseData = ApiDataTransformer::camelToSnake($validatedData);
$model = app("App\\Models\\" . ucfirst($modelType))::findOrFail($modelId);
$note = new Note($validatedData);
$note->user_id = $validatedData['userId'];
$model = app("App\\Models\\" . $snakeCaseData['noteable_type'])::findOrFail($snakeCaseData['noteable_id']);
// Create a new Note instance
$note = new Note($snakeCaseData);
// Set the created_at field if it is provided
if (isset($snakeCaseData['created_at'])) {
$note->created_at = $snakeCaseData['created_at'];
}
$model->notes()->save($note);
return response()->json($this->single($note->id), 201);
@@ -64,22 +70,6 @@ public static function single($id)
}
/**
* Display the specified resource.
*/
public function show(Note $note)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Note $note)
{
//
}
/**
* Update the specified resource in storage.
*/
@@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\PipelineLane;
use App\Models\PipelineItem;
use App\Support\ApiDataTransformer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Inertia\Inertia;
class PipelineController extends Controller
{
public function index()
{
$lanes = PipelineLane::with(['items' => function ($q) {
$q->withCount(['notes' => function ($query) {
$query->where('notable_type', 'App\Models\PipelineItem');
}])->orderBy('position');
}])->orderBy('position')->get();
return $lanes->map(function ($lane) {
return ApiDataTransformer::snakeToCamel($lane->toArray());
});
}
public function show()
{
return Inertia::render('Pipeline', [
'pipeline' => $this->index(),
]);
}
/**
* Update positions in bulk. Expect array of objects with camelCase keys: id, stage, position
*/
public function updatePositions(Request $request)
{
$payload = $request->all();
if (!is_array($payload)) {
return response()->json(['message' => 'Invalid payload'], 422);
}
$items = array_map(function ($item) {
return ApiDataTransformer::camelToSnake((array)$item);
}, $payload);
$validator = Validator::make(['items' => $items], [
'items.*.id' => 'required|integer|exists:pipeline_items,id',
'items.*.pipeline_lane_id' => 'required|integer|exists:pipeline_lanes,id',
'items.*.title' => 'string',
'items.*.position' => 'required|integer|min:0',
'items.*.expected_revenue' => 'decimal',
'items.*.description' => 'string',
]);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 422);
}
DB::beginTransaction();
try {
foreach ($items as $it) {
PipelineItem::where('id', $it['id'])->update([
'pipeline_lane_id' => $it['pipeline_lane_id'],
'position' => $it['position'],
]);
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
Log::error('Failed to update pipeline positions: ' . $e->getMessage());
return response()->json(['message' => 'Failed to update positions'], 500);
}
return response()->json(['status' => 'ok']);
}
}
@@ -0,0 +1,81 @@
<?php
namespace App\Http\Controllers;
use App\Models\PipelineItem;
use App\Support\ApiDataTransformer;
use Illuminate\Http\Request;
class PipelineItemController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$pipelineItems = PipelineItem::withCount(['notes' => function ($query) {
$query->where('notable_type', 'App\Models\PipelineItem');
}])->orderBy('position')->get();
return ApiDataTransformer::snakeToCamel($pipelineItems->toArray());
}
/**
* Display a listing of the resource.
*/
public function single(int $id)
{
$pipelineItem = PipelineItem::withCount(['notes' => function ($query) {
$query->where('notable_type', 'App\Models\PipelineItem');
}])->orderBy('position')->findOrFail($id);
return ApiDataTransformer::snakeToCamel($pipelineItem->toArray());
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validatedData = $request->validate([
'pipeline_lane_id' => 'required|integer|exists:pipeline_lanes,id',
'title' => 'required|string',
'position' => 'required|integer|min:0',
'expected_revenue' => 'nullable|numeric',
'due_date' => 'nullable|date',
'description' => 'nullable|string',
]);
$pipelineItem = PipelineItem::create($validatedData);
return ApiDataTransformer::snakeToCamel($pipelineItem->toArray());
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, PipelineItem $pipelineItem)
{
$validatedData = $request->validate([
'pipeline_lane_id' => 'sometimes|integer|exists:pipeline_lanes,id',
'title' => 'sometimes|string',
'position' => 'sometimes|integer|min:0',
'expected_revenue' => 'nullable|numeric',
'due_date' => 'nullable|date',
'description' => 'nullable|string',
]);
$pipelineItem->update($validatedData);
return ApiDataTransformer::snakeToCamel($pipelineItem->toArray());
}
/**
* Remove the specified resource from storage.
*/
public function delete(int $id)
{
PipelineItem::findOrFail($id)->delete();
return response()->noContent();
}
}
@@ -0,0 +1,101 @@
<?php
namespace App\Http\Controllers;
use Inertia\Inertia;
use App\Models\Timesheet;
use Illuminate\Http\Request;
use App\Support\ApiDataTransformer;
class TimesheetController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$timesheets = Timesheet::with(['entries' => function ($query) {
$query->selectRaw('
timesheet_id,
SUM(hours) as total_hours,
SUM(CASE WHEN billed = 1 THEN hours ELSE 0 END) as hours_billed,
MIN(date) as earliest_date,
MAX(date) as latest_date
')
->groupBy('timesheet_id');
}])->get();
// Sort timesheets by the updated_at timestamp of the latest entry
$timesheets = $timesheets->sortByDesc(function ($timesheet) {
$latestEntry = $timesheet->entries()->latest('updated_at')->first();
return $latestEntry ? $latestEntry->updated_at : $timesheet->updated_at;
})->values();
$snakeCaseData = $timesheets->map(function ($timesheet) {
$entryStats = $timesheet->entries->first();
return [
'id' => $timesheet->id,
'title' => $timesheet->title,
'total_hours' => $entryStats->total_hours ?? 0,
'hours_billed' => $entryStats->hours_billed ?? 0,
'earliest_date' => $entryStats->earliest_date ?? null,
'latest_date' => $entryStats->latest_date ?? null,
'created_at' => $timesheet->created_at->toISOString(),
'updated_at' => $timesheet->updated_at->toISOString(),
];
});
return ApiDataTransformer::snakeToCamel($snakeCaseData->toArray());
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
]);
$snakeCaseData = Timesheet::create($validated);
return ApiDataTransformer::snakeToCamel($snakeCaseData->toArray());
}
/**
* Display the specified resource.
*/
public function show(Timesheet $timesheet)
{
return Inertia::render('Timesheets', [
'timesheetData' => $this->index()
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Timesheet $timesheet)
{
$validatedData = $request->validate([
'title' => 'required|string|max:255',
]);
// Convert camelCase to snake_case
$snakeCaseData = ApiDataTransformer::camelToSnake($validatedData);
$timesheet->update($snakeCaseData);
return $timesheet;
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Timesheet $timesheet)
{
$timesheet->delete();
return response()->noContent();
}
}
@@ -0,0 +1,102 @@
<?php
namespace App\Http\Controllers;
use App\Models\TimesheetEntry;
use App\Models\Timesheet;
use Illuminate\Http\Request;
use App\Support\ApiDataTransformer;
class TimesheetEntryController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$entries = TimesheetEntry::with(['timesheet', 'user'])->orderBy('date', 'asc')->get();
return $entries->map(function ($entry) {
return ApiDataTransformer::snakeToCamel($entry->toArray());
});
}
/**
* Get all entries for a specific timesheet
*/
public function getEntriesForTimesheet(Timesheet $timesheet)
{
$entries = $timesheet->entries()->with('user')->orderBy('date', 'asc')->get();
return $entries->map(function ($entry) {
return ApiDataTransformer::snakeToCamel($entry->toArray());
});
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validatedData = $request->validate([
'timesheetId' => 'required|exists:timesheets,id',
'date' => 'required|date',
'userId' => 'required|exists:users,id',
'description' => 'nullable|string',
'hours' => 'required|numeric',
'billed' => 'sometimes|boolean',
]);
// Convert camelCase to snake_case
$snakeCaseData = ApiDataTransformer::camelToSnake($validatedData);
$entry = TimesheetEntry::create($snakeCaseData);
$entry = TimesheetEntry::with(['timesheet', 'user'])->find($entry->id);
return ApiDataTransformer::snakeToCamel($entry->toArray());
}
/**
* Display the specified resource.
*/
public function show(TimesheetEntry $timesheetEntry)
{
$entry = $timesheetEntry->load(['timesheet', 'user']);
return ApiDataTransformer::snakeToCamel($entry->toArray());
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, TimesheetEntry $timesheetEntry)
{
$validatedData = $request->validate([
'timesheetId' => 'sometimes|exists:timesheets,id',
'date' => 'sometimes|date',
'userId' => 'sometimes|exists:users,id',
'description' => 'nullable|string',
'hours' => 'sometimes|numeric',
'billed' => 'sometimes|boolean',
]);
// Convert camelCase to snake_case
$snakeCaseData = ApiDataTransformer::camelToSnake($validatedData);
$timesheetEntry->update($snakeCaseData);
return ApiDataTransformer::snakeToCamel($timesheetEntry->toArray());
}
/**
* Remove the specified resource from storage.
*/
public function destroy(TimesheetEntry $timesheetEntry)
{
$timesheetEntry->delete();
return response()->noContent();
}
public function toggleBilled(TimesheetEntry $timesheetEntry)
{
$timesheetEntry = $timesheetEntry->update(['billed' => !$timesheetEntry->billed]);
return ApiDataTransformer::snakeToCamel($timesheetEntry->toArray());
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ class TodoController extends Controller
{
public function index()
{
$todos = Todo::with('type')->get();
$todos = Todo::with('type')->orderBy('due_date')->get();
return ApiDataTransformer::snakeToCamel($todos->toArray());
}