feat wiki/ayuda section

This commit is contained in:
amazuecos
2025-03-02 10:22:01 +01:00
parent 3406fb3005
commit 3140e527e8
24 changed files with 735 additions and 168 deletions

View File

@ -7,11 +7,14 @@ $routes->group('wiki', ['namespace' => 'App\Controllers\Wiki'], function ($route
$routes->get('','WikiController::index',["as" => "wikiIndex"]);
$routes->get('view/(:segment)','WikiController::show_page/$1',["as" => "showWikiPage"]);
$routes->get('section/(:num)','WikiController::get_section/$1',["as" => "getWikiSection"]);
$routes->post('section','WikiController::store_section/$1',["as" => "storeWikiSection"]);
$routes->post('section','WikiController::store_section',["as" => "storeWikiSection"]);
$routes->delete('section/(:num)','WikiController::delete_section/$1',["as" => "deleteWikiSection"]);
$routes->post('update/section','WikiController::update_section',["as" => "updateWikiSection"]);
$routes->post('save/(:num)','WikiController::store_save_page/$1',["as" => "storeWikiSavePage"]);
$routes->post('publish/(:num)','WikiController::store_publish_page/$1',["as" => "storeWikiPublishPage"]);
$routes->post('file/upload/(:num)','WikiController::wiki_file_upload/$1',["as" => "storeWikiFileUpload"]);
$routes->get('file/(:num)','WikiController::get_wiki_file/$1',["as" => "getWikiFile"]);
$routes->post('section/update/order','WikiController::wiki_section_update_order',["as" => "updateWikiSectionOrder"]);
});

View File

@ -7,10 +7,12 @@ use App\Models\Wiki\WikiContentModel;
use App\Models\Wiki\WikiFileModel;
use App\Models\Wiki\WikiPageModel;
use App\Models\Wiki\WikiSectionModel;
use App\Models\Wiki\WikiSectionRoleModel;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\I18n\Time;
use Psr\Log\LoggerInterface;
use Throwable;
class WikiController extends BaseController
{
@ -18,6 +20,8 @@ class WikiController extends BaseController
protected WikiContentModel $wikiContentModel;
protected WikiPageModel $wikiPageModel;
protected WikiFileModel $wikiFileModel;
protected WikiSectionRoleModel $wikiSectionRoleModel;
protected string $locale;
protected array $viewData;
@ -28,6 +32,7 @@ class WikiController extends BaseController
$this->wikiPageModel = model(WikiPageModel::class);
$this->wikiContentModel = model(WikiContentModel::class);
$this->wikiFileModel = model(WikiFileModel::class);
$this->wikiSectionRoleModel = model(WikiSectionRoleModel::class);
$sections = $this->wikiSectionModel->sections();
$this->locale = session()->get('lang');
@ -41,18 +46,32 @@ class WikiController extends BaseController
}
public function show_page(string $slug)
{
$section = $this->wikiSectionModel->where('slug', $slug)->first();
$this->viewData['slug'] = $slug;
$this->viewData['section'] = $section->withAll($this->locale);
$this->viewData['breadcrumb'] = [
['title' => lang("Wiki.".$section->slug), 'route' => route_to('showWikiPage',$slug), 'active' => true],
];
return view('themes/vuexy/wiki/pages/render', $this->viewData);
if($section){
if(auth()->user()->inGroup(...$section->roles_array()) || auth()->user()->inGroup('admin')){
$this->viewData['slug'] = $slug;
$this->viewData['section'] = $section->withAll($this->locale);
$this->viewData['breadcrumb'] = [
['title' => lang("Wiki." . $section->slug), 'route' => route_to('showWikiPage', $slug), 'active' => true],
];
return view('themes/vuexy/wiki/pages/render', $this->viewData);
}else{
return $this->response->setStatusCode(403);
}
}else{
return redirect_to('/');
}
}
public function get_section(int $section_id)
{
$section = $this->wikiSectionModel->find($section_id)->withAll($this->locale);
return $this->response->setJSON(["data" => $section, "message" => lang("App.global_alert_fetch_success")]);
if(auth()->user()->inGroup(...$section->roles_array()) || auth()->user()->inGroup('admin')){
return $this->response->setJSON(["data" => $section, "message" => lang("App.global_alert_fetch_success")]);
}else{
return $this->response->setStatusCode(403);
}
}
public function store_save_page(int $section_id)
{
@ -82,8 +101,8 @@ class WikiController extends BaseController
$wikiSectionPage = $this->wikiSectionModel->find($section_id)->page();
if ($wikiSectionPage) {
$wikiPageId = $wikiSectionPage->id;
$wikiContentId = $this->wikiContentModel->where("page_id",$wikiPageId)->where('locale',$this->locale)->first()->id;
$this->wikiContentModel->update($wikiContentId,[
$wikiContentId = $this->wikiContentModel->where("page_id", $wikiPageId)->where('locale', $this->locale)->first()->id;
$this->wikiContentModel->update($wikiContentId, [
"published_by" => auth()->user()->id,
"last_edit_by" => auth()->user()->id,
"editor_data" => json_encode($bodyData),
@ -109,16 +128,16 @@ class WikiController extends BaseController
$fullpath = $file->store('wiki_images/' . $section->slug);
$r = $this->wikiFileModel->insert(["content_id" => $content->id, "path" => $fullpath]);
return $this->response->setJSON(["success" => 1, "file" => [
"url" => '/wiki/file/'.$r
"url" => '/wiki/file/' . $r
]]);
}else{
} else {
return $this->response->setJSON(["success" => 0, "file" => [
"url" => null
]]);
}
} catch (\Throwable $th) {
return $this->response->setJSON(["success" => 0, "error" => $th->getMessage()])->setStatusCode($th->getCode());
return $this->response->setJSON(["success" => 0, "error" => $th->getMessage()])->setStatusCode(403);
}
}
public function get_wiki_file(int $wiki_file_id)
@ -132,7 +151,69 @@ class WikiController extends BaseController
->setHeader('Content-Length', filesize($filePath))
->setBody(file_get_contents($filePath));
} else {
return $this->response->setJSON(["message" => "Portada error", "error" => "No hay portada"])->setStatusCode(400);
return $this->response->setJSON(["success" => 0, "error" => lang('Wiki.file_dont_exist')])->setStatusCode(400);
}
}
public function wiki_section_update_order()
{
try {
$bodyData = $this->request->getPost();
$orders = $bodyData['orders'];
foreach ($orders as $key => $section_id) {
$this->wikiSectionModel->update($section_id, ['order' => $key]);
}
return $this->response->setJSON(["status" => 1, "message" => lang('Wiki.order_success')]);
} catch (\Throwable $th) {
return $this->response->setJSON(["status" => 0, "message" => lang('Wiki.order_error')])->setStatusCode(400);
}
}
public function store_section()
{
try {
$bodyData = $this->request->getPost();
$sectionName = $bodyData['name'];
$roles = $bodyData['roles'];
$bodyData["slug"] = implode('-', array_map(fn($e) => strtolower($e), explode(' ', $sectionName)));
$bodyData["order"] = $this->wikiSectionModel->selectMax('order')->first()->order + 1;
$wikiSectionId = $this->wikiSectionModel->insert($bodyData);
if(count($roles) > 0){
foreach ($roles as $key => $role) {
$this->wikiSectionRoleModel->insert(['wiki_section_id' => $wikiSectionId , 'role' => $role]);
}
}
return $this->response->setJSON(["status" => 1, "message" => lang('Wiki.section_new_success')]);
} catch (\Throwable $th) {
return $this->response->setJSON(["status" => 0, "message" => $th->getMessage()])->setStatusCode(400);
}
}
public function update_section()
{
try {
$bodyData = $this->request->getPost();
$wikiSectionId = $bodyData['wiki_section_id'];
$roles = $bodyData['roles'];
$this->wikiSectionModel->update($wikiSectionId, [
"name" => $bodyData['name'],
"icon" => $bodyData['icon']
]);
$this->wikiSectionRoleModel->where('wiki_section_id',$wikiSectionId)->delete();
if(count($roles) > 0){
foreach ($roles as $key => $role) {
$this->wikiSectionRoleModel->insert(['wiki_section_id' => $wikiSectionId , 'role' => $role]);
}
}
return $this->response->setJSON(["status" => 1, "message" => lang('Wiki.section_edit_success')]);
} catch (\Throwable $th) {
return $this->response->setJSON(["status" => 0, "message" => $th->getMessage()])->setStatusCode(400);
}
}
public function delete_section(int $section_id)
{
try{
$this->wikiSectionModel->delete($section_id);
return $this->response->setJSON(["status" => 1, "message" => lang('Wiki.section_delete_success')]);
}catch(Throwable $th){
return $this->response->setJSON(["status" => 0, "message" => $th->getMessage()])->setStatusCode(400);
}
}
}

View File

@ -14,6 +14,11 @@ class WikiSectionsMigration extends Migration
'unsigned' => true,
'auto_increment' => true,
],
'order' => [
'type' => 'INT',
'unsigned' => true,
'null'=> true,
],
'name' => [
'type' => 'VARCHAR',
'constraint' => 255,
@ -22,11 +27,6 @@ class WikiSectionsMigration extends Migration
'type' => 'VARCHAR',
'constraint' => 255,
],
'role' => [
'type' => 'VARCHAR',
'constraint' => 255,
'default' => 'admin'
],
'icon' => [
'type' => 'VARCHAR',
'constraint' => 255,

View File

@ -0,0 +1,60 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
use CodeIgniter\Database\RawSql;
class WikiSectionRolesMigration extends Migration
{
protected array $COLUMNS = [
'id' => [
'type' => 'INT',
'unsigned' => true,
'auto_increment' => true,
],
'wiki_section_id' => [
'type' => 'INT',
'unsigned' => true,
],
'role' => [
'type' => 'VARCHAR',
'constraint' => 255,
'default' => 'admin'
],
];
public function up()
{
$this->forge->addField($this->COLUMNS);
$currenttime = new RawSql('CURRENT_TIMESTAMP');
$this->forge->addField([
'created_at' => [
'type' => 'TIMESTAMP',
'default' => $currenttime,
],
'updated_at' => [
'type' => 'TIMESTAMP',
'null' => true,
],
'deleted_at' => [
'type' => 'TIMESTAMP',
'null' => true,
],
]);
$this->forge->addPrimaryKey('id');
$this->forge->addForeignKey('wiki_section_id','wiki_sections','id');
$this->forge->createTable("wiki_section_roles");
}
public function down()
{
$this->forge->dropTable("wiki_section_roles");
}
}

View File

@ -4,102 +4,162 @@ namespace App\Database\Seeds;
use App\Models\Configuracion\ConfigVariableModel;
use App\Models\Wiki\WikiSectionModel;
use App\Models\Wiki\WikiSectionRoleModel;
use CodeIgniter\Database\Seeder;
class WikiSectionSeeder extends Seeder
{
protected array $dataAdmin = [
[
"name" => 'Introducción',
"slug" => 'intro-admin',
"icon" => 'ti ti-home-2'
[
"name" => 'Introducción',
"slug" => 'intro-admin',
"icon" => 'ti ti-home-2',
"roles" => [
"admin",
],
[
"name" => 'Presupuesto',
"slug" => 'presupuesto-admin',
"icon" => 'ti ti-currency-dollar'
],
[
"name" => 'Presupuesto',
"slug" => 'presupuesto-admin',
"icon" => 'ti ti-currency-dollar',
"roles" => [
"admin",
],
],
[
"name" => 'Pedidos',
"slug" => 'pedidos-admin',
"icon" => 'ti ti-file-description',
"roles" => [
"admin",
],
[
"name" => 'Pedidos',
"slug" => 'pedidos-admin',
"icon" => 'ti ti-file-description'
],
[
"name" => 'Facturación',
"slug" => 'facturacion-admin',
"icon" => 'ti ti-file-dollar',
"roles" => [
"admin",
],
[
"name" => 'Facturación',
"slug" => 'facturacion-admin',
"icon" => 'ti ti-file-dollar'
],
[
"name" => 'Logística',
"slug" => 'logistica-admin',
"icon" => 'ti ti-truck',
"roles" => [
"admin",
],
[
"name" => 'Logística',
"slug" => 'logistica-admin',
"icon" => 'ti ti-truck'
],
[
"name" => 'Tarifas',
"slug" => 'tarifas-admin',
"icon" => 'ti ti-receipt',
"roles" => [
"admin",
],
[
"name" => 'Tarifas',
"slug" => 'tarifas-admin',
"icon" => 'ti ti-receipt'
],
[
"name" => 'Configuración',
"slug" => 'config-admin',
"icon" => 'ti ti-adjustments-horizontal',
"roles" => [
"admin",
],
[
"name" => 'Configuración',
"slug" => 'config-admin',
"icon" => 'ti ti-adjustments-horizontal'
],
[
"name" => 'Mensajería',
"slug" => 'messages-admin',
"icon" => 'ti ti-message',
"roles" => [
"admin",
],
[
"name" => 'Mensajería',
"slug" => 'messages-admin',
"icon" => 'ti ti-message'
]
]
];
protected array $dataCliente = [
[
"name" => 'Introducción',
"slug" => 'intro-cliente',
"icon" => 'ti ti-home-2',
"roles" => [
"cliente-admin",
"cliente-editor",
]
],
[
"name" => 'Presupuesto(Cliente)',
"slug" => 'presupuesto-cliente',
"role" => 'cliente',
"icon" => 'ti ti-currency-dollar',
"role" => 'cliente',
"roles" => [
"cliente-admin",
"cliente-editor",
]
],
[
"name" => 'Pedidos(Cliente)',
"slug" => 'pedidos-cliente',
"icon" => 'ti ti-file-description',
"role" => 'cliente',
"roles" => [
"cliente-admin",
"cliente-editor",
]
],
[
"name" => 'Facturación (Cliente)',
"slug" => 'facturacion-cliente',
"icon" => 'ti ti-file-dollar',
"role" => 'cliente',
"roles" => [
"cliente-admin",
"cliente-editor",
]
],
[
"name" => 'Tarifas (Cliente)',
"slug" => 'tarifas-cliente',
"icon" => 'ti ti-receipt',
"role" => 'cliente',
"roles" => [
"cliente-admin",
"cliente-editor",
]
],
[
"name" => 'Mensajería (Cliente)',
"slug" => 'messages-cliente',
"icon" => 'ti ti-message',
"role" => 'cliente',
"roles" => [
"cliente-admin",
"cliente-editor",
]
]
];
];
public function run()
{
$wikiSectionModel = model(WikiSectionModel::class);
$wikiSectionRoleModel = model(WikiSectionRoleModel::class);
$section_order = 0;
foreach ($this->dataAdmin as $key => $row) {
# code...
$wikiSectionModel->insert($row);
$row['order'] = $section_order;
$wikiSectionId = $wikiSectionModel->insert($row);
$section_order++;
foreach ($row['roles'] as $key => $role) {
$wikiSectionRoleModel->insert(['wiki_section_id' => $wikiSectionId,"role" => $role]);
}
}
foreach ($this->dataCliente as $key => $row) {
# code...
$wikiSectionModel->insert($row);
$row['order'] = $section_order;
$wikiSectionId = $wikiSectionModel->insert($row);
$section_order++;
foreach ($row['roles'] as $key => $role) {
$wikiSectionRoleModel->insert(['wiki_section_id' => $wikiSectionId,"role" => $role]);
}
}
}
}

View File

@ -3,6 +3,7 @@ namespace App\Entities\Wiki;
use App\Models\Wiki\WikiContentModel;
use App\Models\Wiki\WikiPageModel;
use App\Models\Wiki\WikiSectionRoleModel;
use CodeIgniter\Entity\Entity;
class WikiSectionEntity extends Entity
@ -11,6 +12,7 @@ class WikiSectionEntity extends Entity
"name" => null,
"slug" => null,
"role" => null,
"order" => null,
"icon" => null,
"parent_id" => null
];
@ -19,6 +21,7 @@ class WikiSectionEntity extends Entity
"slug" => "string",
"role" => "string",
"icon" => "string",
"order" => "int",
"parent_id" => "int"
];
@ -34,10 +37,25 @@ class WikiSectionEntity extends Entity
$this->attributes['contents'] = $this->content($locale);
return $this;
}
public function withRoles(): self
{
$m = model(WikiSectionRoleModel::class);
$this->attributes['roles'] = $this->roles();
return $this;
}
public function withRolesArray(): self
{
$m = model(WikiSectionRoleModel::class);
$this->attributes['roles_array'] = $this->roles_array();
return $this;
}
public function withAll(string $locale = "es") : self
{
$this->withPage();
$this->withContents($locale);
$this->withRoles();
$this->withRolesArray();
return $this;
}
@ -46,6 +64,19 @@ class WikiSectionEntity extends Entity
$m = model(WikiPageModel::class);
return $m->where('section_id',$this->attributes['id'])->first();
}
public function roles_array(): ?array
{
$m = model(WikiSectionRoleModel::class);
$section_roles = $m->where('wiki_section_id',$this->attributes['id'])->findAll();
$roles = array_map(fn($r) => $r->role,$section_roles);
return $roles;
}
public function roles(): ?array
{
$m = model(WikiSectionRoleModel::class);
$section_roles = $m->where('wiki_section_id',$this->attributes['id'])->findAll();
return $section_roles;
}
public function content(string $locale = "es"): ?WikiContentEntity
{
$page = $this->page();

View File

@ -0,0 +1,28 @@
<?php
namespace App\Entities\Wiki;
use App\Models\Wiki\WikiContentModel;
use App\Models\Wiki\WikiPageModel;
use App\Models\Wiki\WikiSectionModel;
use CodeIgniter\Entity\Entity;
class WikiSectionRoleEntity extends Entity
{
protected $attributes = [
"wiki_section_id" => null,
"role" => null,
];
protected $casts = [
"wiki_section_id" => "int",
"role" => "string",
];
public function page(): ?WikiSectionEntity
{
$m = model(WikiSectionModel::class);
return $m->where('id',$this->attributes['section_id'])->first();
}
}

View File

@ -1,23 +1,44 @@
<?php
return [
'intro' => "Introduction",
'presupuesto-cliente' => "Cliente budget",
'presupuesto-administrador' => "Admin budget",
'pedidos' => "Orders",
'facturacion' => "Invoice",
'logistica' => "Logistic",
'tarifas' => "Tariff",
'config' => "Configuration",
'messages' => "Messages",
'help' => "Help",
'intro-admin' => "Introduction",
'presupuesto-cliente' => "Client budget",
'presupuesto-admin' => "Admin budget",
'pedidos-cliente' => "Orders",
'pedidos-admin' => "Orders",
'facturacion-admin' => "Invoice",
'facturacion-cliente' => "Invoice (Client)",
'logistica-admin' => "Logistic",
'tarifas-admin' => "Tariff",
'tarifas-cliente' => "Tariff (Client)",
'config-admin' => "Configuration",
'messages-admin' => "Messages",
'messages-cliente' => "Messages (Client)",
'save' => "Save",
'release' => "Release",
'preview' => "Preview",
'edit' => "Edit",
'new_section' => "New section",
'edit_section' => "Edit section",
'header-placeholder' => "Start writing a header ...",
'errors' => [
'publish_before_save' => "You have to save before publish the content"
],
'alt' => [
"sort" => "Drag to set the section order"
],
'order_success' => 'Order updated',
'order_error' => 'An error has ocurred',
'published' => 'Released',
'not_published' => 'Not released'
'not_published' => 'Not released',
'section_new_success' => 'Section created successfully',
'section_edit_success' => 'Section updated successfully',
'need_reload' => 'You need to refresh the page after creating/updating a section to see the changes.',
'file_dont_exist' => "File doesn't exist",
'need_editor_to_save' => 'Need to be in edit mode to save the content.',
'no_content' => 'Page is empty',
'dropdown_roles' => 'Roles that can see this section',
];

View File

@ -1,6 +1,7 @@
<?php
return [
'help' => "Ayuda",
'intro-admin' => "Introducción",
'intro-cliente' => "Introducción",
'presupuesto-cliente' => "Presupuesto (Cliente)",
@ -22,6 +23,7 @@ return [
'name' => "Nombre sección",
'icon' => "Icono sección",
'section_placeholder' => "Introduce el nombre de la sección",
'section_order' => "Orden",
'section_icon_placeholder' => "Introduce el nombre de la sección",
'edit_section' => "Editar sección",
'new_section' => "Nueva sección",
@ -29,6 +31,20 @@ return [
'errors' => [
'publish_before_save' => "Es necesario guardar antes de publicar"
],
'alt' => [
"sort" => "Arrastra para establecer el orden de la sección"
],
'order_success' => 'Orden actualizado correctamente',
'order_error' => 'Ha ocurriendo un error al ordenar',
'published' => 'Publicado',
'not_published' => 'Sin publicar',
'section_new_success' => 'Sección creada correctamente',
'section_edit_success' => 'Sección actualizada correctamente',
'need_reload' => 'Es necesario recargar la página para ver la nueva sección creada o editada.',
'file_dont_exist' => 'El fichero no existe',
'need_editor_to_save' => 'Tienes que estar en modo editar para guardar.',
'no_content' => 'No hay contenido en la página',
'dropdown_roles' => 'Roles que pueden ver la sección',
];

View File

@ -16,8 +16,8 @@ class WikiSectionModel extends Model
protected $allowedFields = [
"name",
"slug",
"role",
"icon",
"order",
"parent_id"
];
@ -58,6 +58,6 @@ class WikiSectionModel extends Model
*/
public function sections() : array
{
return $this->findAll();
return $this->orderBy('order','asc')->findAll();
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Models\Wiki;
use App\Entities\Wiki\WikiSectionRoleEntity;
use CodeIgniter\Model;
class WikiSectionRoleModel extends Model
{
protected $table = 'wiki_section_roles';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $returnType = WikiSectionRoleEntity::class;
protected $useSoftDeletes = false;
protected $protectFields = true;
protected $allowedFields = [
"wiki_section_id",
"role"
];
protected bool $allowEmptyInserts = false;
protected bool $updateOnlyChanged = true;
protected array $casts = [];
protected array $castHandlers = [];
// Dates
protected $useTimestamps = true;
protected $dateFormat = 'datetime';
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $deletedField = 'deleted_at';
// Validation
protected $validationRules = [];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
// Callbacks
protected $allowCallbacks = true;
protected $beforeInsert = [];
protected $afterInsert = [];
protected $beforeUpdate = [];
protected $afterUpdate = [];
protected $beforeFind = [];
protected $afterFind = [];
protected $beforeDelete = [];
protected $afterDelete = [];
}

View File

@ -3,36 +3,47 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel1"><?= lang('Wiki.new_section') ?></h5>
<h5 class="modal-title d-none" id="modalTitleNew"><?= lang('Wiki.new_section') ?></h5>
<h5 class="modal-title d-none" id="modalTitleEdit"><?= lang('Wiki.edit_section') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-primary" role="alert">
<?= lang('Wiki.need_reload') ?>
</div>
<form id="formSection">
<div class="form-group">
<div class="row">
<div class="col-12 mb-2">
<label for="section-name" class="form-label"><?= lang('Wiki.name') ?></label>
<input type="text" id="section-name" name="title" placeholder="<?= lang('Wiki.section_placeholder') ?>" name="name" class="form-control" required />
<input type="text" id="section-name" name="name" placeholder="<?= lang('Wiki.section_placeholder') ?>" class="form-control" required />
</div>
<div class="col-12 mb-2">
<div class="mb-3">
<label for="section-icon" class="form-label"><?= lang('Wiki.icon') ?></label>
<input type="text" id="section-icon" name="icon" placeholder="<?= lang('Wiki.section_icon_placeholder') ?>" name="name" class="form-control" required />
<select id="section-icon" class="select2-icons form-select">
</select>
</div>
<div class="mb-3">
<label for="section-roles" class="form-label"><?= lang('Wiki.roles') ?></label>
<select id="section-roles" class="select2-icons form-select" name="roles[]" multiple>
<?php
foreach(config('AuthGroups')->groups as $key => $value):?>
<option value="<?=$key?>"><?=isset($value['title']) ? $value['title'] : $key?></option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal"><?= lang('App.global_come_back') ?></button>
<?php if (auth()->user()->inGroup('admin')) { ?>
<?php if (auth()->user()->inGroup('admin')): ?>
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal"><?= lang('App.global_come_back') ?></button>
<button type="button" id="submit-new-section" class="btn btn-primary d-none"><?= lang('App.global_save') ?></button>
<button type="button" id="submit-update-section" class="btn btn-primary d-none"><?= lang('App.global_save') ?></button>
<?php } ?>
<?php endif ?>
</div>
</div>
</div>

View File

@ -346,6 +346,7 @@ $picture = "/assets/img/default-user.png";
<!-- build:js assets/vendor/js/core.js -->
<script src="<?= site_url('themes/vuexy/vendor/libs/jquery/jquery.js') ?>"></script>
<script src="<?= site_url('themes/vuexy/vendor/libs/popper/popper.js') ?>"></script>
<script src="<?= site_url('themes/vuexy/vendor/libs/jquery/jquery.js') ?>"></script>
<script src="<?= site_url('themes/vuexy/vendor/js/bootstrap.js') ?>"></script>
<script src="<?= site_url('themes/vuexy/vendor/libs/flatpickr/flatpickr.js') ?>"></script>
<script src="<?= site_url('themes/vuexy/vendor/libs/perfect-scrollbar/perfect-scrollbar.js') ?>"></script>

View File

@ -4,13 +4,13 @@
<?php if(auth()->user()->inGroup('admin')):?>
<a href="<?= route_to('showWikiPage','intro-admin') ?>" class="menu-link">
<i class="menu-icon tf-icons ti ti-books"></i>
<?= lang("Wiki") ?>
<?= lang("Wiki.help") ?>
</a>
<?php endif;?>
<?php if(auth()->user()->inGroup('cliente-editor')):?>
<a href="<?= route_to('showWikiPage','presupuesto-cliente') ?>" class="menu-link">
<a href="<?= route_to('showWikiPage','intro-cliente') ?>" class="menu-link">
<i class="menu-icon tf-icons ti ti-books"></i>
<?= lang("Wiki") ?>
<?= lang("Wiki.help") ?>
</a>
<?php endif;?>
</li>

View File

@ -105,17 +105,25 @@ $picture = "/assets/img/default-user.png";
<div class="menu-inner-shadow"></div>
<ul class="menu-inner py-1">
<ul class="menu-inner py-1 list-group list-group-flush" id="menu-inner-list">
<!-- Iterate throught sections -->
<?php foreach ($wiki_sections as $key => $value) : ?>
<?php if (auth()->user()->inGroup($value->role) || auth()->user()->inGroup('admin')): ?>
<li class="menu-item <?= $value->slug == $slug ? 'active' : "" ?>">
<?php if (auth()->user()->inGroup(...$value->roles_array()) || auth()->user()->inGroup('admin')): ?>
<li class="menu-item <?= ($value->slug == $slug) ? "active" : "" ?>" data-id="<?= $value->id ?>">
<!-- Check if user can view the section link -->
<a href="<?= site_url("wiki/view/" . $value->slug) ?>" class="menu-link">
<i class="menu-icon tf-icons <?= $value->icon ?>"></i>
<?= lang("Wiki." . $value->slug) ?>
</a>
<span class="d-flex justify-content-between align-items-center">
<a href="<?= site_url("wiki/view/" . $value->slug) ?>" class="menu-link">
<i class="menu-icon tf-icons <?= $value->icon ?>"></i>
<?= lang("Wiki." . $value->slug) ?>
</a>
<?php if (auth()->user()->inGroup('admin')): ?>
<i class="drag-handle cursor-grab icon-base ti ti-arrows-sort align-text-bottom me-2" title="<?= lang('Wiki.alt.sort') ?>"></i>
<?php endif; ?>
</span>
</li>
<?php endif; ?>
<?php endforeach; ?>
@ -280,6 +288,7 @@ $picture = "/assets/img/default-user.png";
<script src="<?= site_url('themes/vuexy/vendor/libs/hammer/hammer.js') ?>"></script>
<script src="<?= site_url('themes/vuexy/vendor/js/menu.js') ?>"></script>
<script type="module" src="<?= site_url('assets/js/safekat/pages/layout.js') ?>"></script>
<!-- Helpers -->
<script src="<?= site_url('themes/vuexy/vendor/js/helpers.js') ?>"></script>
@ -291,6 +300,10 @@ $picture = "/assets/img/default-user.png";
<?= $this->renderSection('additionalExternalJs') ?>
<script src="<?= site_url('themes/vuexy/js/main.js') ?>"></script>
<?php if (auth()->user()->inGroup('admin')): ?>
<script type="module" src="<?= site_url('assets/js/safekat/pages/wiki/menuSortable.js') ?>"></script>
<?php endif; ?>
<?= sweetAlert() ?>

View File

@ -14,7 +14,7 @@ use CodeIgniter\I18n\Time;
<div class="card card-info">
<div class="card-header">
<div class="row">
<div class="col-md-6 d-flex flex-row flex-wrap justify-content-start align-items-center pb-2 gap-2">
<div class="col-md-10 col-xs-12 col-sm-12 d-flex flex-row flex-wrap justify-content-start align-items-center pb-2 gap-2">
<?php if ($section->content()?->published_data): ?>
<span class="badge badge-center rounded-pill text-bg-success"><i class="ti ti-check"></i></span>
<strong><?= lang('Wiki.published') ?></strong>
@ -28,16 +28,38 @@ use CodeIgniter\I18n\Time;
</div>
<div class="col-md-6 d-flex flex-row flex-wrap justify-content-end align-items-stretch pb-2 gap-2">
<?php if (auth()->user()->inGroup('admin')): ?>
<button type="button" class="btn btn-success btn-xs col-xs-12 " id="new-section"><i class="icon-base ti ti-plus icon-xs me-2"></i><?= lang('Wiki.new_section') ?></button>
<button type="button" class="btn btn-warning btn-xs col-auto " id="edit-section"><i class="icon-base ti ti-pencil icon-xs me-2"></i><?= lang('Wiki.edit_section') ?></button>
<button type="button" class="btn btn-primary btn-xs col-auto " id="save-editor"><i class="icon-base ti ti-device-floppy icon-xs me-2"></i><?= lang('App.global_save') ?></button>
<button type="button" class="btn btn-danger btn-xs col-auto " id="release-editor"><i class="icon-base ti ti-upload icon-xs me-2"></i>Publicar</button>
<button type="button" class="btn btn-secondary btn-xs d-none col-auto " id="preview-editor"><i class="icon-base ti ti-eye icon-xs me-2"></i>Vista previa</button>
<button type="button" class="btn btn-warning btn-xs col-auto " id="edit-editor"><i class="icon-base ti ti-pencil icon-xs me-2"></i>Editar</button>
</div>
<?php endif; ?>
<?php if (auth()->user()->inGroup('admin')): ?>
<div class="col-md-2 d-flex flex-row flex-wrap justify-content-end align-items-stretch pb-2 gap-2">
<div class="btn-sm-group">
<button type="button" class="btn btn-sm btn-primary btn-icon rounded-pill dropdown-toggle hide-arrow" data-bs-toggle="dropdown">
<i class="icon-base ti ti-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><button type="button" class="dropdown-item btn btn-success btn-xs col-xs-12 " id="new-section"><i class="icon-base ti ti-plus icon-xs me-2"></i><?= lang('Wiki.new_section') ?></button></li>
<li><button type="button" class="dropdown-item btn btn-warning btn-xs col-auto " id="edit-section"><i class="icon-base ti ti-pencil icon-xs me-2"></i><?= lang('Wiki.edit_section') ?></button></li>
<li><button type="button" class="dropdown-item btn btn-danger btn-xs col-auto " id="delete-section"><i class="icon-base ti ti-trash icon-xs me-2"></i><?= lang('Wiki.delete_section') ?></button></li>
<li>
<hr class="dropdown-divider">
</li>
<li><button type="button" class="dropdown-item btn btn-primary btn-xs col-auto " id="save-editor"><i class="icon-base ti ti-device-floppy icon-xs me-2"></i><?= lang('App.global_save') ?></button></li>
<li><button type="button" class="dropdown-item btn btn-danger btn-xs col-auto " id="release-editor"><i class="icon-base ti ti-upload icon-xs me-2"></i><?= lang('Wiki.release') ?></button></li>
<li><button type="button" class="dropdown-item btn btn-secondary btn-xs d-none col-auto " id="preview-editor"><i class="icon-base ti ti-eye icon-xs me-2"></i><?= lang('Wiki.preview') ?></button></li>
<li><button type="button" class="dropdown-item btn btn-warning btn-xs col-auto " id="edit-editor"><i class="icon-base ti ti-pencil icon-xs me-2"></i><?= lang('Wiki.edit') ?></button></li>
</ul>
</div>
<div class="btn-sm-group">
<button type="button" class="btn btn-sm btn-warning btn-icon rounded-pill dropdown-toggle hide-arrow" data-bs-toggle="dropdown" title="<?=lang('Wiki.dropdown_roles')?>">
<i class="icon-base ti ti-users"></i>
</button>
<ul class="dropdown-menu">
<?php foreach ($section->roles() as $key => $roleEntity): ?>
<li><span class="dropdown-item badge bg-label-success"><?=config("AuthGroups")->groups[$roleEntity->role]["title"]?></span></li>
<?php endforeach; ?>
</ul>
</div>
</div>
<?php endif; ?>
</div>
</div><!--//.card-header -->

View File

@ -19,10 +19,10 @@ export const initAutonumeric = () => {
new AutoNumeric(this, {
digitGroupSeparator: ".",
decimalCharacter: ",",
allowDecimalPadding : 'floats',
allowDecimalPadding: 'floats',
decimalPlaces: 2,
unformatOnSubmit: true,
});
}
})
@ -32,10 +32,10 @@ export const initAutonumeric = () => {
new AutoNumeric(this, {
digitGroupSeparator: ".",
decimalCharacter: ",",
allowDecimalPadding : 'floats',
allowDecimalPadding: 'floats',
decimalPlaces: $(this).data('decimal-places'),
unformatOnSubmit: true,
});
}
})
@ -49,7 +49,7 @@ export const initAutonumeric = () => {
// allowDecimalPadding : 'floats',
// decimalPlaces: 2,
// unformatOnSubmit: true,
// });
// }
// })
@ -85,4 +85,26 @@ export const initAutonumeric = () => {
// });
// }
// })
}
export const getTablerIconClassName = () => {
const classNames = [];
for (const sheet of document.styleSheets) {
try {
if (sheet.cssRules) {
for (const rule of sheet.cssRules) {
if (rule.selectorText && rule.selectorText.startsWith(".ti-")) {
let className = rule.selectorText.split(" ")[0].replace(".", "");
className = className.split("::")[0]
className = className.split(":")[0]
classNames.push(className);
}
}
}
} catch (e) {
console.warn("Cannot access stylesheet:", sheet.href);
}
}
return classNames.reverse();
}

View File

@ -13,7 +13,6 @@ import EditorJS from '../../../../../themes/vuexy/js/editorjs.mjs';
import TranslationHelper from '../../common/TranslationHelper.js';
import { alertConfirmAction, alertError, alertSuccess, alertWarning } from '../alerts/sweetAlert.js';
import Modal from '../modal.js'
import WikiSectionForm from '../forms/WikiSectionForm.js'
@ -22,29 +21,19 @@ class WikiEditor extends TranslationHelper{
constructor() {
super();
this.sectionId = $("#wiki-section-id").val();
this.modalSection = $("#modalSection")
this.newSectionBtn = $("#new-section")
this.editSectionBtn = $("#edit-section")
this.modal = new Modal(this.modalSection)
this.wikiFormSection = new WikiSectionForm()
this.wikiFormSection.setId(this.sectionId)
}
async initEditor()
{
this.wikiFormSection
this.newSectionBtn.on('click',() => {
this.modal.toggle()
this.wikiFormSection.initPost()
})
this.editSectionBtn.on('click',() => {
this.modal.toggle()
this.wikiFormSection.initUpdate()
})
this.wikiFormSection.init()
await this.get_translations("Wiki")
this.editor = new EditorJS({
holder: 'editorjs',
i18n: this.locale == "es" ? es : null,
autofocus: true,
placeholder: this.get_lang('header-placeholder'),
readOnly: true,
tunes: {
alignment: {
@ -121,6 +110,7 @@ class WikiEditor extends TranslationHelper{
async initViewOnly()
{
try {
await this.initEditor()
await this.editor.isReady;
this.handleGetDataPublished();
} catch (reason) {
@ -135,7 +125,7 @@ class WikiEditor extends TranslationHelper{
/** Do anything you need after editor initialization */
$('#save-editor').on('click', () => {
this.editor.save().then(outputData => {
alertConfirmAction('Guardar contenido').then(result => {
alertConfirmAction(this.get_lang('save_content')).then(result => {
if (result.isConfirmed) {
this.handleSaveContent(outputData)
}
@ -153,7 +143,7 @@ class WikiEditor extends TranslationHelper{
console.log(result)
});
}).catch((error) => {
alertError('Tienes que estar en modo editar para publicar').fire()
alertError(this.get_lang('need_editor_to_save')).fire()
})
})
@ -187,7 +177,7 @@ class WikiEditor extends TranslationHelper{
ajax.get()
}
handleGetDataSuccess(response) {
if (response.data.contents) {
if (response.data.contents?.editor_data) {
alertSuccess(response.message).fire()
this.renderContent(response.data.contents.editor_data)
} else {
@ -202,16 +192,16 @@ class WikiEditor extends TranslationHelper{
const ajax = new Ajax(`/wiki/section/${this.sectionId}`,
null,
null,
this.handleGetDataSuccess.bind(this),
this.handleGetDataError.bind(this))
this.handleGetDataPublishedSuccess.bind(this),
this.handleGetDataPublishedError.bind(this))
ajax.get()
}
handleGetDataPublishedSuccess(response) {
if (response.data.contents) {
if (response.data.contents?.published_data) {
alertSuccess(response.message).fire()
this.renderContent(response.data.contents.published_data)
} else {
alertWarning('No hay contenido').fire()
alertWarning(this.get_lang('no_content')).fire()
}
}
handleGetDataPublishedError(error) {

View File

@ -1,41 +1,73 @@
import Ajax from "../ajax.js";
import { alertError, alertSuccess } from "../alerts/sweetAlert.js";
class WikiSectionForm
{
import { alertConfirmAction, alertConfirmationDelete, alertError, alertSuccess } from "../alerts/sweetAlert.js";
import Modal from "../modal.js"
import { getTablerIconClassName } from "../../common/common.js";
class WikiSectionForm {
constructor() {
this.item = $("#formSection")
this.btnNew = $("#submit-new-section")
this.btnUpdate = $("#submit-update-section")
this.btnDelete = $("#delete-section")
this.name = this.item.find('#section-name')
this.icon = this.item.find('#section-icon')
this.roles = this.item.find('#section-roles').select2({
dropdownParent: this.item.parent()
})
this.newSectionBtn = $("#new-section")
this.editSectionBtn = $("#edit-section")
this.modalSection = $("#modalSection")
this.modal = new Modal(this.modalSection)
this.icons = getTablerIconClassName().map((e, index) => {
return { 'text': e, 'id': `ti ${e}` }
})
}
renderIcon(option) {
var icon = `<i class="ti ${option.text}"></i> ${option.text}`;
return icon;
}
init() {
this.icon.wrap('<div class="position-relative"></div>').select2({
data: this.icons,
dropdownParent: this.icon.parent(),
templateResult: this.renderIcon,
templateSelection: this.renderIcon,
escapeMarkup: function (m) { return m; }
})
this.icon.on('change', () => {
console.log(this.icon.val())
})
this.btnNew.on('click', this.post.bind(this))
this.btnUpdate.on('click', this.update.bind(this))
this.btnDelete.on('click',this.deleteConfirm.bind(this))
initPost()
{
this.newSectionBtn.on('click', () => {
this.initPost()
})
this.editSectionBtn.on('click', () => {
this.initUpdate()
})
}
setId(id) {
this.sectionId = id
}
initPost() {
this.showPost()
this.btnNew.on('click',this.post.bind(this))
this.btnUpdate.off('click')
}
initUpdate()
{
this.showUpdate()
this.btnUpdate.on('click',this.update.bind(this))
this.btnNew.off('click')
}
getFormData()
{
initUpdate() {
this.showUpdate()
}
getFormData() {
return {
name : this.name.val(),
icon : this.icon.val()
name: this.name.val(),
icon: this.icon.val(),
roles: this.roles.val()
}
}
post(){
post() {
const ajax = new Ajax('/wiki/section',
this.getFormData(),
null,
@ -45,30 +77,90 @@ class WikiSectionForm
ajax.post()
}
update(){
update() {
const ajax = new Ajax('/wiki/update/section',
this.getFormData(),
{
wiki_section_id: this.sectionId,
...this.getFormData()
},
null,
this.success.bind(this),
this.error.bind(this)
)
ajax.post()
}
success(response)
{
alertSuccess(response.message).fire()
deleteConfirm() {
alertConfirmAction('Eliminar sección').then(result => {
if (result.isConfirmed) {
this.delete()
}
console.log(result)
});
}
error(error)
{
delete() {
const ajax = new Ajax('/wiki/section/' + this.sectionId,
null,
null,
this.success.bind(this),
this.error.bind(this)
)
ajax.delete()
}
success(response) {
alertSuccess(response.message).fire()
this.modal.toggle()
}
error(error) {
alertError(error?.message).fire()
}
showPost(){
showPost() {
this.empty()
this.btnNew.removeClass('d-none')
this.btnUpdate.addClass('d-none')
$("#modalTitleNew").removeClass('d-none')
$("#modalTitleEdit").addClass('d-none')
this.modal.toggle()
}
showUpdate(){
async showUpdate() {
this.empty()
const sectionData = await this.handleShowSection()
if (sectionData) {
console.log(sectionData)
this.name.val(sectionData.data.name)
this.icon.val(sectionData.data.icon).trigger('change')
this.roles.val(sectionData.data.roles_array).trigger('change')
}
this.btnUpdate.removeClass('d-none')
this.btnNew.addClass('d-none')
$("#modalTitleNew").addClass('d-none')
$("#modalTitleEdit").removeClass('d-none')
this.modal.toggle()
}
handleShowSection() {
return new Promise((resolve, reject) => {
const ajax = new Ajax('/wiki/section/' + this.sectionId,
null,
null,
(response) => {
resolve(response)
},
(error) => {
alertError(error.message).fire()
resolve(false)
}
)
ajax.get()
})
}
empty() {
this.name.val(null)
this.icon.val("").trigger('change')
this.roles.val("").trigger('change')
}
}

View File

@ -0,0 +1,37 @@
import "../../../../../themes/vuexy/vendor/libs/sortablejs/sortable.js"
class ListSortable{
constructor(listItem,config = {}) {
this.list = Sortable.create($(listItem)[0], {
animation: 150,
group: 'menuSorting',
handle: '.drag-handle',
direction : 'vertical',
...config,
});
}
getArray(){
const list = this.list.toArray()
return list
}
onChange(callback)
{
this.list.option('onChange',callback)
}
onMove(callback)
{
this.list.option('onMove',callback)
}
onEnd(callback)
{
this.list.option('onEnd',callback)
}
}
export default ListSortable

View File

@ -9,6 +9,7 @@ class Modal{
show(){
this.item.modal('show');
}
}
export default Modal;

View File

@ -1,11 +1,12 @@
import WikiEditor from "../../components/editorjs/WikiEditor.js"
import Ajax from "../../components/ajax.js"
$(async () => {
try {
const editor = new WikiEditor()
await editor.init()
} catch (error) {
}

View File

@ -0,0 +1,25 @@
import Ajax from "../../components/ajax.js"
import { alertError, alertSuccess } from "../../components/alerts/sweetAlert.js"
import ListSortable from "../../components/listSortable.js"
$(() => {
const menuSectionList = new ListSortable('#menu-inner-list')
menuSectionList.onEnd(updateWikiSectionOrder)
})
const updateWikiSectionOrder = (evt) =>
{
let list = new ListSortable('#menu-inner-list')
const ajax = new Ajax('/wiki/section/update/order',
{
orders : list.getArray()
},
null,
(response) => alertSuccess(response.message).fire(),
(error) => alertError(error.message).fire(),
)
ajax.post();
}

View File

@ -6,7 +6,7 @@ $(async() => {
try {
const editor = new WikiEditor()
await editor.init()
await editor.initViewOnly()
} catch (error) {
}