|null */ public ?array $data = []; /** @var array */ public array $headers = []; public ?string $storedFilePath = null; /** @var array|null */ public ?array $importResult = null; public static function getNavigationLabel(): string { return __('hr.resources.import_wizard'); } public function getTitle(): string|Htmlable { return __('hr.import.title'); } public function mount(): void { $this->form->fill([ 'import_type' => ImportType::Employees->value, 'column_mapping' => [], ]); $this->refreshImportResult(); } public function defaultForm(Schema $schema): Schema { return $schema ->statePath('data') ->columns(1); } public function form(Schema $schema): Schema { return $schema->components([ Wizard::make([ Step::make(__('hr.import.upload')) ->description(__('hr.import.upload_description')) ->schema([ Select::make('import_type') ->label(__('hr.import.import_type')) ->options(ImportType::class) ->required() ->live() ->afterStateUpdated(fn () => $this->resetColumnMapping()), FileUpload::make('file') ->label(__('hr.import.excel_file')) ->disk(config('hr.file_uploads.disk')) ->directory(config('hr.file_uploads.directory') . '/imports') ->acceptedFileTypes([ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel', 'text/csv', ]) ->required() ->afterStateUpdated(fn () => $this->loadHeaders()), ]), Step::make(__('hr.import.map_columns')) ->description(__('hr.import.map_columns_description')) ->schema([ Section::make(__('hr.import.column_mapping')) ->schema(fn (): array => $this->getColumnMappingFields()), ]), Step::make(__('hr.import.preview_step')) ->description(__('hr.import.preview_description')) ->schema([ Placeholder::make('preview_table') ->label(__('hr.import.preview')) ->content(fn (): HtmlString => new HtmlString($this->getPreviewHtml())), ]), Step::make(__('hr.import.import_step')) ->description(__('hr.import.import_description')) ->schema([ Placeholder::make('import_results') ->label(__('hr.import.import_results')) ->content(fn (): HtmlString => new HtmlString($this->getResultsHtml())), ]), ]) ->alpineSubmitHandler('$wire.runImport()') ->submitAction(new HtmlString('')) ->skippable(false), ]); } public function content(Schema $schema): Schema { return $schema->components([ Form::make([EmbeddedSchema::make('form')]) ->id('import-wizard-form') ->livewireSubmitHandler('runImport') ->footer([ Actions::make([]) ->alignment(Alignment::Start) ->key('import-wizard-actions'), ]), ]); } public function runImport(): void { $this->form->validate(); $filePath = $this->resolveStoredFilePath(); if ($filePath === null) { Notification::make() ->title(__('hr.import.upload_required_title')) ->body(__('hr.import.upload_required_body')) ->danger() ->send(); return; } $importType = ImportType::from($this->data['import_type']); $columnMapping = $this->data['column_mapping'] ?? []; ProcessImportJob::dispatch( $importType, $filePath, $columnMapping, Auth::id(), ); Notification::make() ->title(__('hr.import.import_queued_title')) ->body(__('hr.import.import_queued_body')) ->success() ->send(); } public function refreshImportResult(): void { $result = ProcessImportJob::getCachedResult(Auth::id()); $this->importResult = $result?->toArray(); } public function loadHeaders(): void { $path = $this->resolveStoredFilePath(); if ($path === null) { $this->headers = []; return; } $this->headers = app(ImportService::class)->readHeaders($path); $this->resetColumnMapping(); } protected function resetColumnMapping(): void { $this->data['column_mapping'] = []; } /** * @return array */ protected function getColumnMappingFields(): array { if ($this->headers === []) { return [ Placeholder::make('mapping_hint') ->content(__('hr.import.mapping_hint')), ]; } $importType = ImportType::from($this->data['import_type'] ?? ImportType::Employees->value); $headerOptions = array_combine($this->headers, $this->headers); return collect($importType->fields()) ->map(function (string $label, string $field) use ($headerOptions): Select { return Select::make("column_mapping.{$field}") ->label($label) ->options($headerOptions) ->searchable() ->nullable(); }) ->values() ->all(); } protected function getPreviewHtml(): string { $path = $this->resolveStoredFilePath(); if ($path === null) { return '

' . e(__('hr.import.upload_to_preview')) . '

'; } $importType = ImportType::from($this->data['import_type'] ?? ImportType::Employees->value); $importService = app(ImportService::class); $rows = $importService->readRows($path, 10); $mappedRows = $importService->mapRows($rows, $this->data['column_mapping'] ?? []); $validation = $importService->validatePreview($importType, $mappedRows); if ($mappedRows === []) { return '

' . e(__('hr.import.no_rows')) . '

'; } $fields = array_keys($importType->fields()); $html = '
'; $html .= ''; foreach ($fields as $field) { $html .= ''; } $html .= ''; foreach ($mappedRows as $index => $row) { $errors = $validation[$index]['errors'] ?? []; $html .= ''; $html .= ''; foreach ($fields as $field) { $html .= ''; } $html .= ''; } $html .= '
' . e(__('hr.import.row')) . '' . e($importType->fields()[$field]) . '' . e(__('hr.import.validation')) . '
' . ($index + 2) . '' . e((string) ($row[$field] ?? '—')) . ''; if ($errors === []) { $html .= '' . e(__('hr.import.valid')) . ''; } else { $html .= '' . e(implode(' ', $errors)) . ''; } $html .= '
'; return $html; } protected function getResultsHtml(): string { $this->refreshImportResult(); if ($this->importResult === null) { return '

' . e(__('hr.import.submit_to_see_results')) . '

'; } $html = '
'; $html .= '

' . e(__('hr.import.total_rows')) . ': ' . $this->importResult['total'] . '

'; $html .= '

' . e(__('hr.import.imported')) . ': ' . $this->importResult['imported'] . '

'; $html .= '

' . e(__('hr.import.skipped_duplicates')) . ': ' . $this->importResult['skipped'] . '

'; $html .= '

' . e(__('hr.import.failed')) . ': ' . $this->importResult['failed'] . '

'; if (($this->importResult['errors'] ?? []) !== []) { $html .= '
' . e(__('hr.import.errors')) . ':
    '; foreach ($this->importResult['errors'] as $error) { $html .= '
  • ' . e($error) . '
  • '; } $html .= '
'; } $html .= '
'; return $html; } protected function resolveStoredFilePath(): ?string { $file = $this->data['file'] ?? null; if ($file === null || $file === []) { return null; } $relativePath = is_array($file) ? ($file[0] ?? null) : $file; if ($relativePath === null) { return null; } $disk = Storage::disk(config('hr.file_uploads.disk')); $this->storedFilePath = $disk->path($relativePath); return $this->storedFilePath; } }