| Current Path : /var/www/html/reddsis/app/Http/Controllers/Backend/ |
| Current File : /var/www/html/reddsis/app/Http/Controllers/Backend/ArtisanGenController.php |
<?php
namespace App\Http\Controllers\Backend;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
class ArtisanGenController extends Controller
{
// Dashboard
public function index()
{
return view('backend.module.artisanGen.index');
}
// Model Generator page
public function model()
{
return view('backend.module.artisanGen.model', [
'tables' => $this->getTables(),
]);
}
// CRUD Generator page
public function crud()
{
return view('backend.module.artisanGen.crud', [
'models' => $this->getExistingModels(),
'flashSuccess' => session('artisangen_crud_success'),
]);
}
// AJAX: Preview model from DB table
public function previewModel(Request $request)
{
try {
$table = $request->input('table');
$modelName = $request->input('model_name', $request->input('modelName')) ?: $this->tableToModel($table);
$namespace = $this->stripModelNameFromNamespace(
$this->normalizeNamespace($request->input('namespace', 'App\\Models\\Backend')),
Str::studly($modelName)
);
$style = $request->input('style', 'attributes');
if (!$table) {
return response()->json(['error' => 'Table name is required'], 422);
}
if (!Schema::hasTable($table)) {
return response()->json(['error' => "Table '{$table}' does not exist"], 422);
}
$rawNamespace = $this->normalizeNamespace($request->input('namespace', 'App\\Models\\Backend'));
$studlyModel = Str::studly($modelName);
$namespace = $this->stripModelNameFromNamespace($rawNamespace, $studlyModel);
$wasCorrected = $namespace !== $rawNamespace;
$code = $this->generateModelCode($table, $namespace, $modelName, $style);
$path = $this->namespaceToPath($namespace) . "/{$modelName}.php";
return response()->json([
'files' => [
['name' => $modelName . '.php', 'path' => $path, 'code' => $code],
],
'namespace' => $namespace,
'warning' => $wasCorrected ? "Model name '{$studlyModel}' was removed from the namespace automatically." : null,
]);
} catch (\Throwable $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
// AJAX: Generate model file
public function generateModel(Request $request)
{
try {
$table = $request->input('table');
$modelName = $request->input('model_name', $request->input('modelName')) ?: $this->tableToModel($table);
$rawNamespace = $this->normalizeNamespace($request->input('namespace', 'App\\Models\\Backend'));
$studlyModel = Str::studly($modelName);
$namespace = $this->stripModelNameFromNamespace($rawNamespace, $studlyModel);
$wasCorrected = $namespace !== $rawNamespace;
$style = $request->input('style', 'attributes');
if (!$table || !Schema::hasTable($table)) {
return response()->json(['error' => 'Valid table is required'], 422);
}
$code = $this->generateModelCode($table, $namespace, $modelName, $style);
$path = base_path($this->namespaceToPath($namespace) . "/{$modelName}.php");
File::makeDirectory(dirname($path), 0755, true, true);
File::put($path, $code);
return response()->json([
'success' => true,
'files' => [$this->namespaceToPath($namespace) . "/{$modelName}.php"],
'namespace' => $namespace,
'warning' => $wasCorrected ? "Model name '{$studlyModel}' was removed from the namespace automatically." : null,
]);
} catch (\Throwable $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
// Migration Generator page
public function migration()
{
return view('backend.module.artisanGen.migration');
}
// AJAX: Preview migration
public function previewMigration(Request $request)
{
try {
$table = $this->normalizeTableName($request->input('table'));
$fieldsString = $request->input('fields', '');
$primaryKey = $request->input('primaryKey', $request->input('primary_key', 'id'));
if (!$table) {
return response()->json(['error' => 'Table name is required'], 422);
}
$code = $this->generateMigrationCode($table, $fieldsString, $primaryKey);
$filename = date('Y_m_d_His') . "_create_{$table}_table.php";
return response()->json([
'files' => [
['name' => $filename, 'path' => "database/migrations/{$filename}", 'code' => $code],
],
]);
} catch (\Throwable $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
// AJAX: Generate migration file
public function generateMigration(Request $request)
{
try {
$table = $this->normalizeTableName($request->input('table'));
$fieldsString = $request->input('fields', '');
$primaryKey = $request->input('primaryKey', $request->input('primary_key', 'id'));
if (!$table) {
return response()->json(['error' => 'Table name is required'], 422);
}
$code = $this->generateMigrationCode($table, $fieldsString, $primaryKey);
$filename = date('Y_m_d_His') . "_create_{$table}_table.php";
$path = base_path("database/migrations/{$filename}");
File::put($path, $code);
return response()->json([
'success' => true,
'files' => ["database/migrations/{$filename}"],
'filename' => $filename,
]);
} catch (\Throwable $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
// AJAX: Run pending migrations
public function runMigration(Request $request)
{
try {
$filename = $request->input('filename');
if ($filename && !File::exists(base_path("database/migrations/{$filename}"))) {
return response()->json(['error' => "Migration file {$filename} not found"], 422);
}
$options = ['--force' => true];
if ($filename) {
$options['--path'] = "database/migrations/{$filename}";
}
Artisan::call('migrate', $options);
$output = Artisan::output();
return response()->json([
'success' => true,
'output' => $output,
]);
} catch (\Throwable $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
protected function generateMigrationCode($table, $fieldsString, $primaryKey)
{
$fields = $this->parseFields($fieldsString);
$migrationFields = '';
$hasCustomPrimaryKey = false;
foreach ($fields as $f) {
if ($f['name'] === $primaryKey) {
$hasCustomPrimaryKey = true;
}
$migrationFields .= $this->generateColumnLine($f) . "\n ";
}
if ($hasCustomPrimaryKey) {
$idLine = '';
} else {
$idLine = $primaryKey === 'id' ? '$table->id();' : "\$table->id('{$primaryKey}');";
$idLine .= "\n ";
}
$stub = File::get(base_path('stubs/migration.stub'));
return str_replace(
['{{tableName}}', '{{idLine}}', '{{fields}}'],
[$table, $idLine, $migrationFields],
$stub
);
}
protected function generateColumnLine($field)
{
$name = $field['name'];
$type = $field['type'];
$params = $field['params'] ?? null;
$modifiers = $field['modifiers'] ?? [];
$specialNoName = ['timestamps', 'timestampsTz', 'softDeletes', 'softDeletesTz', 'rememberToken'];
if (in_array($type, $specialNoName)) {
return "\$table->{$type}();";
}
$args = [];
if ($type === 'id') {
return $name && $name !== 'id' ? "\$table->id('{$name}');" : "\$table->id();";
}
if ($type === 'foreignIdFor') {
return "\$table->foreignIdFor({$params});";
}
if ($type === 'morphs' || $type === 'nullableMorphs' || $type === 'nullableUlidMorphs' || $type === 'nullableUuidMorphs' || $type === 'ulidMorphs' || $type === 'uuidMorphs') {
return "\$table->{$type}('{$name}');";
}
$args[] = "'{$name}'";
if ($params !== null) {
$args[] = $params;
}
$line = "\$table->{$type}(" . implode(', ', $args) . ')';
foreach ($modifiers as $modifier) {
if (preg_match('/^(\w+)\((.*)\)$/s', $modifier, $m)) {
$line .= "->{$m[1]}({$m[2]})";
} else {
$line .= "->{$modifier}()";
}
}
return $line . ';';
}
protected function parseFields($fieldsString)
{
if (!$fieldsString) return [];
$fieldStrings = $this->splitByCommaRespectingParentheses($fieldsString);
$result = [];
foreach ($fieldStrings as $field) {
if (!$field) continue;
$parts = $this->splitByColonRespectingQuotes($field);
if (count($parts) < 2) {
throw new \InvalidArgumentException("Invalid field definition: '{$field}'. Expected format: name:type or name:type:modifier or name:type(param):modifier");
}
$name = trim($parts[0]);
$typePart = trim($parts[1]);
$modifiers = array_slice($parts, 2);
if (!preg_match('/^[A-Za-z_]\w*$/', $name)) {
throw new \InvalidArgumentException("Invalid field name: '{$name}'");
}
$type = $typePart;
$params = null;
if (preg_match('/^(\w+)(?:\((.*)\))?$/s', $typePart, $matches)) {
$type = $matches[1];
$params = isset($matches[2]) && $matches[2] !== '' ? $matches[2] : null;
}
if (!$type || !preg_match('/^[A-Za-z_]\w*$/', $type)) {
throw new \InvalidArgumentException("Invalid field type: '{$typePart}'");
}
$result[] = [
'name' => $name,
'type' => $type,
'params' => $params,
'modifiers' => $modifiers,
];
}
return $result;
}
protected function splitByCommaRespectingParentheses($string)
{
$items = [];
$current = '';
$depth = 0;
for ($i = 0; $i < strlen($string); $i++) {
$char = $string[$i];
if ($char === '(') $depth++;
elseif ($char === ')') $depth--;
if ($char === ',' && $depth === 0) {
$items[] = trim($current);
$current = '';
} else {
$current .= $char;
}
}
if (trim($current) !== '') {
$items[] = trim($current);
}
return $items;
}
protected function splitByColonRespectingQuotes($string)
{
$parts = [];
$current = '';
$depth = 0;
$inQuote = false;
$quoteChar = null;
for ($i = 0; $i < strlen($string); $i++) {
$char = $string[$i];
if (!$inQuote) {
if ($char === '(') {
$depth++;
$current .= $char;
} elseif ($char === ')') {
$depth--;
$current .= $char;
} elseif ($char === '"' || $char === "'") {
$inQuote = true;
$quoteChar = $char;
$current .= $char;
} elseif ($char === ':' && $depth === 0) {
$parts[] = trim($current);
$current = '';
} else {
$current .= $char;
}
} else {
$current .= $char;
if ($char === $quoteChar && $string[$i - 1] !== '\\') {
$inQuote = false;
$quoteChar = null;
}
}
}
if (trim($current) !== '') {
$parts[] = trim($current);
}
return $parts;
}
// AJAX: Preview CRUD from model
public function previewCrud(Request $request)
{
try {
$modelClass = $request->input('modelClass', $request->input('model_class'));
if (!$modelClass) {
return response()->json(['error' => 'Model class is required'], 422);
}
$options = [
'controllerNamespace' => $request->input('controllerNamespace'),
'requestNamespace' => $request->input('requestNamespace'),
'viewPath' => $request->input('viewPath'),
];
$files = $this->buildCrudFiles($modelClass, false, $options);
return response()->json(['files' => $files]);
} catch (\Throwable $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
// AJAX: Generate CRUD files
public function generateCrud(Request $request)
{
try {
$modelClass = $request->input('modelClass', $request->input('model_class'));
if (!$modelClass) {
return response()->json(['error' => 'Model class is required'], 422);
}
$options = [
'controllerNamespace' => $request->input('controllerNamespace'),
'requestNamespace' => $request->input('requestNamespace'),
'viewPath' => $request->input('viewPath'),
];
$files = $this->buildCrudFiles($modelClass, false, $options);
$paths = array_column($files, 'path');
$route = $this->modelToRoute($modelClass);
$controllerClass = class_basename($modelClass) . 'Controller';
$viewFolder = Str::camel(class_basename($modelClass));
// Write files after response is sent to avoid Vite hot-reload
// race condition (Vite refreshes page when it detects new view files)
app()->terminating(function () use ($files) {
foreach ($files as $file) {
$path = base_path($file['path']);
File::makeDirectory(dirname($path), 0755, true, true);
File::put($path, $file['code']);
}
});
// Store success in session so it survives Vite page reload
session()->flash('artisangen_crud_success', [
'files' => $paths,
'route' => $route,
'controllerClass' => $controllerClass,
'viewFolder' => $viewFolder,
]);
return response()->json([
'success' => true,
'files' => $paths,
'route' => $route,
'controllerClass' => $controllerClass,
'viewFolder' => $viewFolder,
]);
} catch (\Throwable $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
// ──────────────────────────────────────────────
// CORE: Generate Model Code from DB Table
// ──────────────────────────────────────────────
protected function generateModelCode($table, $namespace, $modelName, $style = 'attributes')
{
$columns = Schema::getColumns($table);
$primaryKey = $this->getPrimaryKey($table);
$fillable = [];
$casts = [];
$hidden = [];
foreach ($columns as $col) {
$name = $col['name'];
$type = $col['type_name'] ?? 'string';
if (in_array($name, [$primaryKey, 'created_at', 'updated_at'])) {
continue;
}
$fillable[] = "'{$name}'";
// Type casting
if (in_array($type, ['int', 'integer', 'bigint', 'smallint', 'tinyint'])) {
$casts[$name] = 'integer';
} elseif ($type === 'boolean') {
$casts[$name] = 'boolean';
} elseif (in_array($type, ['datetime', 'timestamp'])) {
$casts[$name] = 'datetime';
} elseif ($type === 'date') {
$casts[$name] = 'date';
} elseif ($type === 'json') {
$casts[$name] = 'json';
}
// Hidden fields detection
if ($this->isHiddenField($name)) {
$hidden[] = "'{$name}'";
}
}
// Always cast audit & timestamp columns
$columnNames = array_column($columns, 'name');
if (in_array('created_by', $columnNames)) {
$casts['created_by'] = 'integer';
}
if (in_array('updated_by', $columnNames)) {
$casts['updated_by'] = 'integer';
}
if (in_array('created_at', $columnNames)) {
$casts['created_at'] = 'datetime';
}
if (in_array('updated_at', $columnNames)) {
$casts['updated_at'] = 'datetime';
}
if (in_array('deleted_at', $columnNames)) {
$casts['deleted_at'] = 'datetime';
}
$itemIndent = $style === 'properties' ? " " : " ";
$fillableStr = implode(",\n{$itemIndent}", $fillable);
$castsLines = [];
foreach ($casts as $k => $v) {
$castsLines[] = "'{$k}' => '{$v}'";
}
$castsStr = implode(",\n ", $castsLines);
$hiddenAttr = '';
$hiddenProp = '';
if (!empty($hidden)) {
$hiddenStr = implode(",\n{$itemIndent}", $hidden);
$hiddenAttr = "#[Hidden([\n {$hiddenStr}\n])]\n";
$hiddenProp = "\n\n protected \$hidden = [\n {$hiddenStr}\n ];";
}
$stubName = $style === 'properties' ? 'stubs/model-properties.stub' : 'stubs/model-attributes.stub';
$stub = File::get(base_path($stubName));
return str_replace(
['{{namespace}}', '{{modelName}}', '{{tableName}}', '{{primaryKey}}', '{{fillable}}', '{{casts}}', '{{hiddenAttr}}', '{{hiddenProp}}'],
[$namespace, $modelName, $table, $primaryKey, $fillableStr, $castsStr, $hiddenAttr, $hiddenProp],
$stub
);
}
// ──────────────────────────────────────────────
// CORE: Build CRUD Files from Model
// ──────────────────────────────────────────────
protected function buildCrudFiles($modelClass, $save, $options = [])
{
$modelName = class_basename($modelClass);
$tableName = Str::snake(Str::pluralStudly($modelName));
$primaryKey = $this->getPrimaryKey($tableName);
$routePrefix = Str::kebab(Str::pluralStudly($modelName));
$viewFolder = Str::camel($modelName);
$controllerNamespace = $this->normalizeNamespace($options['controllerNamespace'] ?? 'App\\Http\\Controllers\\Backend');
$requestNamespace = $this->normalizeNamespace($options['requestNamespace'] ?? 'App\\Http\\Requests\\Backend');
$viewPath = trim($options['viewPath'] ?? 'backend.module._gen.' . $viewFolder, '\\/. ');
$viewPath = preg_replace('#[/\\\\]+#', '.', $viewPath);
$requestClass = $requestNamespace . '\\' . $modelName . 'Request';
$columns = Schema::hasTable($tableName) ? Schema::getColumns($tableName) : [];
$fields = [];
foreach ($columns as $col) {
if (in_array($col['name'], ['id', $primaryKey, 'created_at', 'updated_at', 'deleted_at', 'created_by', 'updated_by'])) {
continue;
}
$fields[] = $col;
}
$files = [];
// Controller
$searchClauses = '';
$storeFields = '';
foreach ($fields as $f) {
$searchClauses .= "\$sub->orWhere('{$f['name']}', 'like', \"%{\$search}%\");\n ";
$storeFields .= "'{$f['name']}' => \$request->{$f['name']},\n ";
}
$controllerStub = File::get(base_path('stubs/controller.stub'));
$controllerCode = str_replace(
['{{controllerNamespace}}', '{{modelName}}', '{{modelClass}}', '{{requestClass}}', '{{tableName}}', '{{routePrefix}}', '{{viewPath}}', '{{primaryKey}}', '{{searchClauses}}', '{{storeFields}}'],
[$controllerNamespace, $modelName, $modelClass, $requestClass, $tableName, $routePrefix, $viewPath, $primaryKey, $searchClauses, $storeFields],
$controllerStub
);
$files[] = ['name' => $modelName . 'Controller.php', 'path' => $this->namespaceToPath($controllerNamespace) . "/{$modelName}Controller.php", 'code' => $controllerCode];
// Request
$rules = '';
foreach ($fields as $f) {
$rules .= "'{$f['name']}' => 'nullable|string|max:255',\n ";
}
$requestStub = File::get(base_path('stubs/request.stub'));
$requestCode = str_replace(['{{requestNamespace}}', '{{modelName}}', '{{rules}}'], [$requestNamespace, $modelName, $rules], $requestStub);
$files[] = ['name' => $modelName . 'Request.php', 'path' => $this->namespaceToPath($requestNamespace) . "/{$modelName}Request.php", 'code' => $requestCode];
// Views
$headers = '';
$rows = '';
$formFields = '';
$formEditFields = '';
foreach ($fields as $f) {
$label = Str::headline($f['name']);
$name = $f['name'];
$type = $f['type_name'] ?? 'string';
$headers .= "<th class=\"px-5 py-3 text-left\"><span class=\"text-xs font-medium uppercase tracking-wider text-gray-500\">{$label}</span></th>\n ";
$rows .= "<td class=\"px-5 py-4\"><span class=\"block text-sm text-gray-600\">{{ \$item->{$name} }}</span></td>\n ";
if (in_array($type, ['text', 'longtext', 'mediumtext'])) {
$formFields .= $this->textareaField($label, $name, false);
$formEditFields .= $this->textareaField($label, $name, true);
} elseif (in_array($type, ['boolean', 'tinyint'])) {
$formFields .= $this->checkboxField($label, $name, false);
$formEditFields .= $this->checkboxField($label, $name, true);
} else {
$formFields .= $this->inputField($label, $name, false);
$formEditFields .= $this->inputField($label, $name, true);
}
}
$viewDir = 'resources/views/' . str_replace('.', '/', $viewPath);
$indexStub = File::get(base_path('stubs/views/index.stub'));
$files[] = ['name' => 'index.blade.php', 'path' => "{$viewDir}/index.blade.php", 'code' => str_replace(
['{{modelName}}', '{{routePrefix}}', '{{headers}}', '{{rows}}', '{{colspan}}'],
[$modelName, $routePrefix, $headers, $rows, count($fields) + 2],
$indexStub
)];
$createStub = File::get(base_path('stubs/views/create.stub'));
$files[] = ['name' => 'create.blade.php', 'path' => "{$viewDir}/create.blade.php", 'code' => str_replace(
['{{modelName}}', '{{routePrefix}}', '{{formFields}}'],
[$modelName, $routePrefix, $formFields],
$createStub
)];
$editStub = File::get(base_path('stubs/views/edit.stub'));
$files[] = ['name' => 'edit.blade.php', 'path' => "{$viewDir}/edit.blade.php", 'code' => str_replace(
['{{modelName}}', '{{routePrefix}}', '{{formEditFields}}'],
[$modelName, $routePrefix, $formEditFields],
$editStub
)];
// Setup guide
$setupStub = File::get(base_path('stubs/views/setup.stub'));
$files[] = ['name' => 'CRUD-SETUP.md', 'path' => "{$viewDir}/CRUD-SETUP.md", 'code' => str_replace(
['{{modelName}}', '{{routePrefix}}'],
[$modelName, $routePrefix],
$setupStub
)];
return $files;
}
// ──────────────────────────────────────────────
// HELPERS
// ──────────────────────────────────────────────
protected function getTables()
{
$tables = DB::select('SHOW TABLES');
$key = 'Tables_in_' . DB::getDatabaseName();
$excluded = ['migrations', 'failed_jobs', 'cache', 'cache_locks', 'job_batches', 'jobs', 'sessions', 'password_reset_tokens', 'personal_access_tokens'];
return collect($tables)
->pluck($key)
->reject(fn($t) => in_array($t, $excluded))
->values()
->toArray();
}
protected function getExistingModels()
{
$models = [];
$dirs = [app_path('Models'), app_path('Models/Backend')];
foreach ($dirs as $dir) {
if (!File::isDirectory($dir)) continue;
foreach (File::allFiles($dir) as $file) {
$relative = $file->getRelativePathname();
$ns = str_replace('/', '\\', dirname($relative));
$base = $file->getFilenameWithoutExtension();
$prefix = str_contains($dir, 'Backend') ? 'App\\Models\\Backend' : 'App\\Models';
$models[] = $ns === '.' ? "{$prefix}\\{$base}" : "{$prefix}\\{$ns}\\{$base}";
}
}
return array_unique($models);
}
protected function getPrimaryKey($table)
{
$cols = DB::select("SHOW KEYS FROM `{$table}` WHERE Key_name = 'PRIMARY'");
return $cols[0]->Column_name ?? 'id';
}
protected function tableToModel($table)
{
$name = Str::singular($table);
return Str::studly($name);
}
protected function modelToRoute($modelClass)
{
$name = class_basename($modelClass);
return Str::kebab(Str::pluralStudly($name));
}
protected function isHiddenField($name)
{
$hiddenPatterns = ['password', 'remember_token', 'api_token', 'secret', 'pin', 'ssn', 'credit_card', 'cvv'];
$lower = strtolower($name);
foreach ($hiddenPatterns as $pattern) {
if (str_contains($lower, $pattern)) {
return true;
}
}
return false;
}
protected function normalizeNamespace($namespace)
{
$namespace = trim($namespace, " \t\n\r\\/");
$namespace = str_replace('/', '\\', $namespace);
while (str_contains($namespace, '\\\\')) {
$namespace = str_replace('\\\\', '\\', $namespace);
}
$parts = array_filter(explode('\\', $namespace));
$parts = array_map(fn($part) => Str::studly($part), $parts);
if (empty($parts) || $parts[0] !== 'App') {
array_unshift($parts, 'App');
} else {
$parts[0] = 'App';
}
return implode('\\', $parts);
}
protected function stripModelNameFromNamespace(string $namespace, string $modelName): string
{
$parts = explode('\\', $namespace);
$lastPart = end($parts);
if ($lastPart === $modelName) {
array_pop($parts);
}
return implode('\\', $parts);
}
protected function namespaceToPath($namespace)
{
return str_replace(['App\\', '\\'], ['app/', '/'], $namespace);
}
protected function normalizeTableName($table)
{
$table = trim($table);
$table = Str::snake($table);
$table = str_replace([' ', '-'], ['_', '_'], $table);
$table = preg_replace('/[^a-z0-9_]/', '', strtolower($table));
$table = preg_replace('/_+/', '_', $table);
return trim($table, '_');
}
protected function inputField($label, $name, $isEdit)
{
$value = $isEdit ? "{{ old('{$name}', \$item->{$name}) }}" : "{{ old('{$name}') }}";
return "<div class=\"mb-6\"><label class=\"mb-1.5 block text-sm font-medium\">{$label}</label><input type=\"text\" name=\"{$name}\" value=\"{$value}\" class=\"h-11 w-full rounded-lg border border-gray-300 px-4 text-sm\"></div>\n";
}
protected function textareaField($label, $name, $isEdit)
{
$value = $isEdit ? "{{ old('{$name}', \$item->{$name}) }}" : "{{ old('{$name}') }}";
return "<div class=\"mb-6\"><label class=\"mb-1.5 block text-sm font-medium\">{$label}</label><textarea name=\"{$name}\" rows=\"4\" class=\"w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm\">{$value}</textarea></div>\n";
}
protected function checkboxField($label, $name, $isEdit)
{
$checked = $isEdit ? "{{ old('{$name}', \$item->{$name}) ? 'checked' : '' }}" : "{{ old('{$name}') ? 'checked' : '' }}";
return "<div class=\"mb-6 flex items-center gap-3\"><label class=\"flex cursor-pointer items-center text-sm font-medium\"><input type=\"hidden\" name=\"{$name}\" value=\"0\"><input type=\"checkbox\" name=\"{$name}\" value=\"1\" {$checked} class=\"h-4 w-4\"><span class=\"ml-3\">{$label}</span></label></div>\n";
}
}