/** One row in the modules table; `ownerLabel` is display-only and has no counterpart in UpdateModulePayload. */ export type ModuleItem = { /** snake_case, unlike moduleName/ownerLabel here and unlike UpdateModulePayload.moduleId — casing isn't symmetric. */ module_id: number; /** One line with the full name in a tooltip; seeds the edit field. */ moduleName: string; /** Shown as is, or "—" when empty. */ ownerLabel?: string; }; /** Shape passed to onUpdateModule — `moduleId` identifies the item being edited, `moduleName` is the new value from the modal. */ export type UpdateModulePayload = { /** `module_id` of the row being edited. */ moduleId: number; /** New name, trimmed; never empty because Update is disabled for a blank name. */ moduleName: string; }; /** * All user-facing strings. Provide translated values from the host app; every * key has a sensible English default so the component works standalone. */ export type CustomModulesTableLabels = { /** Card heading; defaults to "Custom Modules". */ title?: string; /** Header of the module name column. */ moduleName?: string; /** Header of the owner column. */ moduleOwner?: string; /** Header of the right-aligned Edit/Delete column. */ action?: string; /** Text of each row's Edit button. */ edit?: string; /** Row button label, reused for the delete dialog's confirm button. */ delete?: string; /** Empty-state title shown when `modules` is empty. */ noModules?: string; /** @deprecated Never rendered; the loading state shows skeleton bars without text. */ loading?: string; /** Edit dialog heading. */ editModuleTitle?: string; /** Edit field placeholder; the field has no visible or ARIA label besides it. */ moduleNamePlaceholder?: string; /** Cancel button of both the edit and the delete dialog. */ cancel?: string; /** Edit dialog submit; disabled while the trimmed name is empty. */ update?: string; /** Delete dialog heading. */ confirmDeletion?: string; /** Delete prompt, rendered as ` ?`. */ deleteConfirmPrefix?: string; }; /** Fixed 2-column module list (name, owner) built on raw Chakra table parts, not the library Table — closed props, no ref/...rest reach the root Box. */ export interface CustomModulesTableProps { /** Rows keyed by module_id; an empty array shows the empty state. */ modules: ModuleItem[]; /** Replaces the table with four skeleton bars; the heading stays. */ isLoading?: boolean; /** Spinner on the edit dialog's Update button only. */ isUpdating?: boolean; /** Spinner on the delete dialog's confirm button only. */ isDeleting?: boolean; /** Edit modal closes once this promise resolves; a rejection is unhandled (never caught) and isUpdating only toggles the button spinner, not the close. */ onUpdateModule?: (payload: UpdateModulePayload) => void | Promise; /** Delete modal closes once this promise resolves; a rejection is unhandled (never caught) and isDeleting only toggles the button spinner, not the close. */ onDeleteModule?: (moduleId: number) => void | Promise; /** Partial overrides merged over the English defaults, per key. */ labels?: CustomModulesTableLabels; } /** Internal edit-name dialog rendered by CustomModulesTable; `labels` must already be fully resolved, not per-key optional. */ export interface EditModuleModalProps { /** Shows the dialog. */ isOpen: boolean; /** Called on Cancel, the close button, Escape or an overlay click. */ onClose: () => void; /** Seeds the name field each time the dialog opens. */ initialName: string; /** Spinner on the Update button. */ isLoading?: boolean; /** Called with the trimmed name when Update is pressed. */ onSubmit: (name: string) => void | Promise; /** Fully resolved strings; no per-key fallback happens here. */ labels: Required>; } /** Internal delete-confirmation dialog rendered by CustomModulesTable; `moduleName` is optional since the target can be cleared mid-close. */ export interface DeleteModuleModalProps { /** Shows the dialog. */ isOpen: boolean; /** Called on Cancel, the close button, Escape or an overlay click. */ onClose: () => void; /** Named in the confirmation prompt after `deleteConfirmPrefix`. */ moduleName?: string; /** Spinner on the confirm button. */ isLoading?: boolean; /** Called when the confirm button is pressed. */ onConfirm: () => void; /** Fully resolved strings; no per-key fallback happens here. */ labels: Required>; }