From 6393e9b0cab8a47fdcbcd285e7a1c7510779e0a8 Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Thu, 23 Jan 2025 14:37:17 +0300 Subject: [PATCH 01/11] fix: :bug: fix change method that gets path on artisan call in payment operation --- operations/2025_01_23_203858_payments_table_operation.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/operations/2025_01_23_203858_payments_table_operation.php b/operations/2025_01_23_203858_payments_table_operation.php index e3afb61aa..c7ccdf16b 100644 --- a/operations/2025_01_23_203858_payments_table_operation.php +++ b/operations/2025_01_23_203858_payments_table_operation.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Artisan; use Symfony\Component\Console\Output\ConsoleOutput; use TimoKoerber\LaravelOneTimeOperations\OneTimeOperation; +use Unusualify\Modularity\Facades\Modularity; return new class extends OneTimeOperation { @@ -35,7 +36,6 @@ public function __construct() */ public function process(): void { - if (! Schema::hasTable('umod_payments') && Schema::hasTable('unfy_payments')) { @@ -50,8 +50,9 @@ public function process(): void }else { Artisan::call('migrate', [ - '--path' => 'vendor/unusualify/modularity/database/migrations/default/2024_06_24_125121_create_payments_table.php' + '--path' => Modularity::getVendorPath('database/migrations/default/2024_06_24_125121_create_payments_table.php') ]); + $this->info("\tumod_payments created"); } From a0214a728387feed5c4f30cf7c83510e4aa93117 Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Mon, 3 Feb 2025 17:49:19 +0300 Subject: [PATCH 02/11] fix: :bug: fix SpreadableTrait && HasSpreadable && refactor SpreadableTrait --- config/merges/tables.php | 2 + src/Entities/Spread.php | 4 +- src/Entities/Traits/HasSpreadable.php | 92 ++++------- src/Hydrates/Inputs/SpreadHydrate.php | 24 +-- src/Repositories/Traits/SpreadableTrait.php | 171 ++++++++++++++------ 5 files changed, 172 insertions(+), 121 deletions(-) diff --git a/config/merges/tables.php b/config/merges/tables.php index 53f391034..ed67a6101 100644 --- a/config/merges/tables.php +++ b/config/merges/tables.php @@ -17,4 +17,6 @@ 'chats' => 'umod_chats', 'chat_messages' => 'umod_chat_messages', 'spreads' => 'umod_spreads', + 'spreadsheets' => 'umod_spreadsheets', + ]; diff --git a/src/Entities/Spread.php b/src/Entities/Spread.php index 49afca549..9235971a9 100644 --- a/src/Entities/Spread.php +++ b/src/Entities/Spread.php @@ -18,11 +18,11 @@ class Spread extends Model protected $fillable = [ 'name', 'published', - 'json' + 'content' ]; protected $casts = [ - 'json' => 'array' + 'content' => 'array' ]; // protected function casts(): array diff --git a/src/Entities/Traits/HasSpreadable.php b/src/Entities/Traits/HasSpreadable.php index 71815ccdf..ff6513c14 100644 --- a/src/Entities/Traits/HasSpreadable.php +++ b/src/Entities/Traits/HasSpreadable.php @@ -10,62 +10,69 @@ trait HasSpreadable { use ManageEloquent; - protected $_pendingSpreadData; + protected $_pendingSpreadData = []; + + public static function bootHasSpreadable() { - //TODO: Keep the old spreadable data from model and remove attributes based on that don't remove all column fields self::saving(static function(Model $model) { - // Store the spread data before cleaning + if (!$model->exists) { - // Set property to preserve data through events - $model->_pendingSpreadData = $model->_spread ?: $model->prepareSpreadableJson(); - } else if($model->_spread) { - // Handle existing spread updates + // For new models, preserve the spread data for after creation. + // dd($model); + // dd($model->attributesToArray(), $model->fillable); + $model->_pendingSpreadData = array_merge($model->_pendingSpreadData, $model->_spread); + // dd($model, $model->_spread, $model->_pendingSpreadData); + // dd($model->attributesToArray()); + // dd($model->_pendingSpreadData); + } else if($model->_spread){ + // For existing models, rebuild the _spread attribute from the spreadable fillable inputs, + // and clean those fillables from the model. + // dd($model); + + // $spreadData = $model->updateSpreadableFromFillable(); + // Update the related spread record with the rebuilt spread data. + // dd($model); + // dd($model->attributesToArray(), $model->fillable); + $model->spreadable()->update([ - 'json' => $model->_spread + 'content' => $model->_spread ]); } + $model->offsetUnset('_spread'); - $model->cleanSpreadableAttributes(); + // Clean any remaining spreadable attributes from the main model. + // $model->cleanSpreadableAttributes(); }); self::created(static function(Model $model) { - $model->spreadable()->create($model->_pendingSpreadData ?? []); + $model->spreadable()->create([ + 'content' => $model->_pendingSpreadData ?? [] + ]); }); self::retrieved(static function(Model $model) { - // If there's a spread model, load its attributes - // dd('text'); - // dd($model); if ($model->spreadable) { - $jsonData = $model->spreadable->json ?? []; - - // Set spreadable attributes on model, excluding protected attributes + $jsonData = $model->spreadable->content ?? []; foreach ($jsonData as $key => $value) { if (!$model->isProtectedAttribute($key)) { $model->setAttribute($key, $value); } } - - // Set _spread attribute $model->setAttribute('_spread', $jsonData); - // dd($model->_spread); - } else { - // dd('here'); - // Initialize empty _spread if no spreadable exists $model->setAttribute('_spread', []); } }); - } public function initializeHasSpreadable() { - $this->mergeFillable(['_spread']); + $spreadableFields = ['_spread']; + $this->mergeFillable($spreadableFields); } - //TODO: rename relation to spread as well + public function spreadable(): \Illuminate\Database\Eloquent\Relations\MorphOne { return $this->morphOne(\Unusualify\Modularity\Entities\Spread::class, 'spreadable'); @@ -73,12 +80,11 @@ public function spreadable(): \Illuminate\Database\Eloquent\Relations\MorphOne protected function isProtectedAttribute(string $key): bool { - return in_array($key, $this->getReservedKeys()); } - public function getReservedKeys(): array { - + public function getReservedKeys(): array + { return array_merge( $this->getColumns(), // Using ManageEloquent's getColumns $this->definedRelations(), // Using ManageEloquent's definedRelations @@ -86,34 +92,4 @@ public function getReservedKeys(): array { ['spreadable', '_spread'] ); } - - protected function prepareSpreadableJson(): array - { - $attributes = $this->getAttributes(); - $protectedKeys = array_merge( - $this->getColumns(), // Using ManageEloquent's getColumns - $this->definedRelations(), // Using ManageEloquent's definedRelations - array_keys($this->getMutatedAttributes()), - ['spreadable', '_spread'] - ); - - return array_diff_key( - $attributes, - array_flip($protectedKeys) - ); - } - - protected function cleanSpreadableAttributes(): void - { - $columns = $this->getColumns(); - $attributes = $this->getAttributes(); - //TODO: Instead of removing any attribute remove the ones that you know that needs to be removed - // Remove any attributes that aren't database columns - foreach ($attributes as $key => $value) { - if (!in_array($key, $columns)) { - unset($this->attributes[$key]); - } - } - - } } diff --git a/src/Hydrates/Inputs/SpreadHydrate.php b/src/Hydrates/Inputs/SpreadHydrate.php index 2251e23c9..bf9dd9f6e 100644 --- a/src/Hydrates/Inputs/SpreadHydrate.php +++ b/src/Hydrates/Inputs/SpreadHydrate.php @@ -37,23 +37,27 @@ public function hydrate() } $module = Modularity::find($input['_moduleName']); + // dd($module); + // dd($module->getRepository($input['_routeName'])); + $repository = $module->getRepository($input['_routeName']); $model = App::make($module->getRouteClass($input['_routeName'], 'model')); - // dd($model); - + // dd($repository, get_class_methods($repository)); + // dd($repository); if(!isset($input['reservedKeys'])){ $input['reservedKeys'] = $model->getReservedKeys(); } // $allInputs = $model->getRouteInputs(); - $spreadableInputs = collect($model->getRouteInputs()) - ->filter(function ($item) { - return isset($item['spreadable']) && $item['spreadable'] === true; - }) - ->pluck('name'); - + // dd($model->getRouteInputs()); + // $spreadableInputs = collect($model->getRouteInputs()) + // ->filter(function ($item) { + // return isset($item['spreadable']) && $item['spreadable'] === true; + // }) + // ->pluck('name'); + $spreadableInputs = $repository->getSpreadableInputKeys($model); + // dd($spreadableInputs); if(!empty($spreadableInputs) || $spreadableInputs){ - // dd( array_merge($input['reservedKeys'], $spreadableInputs->toArray())); - $input['reservedKeys'] = array_merge($input['reservedKeys'], $spreadableInputs->toArray()); + $input['reservedKeys'] = array_merge($input['reservedKeys'], $spreadableInputs); } // dd($input['reservedKeys']); // $input['reservedKeys'] = collect($this->module->getRouteInput($input['_routeName'])) diff --git a/src/Repositories/Traits/SpreadableTrait.php b/src/Repositories/Traits/SpreadableTrait.php index 897bd0d1c..f2a3c5870 100644 --- a/src/Repositories/Traits/SpreadableTrait.php +++ b/src/Repositories/Traits/SpreadableTrait.php @@ -8,83 +8,152 @@ trait SpreadableTrait { - protected function beforeSaveSpreadTrait($object, $fields) + /** + * Prepare the spreadable JSON data by removing all protected attributes + * from the model's attributes. + * + * @param \Illuminate\Database\Eloquent\Model $object + * @return array + */ + protected function prepareSpreadableJson($object): array + { + // Get all model attributes + $attributes = $object->getAttributes(); + // Build list of protected keys (assume that the model has methods getColumns, definedRelations, and getMutatedAttributes) + $protectedKeys = array_merge( + $object->getColumns(), // Columns from the model (provided by ManageEloquent, for example) + $object->definedRelations(), // Relationship names defined on the model + array_keys($object->getMutatedAttributes()), + ['spreadable', '_spread'] // Hard-coded reserved keys + ); + // Return attributes minus the protected ones + return array_diff_key($attributes, array_flip($protectedKeys)); + } + + /** + * Recursively scan the model's route inputs for fields marked as spreadable. + * + * @param \Illuminate\Database\Eloquent\Model $object + * @return array + */ + public function getSpreadableInputKeys($object): array + { + $spreadableFields = []; + + // Recursive helper closure to scan the fields. + $findSpreadableFields = function($fields) use (&$spreadableFields, &$findSpreadableFields) { + foreach ($fields as $field) { + if (isset($field['spreadable']) && $field['spreadable'] === true) { + $spreadableFields[] = $field['name']; + } + if (isset($field['schema'])) { + $findSpreadableFields($field['schema']); + } + if (is_array($field) && !isset($field['type'])) { + $findSpreadableFields($field); + } + } + }; + + // Assume the model provides a getRouteInputs() method that returns the input definitions. + $findSpreadableFields($object->getRouteInputs()); + + return array_unique($spreadableFields); + } + + /** + * Rebuild the _spread attribute from the spreadable fillable inputs. + * Also removes these keys from the main $fields array. + * + * @param \Illuminate\Database\Eloquent\Model $object + * @param array $fields All fillable input data (passed by reference) + * @return array The rebuilt spread data + */ + protected function updateSpreadableFromFillable($object, array $fields): array + { + $spreadableKeys = $this->getSpreadableInputKeys($object); + $spreadData = []; + + foreach ($spreadableKeys as $key) { + if (array_key_exists($key, $fields)) { + $spreadData[$key] = $fields[$key]; + // Remove the attribute from the main fields array to prevent duplicate storage + unset($fields[$key]); + } + } + + // Update the model's _spread attribute + $object->setAttribute('_spread', $spreadData); + + return $spreadData; + } + + /** + * Remove all spreadable attributes (including _spread and spreadable) + * from the model's attributes. + * + * @param \Illuminate\Database\Eloquent\Model $object + * @return void + */ + public function cleanSpreadableAttributes($object): void + { + $spreadableKeys = array_merge( + $this->getSpreadableInputKeys($object), + ['_spread', 'spreadable'] + ); + + foreach ($spreadableKeys as $key) { + $object->offsetUnset($key); + } + } + + + protected function beforeSaveSpreadableTrait($object, $fields) { // Get the spreadable model instance - // dd($object->_spread); - // $spreadableModel = $object->spreadable; $spreadableModel = $object->spreadable()->first(); - // if (!$spreadableModel) { - // return; - // } - $currentJson = $spreadableModel->json; - $newJson = array_merge($currentJson, $fields['_spread'] ?? []); - // Update the spreadable JSON + $currentJson = $spreadableModel->content; + $newJson = array_merge( + $currentJson, + isset($fields['_spread']) ? $fields['_spread'] : [] + ); - foreach($this->getSpreadableInputKeys() as $key){ - if(isset($fields[$key])){ + // Update with individual spreadable fields if they are in $fields + foreach ($this->getSpreadableInputKeys($object) as $key) { + if (isset($fields[$key])) { $newJson[$key] = $fields[$key]; } } - // $object->_spread = $newJson; - // dd($object->getFillable()); + $this->cleanSpreadableAttributes($object); + // dd($object); $object->setAttribute('_spread', $newJson); - // dd('here'); + } /** - * @param \Unusualify\Modularity\Models\Model $object - * @param array $fields + * Prepare the fields before saving by extracting spreadable data. + * + * @param \Illuminate\Database\Eloquent\Model $object + * @param array $fields * @return array */ - protected function prepareFieldsBeforeSaveSpreadTrait($object, $fields) + protected function prepareFieldsBeforeSaveSpreadableTrait($object, $fields) { - // Get current JSON data - // $currentJson = json_decode($object->spreadable->json ?? '{}', true); - - // Get spreadable fields from the model $spreadableFields = $this->getSpreadableInputKeys($object); - // Initialize _spread array if it doesn't exist + // Initialize _spread if not already set $fields['_spread'] = $fields['_spread'] ?? $object->_spread; - // Process each field + // Process each field to see if it is spreadable foreach ($fields as $key => $value) { - // Check if the field is spreadable if (in_array($key, $spreadableFields)) { - // Add to _spread array - // dd($key,$value); + // Add the field to _spread and remove it from main fields $fields['_spread'][$key] = $value; - - // Remove from main fields array unset($fields[$key]); - - // Update current JSON if the field exists - // if (isset($currentJson[$key])) { - // $currentJson[$key] = $value; - // } } } - return $fields; } - - - /** - * Get the spreadable fields from the model - * @return array - */ - protected function getSpreadableInputKeys() - { - // Filter and return fields that are marked as spreadable - - return collect($this->model->getRouteInputs()) - ->filter(function ($field) { - return isset($field['spreadable']) && $field['spreadable'] === true; - }) - ->pluck('name') - ->toArray(); - } } From 1fe8306036333f257ffbacaa6d6108bf04037b18 Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Tue, 4 Feb 2025 14:12:31 +0300 Subject: [PATCH 03/11] feat: :sparkles: introduce reporting module && spreadsheet features --- ...01_30_103120_create_spreadsheets_table.php | 26 ++ src/Entities/Spreadsheet.php | 29 ++ src/Entities/Traits/HasSpreadsheetable.php | 55 ++++ src/Hydrates/Inputs/SpreadsheetHydrate.php | 72 +++++ .../Traits/SpreadsheetableTrait.php | 160 +++++++++++ vue/src/js/components/inputs/Spreadsheet.vue | 262 ++++++++++++++++++ .../components/v-input-spreadsheet.test.js | 33 +++ 7 files changed, 637 insertions(+) create mode 100644 database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php create mode 100644 src/Entities/Spreadsheet.php create mode 100644 src/Entities/Traits/HasSpreadsheetable.php create mode 100644 src/Hydrates/Inputs/SpreadsheetHydrate.php create mode 100644 src/Repositories/Traits/SpreadsheetableTrait.php create mode 100644 vue/src/js/components/inputs/Spreadsheet.vue create mode 100644 vue/test/components/v-input-spreadsheet.test.js diff --git a/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php b/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php new file mode 100644 index 000000000..e89deed8b --- /dev/null +++ b/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php @@ -0,0 +1,26 @@ + + */ + protected $fillable = [ + 'published', + 'content' + ]; + + public function __construct(array $attributes = []) + { + $this->table = unusualConfig('tables.spreadsheets', 'modularity_spreadsheets'); + parent::__construct($attributes); + } + + +} diff --git a/src/Entities/Traits/HasSpreadsheetable.php b/src/Entities/Traits/HasSpreadsheetable.php new file mode 100644 index 000000000..08f6a8fae --- /dev/null +++ b/src/Entities/Traits/HasSpreadsheetable.php @@ -0,0 +1,55 @@ + [], + + // keynote: application/x-iwork-keynote-sffkey + // pages: application/x-iwork-pages-sffpages + // numbers: application/x-iwork-numbers-sffnumbers + // pdf: application/pdf, + // doc: application/msword, + // docx: application/vnd.openxmlformats-officedocument.wordprocessingml.document + 'accepted-file-types' => [ // Acceptable file types - however not working well for now + 'image/*, file/*', + ], + + // Multiple file upload functionalities + 'allow-multiple' => true, + 'max-files' => null, + + // Other Functionalities + 'allow-drop' => true, + 'allow-replace' => true, // only works when allowMultiple is false + 'allow-remove' => true, + 'allow-reorder' => false, + 'allow-process' => true, + 'allow-image-preview' => false, + + // Drag-Drop Properties + 'drop-on-page' => false, // FilePond will catch all files dropped on the webpage + 'drop-on-element' => true, // Require drop on the FilePond element itself to catch the file. + 'drop-validation' => false, // When enabled, files are validated before they are dropped. A file is not added when it's invalid. + ]; + + /** + * Manipulate Input Schema Structure + * + * @return void + */ + public function hydrate() + { + $input = $this->input; + + $input['type'] = 'input-spreadsheet'; + + $input['credits'] = false; + + $input['inputName'] = $input['name'] ?? 'spreadsheet'; + + $input['max-files'] = $input['max'] ?? $input['max-files']; + + $input['label-idle'] ??= __('Drag & Drop your files or Browse'); + + $input['label-invalid-field'] ??= __('filepon-invalid-field-label'); + $input['label-file-loading'] ??= __('filepond-loading-lable'); + $input['label-file-load-error'] ??= __('filepond-loading-error-lable'); + $input['label-file-processing'] ??= __('filepond-processing-lable'); + $input['label-file-remove-error'] ??= __('filepond-removing-error-lable'); + + + return $input; + } +} diff --git a/src/Repositories/Traits/SpreadsheetableTrait.php b/src/Repositories/Traits/SpreadsheetableTrait.php new file mode 100644 index 000000000..01c3111cf --- /dev/null +++ b/src/Repositories/Traits/SpreadsheetableTrait.php @@ -0,0 +1,160 @@ + +
+ + + + + + + + {{ snackbar.text }} + + + + + + + + + + + + + + + + + + + + + + List + + + + + + + + + + + +
+ + + + + diff --git a/vue/test/components/v-input-spreadsheet.test.js b/vue/test/components/v-input-spreadsheet.test.js new file mode 100644 index 000000000..6a399410d --- /dev/null +++ b/vue/test/components/v-input-spreadsheet.test.js @@ -0,0 +1,33 @@ +// test/components/v-input-spreadsheet.test.js +import { describe, expect, test, vi } from 'vitest' +import { mount } from '@vue/test-utils' + +import vuetify from '../../src/js/plugins/vuetify' + +import VInputSpreadsheet from '../../src/js/components/VInputSpreadsheet.vue' + +let getModel = () => {} + +async function factory(props = {}, options = {}) { + + return await mount(VInputSpreadsheet, { + global: { + plugins: [vuetify], + }, + ...options, + props: { + ...props + } + }) +} + + +describe('VInputSpreadsheet tests', () => { + + test('renders the component', async () => { + + + }) + +}) + From f6daf8245fbd5c678435ab93a4ea767dc79baa3f Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Fri, 7 Feb 2025 16:25:30 +0300 Subject: [PATCH 04/11] feat: :sparkles: introduce spreadsheet feature && fix small bug at init_connector --- ...2025_01_06_161730_create_spreads_table.php | 3 +- ...01_30_103120_create_spreadsheets_table.php | 14 ++- src/Entities/Spreadsheet.php | 34 ++++-- src/Entities/Traits/HasSpreadable.php | 1 + src/Entities/Traits/HasSpreadsheetable.php | 22 +++- src/Helpers/connector.php | 4 +- src/Hydrates/Inputs/SpreadsheetHydrate.php | 104 ++++++++++++++++++ src/Repositories/Traits/SpreadableTrait.php | 9 +- vue/src/js/components/inputs/Spreadsheet.vue | 12 +- 9 files changed, 176 insertions(+), 27 deletions(-) diff --git a/database/migrations/default/2025_01_06_161730_create_spreads_table.php b/database/migrations/default/2025_01_06_161730_create_spreads_table.php index 5bee82f0d..de0c6bcb4 100644 --- a/database/migrations/default/2025_01_06_161730_create_spreads_table.php +++ b/database/migrations/default/2025_01_06_161730_create_spreads_table.php @@ -16,8 +16,7 @@ public function up() createDefaultTableFields($table); $table->uuidMorphs('spreadable'); $table->json('content')->default(new Expression('(JSON_ARRAY())')); - $table->timestamps(); - $table->softDeletes(); + createDefaultExtraTableFields($table); }); } diff --git a/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php b/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php index e89deed8b..9fe5fda18 100644 --- a/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php +++ b/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php @@ -3,24 +3,30 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Query\Expression; + class CreateSpreadsheetsTable extends Migration { public function up() { - Schema::create('spreadsheets', function (Blueprint $table) { + $modularitySpreadssheetsTable = modularityConfig('tables.spreadsheets', 'modularity_spreadsheets'); + + Schema::create($modularitySpreadssheetsTable, function (Blueprint $table) { // this will create an id, name field createDefaultTableFields($table); - + $table->uuidMorphs('spreadsheetable'); + $table->json('content')->default(new Expression('(JSON_ARRAY())')); // a "published" column, and soft delete and timestamps columns createDefaultExtraTableFields($table); }); - + } public function down() { - Schema::dropIfExists('spreadsheets'); + Schema::dropIfExists(modularityConfig('tables.spreadsheets', 'modularity_spreadsheets')); + } } diff --git a/src/Entities/Spreadsheet.php b/src/Entities/Spreadsheet.php index c1c4f3589..139429c2c 100644 --- a/src/Entities/Spreadsheet.php +++ b/src/Entities/Spreadsheet.php @@ -3,27 +3,37 @@ namespace Unusualify\Modularity\Entities; use Unusualify\Modularity\Entities\Model; - - +use Modules\PressRelease\Entities\Report; class Spreadsheet extends Model { - /** - * The attributes that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'published', - 'content' - ]; + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'published', + 'content', + 'report_id', // Ensure report_id is fillable if needed + ]; public function __construct(array $attributes = []) { - $this->table = unusualConfig('tables.spreadsheets', 'modularity_spreadsheets'); + $this->table = modularityConfig('tables.spreadsheets', 'modularity_spreadsheets'); parent::__construct($attributes); } + public function getTable() + { + return modularityConfig('tables.spreadsheets', 'modularity_spreads'); + } + /** + * Get the report that owns the spreadsheet. + */ + // public function report() + // { + // return $this->belongsTo(Report::class, 'report_id', 'id'); + // } } diff --git a/src/Entities/Traits/HasSpreadable.php b/src/Entities/Traits/HasSpreadable.php index 1fe742426..3ca53a082 100644 --- a/src/Entities/Traits/HasSpreadable.php +++ b/src/Entities/Traits/HasSpreadable.php @@ -21,6 +21,7 @@ public static function bootHasSpreadable() // For new models, preserve the spread data for after creation. // dd($model); // dd($model->attributesToArray(), $model->fillable); + // dd($model->_pendingSpreadData, $model->_spread) $model->_pendingSpreadData = array_merge($model->_pendingSpreadData, $model->_spread); // dd($model, $model->_spread, $model->_pendingSpreadData); // dd($model->attributesToArray()); diff --git a/src/Entities/Traits/HasSpreadsheetable.php b/src/Entities/Traits/HasSpreadsheetable.php index 08f6a8fae..a12703a27 100644 --- a/src/Entities/Traits/HasSpreadsheetable.php +++ b/src/Entities/Traits/HasSpreadsheetable.php @@ -6,6 +6,9 @@ trait HasSpreadsheetable { + + protected $_pendingSpreadsheetData = []; + /** * Perform any actions when booting the trait * @@ -23,7 +26,18 @@ public static function bootHasSpreadsheetable(): void static::updated(function (Model $model) {}); - static::saving(function (Model $model) {}); + static::saving(function (Model $model) { + // dd($model); + if (!$model->exists) { + // For new models, preserve the spreadsheet data for after creation. + $model->_pendingSpreadData = array_merge($model->_pendingSpreadsheetData, $model->_spreadsheet); + + } else if($model->_spread){ + $model->spreadsheetable()->update([ + 'content' => json_encode($model->_spreadsheet) + ]); + } + }); static::saved(function (Model $model) {}); @@ -49,7 +63,13 @@ public static function bootHasSpreadsheetable(): void */ public function initializeHasSpreadsheetable(): void { + $spreadsheetableFields = ['_spreadsheet']; + $this->mergeFillable($spreadsheetableFields); + } + public function spreadsheetable(): \Illuminate\Database\Eloquent\Relations\MorphOne + { + return $this->morphOne(\Unusualify\Modularity\Entities\Spreadsheet::class, 'spreadsheetable'); } } diff --git a/src/Helpers/connector.php b/src/Helpers/connector.php index f40b0bf29..5d8f523f0 100644 --- a/src/Helpers/connector.php +++ b/src/Helpers/connector.php @@ -119,7 +119,6 @@ function find_target(Module $moduleClass, string $routeName, $events) function exec_target($item) { - if (isset($item['repository'])) { $args = explode(':', $item['repository']); // dd($args); @@ -154,8 +153,9 @@ function exec_target($item) $item['itemTitle'] = array_keys(Arr::except($item['items'][0], ['name']))[0]; } } + $item['repository'] = $repository; + } - $item['repository'] = $repository; return $item; diff --git a/src/Hydrates/Inputs/SpreadsheetHydrate.php b/src/Hydrates/Inputs/SpreadsheetHydrate.php index 0e967c68c..735210863 100644 --- a/src/Hydrates/Inputs/SpreadsheetHydrate.php +++ b/src/Hydrates/Inputs/SpreadsheetHydrate.php @@ -2,6 +2,11 @@ namespace Unusualify\Modularity\Hydrates\Inputs; +use Exception; +use Illuminate\Support\Facades\Storage; +use Maatwebsite\Excel\Facades\Excel; +use Maatwebsite\Excel\Concerns\FromArray; + class SpreadsheetHydrate extends InputHydrate { /** @@ -66,7 +71,106 @@ public function hydrate() $input['label-file-processing'] ??= __('filepond-processing-lable'); $input['label-file-remove-error'] ??= __('filepond-removing-error-lable'); + foreach ($input['sheet_columns'] as $key => $value) { + if(preg_match('/[şİıöüÇçĞğ]/u', $value)) { + throw new Exception("Can't use Turkish special characters in: " . $value); + } + $input['sheet_columns'][$key] = mb_strtolower($value); + } + $input['example_file'] = $this->generateExampleFile($input); + // dd($input); return $input; } + + protected function generateExampleFile($input) + { + // Retrieve the model type information. + $modelType = init_connector($input['connector']); + $modelType = $modelType['module']->getRouteClass($modelType['route'], 'model'); + + // Build a unique filename and path. + $columnsPart = implode('_', $input['sheet_columns']); + $filename = "Spreadsheet_{$modelType}_{$input['name']}_{$columnsPart}.xlsx"; + $path = 'spreadsheet_examples/' . $filename; + + // Use the path as the cache key (since it uniquely identifies the file). + $cacheKey = $path; + + // Use Laravel's cache to avoid regenerating the file if it already exists. + // The closure passed to Cache::remember will only run if the cache key is not set. + return \Illuminate\Support\Facades\Cache::remember($cacheKey, now()->addDays(7), function () use ($input, $path) { + // Initialize Faker (this code runs only if the file is not cached). + $faker = \Faker\Factory::create(); + + // Create a header row using the sheet columns. + $header = $input['sheet_columns']; + + // Generate a sample data row based on the header columns using switch-case. + $dataRow = []; + foreach ($input['sheet_columns'] as $column) { + switch (true) { + case preg_match('/(url|link)/i', $column): + $dataRow[] = $faker->url; + break; + + case preg_match('/id/i', $column): + $dataRow[] = $faker->randomNumber(); + break; + + case preg_match('/name/i', $column): + $dataRow[] = $faker->name; + break; + + case preg_match('/description/i', $column): + $dataRow[] = $faker->paragraph; + break; + + case preg_match('/(number|integer)/i', $column): + $dataRow[] = $faker->numberBetween(1, 100); + break; + + default: + $dataRow[] = $faker->word; + break; + } + } + + // Combine the header and data row into one data array. + $data = [ + $header, + $dataRow, + ]; + + // Create an anonymous export class implementing FromArray. + $export = new class($data) implements \Maatwebsite\Excel\Concerns\FromArray { + private $data; + + public function __construct(array $data) + { + $this->data = $data; + } + + public function array(): array + { + return $this->data; + } + }; + + // Check if the file already exists on disk. + if (\Illuminate\Support\Facades\Storage::disk('public')->exists($path)) { + return \Illuminate\Support\Facades\Storage::disk('public')->url($path); + } + + // Store the Excel file on the 'public' disk. + $stored = \Maatwebsite\Excel\Facades\Excel::store($export, $path, 'public'); + + if ($stored) { + return \Illuminate\Support\Facades\Storage::disk('public')->url($path); + } + + throw new \Exception("Couldn't store the file; something went wrong."); + }); + } + } diff --git a/src/Repositories/Traits/SpreadableTrait.php b/src/Repositories/Traits/SpreadableTrait.php index 58b81288e..66d1d44a6 100644 --- a/src/Repositories/Traits/SpreadableTrait.php +++ b/src/Repositories/Traits/SpreadableTrait.php @@ -108,7 +108,7 @@ protected function beforeSaveSpreadableTrait($object, $fields) { // Get the spreadable model instance $spreadableModel = $object->spreadable()->first(); - + // dd($fields); $currentJson = $spreadableModel->content; $newJson = array_merge( $currentJson, @@ -151,5 +151,12 @@ protected function prepareFieldsBeforeSaveSpreadableTrait($object, $fields) } return $fields; } + protected function prepareFieldsBeforeCreateSpreadableTrait($fields){ + if(isset($fields['_spread'])){ + return $fields; + } + return $this->prepareFieldsBeforeSaveSpreadableTrait($this->model, $fields); + } + } diff --git a/vue/src/js/components/inputs/Spreadsheet.vue b/vue/src/js/components/inputs/Spreadsheet.vue index 3aa5edaef..6559bc4b7 100644 --- a/vue/src/js/components/inputs/Spreadsheet.vue +++ b/vue/src/js/components/inputs/Spreadsheet.vue @@ -45,10 +45,10 @@ /> @@ -94,7 +94,7 @@ import { useInput, makeInputProps, makeInputEmits } from '@/hooks' // Props const props = defineProps({ ...makeInputProps(), - columns: { + sheet_columns: { type: Array, default: () => [ { value: 'Yayin_Adi' }, @@ -102,7 +102,7 @@ const props = defineProps({ { value: 'highlighted' } ] }, - sampleFileUrl: { + example_file: { type: String, default: '' } @@ -139,6 +139,8 @@ onMounted(() => { fileInput.value = [new File([], 'existing-data.xlsx')] processTableData() } + console.log(props.example_file) + }) // Watch for external changes @@ -213,7 +215,7 @@ const processData = (data) => { if (hasValidHeaders) { return data.map(row => { - return props.columns.reduce((acc, col) => { + return props.sheet_columns.reduce((acc, col) => { acc[col.value] = row[col.value] || '' return acc }, {}) From 4b3de7a71be5244cf924e5a1877ae2384c2811c9 Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Thu, 13 Feb 2025 11:49:07 +0300 Subject: [PATCH 05/11] fix: :bug: fix variable name typo && update migrationg with role, locale columns --- .../2025_01_30_103120_create_spreadsheets_table.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php b/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php index 9fe5fda18..d20f47e3f 100644 --- a/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php +++ b/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php @@ -10,13 +10,16 @@ class CreateSpreadsheetsTable extends Migration { public function up() { - $modularitySpreadssheetsTable = modularityConfig('tables.spreadsheets', 'modularity_spreadsheets'); + $modularitySpreadsheetsTable = modularityConfig('tables.spreadsheets', 'modularity_spreadsheets'); - Schema::create($modularitySpreadssheetsTable, function (Blueprint $table) { + Schema::create($modularitySpreadsheetsTable, function (Blueprint $table) { // this will create an id, name field createDefaultTableFields($table); $table->uuidMorphs('spreadsheetable'); $table->json('content')->default(new Expression('(JSON_ARRAY())')); + $table->string('role')->nullable(); + $table->string('locale')->nullable(); + // a "published" column, and soft delete and timestamps columns createDefaultExtraTableFields($table); }); From c392047e5013e449d16e81e111ec0e8a9c55dc53 Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Thu, 13 Feb 2025 11:49:39 +0300 Subject: [PATCH 06/11] refactor: :recycle: remove unnecessary line of code --- src/Entities/Spreadsheet.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Entities/Spreadsheet.php b/src/Entities/Spreadsheet.php index 139429c2c..cb5bc6f7f 100644 --- a/src/Entities/Spreadsheet.php +++ b/src/Entities/Spreadsheet.php @@ -15,12 +15,17 @@ class Spreadsheet extends Model protected $fillable = [ 'published', 'content', - 'report_id', // Ensure report_id is fillable if needed + 'role', + 'locale' + ]; + + protected $casts = [ + 'content' => 'array' ]; public function __construct(array $attributes = []) { - $this->table = modularityConfig('tables.spreadsheets', 'modularity_spreadsheets'); + $this->table = $this->getTable(); parent::__construct($attributes); } @@ -28,12 +33,4 @@ public function getTable() { return modularityConfig('tables.spreadsheets', 'modularity_spreads'); } - - /** - * Get the report that owns the spreadsheet. - */ - // public function report() - // { - // return $this->belongsTo(Report::class, 'report_id', 'id'); - // } } From 7490975f8550e1cf512f54e6cf52a539927ccdf8 Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Thu, 13 Feb 2025 11:50:35 +0300 Subject: [PATCH 07/11] refactor: :recycle: clean unnecessary comment lines --- src/Entities/Traits/HasSpreadable.php | 17 ++--------- src/Entities/Traits/HasSpreadsheetable.php | 34 +++++++++++++++++----- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/Entities/Traits/HasSpreadable.php b/src/Entities/Traits/HasSpreadable.php index 3ca53a082..8b3216560 100644 --- a/src/Entities/Traits/HasSpreadable.php +++ b/src/Entities/Traits/HasSpreadable.php @@ -19,31 +19,18 @@ public static function bootHasSpreadable() if (!$model->exists) { // For new models, preserve the spread data for after creation. - // dd($model); - // dd($model->attributesToArray(), $model->fillable); - // dd($model->_pendingSpreadData, $model->_spread) + $model->_pendingSpreadData = array_merge($model->_pendingSpreadData, $model->_spread); - // dd($model, $model->_spread, $model->_pendingSpreadData); - // dd($model->attributesToArray()); - // dd($model->_pendingSpreadData); + } else if($model->_spread){ // For existing models, rebuild the _spread attribute from the spreadable fillable inputs, // and clean those fillables from the model. - // dd($model); - - // $spreadData = $model->updateSpreadableFromFillable(); - // Update the related spread record with the rebuilt spread data. - // dd($model); - // dd($model->attributesToArray(), $model->fillable); $model->spreadable()->update([ 'content' => $model->_spread ]); } $model->offsetUnset('_spread'); - - // Clean any remaining spreadable attributes from the main model. - // $model->cleanSpreadableAttributes(); }); self::created(static function(Model $model) { diff --git a/src/Entities/Traits/HasSpreadsheetable.php b/src/Entities/Traits/HasSpreadsheetable.php index a12703a27..3610bdd0d 100644 --- a/src/Entities/Traits/HasSpreadsheetable.php +++ b/src/Entities/Traits/HasSpreadsheetable.php @@ -16,27 +16,47 @@ trait HasSpreadsheetable */ public static function bootHasSpreadsheetable(): void { - static::retrieved(function (Model $model) {}); + static::retrieved(function (Model $model) { + if ($model->spreadsheetable) { + + $jsonData = $model->spreadsheetable->first()->content ?? []; + $model->setAttribute('_spreadsheet', $jsonData); + + } else { + + $model->setAttribute('_spreadsheet', []); + } + }); static::creating(function (Model $model) {}); - static::created(function (Model $model) {}); + static::created(function (Model $model) { + + $model->spreadsheetable()->create([ + 'content' => $model->_pendingSpreadsheetData ?? [] + ]); + + }); static::updating(function (Model $model) {}); static::updated(function (Model $model) {}); static::saving(function (Model $model) { - // dd($model); + if (!$model->exists) { // For new models, preserve the spreadsheet data for after creation. - $model->_pendingSpreadData = array_merge($model->_pendingSpreadsheetData, $model->_spreadsheet); + $model->_pendingSpreadsheetData = array_merge($model->_pendingSpreadsheetData, $model->_spreadsheet ?? []); + + } else if($model->_spreadsheet){ - } else if($model->_spread){ $model->spreadsheetable()->update([ 'content' => json_encode($model->_spreadsheet) ]); } + + $model->offsetUnset('_spreadsheet'); + }); static::saved(function (Model $model) {}); @@ -67,9 +87,9 @@ public function initializeHasSpreadsheetable(): void $this->mergeFillable($spreadsheetableFields); } - public function spreadsheetable(): \Illuminate\Database\Eloquent\Relations\MorphOne + public function spreadsheetable(): \Illuminate\Database\Eloquent\Relations\MorphMany { - return $this->morphOne(\Unusualify\Modularity\Entities\Spreadsheet::class, 'spreadsheetable'); + return $this->morphMany(\Unusualify\Modularity\Entities\Spreadsheet::class, 'spreadsheetable'); } } From 52de313aa9968bee360b7e11d0fbfd67d62e5c18 Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Thu, 13 Feb 2025 11:52:08 +0300 Subject: [PATCH 08/11] refactor: :recycle: clean unnecessary comment lines --- src/Hydrates/Inputs/SpreadHydrate.php | 26 +++++----------------- src/Hydrates/Inputs/SpreadsheetHydrate.php | 2 -- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/Hydrates/Inputs/SpreadHydrate.php b/src/Hydrates/Inputs/SpreadHydrate.php index 757fc876b..9ec93dedc 100644 --- a/src/Hydrates/Inputs/SpreadHydrate.php +++ b/src/Hydrates/Inputs/SpreadHydrate.php @@ -28,8 +28,7 @@ public function hydrate() $input = $this->input; // add your logic $input['type'] = 'input-spread'; - // dd($input); - // $input['items'] = ['test']; + if (in_array('scrollable', $input)) { $input = array_diff($input, ['scrollable']); $input['scrollable'] = true; @@ -37,36 +36,21 @@ public function hydrate() } $module = Modularity::find($input['_moduleName']); - // dd($module); - // dd($module->getRepository($input['_routeName'])); + $repository = $module->getRepository($input['_routeName']); $model = App::make($module->getRouteClass($input['_routeName'], 'model')); - // dd($repository, get_class_methods($repository)); - // dd($repository); + if(!isset($input['reservedKeys'])){ $input['reservedKeys'] = $model->getReservedKeys(); } - // $allInputs = $model->getRouteInputs(); - // dd($model->getRouteInputs()); - // $spreadableInputs = collect($model->getRouteInputs()) - // ->filter(function ($item) { - // return isset($item['spreadable']) && $item['spreadable'] === true; - // }) - // ->pluck('name'); + $spreadableInputs = $repository->getSpreadableInputKeys($model); - // dd($spreadableInputs); + if(!empty($spreadableInputs) || $spreadableInputs){ $input['reservedKeys'] = array_merge($input['reservedKeys'], $spreadableInputs); } - // dd($input['reservedKeys']); - // $input['reservedKeys'] = collect($this->module->getRouteInput($input['_routeName'])) - // ->filter(fn($item) => $item['name'] !== '_spread') - // ->pluck('name') - // ->toArray(); - // dd($reservedKeys); - // dd($input, $this->module, get_class_methods($this->module)); return $input; } } diff --git a/src/Hydrates/Inputs/SpreadsheetHydrate.php b/src/Hydrates/Inputs/SpreadsheetHydrate.php index 735210863..728a4b0b6 100644 --- a/src/Hydrates/Inputs/SpreadsheetHydrate.php +++ b/src/Hydrates/Inputs/SpreadsheetHydrate.php @@ -78,8 +78,6 @@ public function hydrate() $input['sheet_columns'][$key] = mb_strtolower($value); } $input['example_file'] = $this->generateExampleFile($input); - - // dd($input); return $input; } From b0b83808772345968541c4f84232c90545a91777 Mon Sep 17 00:00:00 2001 From: gunesbizim Date: Thu, 13 Feb 2025 11:52:31 +0300 Subject: [PATCH 09/11] refactor: :recycle: wrap spreadsheet input with v-input --- vue/src/js/components/inputs/Spreadsheet.vue | 30 ++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/vue/src/js/components/inputs/Spreadsheet.vue b/vue/src/js/components/inputs/Spreadsheet.vue index 6559bc4b7..27505d4c8 100644 --- a/vue/src/js/components/inputs/Spreadsheet.vue +++ b/vue/src/js/components/inputs/Spreadsheet.vue @@ -1,5 +1,7 @@