diff --git a/app/Actions/Tickets/CreateProjectTicket.php b/app/Actions/Tickets/CreateProjectTicket.php index 383f7f7..5b73bd0 100644 --- a/app/Actions/Tickets/CreateProjectTicket.php +++ b/app/Actions/Tickets/CreateProjectTicket.php @@ -5,6 +5,7 @@ namespace App\Actions\Tickets; use App\Models\Project; use App\Models\Ticket; use App\Models\User; +use Arr; use Illuminate\Support\Facades\DB; class CreateProjectTicket @@ -39,6 +40,7 @@ class CreateProjectTicket 'ticket_priority_id' => $attributes['ticket_priority_id'], 'ticket_type_id' => $attributes['ticket_type_id'], 'title' => $attributes['title'], + 'story_points' => Arr::get($attributes, 'story_points'), ]); if ($actor !== null) { diff --git a/app/Http/Controllers/ProjectTicketController.php b/app/Http/Controllers/ProjectTicketController.php index aa28d0c..70ac994 100644 --- a/app/Http/Controllers/ProjectTicketController.php +++ b/app/Http/Controllers/ProjectTicketController.php @@ -47,7 +47,7 @@ class ProjectTicketController extends Controller 'parentTicketOptions' => Ticket::query() ->where('project_id', $project->id) ->whereNotIn('id', $excludedParentIds) - ->whereHas('ticketType', fn ($query) => $query->where('system_key', 'parent')) + ->whereHas('ticketType', fn ($query) => $query->where('is_parent', true)) ->orderByDesc('sequence_number') ->get() ->map(fn (Ticket $parentTicket): array => [ @@ -119,6 +119,7 @@ class ProjectTicketController extends Controller 'ticket_priority_id' => Arr::get($validated, 'ticket_priority_id'), 'ticket_type_id' => Arr::get($validated, 'ticket_type_id'), 'title' => Arr::get($validated, 'title'), + 'story_points' => Arr::get($validated, 'story_points'), ]; $wasUpdated = $this->updateProjectTicket->handle( diff --git a/app/Http/Controllers/ProjectTicketMetadataSettingsController.php b/app/Http/Controllers/ProjectTicketMetadataSettingsController.php index eb72a8d..8db2172 100644 --- a/app/Http/Controllers/ProjectTicketMetadataSettingsController.php +++ b/app/Http/Controllers/ProjectTicketMetadataSettingsController.php @@ -8,6 +8,7 @@ use App\Http\Requests\StoreProjectNamedSettingRequest; use App\Http\Requests\StoreProjectTicketTypeRequest; use App\Http\Requests\StoreTicketFieldDefinitionRequest; use App\Http\Requests\UpdateProjectNamedSettingRequest; +use App\Http\Requests\UpdateProjectTicketMetadataSettings; use App\Http\Requests\UpdateTicketFieldDefinitionRequest; use App\Models\Project; use App\Models\TicketFieldDefinition; @@ -50,6 +51,16 @@ class ProjectTicketMetadataSettingsController extends Controller ]); } + public function update(UpdateProjectTicketMetadataSettings $request, Project $project): RedirectResponse + { + $validated = $request->validated(); + + $project->update(['show_story_points' => Arr::get($validated, 'show_story_points', false)]); + + return to_route('projects.settings.metadata', $project) + ->with('status', 'Settings have been updated.'); + } + public function storeType(StoreProjectTicketTypeRequest $request, Project $project): RedirectResponse { $this->abortUnlessCanManage($request, $project); diff --git a/app/Http/Requests/StoreProjectTicketRequest.php b/app/Http/Requests/StoreProjectTicketRequest.php index b45df86..bd46303 100644 --- a/app/Http/Requests/StoreProjectTicketRequest.php +++ b/app/Http/Requests/StoreProjectTicketRequest.php @@ -40,6 +40,7 @@ class StoreProjectTicketRequest extends FormRequest 'integer', Rule::exists('ticket_types', 'id')->where('project_id', $project->id), ], + 'story_points' => ['nullable', 'integer', 'min:0'], 'title' => ['required', 'string', 'max:255'], ]; } diff --git a/app/Http/Requests/UpdateProjectTicketMetadataSettings.php b/app/Http/Requests/UpdateProjectTicketMetadataSettings.php new file mode 100644 index 0000000..227f399 --- /dev/null +++ b/app/Http/Requests/UpdateProjectTicketMetadataSettings.php @@ -0,0 +1,29 @@ +user() !== null; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'show_story_points' => 'nullable|boolean', + ]; + } +} diff --git a/app/Http/Requests/UpdateProjectTicketRequest.php b/app/Http/Requests/UpdateProjectTicketRequest.php index 1072471..8acc8e0 100644 --- a/app/Http/Requests/UpdateProjectTicketRequest.php +++ b/app/Http/Requests/UpdateProjectTicketRequest.php @@ -61,6 +61,7 @@ class UpdateProjectTicketRequest extends FormRequest Rule::exists('ticket_types', 'id')->where('project_id', $project->id), ], 'title' => ['required', 'string', 'max:255'], + 'story_points' => ['nullable', 'integer', 'gt:0'], ]; } diff --git a/app/Models/Project.php b/app/Models/Project.php index b7fec0d..3afe27b 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -11,12 +11,19 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Support\Str; -#[Fillable(['name', 'key'])] +#[Fillable(['name', 'key', 'show_story_points'])] class Project extends Model { /** @use HasFactory */ use HasFactory; + protected function casts(): array + { + return [ + 'show_story_points' => 'boolean', + ]; + } + protected static function booted(): void { static::deleting(function (self $project): void { diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index 2d23953..4a4fa29 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -22,6 +22,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; 'ticket_priority_id', 'ticket_type_id', 'title', + 'story_points', ])] class Ticket extends Model { diff --git a/database/migrations/2026_03_24_234007_create_projects_table.php b/database/migrations/2026_03_24_234007_create_projects_table.php index fe5a66e..52e763a 100644 --- a/database/migrations/2026_03_24_234007_create_projects_table.php +++ b/database/migrations/2026_03_24_234007_create_projects_table.php @@ -15,6 +15,7 @@ return new class extends Migration $table->id(); $table->string('name'); $table->string('key', 5)->unique(); + $table->boolean('show_story_points')->default(true); $table->timestamps(); }); } diff --git a/database/migrations/2026_03_25_004100_create_tickets_table.php b/database/migrations/2026_03_25_004100_create_tickets_table.php index 46da13f..aa4c293 100644 --- a/database/migrations/2026_03_25_004100_create_tickets_table.php +++ b/database/migrations/2026_03_25_004100_create_tickets_table.php @@ -22,6 +22,7 @@ return new class extends Migration $table->string('title'); $table->text('description')->nullable(); $table->text('acceptance_criteria')->nullable(); + $table->unsignedInteger('story_points')->nullable()->default(null); $table->timestamps(); $table->unique(['project_id', 'sequence_number']); diff --git a/resources/js/components/ui/input/Input.vue b/resources/js/components/ui/input/Input.vue index e5135c1..e6486c7 100644 --- a/resources/js/components/ui/input/Input.vue +++ b/resources/js/components/ui/input/Input.vue @@ -5,7 +5,7 @@ import { cn } from "@/lib/utils" const props = defineProps<{ defaultValue?: string | number - modelValue?: string | number + modelValue?: string | number | null class?: HTMLAttributes["class"] }>() diff --git a/resources/js/pages/projects/Backlog.vue b/resources/js/pages/projects/Backlog.vue index 8453b1c..b3b90ad 100644 --- a/resources/js/pages/projects/Backlog.vue +++ b/resources/js/pages/projects/Backlog.vue @@ -107,74 +107,100 @@ const hasTicketMetadata = -
-
- - - -
+
+
+
+ +