diff --git a/README.md b/README.md index acf6cab..f43f2bc 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,17 @@ This project was bootstrapped from the official [Laravel Vue Starter Kit](https: - Assignee (filtered by specific team members or unassigned tasks). - Tags (multi-select filter). - Due Dates (shortcuts like Today, This Week, Overdue, or custom exact dates and date ranges). -- **Task Details & Collaboration:** Task editing with rich description styling (Tiptap editor), comments with threaded replies, and a detailed activity log timeline. -- **Workload Dashboard:** Team workspace overview featuring task metrics, urgent "To Handle Now" tasks, column breakdowns, and a recent activity feed (with direct links back to tasks). +- **Task Details & Collaboration:** + - Task editing with rich description styling (Tiptap editor), comments with threaded replies, and a detailed activity log timeline. + - Discussion threads with nested replies. + - Detailed activity log timeline for full auditability. + - **Document Uploads:** Attach files directly to tasks (currently supporting Images and PDFs). + - **Smart Link Previews:** Automatic, rich previews for URLs embedded in task descriptions and comments. +- **Workload Dashboard:** Team workspace overview featuring task metrics, urgent "To Handle Now" tasks, column breakdowns, and a recent activity feed. ## Tech Stack -- **Backend:** Laravel 12 (PHP >= 8.2), Inertia.js V3 +- **Backend:** Laravel 12 (PHP >= 8.4), Inertia.js V3 - **Frontend:** Vue 3 - **Styling:** Tailwind CSS & Shadcn - **CI/CD:** GitHub Actions workflow for automated testing, linting and deployment. @@ -28,7 +33,7 @@ This project was bootstrapped from the official [Laravel Vue Starter Kit](https: ### Prerequisites Make sure you have the following installed on your local system: -- **PHP** >= 8.2 +- **PHP** >= 8.4 - **Composer** - **Node.js** >= 20 - **NPM** diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index cfcd038..5a41b49 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -29,10 +29,12 @@ public function __invoke(Request $request): Response $totalTasks = (clone $baseTasksQuery)->count(); $overdueQuery = (clone $baseTasksQuery) + ->whereHas('column', fn($query) => $query->where('type', '!=', 'done')) ->whereNotNull('due_date') ->whereDate('due_date', '<', today()); $dueTodayQuery = (clone $baseTasksQuery) + ->whereHas('column', fn($query) => $query->where('type', '!=', 'done')) ->whereNotNull('due_date') ->whereDate('due_date', today()); diff --git a/app/Http/Requests/TaskCreateRequest.php b/app/Http/Requests/TaskCreateRequest.php index 210c6dc..23d500b 100644 --- a/app/Http/Requests/TaskCreateRequest.php +++ b/app/Http/Requests/TaskCreateRequest.php @@ -20,6 +20,12 @@ public function rules(): array 'title' => ['required', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:1000'], 'due_date' => ['nullable', 'date'], + 'created_by' => [ + 'nullable', + Rule::exists('users', 'id')->where( + fn($query) => $query->where('team_id', $this->user()?->team_id) + ), + ], 'assigned_to' => [ 'nullable', Rule::exists('users', 'id')->where( diff --git a/app/Http/Requests/TaskUpdateRequest.php b/app/Http/Requests/TaskUpdateRequest.php index 47ffbe1..553f589 100644 --- a/app/Http/Requests/TaskUpdateRequest.php +++ b/app/Http/Requests/TaskUpdateRequest.php @@ -18,6 +18,13 @@ public function rules(): array 'title' => ['sometimes', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:1000'], 'due_date' => ['sometimes', 'nullable', 'date'], + 'created_by' => [ + 'sometimes', + 'nullable', + Rule::exists('users', 'id')->where( + fn($query) => $query->where('team_id', $this->user()?->team_id) + ), + ], 'assigned_to' => [ 'sometimes', 'nullable', diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index 9fe6a59..9f02765 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -34,7 +34,7 @@ public function createTask(array $data, User $user): Task return DB::transaction(function () use ($data, $user, $columnId, $order, $columnName, $tagIds, $newFiles) { $task = Task::create(array_merge($data, [ 'team_id' => $user->team_id, - 'created_by' => $user->id, + 'created_by' => $data['created_by'] ?? $user->id, 'column_id' => $columnId, 'order' => $order, 'column_updated_at' => now(), // Initialize column timing diff --git a/composer.json b/composer.json index 9d8a4bb..fb569bc 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ ], "license": "MIT", "require": { - "php": "^8.2", + "php": "^8.4", "inertiajs/inertia-laravel": "^3.0", "laravel/fortify": "^1.30", "laravel/framework": "^12.0", diff --git a/resources/js/components/tasks/TaskAssigneeSelect.vue b/resources/js/components/tasks/TaskAssigneeSelect.vue index 1cb45cc..3455ef0 100644 --- a/resources/js/components/tasks/TaskAssigneeSelect.vue +++ b/resources/js/components/tasks/TaskAssigneeSelect.vue @@ -17,6 +17,7 @@ import { computed } from 'vue'; const props = defineProps<{ modelValue: number | null; teamMembers: TeamMember[]; + allowUnassigned?: boolean; }>(); const emit = defineEmits<{ @@ -72,6 +73,7 @@ const selectMember = (memberId: number | null) => { diff --git a/resources/js/components/tasks/TaskCreateDialog.vue b/resources/js/components/tasks/TaskCreateDialog.vue index c732f28..148ae91 100644 --- a/resources/js/components/tasks/TaskCreateDialog.vue +++ b/resources/js/components/tasks/TaskCreateDialog.vue @@ -37,6 +37,7 @@ const form = useForm({ description: '', due_date: '', assigned_to: currentUserId.value, + created_by: currentUserId.value, tag_ids: [], attachments: [], }); @@ -54,6 +55,7 @@ const submit = () => { onSuccess: () => { form.reset(); form.assigned_to = currentUserId.value; + form.created_by = currentUserId.value; form.tag_ids = []; selectedTags.value = []; isOpen.value = false; @@ -128,6 +130,7 @@ const submit = () => {
@@ -135,11 +138,23 @@ const submit = () => {
- +
+
+ + + +
+ + +