first commit

This commit is contained in:
Noor E Ilahi
2026-01-09 12:54:53 +05:30
commit 7ccf44f7da
1070 changed files with 113036 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
export default class Builder {
routerConfig = {};
constructor(moduleContext) {
this.moduleContext = moduleContext;
this.routerPrefix =
(moduleContext.getRouterPrefix().startsWith('/') ? '' : '/') + moduleContext.getRouterPrefix();
}
}

View File

@@ -0,0 +1,62 @@
import Builder from '../builder';
import View from './crud/view';
import Edit from './crud/edit';
import New from './crud/new';
export default class Crud extends Builder {
view = {};
edit = {};
new = {};
constructor(
crudName,
id,
serviceClass,
filters = {},
moduleContext,
defaultPrefix = '',
hasPages = { edit: true, view: true, new: true },
) {
super(moduleContext);
this.moduleContext = moduleContext;
this.crudName = crudName;
this.id = id;
this.serviceClass = typeof serviceClass === 'object' ? serviceClass : new serviceClass();
this.hasEdit = hasPages.edit;
this.hasView = hasPages.view;
this.hasNew = hasPages.new;
this.defaultPrefix = defaultPrefix;
this.routerConfig = [];
this.filters = filters;
if (this.hasView) {
this.view = new View(this);
}
if (this.hasEdit) {
this.edit = new Edit(this);
}
if (this.hasNew) {
this.new = new New(this);
}
}
getRouterConfig() {
const toReturn = [];
if (this.view) {
toReturn.push(this.view.getRouterConfig());
}
if (this.edit) {
toReturn.push(this.edit.getRouterConfig());
}
if (this.new) {
toReturn.push(this.new.getRouterConfig());
}
return toReturn;
}
}

View File

@@ -0,0 +1,12 @@
import set from 'lodash/set';
export default class AbstractCrud {
/**
* @param property
* @param data
* @param routerConfig
*/
addToMetaProperties(property, data, routerConfig) {
set(routerConfig.meta, property, data);
}
}

View File

@@ -0,0 +1,64 @@
import AbstractCrud from './abstractCrud';
export default class Edit extends AbstractCrud {
context = {};
routerConfig = {};
constructor(context) {
super();
this.context = context;
this.routerConfig = {
path: `${context.routerPrefix}${context.defaultPrefix.length ? '/' + context.defaultPrefix : ''}/edit/:id`,
name: this.getEditRouteName(),
component: () => import(/* webpackChunkName: "editview" */ '@/views/Crud/EditView.vue'),
meta: {
auth: true,
service: context.serviceClass,
filters: context.filters,
fields: [],
pageData: {
title: context.crudName,
type: 'edit',
pageControls: [],
},
},
};
}
addPageControls() {
const arg = arguments[0];
this.addToMetaProperties('pageData.pageControls', arg, this.getRouterConfig());
return this;
}
addPageControlsToBottom() {
const arg = arguments[0];
this.addToMetaProperties('pageData.bottomComponents.pageControls', arg, this.getRouterConfig());
return this;
}
addBottomComponents() {
const arg = arguments[0];
this.addToMetaProperties('pageData.bottomComponents.components', arg, this.getRouterConfig());
return this;
}
/**
* @param {Config} fieldConfig
* @returns {Edit}
*/
addField(fieldConfig) {
this.addToMetaProperties('fields', fieldConfig, this.getRouterConfig());
return this;
}
getRouterConfig() {
return this.routerConfig;
}
getEditRouteName() {
return this.context.moduleContext.getModuleRouteName() + `.crud.${this.context.id}.edit`;
}
}

View File

@@ -0,0 +1,56 @@
import AbstractCrud from './abstractCrud';
export default class New extends AbstractCrud {
context = {};
routerConfig = {};
constructor(context) {
super();
this.context = context;
this.routerConfig = {
path: `${context.routerPrefix}${context.defaultPrefix.length ? '/' + context.defaultPrefix : ''}/new`,
name: this.getNewRouteName(),
component: () => import(/* webpackChunkName: "editview" */ '@/views/Crud/EditView.vue'),
meta: {
auth: true,
service: context.serviceClass,
filters: context.filters,
fields: [],
pageData: {
title: context.crudName,
type: 'new',
pageControls: [],
},
},
};
}
/**
*
* @param config
* @returns {New}
*/
addPageControls(config) {
this.addToMetaProperties('pageData.pageControls', config, this.getRouterConfig());
return this;
}
/**
* @param fieldConfig
* @returns {New}
*/
addField(fieldConfig) {
this.addToMetaProperties('fields', fieldConfig, this.getRouterConfig());
return this;
}
getRouterConfig() {
return this.routerConfig;
}
getNewRouteName() {
return this.context.moduleContext.getModuleRouteName() + `.crud.${this.context.id}.new`;
}
}

View File

@@ -0,0 +1,53 @@
import AbstractCrud from './abstractCrud';
export default class View extends AbstractCrud {
context = {};
routerConfig = {};
constructor(context) {
super();
this.context = context;
this.routerConfig = {
path: `${context.routerPrefix}${context.defaultPrefix.length ? '/' + context.defaultPrefix : ''}/view/:id`,
name: this.getViewRouteName(context),
component: () => import(/* webpackChunkName: "itemview" */ '@/views/Crud/ItemView.vue'),
meta: {
auth: true,
service: context.serviceClass,
filters: context.filters,
fields: [],
pageData: {
title: context.crudName,
type: 'view',
pageControls: [],
},
},
};
}
addPageControls() {
const arg = arguments[0];
this.addToMetaProperties('pageData.pageControls', arg, this.getRouterConfig());
return this;
}
/**
* Add field to page
* @param {Object} fieldConfig
* @returns {View}
*/
addField(fieldConfig) {
this.addToMetaProperties('fields', fieldConfig, this.getRouterConfig());
return this;
}
getRouterConfig() {
return this.routerConfig;
}
getViewRouteName() {
return this.context.moduleContext.getModuleRouteName() + `.crud.${this.context.id}.view`;
}
}

View File

@@ -0,0 +1,86 @@
import Builder from '../builder';
import set from 'lodash/set';
export default class Grid extends Builder {
constructor(label, id, serviceClass, moduleContext, gridData = undefined, gridRouterPath = '') {
super(moduleContext);
this.label = label;
this.id = id;
this.routerConfig = {
name: moduleContext.getModuleRouteName() + `.crud.${id}`,
path: this.routerPrefix + '/' + gridRouterPath,
component: () => import(/* webpackChunkName: "gridview" */ '../../views/Crud/GridView.vue'),
meta: {
auth: true,
pageControls: [],
gridData: {
title: label,
columns: [],
filters: [],
filterFields: [],
actions: [],
service: typeof serviceClass === 'object' ? serviceClass : new serviceClass(),
...gridData,
},
},
};
}
addColumn() {
const arg = arguments[0];
this.addToGridData('columns', arg);
return this;
}
addAction() {
const arg = arguments[0];
this.addToGridData('actions', arg);
return this;
}
addFilter() {
const arg = arguments[0];
this.addToGridData('filters', arg);
return this;
}
addFilterField() {
const arg = arguments[0];
this.addToGridData('filterFields', arg);
return this;
}
addToGridData(property, data) {
if (Array.isArray(data)) {
data.forEach(p => {
this.routerConfig.meta.gridData[property].push(p);
});
} else {
this.routerConfig.meta.gridData[property].push(data);
}
}
addToMetaProperties(property, data, routerConfig) {
set(routerConfig.meta, property, data);
}
addPageControls() {
const data = arguments[0];
if (Array.isArray(data)) {
data.forEach(p => {
this.routerConfig.meta.pageControls.push(p);
});
} else {
this.routerConfig.meta.pageControls.push(data);
}
return this;
}
getRouterConfig() {
return this.routerConfig;
}
}

View File

@@ -0,0 +1,24 @@
import isObject from 'lodash/isObject';
export default class NavbarEntry {
constructor({ label, to, displayCondition = () => true, section, icon }) {
if (!isObject(to)) {
throw new Error('[to] instance must be a JavaScript object');
}
this.label = label;
this.to = to;
this.displayCondition = displayCondition;
this.section = section;
this.icon = icon;
}
getData() {
return {
label: this.label,
to: this.to,
displayCondition: this.displayCondition,
section: this.section,
icon: this.icon,
};
}
}

View File

@@ -0,0 +1,74 @@
import { store } from '@/store';
/**
* Section class - create new section with provided params
*
* @param path string - section route path
* @param name string - section route name
* @param meta - section route meta with described fields, service etc
* @param accessCheck - function to check if user has access to work with section content
*/
export default class SettingsSection {
path = '';
name = '';
meta = {};
component = null;
children = [];
accessCheck = null;
section = {};
scope = 'settings';
constructor(
path,
name,
meta,
accessCheck = () => true,
scope = 'settings',
order = 99,
component = null,
children = [],
) {
this.path = path;
this.name = name;
this.meta = meta;
this.component =
component || (() => import(/* webpackChunkName: "settings" */ '@/views/Settings/DynamicSettings.vue'));
this.children = children;
this.accessCheck = accessCheck;
this.scope = scope;
this.order = order;
this.section = {
path: this.path,
name: this.name,
meta: this.meta,
component: this.component,
children: this.children,
accessCheck: this.accessCheck,
scope: this.scope,
order: this.order,
};
}
/**
* Init new section in store
* @returns {Promise<void>}
*/
async initSection() {
await store.dispatch('settings/setSettingSection', this.section);
}
/**
* Get section route, used to create settings child route
* @returns {{path: string, component: null, meta, name: string}}
*/
getRoute() {
return {
path: this.path,
name: this.name,
meta: this.meta,
component: this.component,
children: this.children,
};
}
}

View File

@@ -0,0 +1,331 @@
import Grid from './builder/grid';
import Crud from './builder/crud';
import NavbarEntry from './builder/navbar';
import SettingsSection from './builder/sections';
import isObject from 'lodash/isObject';
import { store } from '@/store';
/**
* Module class. This class represents the context of a module in module.init.js -> init() function.
*/
export default class Module {
routes = [];
navEntries = [];
navEntriesDropdown = {};
navEntriesMenuDropdown = [];
settingsSections = [];
companySections = [];
locales = {};
pluralizationRules = {};
additionalFields = [];
constructor(routerPrefix, moduleName) {
this.routerPrefix = routerPrefix;
this.moduleName = moduleName;
}
/**
* Add locale code to allow custom locale select
*
* @param {String} code
* @param {String} label
*
* @returns {Module}
*/
addLocaleCode(code, label) {
store.dispatch('lang/setLang', { code, label });
return this;
}
/**
* Add module to Vuex store
*
* @param {Object} vuexModule
* @returns {Module}
*/
registerVuexModule(vuexModule) {
if (!isObject(vuexModule)) {
throw new Error('Vuex Module must be an object.');
}
store.registerModule(this.moduleName.toLowerCase(), { ...vuexModule, namespaced: true });
store.dispatch(`${this.moduleName.toLowerCase()}/init`);
return this;
}
/**
* Create GRID instance, which can be exported to RouterConfig
* @param label
* @param id
* @param serviceClass
* @param gridData
* @param gridRouterPath
* @returns {Grid}
*/
createGrid(label, id, serviceClass, gridData = undefined, gridRouterPath = '') {
return new Grid(label, id, serviceClass, this, gridData, gridRouterPath);
}
/**
* Create CRUD instance, which can be exported to RouterConfig
*
* @param label
* @param id
* @param serviceClass
* @param filters
* @param defaultPrefix
* @param pages
* @returns {Crud}
*/
createCrud(label, id, serviceClass, filters, defaultPrefix = '', pages = { edit: true, view: true, new: true }) {
return new Crud(label, id, serviceClass, filters, this, defaultPrefix, pages);
}
/**
* Add route to module-scoped routerConfig
*
* @param routerConfig
* @returns {Module}
*/
addRoute(routerConfig) {
if (Array.isArray(routerConfig)) {
routerConfig.forEach(p => {
this.routes.push(p);
});
} else {
this.routes.push(routerConfig);
}
return this;
}
/**
* Add navbar entry
*/
addNavbarEntry(...args) {
Array.from(args).forEach(p => {
this.navEntries.push(
new NavbarEntry({
label: p.label,
to: p.to,
displayCondition: Object.prototype.hasOwnProperty.call(p, 'displayCondition')
? p.displayCondition
: () => true,
}),
);
});
}
/**
* Add navbar Dropdown Entry
*/
addNavbarEntryDropDown(...args) {
Array.from(args).forEach(p => {
if (!Object.prototype.hasOwnProperty.call(this.navEntriesDropdown, p.section)) {
this.navEntriesDropdown[p.section] = [];
}
this.navEntriesDropdown[p.section].push(
new NavbarEntry({
label: p.label,
to: p.to,
displayCondition: Object.prototype.hasOwnProperty.call(p, 'displayCondition')
? p.displayCondition
: () => true,
section: p.section,
}),
);
});
}
/**
* Add to user menu entry of the navbar
*/
addUserMenuEntry(...args) {
Array.from(args).forEach(a => {
this.navEntriesMenuDropdown.push(
new NavbarEntry({
label: a.label,
to: a.to,
displayCondition: Object.prototype.hasOwnProperty.call(a, 'displayCondition')
? a.displayCondition
: () => true,
icon: a.icon,
}),
);
});
}
/**
* Create new section with provided params
*/
addSettingsSection(...args) {
Array.from(args).forEach(({ route, accessCheck, scope, order, component }) => {
const { path, name, meta, children } = route;
const section = new SettingsSection(path, name, meta, accessCheck, scope, order, component, children);
this.settingsSections.push(section);
});
}
/**
* Create new section with provided params
*/
addCompanySection(...args) {
Array.from(args).forEach(({ route, accessCheck, scope, order, component }) => {
const { path, name, meta, children } = route;
const section = new SettingsSection(path, name, meta, accessCheck, scope, order, component, children);
this.companySections.push(section);
});
}
addField(scope, path, field) {
this.additionalFields.push({ scope, path, field });
}
/**
* Add locales
*/
addLocalizationData(locales) {
this.locales = locales;
}
/**
* Add pluralization rules
*/
addPluralizationRules(rules) {
this.pluralizationRules = rules;
}
/**
* Init all available sections
* @returns {Promise<void>[]}
*/
initSettingsSections() {
this.additionalFields
.filter(({ scope }) => scope === 'settings')
.forEach(({ scope, path, field }) => {
store.dispatch('settings/addField', { scope, path, field });
});
return this.settingsSections.map(s => s.initSection());
}
/**
* Init all available sections
* @returns {Promise<void>[]}
*/
initCompanySections() {
this.additionalFields
.filter(({ scope }) => scope === 'company')
.forEach(({ scope, path, field }) => {
store.dispatch('settings/addField', { scope, path, field });
});
return this.companySections.map(s => s.initSection());
}
/**
* Init all available sections
*/
reinitAllSections() {
this.initSettingsSections();
this.initCompanySections();
}
/**
* Get all available to fill /settings route children
* @returns {{path: string, component: null, meta, name: string}[]}
*/
getSettingSectionsRoutes() {
return this.settingsSections.map(s => s.getRoute());
}
/**
* Get all available to fill /settings route children
* @returns {{path: string, component: null, meta, name: string}[]}
*/
getCompanySectionsRoutes() {
return this.companySections.map(s => s.getRoute());
}
/**
* Get Navigation bar entries array
*
* @returns {Array<Object>}
*/
getNavbarEntries() {
return this.navEntries;
}
/**
* Get Navigation Dropdown bar entries array
*
* @returns {Array<Object>}
*/
getNavbarEntriesDropdown() {
return this.navEntriesDropdown;
}
/**
* Get Navigation Menu Dropdown entries array
*
* @returns {Array<Object>}
*/
getNavbarMenuEntriesDropDown() {
return this.navEntriesMenuDropdown;
}
/**
* Get module-scoped routes for Vue Router
*
* @returns {Array<Object>}
*/
getRoutes() {
return this.routes;
}
/**
* Get locales
*
* @returns {Array}
*/
getLocalizationData() {
return this.locales;
}
/**
* Get pluralization rules
*
* @returns {Object}
*/
getPluralizationRules() {
return this.pluralizationRules;
}
/**
* Get module name
*
* @returns {string}
*/
getModuleName() {
return this.moduleName;
}
/**
* Get module route name
*
* @returns {string}
*/
getModuleRouteName() {
return this.moduleName;
}
/**
* Get router prefix for module-scoped routes
*
* @returns {string}
*/
getRouterPrefix() {
return this.routerPrefix;
}
}