diff --git a/.gitignore b/.gitignore index f7648bc0d..35b1eceb9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ yarn-error.log /.vscode /.docker .DS_Store +config/app.php diff --git a/app/Livewire/Conducting/FileUpload.php b/app/Livewire/Conducting/FileUpload.php index 90e0a9c78..bd1ebc7a7 100644 --- a/app/Livewire/Conducting/FileUpload.php +++ b/app/Livewire/Conducting/FileUpload.php @@ -22,12 +22,14 @@ use App\Utils\CheckProjectDataPlanning; use App\Traits\ProjectPermissions; +use App\Traits\LivewireExceptionHandler; class FileUpload extends Component { use ProjectPermissions; use WithFileUploads; + use LivewireExceptionHandler; private $translationPath = 'project/conducting.import-studies.livewire'; private $toastMessages = 'project/conducting.import-studies.livewire.toasts'; @@ -192,11 +194,13 @@ public function save() $errorMessage = $e->getMessage(); FacadesLog::error('Erro ao salvar o arquivo ou processar BibUpload.', ['error' => $errorMessage]); - $toastMessage = __($this->toastMessages . '.file_upload_error', ['message' => $errorMessage]); - $this->toast( - message: $toastMessage, - type: 'error' - ); + // Deleta o bibUpload se houver erro + if (isset($bibUpload) && $bibUpload->exists()) { + $bibUpload->delete(); + FacadesLog::info('Registro BibUpload deletado após falha no processamento.', ['id_bib' => $bibUpload->id_bib]); + } + + $this->handleException($e); } } else { @@ -214,11 +218,7 @@ public function save() $errorMessage = $e->getMessage(); FacadesLog::error('Erro geral ao tentar salvar o arquivo.', ['error' => $errorMessage]); - $toastMessage = __($this->toastMessages . '.file_upload_error', ['message' => $errorMessage]); - $this->toast( - message: $toastMessage, - type: 'error' - ); + $this->handleException($e); } } @@ -262,15 +262,15 @@ private function mapCsvFields($csvRow) return [ 'type' => $csvRow['Content Type'] ?? '', 'citation-key' => '', - 'title' => $csvRow['Item Title'] ?? '', - 'author' => $csvRow['Authors'] ?? '', + 'title' => !empty($csvRow['Item Title']) ? $csvRow['Item Title'] : null, + 'author' =>!empty($csvRow['Authors']) ? $csvRow['Authors'] : null, 'booktitle' => $csvRow['Book Series Title'] ?? '', 'volume' => $csvRow['Journal Volume'] ?? '', 'pages' => '', 'numpages' => '', 'abstract' => '', 'keywords' => '', - 'doi' => $csvRow['Item DOI'] ?? '', + 'doi' => !empty($csvRow['Item DOI']) ? $csvRow['Item DOI'] : null, 'journal' => $csvRow['Publication Title'] ?? '', 'issn' => '', 'location' => '', @@ -288,7 +288,7 @@ public function deleteFile($id) if (!$this->checkEditPermission($this->toastMessages . '.denied')) { return; } - + $file = BibUpload::findOrFail($id); try { diff --git a/app/Livewire/Planning/Criteria/Criteria.php b/app/Livewire/Planning/Criteria/Criteria.php index 1cdea523e..2e2cc3e6f 100644 --- a/app/Livewire/Planning/Criteria/Criteria.php +++ b/app/Livewire/Planning/Criteria/Criteria.php @@ -9,11 +9,13 @@ use App\Utils\ActivityLogHelper as Log; use App\Utils\ToastHelper; use App\Traits\ProjectPermissions; +use App\Traits\LivewireExceptionHandler; class Criteria extends Component { use ProjectPermissions; + use LivewireExceptionHandler; private $toastMessages = 'project/planning.criteria.livewire.toasts'; @@ -46,8 +48,7 @@ protected function rules() 'currentProject' => 'required', 'criteriaId' => 'required|string|max:20|regex:/^[a-zA-Z0-9]+$/', 'description' => 'required|string|max:255', - 'type' => 'required|array', - 'type.*.value' => 'string' + 'type.value' => 'required|in:Inclusion,Exclusion' ]; } @@ -63,7 +64,8 @@ protected function messages() 'description.required' => __($tpath . '.description.required'), 'criteriaId.required' => __($tpath . '.criteriaId.required'), 'criteriaId.regex' => __($tpath . '.criteriaId.regex'), - 'type.required' => __($tpath . '.type.required'), + 'type.value.required' => __($tpath . '.type.required'), + 'type.value.in' => __($tpath . '.type.in'), ]; } @@ -88,7 +90,7 @@ public function mount() 'id_project', $this->currentProject->id_project )->where('type', 'Exclusion')->first()->rule ?? 'ANY'; - $this->type['value'] = 'NONE'; + $this->type['value'] = null; } /** @@ -96,8 +98,8 @@ public function mount() */ public function resetFields() { - $this->criteriaId = ''; - $this->description = ''; + $this->criteriaId = null; + $this->description = null; $this->type['value'] = null; $this->currentCriteria = null; $this->form['isEditing'] = false; @@ -253,14 +255,6 @@ public function submit() $this->validate(); - if (strcmp($this->type['value'], 'NONE') === 0) { - $this->toast( - message: $this->translate('type.required'), - type: 'info' - ); - return; - } - $updateIf = [ 'id_criteria' => $this->currentCriteria?->id_criteria, ]; @@ -311,10 +305,7 @@ public function submit() type: 'success' ); } catch (\Exception $e) { - $this->toast( - message: $e->getMessage(), - type: 'error' - ); + $this->handleException($e); } finally { $this->resetFields(); } diff --git a/app/Models/BibUpload.php b/app/Models/BibUpload.php index 4e30332a7..93265ab23 100644 --- a/app/Models/BibUpload.php +++ b/app/Models/BibUpload.php @@ -44,22 +44,22 @@ public function importPapers(array $papers, $database, int $id_project, int $id_ 'id_bib' => $id_bib, 'type' => $p['type'] ?? '', 'bib_key' => $p['citation-key'] ?? '', - 'title' => $p['title'] ?? '', - 'author' => $p['author'] ?? '', - 'book_title' => $p['booktitle'] ?? null, - 'volume' => $p['volume'] ?? null, - 'pages' => $p['pages'] ?? null, - 'num_pages' => $p['numpages'] ?? null, - 'abstract' => $p['abstract'] ?? null, - 'keywords' => $p['keywords'] ?? null, - 'doi' => $p['doi'] ?? '', + 'title' => $p['title'] ?? null, + 'author' => $p['author'] ?? null, + 'book_title' => $p['booktitle'] ?? '', + 'volume' => $p['volume'] ?? '', + 'pages' => $p['pages'] ?? '', + 'num_pages' => $p['numpages'] ?? '', + 'abstract' => $p['abstract'] ?? '', + 'keywords' => $p['keywords'] ?? '', + 'doi' => $p['doi'] ?? null, 'journal' => $p['journal'] ?? '', - 'issn' => $p['issn'] ?? null, - 'location' =>$p['location'] ?? null, - 'isbn' => $p['isbn'] ?? null, - 'address' =>$p['address'] ?? null, + 'issn' => $p['issn'] ?? '', + 'location' =>$p['location'] ?? '', + 'isbn' => $p['isbn'] ?? '', + 'address' =>$p['address'] ?? '', 'url' => $p['url'] ?? '', - 'publisher' => $p['series'] ?? null, + 'publisher' => $p['publisher'] ?? '', 'year' => $p['year'] ?? '', 'score' => 0, 'status_qa' => 3, diff --git a/app/Traits/LivewireExceptionHandler.php b/app/Traits/LivewireExceptionHandler.php new file mode 100644 index 000000000..a3c574314 --- /dev/null +++ b/app/Traits/LivewireExceptionHandler.php @@ -0,0 +1,81 @@ +errorInfo[1] ?? null; // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html + + switch ($errorCode){ + case 1048: + $column = $this->extractColumnFromMessage($e->getMessage()); + $this->toast( + message: $column + ? __("errors.database.column_cannot_be_null", ['column' => $column]) + : __("errors.database.missing_required_field"), + type: 'error' + ); + break; + + default: + $this->toast( + message: __('errors.database.generic'), + type: 'error' + ); + } + return; + } + // ---------------------------------------------------------------- + + /** + * ---------------------------------------------------------------- + * Validation Exceptions + * ---------------------------------------------------------------- + */ + if ($e instanceof ValidationException) { + $errors = $e->validator->errors()->all(); + $this->toast( + message: implode(' ', $errors), + type: 'error' + ); + return; + } + // ---------------------------------------------------------------- + + $this->toast( + message: __('errors.generic'), + type: 'error' + ); + } + + /** + * Extract the column name from the error message. + * @param string $message + * @return string|null + */ + protected function extractColumnFromMessage(string $message): ?string + { + if (preg_match("/Column '(.+?)' cannot be null/", $message, $matches)) { + return $matches[1]; + } + + return null; + } +} diff --git a/lang/en/errors.php b/lang/en/errors.php new file mode 100644 index 000000000..14de367f1 --- /dev/null +++ b/lang/en/errors.php @@ -0,0 +1,10 @@ + 'Something went wrong. Please try again or contact support.', + 'database' => [ + 'generic' => 'There was a problem with the database. Please try again.', + 'column_cannot_be_null' => "The ':column' field cannot be null.", + 'missing_required_field' => 'A required field was not filled in.', + ], +]; diff --git a/lang/en/project/conducting.php b/lang/en/project/conducting.php index a39ae0ee6..f2c177c17 100644 --- a/lang/en/project/conducting.php +++ b/lang/en/project/conducting.php @@ -471,7 +471,21 @@ ' + +
+ CSV Format Guidelines:
+ Your CSV file must include the following column headers:
+ + Important: If any of the fields in bold are missing or empty, the import will not occur.' ], 'table' => [ 'database' => 'Database', diff --git a/lang/en/project/planning.php b/lang/en/project/planning.php index 2506b264c..b0cc0847d 100644 --- a/lang/en/project/planning.php +++ b/lang/en/project/planning.php @@ -527,6 +527,7 @@ ], 'type' => [ 'required' => 'The type field is required.', + 'in' => 'Select a valid type.' ], 'logs' => [ 'added' => 'Criteria added', diff --git a/lang/pt_BR/errors.php b/lang/pt_BR/errors.php new file mode 100644 index 000000000..6cd9393b9 --- /dev/null +++ b/lang/pt_BR/errors.php @@ -0,0 +1,10 @@ + 'Algo deu errado. Tente novamente ou entre em contato com o suporte.', + 'database' => [ + 'generic' => 'Problemas com o banco de dados. Tente novamente.', + 'column_cannot_be_null' => "O campo ':column' não pode ser nulo.", + 'missing_required_field' => 'Um campo obrigatório não foi preenchido.', + ], +]; diff --git a/lang/pt_BR/project/conducting.php b/lang/pt_BR/project/conducting.php index afe5f78fd..67bd99e18 100644 --- a/lang/pt_BR/project/conducting.php +++ b/lang/pt_BR/project/conducting.php @@ -297,7 +297,21 @@ ' + +
+ Orientações para o formato CSV:
+ O arquivo CSV deve conter os seguintes cabeçalhos de coluna:
+ + Atenção: Se algum dos campos em negrito estiver ausente ou vazio, a importação não será realizada.' ], 'table' => [ 'database' => 'Base de dados', diff --git a/lang/pt_BR/project/planning.php b/lang/pt_BR/project/planning.php index 8238e9138..e4d709ef2 100644 --- a/lang/pt_BR/project/planning.php +++ b/lang/pt_BR/project/planning.php @@ -527,6 +527,7 @@ ], 'type' => [ 'required' => 'Selecionar um tipo é obrigatório.', + 'in' => 'Selecione um tipo válido.', ], 'logs' => [ 'added' => 'Critério adicionado', diff --git a/resources/views/livewire/planning/criteria/criteria.blade.php b/resources/views/livewire/planning/criteria/criteria.blade.php index d82c558ec..298b90f9f 100644 --- a/resources/views/livewire/planning/criteria/criteria.blade.php +++ b/resources/views/livewire/planning/criteria/criteria.blade.php @@ -21,9 +21,9 @@ required /> @error("criteriaId") - - {{ $message }} - + + {{ $message }} + @enderror @@ -37,9 +37,9 @@ required /> @error("description") - - {{ $message }} - + + {{ $message }} + @enderror @@ -67,6 +67,11 @@ {{ __("project/planning.criteria.form.select-exclusion") }} + @error("type.value") + + {{ $message }} + + @enderror