You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
10 KiB

<?php
namespace App\Support;
use App\Models\Application;
use App\Models\AudienceRegistration;
use App\Models\Competition;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
final class SignupSchemaLayout
{
/** @var list<string> */
private const SKIP_KEYS = ['commitment_accepted', 'plan', 'supporting', 'promise_signature'];
/** @var list<string> */
private const SKIP_TYPES = ['file'];
/** @var list<string> */
private const AUDIENCE_COLUMNS = ['name', 'phone', 'company', 'job', 'purpose'];
/**
* @param list<mixed>|null $schemaJson
* @return list<array{key: string, type: string, label: string, list: bool, filter: bool, options: list<array{value: string, label: string}>}>
*/
public static function fields(?array $schemaJson): array
{
if (! is_array($schemaJson)) {
return [];
}
$out = [];
foreach ($schemaJson as $row) {
if (! is_array($row)) {
continue;
}
$key = trim((string) ($row['key'] ?? ''));
$type = trim((string) ($row['type'] ?? ''));
if ($key === '' || in_array($key, self::SKIP_KEYS, true) || in_array($type, self::SKIP_TYPES, true)) {
continue;
}
$label = trim((string) ($row['label'] ?? $key));
$options = [];
if (($row['options'] ?? null) && is_array($row['options'])) {
foreach ($row['options'] as $opt) {
if (! is_array($opt)) {
continue;
}
$value = trim((string) ($opt['value'] ?? ''));
if ($value === '') {
continue;
}
$options[] = [
'value' => $value,
'label' => trim((string) ($opt['label'] ?? $value)),
];
}
}
$out[] = [
'key' => $key,
'type' => $type !== '' ? $type : 'text',
'label' => $label !== '' ? $label : $key,
'list' => ($row['list'] ?? false) === true,
'filter' => ($row['filter'] ?? false) === true,
'options' => $options,
];
}
return $out;
}
/**
* @param list<mixed>|null $schemaJson
* @return list<array{key: string, type: string, label: string, list: bool, filter: bool, options: list<array{value: string, label: string}>}>
*/
public static function listFields(?array $schemaJson): array
{
return array_values(array_filter(self::fields($schemaJson), fn (array $f) => $f['list']));
}
/**
* @param list<mixed>|null $schemaJson
* @return list<array{key: string, type: string, label: string, list: bool, filter: bool, options: list<array{value: string, label: string}>}>
*/
public static function filterFields(?array $schemaJson): array
{
return array_values(array_filter(
self::fields($schemaJson),
fn (array $f) => $f['filter'] && ($f['type'] === 'select' || in_array($f['key'], ['track', 'purpose'], true))
));
}
/**
* 管理端筛选项(中文标签);赛道/场次补全选项。
*
* @return list<array{key: string, type: string, label: string, options: list<array{value: string, label: string}>}>
*/
public static function adminFilterFields(Competition $competition, string $purpose = 'signup'): array
{
$schema = $purpose === 'audience'
? ($competition->audienceFormSchema?->schema_json)
: ($competition->formSchema?->schema_json);
$fields = self::filterFields(is_array($schema) ? $schema : null);
$out = [];
foreach ($fields as $field) {
$options = $field['options'];
if ($field['key'] === 'track') {
if ($options === []) {
$competition->loadMissing('tracks');
foreach ($competition->tracks as $track) {
$options[] = [
'value' => (string) $track->track_code,
'label' => (string) $track->title,
];
}
}
$hasOther = false;
foreach ($options as $opt) {
if (($opt['value'] ?? '') === SignupDisplay::TRACK_OTHER_CODE) {
$hasOther = true;
break;
}
}
if (! $hasOther) {
$options[] = [
'value' => SignupDisplay::TRACK_OTHER_CODE,
'label' => '其他',
];
}
}
if ($purpose === 'audience' && $field['key'] === 'audience_session_id' && $options === []) {
$competition->loadMissing('audienceSessions');
foreach ($competition->audienceSessions as $session) {
$options[] = [
'value' => (string) $session->id,
'label' => (string) $session->name,
];
}
}
$out[] = [
'key' => $field['key'],
'type' => $field['type'],
'label' => $field['label'],
'options' => $options,
];
}
return $out;
}
/**
* @return list<array{key: string, type: string, label: string}>
*/
public static function adminListFields(Competition $competition, string $purpose = 'signup'): array
{
$schema = $purpose === 'audience'
? ($competition->audienceFormSchema?->schema_json)
: ($competition->formSchema?->schema_json);
$out = [];
foreach (self::listFields(is_array($schema) ? $schema : null) as $field) {
$out[] = [
'key' => $field['key'],
'type' => $field['type'],
'label' => $field['label'],
];
}
return $out;
}
/**
* @param array<string, mixed> $filters
*/
public static function extractFilterValues(Request $request, Competition $competition, string $purpose = 'signup'): array
{
$out = [];
foreach (self::adminFilterFields($competition, $purpose) as $field) {
$key = $field['key'];
if (! $request->exists($key)) {
continue;
}
$value = trim((string) $request->input($key));
if ($value !== '') {
$out[$key] = $value;
}
}
return $out;
}
/**
* @param Builder<\App\Models\Application|\App\Models\AudienceRegistration> $query
* @param array<string, mixed> $filters
* @param list<string> $alreadyApplied
*/
public static function applyFilters(
Builder $query,
Competition $competition,
array $filters,
string $purpose = 'signup',
array $alreadyApplied = ['track', 'event_location', 'purpose'],
): void {
$schema = $purpose === 'audience'
? ($competition->audienceFormSchema?->schema_json)
: ($competition->formSchema?->schema_json);
$columnKeys = $purpose === 'audience'
? array_flip(self::AUDIENCE_COLUMNS)
: array_flip(SignupApplicationFieldRegistry::scalarColumnKeys());
$skip = array_flip($alreadyApplied);
foreach (self::filterFields(is_array($schema) ? $schema : null) as $field) {
$key = $field['key'];
if (isset($skip[$key])) {
continue;
}
$value = trim((string) ($filters[$key] ?? ''));
if ($value === '') {
continue;
}
if ($purpose === 'signup' && in_array($key, ['degree', 'location_country', 'event_location'], true)) {
$value = SignupOptionCatalog::normalize($key, $value) ?: $value;
}
if ($purpose === 'audience' && $key === 'purpose') {
$value = SignupOptionCatalog::normalize('purpose', $value) ?: $value;
}
if (isset($columnKeys[$key])) {
$query->where($key, $value);
} else {
$query->where('answers_json->'.$key, $value);
}
}
}
/**
* @return array<string, string>
*/
public static function applicationDisplayMap(Competition $competition, Application $app): array
{
$competition->loadMissing('formSchema');
$schema = $competition->formSchema?->schema_json;
$schemaArr = is_array($schema) ? $schema : null;
$out = [];
foreach (self::listFields($schemaArr) as $field) {
$out[$field['key']] = SignupDisplay::applicationFieldLabel($competition, $app, $field['key'], $schemaArr);
}
return $out;
}
/**
* @return array<string, string>
*/
public static function audienceDisplayMap(Competition $competition, AudienceRegistration $row): array
{
$competition->loadMissing('audienceFormSchema');
$schema = $competition->audienceFormSchema?->schema_json;
$schemaArr = is_array($schema) ? $schema : null;
$locale = SignupDisplay::audienceLocale((int) $row->user_id, (int) $row->competition_id);
$out = [];
foreach (self::listFields($schemaArr) as $field) {
$key = $field['key'];
$raw = SignupDisplay::audienceFieldValue($row, $key);
$value = is_scalar($raw) ? trim((string) $raw) : '';
if ($value === '') {
$out[$key] = '';
continue;
}
if ($key === 'purpose') {
$out[$key] = SignupDisplay::purposeLabel($value, $locale);
continue;
}
$fieldDef = LocalizedSchema::fieldByKey($schemaArr, $key);
if ($fieldDef !== null && ($fieldDef['type'] ?? '') === 'select') {
$out[$key] = LocalizedSchema::optionLabelForValue($fieldDef, $value, $locale);
continue;
}
$out[$key] = $value;
}
return $out;
}
}