Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions app/Http/Controllers/Settings/ColumnSettingsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace App\Http\Controllers\Settings;

use App\Http\Controllers\Controller;
use App\Models\Column;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class ColumnSettingsController extends Controller
{
/**
* Show the column status mapping settings page.
*/
public function index(Request $request): Response
{
$columns = Column::query()
->where('team_id', $request->user()->team_id)
->orderBy('order')
->get();

return Inertia::render('settings/Columns', [
'columns' => $columns,
]);
}
}
1 change: 1 addition & 0 deletions app/Http/Controllers/TaskController.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public function show(Request $request, Task $task): JsonResponse
$this->authorize('view', $task);

$task->load([
'column:id,name,type',
'creator:id,name',
'assignee:id,name',
'tags:id,name,color',
Expand Down
1 change: 1 addition & 0 deletions app/Http/Requests/ColumnStoreRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'type' => ['nullable', 'string', 'in:todo,in_progress,done'],
];
}
}
1 change: 1 addition & 0 deletions app/Http/Requests/ColumnUpdateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'type' => ['sometimes', 'required', 'string', 'in:todo,in_progress,done'],
];
}
}
1 change: 1 addition & 0 deletions app/Http/Resources/ColumnResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function toArray(Request $request): array
'id' => $this->id,
'team_id' => $this->team_id,
'name' => $this->name,
'type' => $this->type,
'order' => $this->order,
'tasks' => $tasksData,
'pagination' => $pagination,
Expand Down
5 changes: 5 additions & 0 deletions app/Http/Resources/TaskResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ public function toArray(Request $request): array
'id' => $this->assignee->id,
'name' => $this->assignee->name,
]),
'column' => $this->whenLoaded('column', fn() => [
'id' => $this->column->id,
'name' => $this->column->name,
'type' => $this->column->type,
]),
'comments' => $this->whenLoaded(
'comments',
fn() => $this->comments
Expand Down
1 change: 1 addition & 0 deletions app/Models/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Column extends Model
protected $fillable = [
'team_id',
'name',
'type',
'order',
];

Expand Down
1 change: 1 addition & 0 deletions app/Services/ColumnService.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public function createColumn(array $data, User $user): Column

return Column::create([
'name' => $data['name'],
'type' => $data['type'] ?? 'todo',
'team_id' => $user->team_id,
'order' => $order,
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('columns', function (Blueprint $table) {
$table->enum('type', ['todo', 'in_progress', 'done'])->default('todo')->after('name');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('columns', function (Blueprint $table) {
$table->dropColumn('type');
});
}
};
9 changes: 6 additions & 3 deletions database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,22 @@ public function run(): void
$todoColumn = Column::create([
'team_id' => $team->id,
'name' => 'To Do',
'order' => 1
'order' => 1,
'type' => 'todo'
]);

$progressColumn = Column::create([
'team_id' => $team->id,
'name' => 'In Progress',
'order' => 2
'order' => 2,
'type' => 'in_progress'
]);

$doneColumn = Column::create([
'team_id' => $team->id,
'name' => 'Done',
'order' => 3
'order' => 3,
'type' => 'done'
]);

$teamUsers = User::where('team_id', $team->id)->pluck('id');
Expand Down
6 changes: 3 additions & 3 deletions resources/js/components/tasks/KanbanBoard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ const onColumnDragChange = (event: {

<template>
<div
class="relative flex h-full w-full items-start gap-4 overflow-x-auto pb-4"
class="relative h-full w-full overflow-auto pb-4"
>
<draggable
v-model="localColumns"
item-key="id"
class="flex h-full w-full items-start gap-4"
class="flex min-h-full min-w-max w-full items-stretch gap-4"
handle=".column-drag-handle"
ghost-class="opacity-60"
@start="onColumnDragStart"
Expand All @@ -91,7 +91,7 @@ const onColumnDragChange = (event: {
</template>

<template #footer>
<div class="mt-0 shrink-0">
<div class="mt-0 shrink-0 p-1">
<Button
variant="outline"
class="border-dashed text-muted-foreground shadow-sm transition-all hover:border-foreground hover:text-foreground"
Expand Down
87 changes: 55 additions & 32 deletions resources/js/components/tasks/KanbanColumn.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ import {
Check,
ChevronDown,
GripVertical,
MoreHorizontal,
Pencil,
Trash2,
X,
} from 'lucide-vue-next';
import { ref, watch } from 'vue';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { computed, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
import draggable from 'vuedraggable';
import ColumnDeleteDialog from './ColumnDeleteDialog.vue';
Expand All @@ -34,16 +41,7 @@ const pagination = ref<Column['pagination']>(
props.column.pagination ? { ...props.column.pagination } : undefined,
);
const isLoadingMore = ref(false);

watch(
() => props.column.tasks,
(newTasks) => {
localTasks.value = [...(newTasks || [])];
if (props.column.pagination) {
pagination.value = { ...props.column.pagination };
}
},
);
const isDone = computed(() => props.column.type === 'done');

const loadMoreTasks = async () => {
if (!pagination.value || !pagination.value.has_more) return;
Expand Down Expand Up @@ -150,13 +148,24 @@ const onDragChange = (event: any) => {
);
}
};


watch(
() => props.column.tasks,
(newTasks) => {
localTasks.value = [...(newTasks || [])];
if (props.column.pagination) {
pagination.value = { ...props.column.pagination };
}
},
);
</script>

<template>
<div
class="flex h-full max-h-[calc(100vh-12rem)] w-[350px] shrink-0 flex-col rounded-xl bg-muted/40 p-3"
class="flex w-[350px] shrink-0 flex-col rounded-xl bg-[color-mix(in_srgb,var(--muted)_40%,var(--background))]"
>
<div class="mb-3 flex items-center justify-between px-1">
<div class="sticky top-0 z-10 flex items-center justify-between px-4 pt-3 pb-3 bg-[color-mix(in_srgb,var(--muted)_40%,var(--background))] rounded-t-xl">
<div v-if="isEditingColumn" class="flex flex-1 items-center gap-2">
<input
v-model="editName"
Expand Down Expand Up @@ -200,41 +209,55 @@ const onDragChange = (event: any) => {
>
{{ column.pagination?.total }}
</span>
<Check
v-if="isDone"
class="ml-2 size-3.5 shrink-0 text-green-500 dark:text-green-400"
title="Work items moved to this column are marked as done"
/>
</h3>

<div class="flex items-center space-x-1" v-if="!isEditingColumn">
<Button
variant="ghost"
size="icon-sm"
class="h-6 w-6 text-muted-foreground"
@click="isEditingColumn = true"
>
<Pencil class="size-3" />
</Button>
<Button
variant="ghost"
size="icon-sm"
class="h-6 w-6 text-destructive hover:bg-destructive/10 hover:text-destructive"
@click="isDeleteColumnOpen = true"
>
<Trash2 class="size-3" />
</Button>
<div v-if="!isEditingColumn">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button
variant="ghost"
size="icon-sm"
class="h-6 w-6 text-muted-foreground"
>
<MoreHorizontal class="size-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-32">
<DropdownMenuItem @click="isEditingColumn = true">
<Pencil class="mr-2 size-3" />
<span>Rename</span>
</DropdownMenuItem>
<DropdownMenuItem
@click="isDeleteColumnOpen = true"
class="text-destructive focus:text-destructive focus:bg-destructive/10"
>
<Trash2 class="mr-2 size-3" />
<span>Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>

<div class="no-scrollbar min-h-[100px] flex-1 overflow-y-auto">
<div class="no-scrollbar flex flex-1 flex-col px-3 pb-3">
<draggable
v-model="localTasks"
group="tasks"
item-key="id"
class="flex min-h-full flex-col gap-3 pb-2"
class="flex flex-1 flex-col gap-3 pb-2 min-h-[100px]"
ghost-class="opacity-50"
@change="onDragChange"
>
<template #item="{ element }">
<div class="cursor-grab active:cursor-grabbing">
<TaskItem
:task="element"
:is-done="props.column.type === 'done'"
@edit="emit('edit', $event)"
/>
</div>
Expand Down
Loading
Loading