feat: registration field polish, multi-category tags, file uploads, Partner icon

- Restructure field editor dialog: move Options section to bottom with
  divider and subheader, fix delete button with flex layout
- Change tag_category (single string) to tag_categories (JSON array)
  supporting multiple category selection in tag picker fields
- Portal tag picker now groups tags by category with subheaders
- Add generic file upload endpoint (FileUploadService + UploadController)
- Replace email branding logo URL text field with ImageUploadField
- Update Partner crowd type default icon to tabler-affiliate
- Apply changes consistently to both field and template dialogs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 18:03:49 +02:00
parent d57dcdb616
commit 6a8d21a5b6
31 changed files with 813 additions and 239 deletions

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// registration_form_fields: add tag_categories JSON, migrate data, drop tag_category
Schema::table('registration_form_fields', function (Blueprint $table) {
$table->json('tag_categories')->nullable()->after('options');
});
DB::table('registration_form_fields')
->whereNotNull('tag_category')
->where('tag_category', '!=', '')
->eachById(function ($row) {
DB::table('registration_form_fields')
->where('id', $row->id)
->update(['tag_categories' => json_encode([$row->tag_category])]);
});
Schema::table('registration_form_fields', function (Blueprint $table) {
$table->dropColumn('tag_category');
});
// registration_field_templates: add tag_categories JSON, migrate data, drop tag_category
Schema::table('registration_field_templates', function (Blueprint $table) {
$table->json('tag_categories')->nullable()->after('options');
});
DB::table('registration_field_templates')
->whereNotNull('tag_category')
->where('tag_category', '!=', '')
->eachById(function ($row) {
DB::table('registration_field_templates')
->where('id', $row->id)
->update(['tag_categories' => json_encode([$row->tag_category])]);
});
Schema::table('registration_field_templates', function (Blueprint $table) {
$table->dropColumn('tag_category');
});
}
public function down(): void
{
Schema::table('registration_form_fields', function (Blueprint $table) {
$table->string('tag_category', 50)->nullable()->after('options');
});
DB::table('registration_form_fields')
->whereNotNull('tag_categories')
->eachById(function ($row) {
$categories = json_decode($row->tag_categories, true);
DB::table('registration_form_fields')
->where('id', $row->id)
->update(['tag_category' => $categories[0] ?? null]);
});
Schema::table('registration_form_fields', function (Blueprint $table) {
$table->dropColumn('tag_categories');
});
Schema::table('registration_field_templates', function (Blueprint $table) {
$table->string('tag_category', 50)->nullable()->after('options');
});
DB::table('registration_field_templates')
->whereNotNull('tag_categories')
->eachById(function ($row) {
$categories = json_decode($row->tag_categories, true);
DB::table('registration_field_templates')
->where('id', $row->id)
->update(['tag_category' => $categories[0] ?? null]);
});
Schema::table('registration_field_templates', function (Blueprint $table) {
$table->dropColumn('tag_categories');
});
}
};

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
DB::table('crowd_types')
->where('system_type', 'PARTNER')
->where(function ($query) {
$query->whereNull('icon')
->orWhere('icon', '')
->orWhere('icon', 'tabler-handshake');
})
->update(['icon' => 'tabler-affiliate']);
}
public function down(): void
{
DB::table('crowd_types')
->where('system_type', 'PARTNER')
->where('icon', 'tabler-affiliate')
->update(['icon' => 'tabler-handshake']);
}
};