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/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 new file mode 100644 index 000000000..c5529401b --- /dev/null +++ b/database/migrations/default/2025_01_30_103120_create_spreadsheets_table.php @@ -0,0 +1,44 @@ +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); + }); + + Schema::create('spreadsheet_translations', function (Blueprint $table) { + // createDefaultTranslationsTableFields($table, Str::singular(modularityConfig('tables.spreadsheets', 'modularity_spreadsheets'))); + createDefaultTranslationsTableFields($table, 'spreadsheet'); + + $table->json('content')->default(new Expression('(JSON_ARRAY())'));; + }); + + + } + + public function down() + { + Schema::dropIfExists('spreadsheet_translations'); + Schema::dropIfExists(modularityConfig('tables.spreadsheets', 'modularity_spreadsheets')); + + } +} 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"); } diff --git a/src/Entities/Spread.php b/src/Entities/Spread.php index 371919974..fb49d5b8d 100644 --- a/src/Entities/Spread.php +++ b/src/Entities/Spread.php @@ -14,11 +14,11 @@ class Spread extends Model protected $fillable = [ 'name', 'published', - 'content', + 'content' ]; protected $casts = [ - 'content' => 'array', + 'content' => 'array' ]; // protected function casts(): array diff --git a/src/Entities/Spreadsheet.php b/src/Entities/Spreadsheet.php new file mode 100644 index 000000000..e9a0e68bc --- /dev/null +++ b/src/Entities/Spreadsheet.php @@ -0,0 +1,43 @@ + + */ + use HasTranslation; + + public $translatedAttributes = [ + 'content' + ]; + + protected $fillable = [ + 'published', + // 'content', + 'role', + 'locale' + ]; + + protected $casts = [ + 'content' => 'array' + ]; + + public function __construct(array $attributes = []) + { + $this->table = $this->getTable(); + parent::__construct($attributes); + } + + public function getTable() + { + return modularityConfig('tables.spreadsheets', 'modularity_spreads'); + } +} diff --git a/src/Entities/Traits/HasSpreadable.php b/src/Entities/Traits/HasSpreadable.php index 281a6debc..8b3216560 100644 --- a/src/Entities/Traits/HasSpreadable.php +++ b/src/Entities/Traits/HasSpreadable.php @@ -9,63 +9,57 @@ 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(); - } elseif ($model->_spread) { - // Handle existing spread updates + self::saving(static function(Model $model) { + + if (!$model->exists) { + // For new models, preserve the spread data for after creation. + + $model->_pendingSpreadData = array_merge($model->_pendingSpreadData, $model->_spread); + + } else if($model->_spread){ + // For existing models, rebuild the _spread attribute from the spreadable fillable inputs, + // and clean those fillables from the model. + $model->spreadable()->update([ - 'content' => $model->_spread, + 'content' => $model->_spread ]); } - - $model->cleanSpreadableAttributes(); + $model->offsetUnset('_spread'); }); - self::created(static function (Model $model) { - $model->spreadable()->create($model->_pendingSpreadData ?? []); + self::created(static function(Model $model) { + $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); + self::retrieved(static function(Model $model) { if ($model->spreadable) { $jsonData = $model->spreadable->content ?? []; - - // Set spreadable attributes on model, excluding protected attributes 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,13 +67,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 { - return array_merge( $this->getColumns(), // Using ManageEloquent's getColumns $this->definedRelations(), // Using ManageEloquent's definedRelations @@ -87,34 +79,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/Entities/Traits/HasSpreadsheetable.php b/src/Entities/Traits/HasSpreadsheetable.php new file mode 100644 index 000000000..e404daefb --- /dev/null +++ b/src/Entities/Traits/HasSpreadsheetable.php @@ -0,0 +1,96 @@ +spreadsheetable()->first(); + if ($spreadsheet) { + // Load the spreadsheet's translation for the current locale + $translatedSpreadsheet = $spreadsheet->translateOrDefault(app()->getLocale()); + $model->setAttribute('_spreadsheet', $translatedSpreadsheet->content); + } else { + $model->setAttribute('_spreadsheet', []); + } + }); + + static::creating(function (Model $model) {}); + + static::created(function (Model $model) { + $model->spreadsheetable()->create([ + 'content' => $model->_pendingSpreadsheetData ?? [], + 'locale' => app()->getLocale(), + ]); + }); + + static::updating(function (Model $model) {}); + + static::updated(function (Model $model) {}); + + static::saving(function (Model $model) { + if (!$model->exists) { + $model->_pendingSpreadsheetData = array_merge($model->_pendingSpreadsheetData, $model->_spreadsheet ?? []); + } else if ($model->_spreadsheet) { + $locale = app()->getLocale(); + // Update or create the spreadsheet translation via the relationship + $model->spreadsheetable()->updateOrCreate( + ['locale' => $locale], + ['content' => $model->_spreadsheet] + ); + } + $model->offsetUnset('_spreadsheet'); + }); + + static::saved(function (Model $model) {}); + + static::restoring(function (Model $model) {}); + + static::restored(function (Model $model) {}); + + static::replicating(function (Model $model) {}); + + static::deleting(function (Model $model) {}); + + static::deleted(function (Model $model) {}); + + static::forceDeleting(function (Model $model) {}); + + static::forceDeleted(function (Model $model) {}); + } + + /** + * Laravel hook to initialize the trait + * + * @return void + */ + public function initializeHasSpreadsheetable(): void + { + $spreadsheetableFields = ['_spreadsheet']; + $this->mergeFillable($spreadsheetableFields); + } + + public function spreadsheetable(): \Illuminate\Database\Eloquent\Relations\MorphMany + { + return $this->morphMany(\Unusualify\Modularity\Entities\Spreadsheet::class, 'spreadsheetable'); + } + + // protected function handleTranslatedSpreadsheet($data){ + + // } + +} diff --git a/src/Entities/Translations/SpreadsheetTranslation.php b/src/Entities/Translations/SpreadsheetTranslation.php new file mode 100644 index 000000000..580e9ce9e --- /dev/null +++ b/src/Entities/Translations/SpreadsheetTranslation.php @@ -0,0 +1,17 @@ + 'array', + ]; +} 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/SpreadHydrate.php b/src/Hydrates/Inputs/SpreadHydrate.php index dbcd32f11..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,32 +36,21 @@ public function hydrate() } $module = Modularity::find($input['_moduleName']); + + $repository = $module->getRepository($input['_routeName']); $model = App::make($module->getRouteClass($input['_routeName'], 'model')); - // dd($model); - if (! isset($input['reservedKeys'])) { + 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'); - if (! empty($spreadableInputs) || $spreadableInputs) { - // dd( array_merge($input['reservedKeys'], $spreadableInputs->toArray())); - $input['reservedKeys'] = array_merge($input['reservedKeys'], $spreadableInputs->toArray()); + $spreadableInputs = $repository->getSpreadableInputKeys($model); + + 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 new file mode 100644 index 000000000..728a4b0b6 --- /dev/null +++ b/src/Hydrates/Inputs/SpreadsheetHydrate.php @@ -0,0 +1,174 @@ + [], + + // 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'); + + 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); + 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 58044672b..66d1d44a6 100644 --- a/src/Repositories/Traits/SpreadableTrait.php +++ b/src/Repositories/Traits/SpreadableTrait.php @@ -4,82 +4,159 @@ 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; - // } + // dd($fields); $currentJson = $spreadableModel->content; - $newJson = array_merge($currentJson, $fields['_spread'] ?? []); - // Update the spreadable JSON + $newJson = array_merge( + $currentJson, + isset($fields['_spread']) ? $fields['_spread'] : [] + ); - foreach ($this->getSpreadableInputKeys() as $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(); + protected function prepareFieldsBeforeCreateSpreadableTrait($fields){ + if(isset($fields['_spread'])){ + return $fields; + } + return $this->prepareFieldsBeforeSaveSpreadableTrait($this->model, $fields); } + + } 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 () => { + + + }) + +}) +