mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 01:04:03 +00:00
Adds the read-only company "Active Modules" index page (lists every
instance-activated module with a Settings shortcut) and the schema-driven
settings framework (generic BaseSchemaForm.vue renderer + per-company
persistence in CompanySetting). Bundled because they share the same
routes/api.php edit and the index page's Settings button targets the
settings page.
Backend:
- CompanyModulesController::index() returns every Module::enabled = true row
with a kebab-case slug (via Str::kebab()) and a has_settings flag computed
from \InvoiceShelf\Modules\Registry::settingsFor(). nwidart stores module
names in PascalCase ("HelloWorld") but URLs and registry keys use kebab
("hello-world") — the controller normalizes so module authors can call
Registry::registerSettings('hello-world') naturally without thinking
about the storage format.
- ModuleSettingsController::show(\$slug) returns the registered Schema +
per-company values from CompanySetting (defaults flow through when nothing
has been saved yet). update(\$slug) builds Laravel validator rules from
the Schema's per-field rules arrays — with type-rule fallbacks for
switch -> boolean, number -> numeric, multiselect -> array — silently
drops unknown keys, and persists via CompanySetting::setSettings() under
the module.{slug}.{key} prefix. Activation is instance-global, but
settings are per-company: two companies on the same instance can
configure the same activated module differently.
- routes/api.php mounts GET /api/v1/company-modules at the root of the
company API group and GET/PUT /api/v1/modules/{slug}/settings inside the
existing modules prefix.
Frontend:
- BaseSchemaForm.vue is the central new component — a generic schema-driven
form renderer that maps schema fields to BaseInput / BaseTextarea /
BaseSwitch / BaseMultiselect by type, and builds Vuelidate rules
dynamically from each field's rules array (supports required, email, url,
numeric, min:N, max:N). New fields are added by extending the type ->
component map.
- CompanyModulesIndexView.vue fetches /company-modules and renders a card
grid (with empty/loading states); CompanyModuleCard.vue is the per-row
component with the Settings button. ModuleSettingsView.vue fetches
/modules/{slug}/settings, hands {schema, values} to BaseSchemaForm, and
posts back on submit.
- Company-context routes.ts is rebuilt after the previous commit relocated
the marketplace browser away. It now declares modules.index +
modules.settings, both gated by manage-module ability.
- New api/services/{companyModules,moduleSettings}.service.ts thin clients.
- lang/en.json adds modules.index.{description,empty_title,empty_description},
modules.settings.{title,open,saved,not_found,none}, and
modules.sidebar.section_title. The sidebar key is added here even though
the dynamic sidebar rendering lands in the next commit — keeping all i18n
additions in one file edit avoids hunk-splitting lang/en.json.
37 lines
949 B
TypeScript
37 lines
949 B
TypeScript
import { client } from '../client'
|
|
|
|
export interface ModuleSettingsField {
|
|
key: string
|
|
type: 'text' | 'password' | 'textarea' | 'switch' | 'number' | 'select' | 'multiselect'
|
|
label: string
|
|
rules: string[]
|
|
default: unknown
|
|
options?: Record<string, string>
|
|
}
|
|
|
|
export interface ModuleSettingsSection {
|
|
title: string
|
|
fields: ModuleSettingsField[]
|
|
}
|
|
|
|
export interface ModuleSettingsSchema {
|
|
sections: ModuleSettingsSection[]
|
|
}
|
|
|
|
export interface ModuleSettingsResponse {
|
|
schema: ModuleSettingsSchema
|
|
values: Record<string, unknown>
|
|
}
|
|
|
|
export const moduleSettingsService = {
|
|
async fetch(slug: string): Promise<ModuleSettingsResponse> {
|
|
const { data } = await client.get(`/api/v1/modules/${slug}/settings`)
|
|
return data
|
|
},
|
|
|
|
async update(slug: string, values: Record<string, unknown>): Promise<{ success: boolean }> {
|
|
const { data } = await client.put(`/api/v1/modules/${slug}/settings`, values)
|
|
return data
|
|
},
|
|
}
|