diff --git a/timeliner/app/Http/Controllers/TimelineController.php b/timeliner/app/Http/Controllers/TimelineController.php index c5b5771..03738de 100644 --- a/timeliner/app/Http/Controllers/TimelineController.php +++ b/timeliner/app/Http/Controllers/TimelineController.php @@ -79,7 +79,7 @@ public function store(Request $request) 'nodes.*.milestones.*.description' => 'nullable|string|max:255', ]); - $timeline = Timeline::create(['name' => $validated['name'], 'description' => $validated['description'], 'private'=> $validated['private']]); + $timeline = Timeline::create(['name' => $validated['name'], 'description' => $validated['description'], 'private'=> !$validated['private']]); Ownership::create(['id' => $timeline->id . Auth::user()->id]); if (!empty($validated['nodes'])) { @@ -106,4 +106,107 @@ public function store(Request $request) return redirect()->route('timeline.index') ->with('success','Timeline created successfully.'); } + + public function edit($id) + { + $timeline = Timeline::findOrFail($id); + + if(($timeline != null) && (Auth::check() && Ownership::find($timeline->id . Auth::user()->id))) + { + $nodes = Node::where('timeline','=',$timeline->id) + ->with('milestones') + ->get(); + + return view('timeline.edit', ['timeline' => $timeline, 'nodes' => $nodes]); + } + + return redirect()->route('timeline.show', $id) + ->withErrors(["You don't have access."]); + } + + public function update(Request $request, $id) + { + $timeline = Timeline::findOrFail($id); + + if (($timeline != null) && (!$timeline->private || (Auth::check() && Ownership::find($timeline->id . Auth::user()->id)))) { + + $request->merge([ + 'private' => $request->has('private'), + ]); + + $validated = $request->validate([ + 'name' => 'required|max:50', + 'description' => 'required|max:200', + 'private' => 'required|boolean', + 'nodes' => 'nullable|array', + 'nodes.*.name' => 'required|string|max:255', + 'nodes.*.milestones' => 'nullable|array', + 'nodes.*.milestones.*.date' => 'required|date', + 'nodes.*.milestones.*.description' => 'nullable|string|max:255', + ]); + + // Update timeline details + $timeline->update([ + 'name' => $validated['name'], + 'description' => $validated['description'], + 'private' => !$validated['private'] + ]); + + // Handle nodes + if (!empty($validated['nodes'])) { + // First, delete any nodes that were removed from the form + $existingNodeIds = collect($validated['nodes'])->pluck('id')->filter(); + Node::where('timeline', $timeline->id) + ->whereNotIn('id', $existingNodeIds) + ->delete(); + + // Update existing nodes or create new ones + foreach ($validated['nodes'] as $nodeData) { + if (isset($nodeData['id'])) { + // Update existing node + $node = Node::findOrFail($nodeData['id']); + $node->update([ + 'name' => $nodeData['name'], + 'color' => '#FFFFFF', // Default color (you can adjust this) + ]); + } else { + // Create new node + $node = Node::create([ + 'name' => $nodeData['name'], + 'color' => '#FFFFFF', // Default color (you can adjust this) + 'timeline' => $timeline->id, + ]); + } + + // Handle milestones + if (!empty($nodeData['milestones'])) { + foreach ($nodeData['milestones'] as $milestoneData) { + if (isset($milestoneData['id'])) { + // Update existing milestone + $milestone = Milestone::findOrFail($milestoneData['id']); + $milestone->update([ + 'date' => $milestoneData['date'], + 'description' => $milestoneData['description'] ?? null, + ]); + } else { + // Create new milestone + Milestone::create([ + 'date' => $milestoneData['date'], + 'description' => $milestoneData['description'] ?? null, + 'node' => $node->id + ]); + } + } + } + } + } + + return redirect()->route('timeline.show', $id) + ->with('success', 'Timeline updated successfully.'); + } + + return redirect()->route('timeline.index') + ->withErrors(["You don't have access."]); + } + } diff --git a/timeliner/database/seeders/DatabaseSeeder.php b/timeliner/database/seeders/DatabaseSeeder.php index 4de21dc..0db30ed 100644 --- a/timeliner/database/seeders/DatabaseSeeder.php +++ b/timeliner/database/seeders/DatabaseSeeder.php @@ -44,7 +44,5 @@ public function run(): void $this->call(CommentSeeder::class); $this->call(NodeSeeder::class); $this->call(MilestoneSeeder::class); - - DB::statement('PRAGMA foreign_keys=ON;'); } } diff --git a/timeliner/resources/js/app.js b/timeliner/resources/js/app.js index 630cc78..79d4f1a 100644 --- a/timeliner/resources/js/app.js +++ b/timeliner/resources/js/app.js @@ -4,7 +4,7 @@ import './timelinelistener'; import './formfunctions'; import './createformfunctions'; -import './bootstrap-toggle.min.js' +import './bootstrap-toggle.min' import Alpine from 'alpinejs'; diff --git a/timeliner/resources/js/createformfunctions.js b/timeliner/resources/js/createformfunctions.js index 79fd1de..4e0d469 100644 --- a/timeliner/resources/js/createformfunctions.js +++ b/timeliner/resources/js/createformfunctions.js @@ -3,9 +3,11 @@ Author: Brandt Mael */ -let ni = 0; // node index +let ni = window.ni || 0; // use global ni if set, else defaults to 0 let mi = 0; // milestone index +window.addMilestone = addMilestone; // makes the function available to the global scope + //add a row to a list of milestone creation form function addMilestone(milestone_list_id, nodeIndex, nodeMilestoneCount) { const ms_id = `ms-${nodeIndex}-${nodeMilestoneCount}` // the id of the milestone depend of it's parent node @@ -43,10 +45,10 @@ function addMilestone(milestone_list_id, nodeIndex, nodeMilestoneCount) { // create delete button let delete_button = document.createElement('button'); let delete_button_id = "ms-delete-button-" + mi; - delete_button.setAttribute("class", "btn btn-primary"); + delete_button.setAttribute("class", "btn btn-danger bi bi-trash"); delete_button.setAttribute("type", "button"); delete_button.setAttribute("id", delete_button_id); - delete_button.innerHTML += 'delete'; + delete_button.innerHTML += ' delete'; // add listener to delete_button delete_button.addEventListener('click', function() { @@ -78,10 +80,10 @@ if (nodeCreateButton) { nodeCreateButton.addEventListener('click', function() { // recover the node table - let node_table = document.getElementById('node-list'); + let node_table = document.getElementById('node-list-body'); // declare new row of milestone table - let tr_node_form = document.createElement("div"); // row holding the node form + let tr_node_form = document.createElement("tr"); // row holding the node form let tr_milestone_table = document.createElement("tr"); // row holding the milestone table let td_label = document.createElement("td"); @@ -129,10 +131,10 @@ if (nodeCreateButton) { // create 'add milestone' button let add_milestone_button = document.createElement('button'); let milestone_create_button_id = "milestone-create-button-" + ni; - add_milestone_button.setAttribute("class", "btn btn-primary"); + add_milestone_button.setAttribute("class", "btn btn-primary bi bi-calendar-plus"); add_milestone_button.setAttribute("type", "button"); add_milestone_button.setAttribute("id", milestone_create_button_id); - add_milestone_button.innerHTML += 'create milestone'; + add_milestone_button.innerHTML += ' create milestone'; let current_ni_number = ni; let nodeMilestoneCount = 0; @@ -145,10 +147,10 @@ if (nodeCreateButton) { // create delete button let delete_button = document.createElement('button'); let delete_button_id = "node-delete-button-" + ni; - delete_button.setAttribute("class", "btn btn-primary"); + delete_button.setAttribute("class", "btn btn-danger bi bi-trash "); delete_button.setAttribute("type", "button"); delete_button.setAttribute("id", delete_button_id); - delete_button.innerHTML += 'delete'; + delete_button.innerHTML += ' delete'; // add listener to delete_button delete_button.addEventListener('click', function() { diff --git a/timeliner/resources/views/index.blade.php b/timeliner/resources/views/index.blade.php index 03260d4..19442d1 100644 --- a/timeliner/resources/views/index.blade.php +++ b/timeliner/resources/views/index.blade.php @@ -5,8 +5,6 @@ - -
diff --git a/timeliner/resources/views/layouts/app.blade.php b/timeliner/resources/views/layouts/app.blade.php index 7109ed6..018dbbb 100644 --- a/timeliner/resources/views/layouts/app.blade.php +++ b/timeliner/resources/views/layouts/app.blade.php @@ -32,6 +32,8 @@
@endisset + +
{{ $slot }} diff --git a/timeliner/resources/views/layouts/navigation.blade.php b/timeliner/resources/views/layouts/navigation.blade.php index e3f91db..03abd2a 100644 --- a/timeliner/resources/views/layouts/navigation.blade.php +++ b/timeliner/resources/views/layouts/navigation.blade.php @@ -29,7 +29,7 @@
diff --git a/timeliner/resources/views/timeline/createtimeline.blade.php b/timeliner/resources/views/timeline/create.blade.php similarity index 84% rename from timeliner/resources/views/timeline/createtimeline.blade.php rename to timeliner/resources/views/timeline/create.blade.php index df27423..dbb0e97 100644 --- a/timeliner/resources/views/timeline/createtimeline.blade.php +++ b/timeliner/resources/views/timeline/create.blade.php @@ -44,29 +44,16 @@
- +
- - @if ($errors->any()) -
- Oops! There's a problem with your entries.

- -
- @endif - - diff --git a/timeliner/resources/views/timeline/edit.blade.php b/timeliner/resources/views/timeline/edit.blade.php new file mode 100644 index 0000000..325aa94 --- /dev/null +++ b/timeliner/resources/views/timeline/edit.blade.php @@ -0,0 +1,57 @@ + + +

+ {{ __('Edit timeline ') . $timeline->name }} +

+
+ +
id) }}" method="POST"> + @csrf + @method('PUT') +
+
+
+
+ Edit timeline +
+
+
+
+ + +
+ + + +
+ + +
+
+
+
+ +
+
+ @include('timeline.partials.nodeedit', ['nodes' => $nodes]) +
+ +
+ + private ? '' : 'checked' }}> +
+ +
+ Back + + +
+
+
+
+
+
+
+ +
+
\ No newline at end of file diff --git a/timeliner/resources/views/timeline/partials/nodecreate.blade.php b/timeliner/resources/views/timeline/partials/nodecreate.blade.php index 78e575e..9c992b5 100644 --- a/timeliner/resources/views/timeline/partials/nodecreate.blade.php +++ b/timeliner/resources/views/timeline/partials/nodecreate.blade.php @@ -1,16 +1,86 @@ + @csrf
- - +
- - +
+
+ + @php + $oldNodes = old('nodes', []); + $lastNodeIndex = count($oldNodes) > 0 ? max(array_keys($oldNodes)) + 1 : 0; + $nodeMilestoneCounts = []; + foreach ($oldNodes as $nodeIndex => $node) { + $nodeMilestoneCounts[$nodeIndex] = count($node['milestones'] ?? []); + } + @endphp + + + @foreach ($oldNodes as $nodeIndex => $node) + + + + + + + + + + + @endforeach + +
+ + + + + + + +
+ + + @foreach ($node['milestones'] ?? [] as $milestoneIndex => $milestone) + + + + + + + @endforeach + +
+ + + + + + + +
+
+
- - diff --git a/timeliner/resources/views/timeline/partials/nodeedit.blade.php b/timeliner/resources/views/timeline/partials/nodeedit.blade.php index e69de29..cb73cf9 100644 --- a/timeliner/resources/views/timeline/partials/nodeedit.blade.php +++ b/timeliner/resources/views/timeline/partials/nodeedit.blade.php @@ -0,0 +1,82 @@ +
+ +
+ +
+
+ + @php + $nodes = $nodes->toArray(); + $currentNodes = old('nodes', $nodes); + $currentNodeIndex = count($currentNodes) > 0 ? max(array_keys($currentNodes)) + 1 : 0; + $nodeMilestoneCounts = []; + foreach ($currentNodes as $nodeIndex => $node) { + $nodeMilestoneCounts[$nodeIndex] = count($node['milestones'] ?? []); + } + @endphp + + @foreach ($currentNodes as $nodeIndex => $node) + + + + + + + + + + + @endforeach + +
+ + + + + + + +
+ + + @foreach ($node['milestones'] ?? [] as $milestoneIndex => $milestone) + + + + + + + @endforeach + +
+ + + + + + + +
+
+
+ +
+ diff --git a/timeliner/resources/views/timeline/timeline.blade.php b/timeliner/resources/views/timeline/timeline.blade.php index 9034b62..29c1e39 100644 --- a/timeliner/resources/views/timeline/timeline.blade.php +++ b/timeliner/resources/views/timeline/timeline.blade.php @@ -5,22 +5,19 @@ - -
@auth - @if ($isOwner) Edit @endif + @if ($isOwner) Edit @endif @endauth
- @foreach ($nodes as $node) @include("timeline.partials.node", ["node"=>$node]) @endforeach diff --git a/timeliner/routes/web.php b/timeliner/routes/web.php index 8adfc91..52aabef 100644 --- a/timeliner/routes/web.php +++ b/timeliner/routes/web.php @@ -15,9 +15,7 @@ return view('dashboard'); })->middleware(['auth', 'verified'])->name('dashboard'); -Route::get('/createtimeline', function () { - return view('timeline.createtimeline'); -})->middleware(['auth', 'verified'])->name('createtimeline'); + Route::middleware('auth')->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); @@ -27,8 +25,8 @@ require __DIR__.'/auth.php'; -Route::resource('timeline', TimelineController::class); +Route::resource('timeline', TimelineController::class)->middleware(['auth', 'verified']); -Route::post('comment', [CommentController::class, 'store'])->name('comment.store'); -Route::delete('comment/{comment}', [CommentController::class, 'destroy'])->name('comment.destroy'); -Route::put('comment/{comment}', [CommentController::class, 'update'])->name('comment.update'); \ No newline at end of file +Route::post('comment', [CommentController::class, 'store'])->middleware(['auth', 'verified'])->name('comment.store'); +Route::delete('comment/{comment}', [CommentController::class, 'destroy'])->middleware(['auth', 'verified'])->name('comment.destroy'); +Route::put('comment/{comment}', [CommentController::class, 'update'])->middleware(['auth', 'verified'])->name('comment.update'); \ No newline at end of file