dhis2-mcp-server
Version:
A Model Context Protocol server providing 108 tools for DHIS2 development, including code generators, debugging helpers, and documentation access for web and Android app development.
1,722 lines (1,528 loc) • 63 kB
JavaScript
/**
* DHIS2 Web App Platform Integration and Debugging Functions
* Phase 2 implementation for DHIS2 MCP Server
*/
export function generateWebAppInitInstructions(appName, appTitle, appDescription, options = {}) {
const { namespace, appType = 'app', template = 'basic', typescript = true, pwa = false, outputPath = './' } = options;
return `# DHIS2 Web App Initialization Guide
## App Configuration
- **App Name**: ${appName}
- **App Title**: ${appTitle}
- **Description**: ${appDescription || 'A DHIS2 web application'}
- **Namespace**: ${namespace || appName}
- **App Type**: ${appType}
- **Template**: ${template}
- **TypeScript**: ${typescript ? 'Yes' : 'No'}
- **PWA Enabled**: ${pwa ? 'Yes' : 'No'}
- **Output Path**: ${outputPath}
## Quick Start Commands
\`\`\`bash
# Initialize new DHIS2 app using App Platform
npx @dhis2/cli-app-scripts init ${appName}
# Navigate to project directory
cd ${appName}
# Install dependencies
yarn install
# Start development server
yarn start
\`\`\`
## Project Structure
\`\`\`
${appName}/
├── src/
│ ├── App.js${typescript ? 'x' : ''}
│ ├── components/
│ └── index.js${typescript ? 'x' : ''}
├── public/
│ └── manifest.webapp
├── d2.config.js
├── package.json
${typescript ? '├── tsconfig.json' : ''}
${pwa ? '├── workbox.config.js' : ''}
└── README.md
\`\`\`
## Next Steps
1. Configure your DHIS2 instance connection
2. Set up proxy for development
3. Add CORS allowlist configuration
4. Implement your app components
5. Test with different DHIS2 versions
## Template-Specific Features
${generateTemplateFeatures(template)}
${pwa ? generatePWAConfiguration() : ''}
`;
}
function generateTemplateFeatures(template) {
switch (template) {
case 'with-ui-library':
return `### UI Library Template Features
- @dhis2/ui components pre-configured
- Design system tokens
- Responsive layout patterns
- Form validation helpers
- Data table components`;
case 'with-analytics':
return `### Analytics Template Features
- Analytics API integration
- Chart.js / D3.js setup
- Dashboard components
- Visualization helpers
- Data export utilities`;
case 'tracker-capture':
return `### Tracker Capture Template Features
- Event capture forms
- Tracked entity registration
- Program rules integration
- Offline data sync
- GPS coordinate capture`;
default:
return `### Basic Template Features
- Clean React application structure
- DHIS2 App Runtime integration
- Basic routing setup
- Authentication handling
- API client configuration`;
}
}
function generatePWAConfiguration() {
return `
## PWA Configuration
### Service Worker Setup
\`\`\`javascript
// workbox.config.js
module.exports = {
globDirectory: 'build/',
globPatterns: ['**/*.{js,css,html,png,jpg,svg}'],
swDest: 'build/sw.js',
runtimeCaching: [
{
urlPattern: /^https:\\/\\/.*\\/api\\//,
handler: 'NetworkFirst',
options: {
cacheName: 'dhis2-api-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 300, // 5 minutes
},
},
},
],
};
\`\`\`
### App Manifest Features
- Offline capability
- Install prompts
- Background sync
- Push notifications (if supported)
`;
}
export function generateManifestContent(args) {
const { name, version, description, developer, icons, activities, authorities, appType, launch_path } = args;
const manifest = {
version: version || '1.0.0',
name: name,
description: description,
developer: {
name: developer.name,
...(developer.email && { email: developer.email })
},
icons: icons || {
"48": "./icon-48.png",
"128": "./icon-128.png"
},
activities: activities,
...(authorities && { authorities }),
...(appType && { appType }),
...(launch_path && { launch_path })
};
return `# DHIS2 App Manifest Configuration
## Generated manifest.webapp
\`\`\`json
${JSON.stringify(manifest, null, 2)}
\`\`\`
## Installation Instructions
1. Save the above content as \`public/manifest.webapp\` in your app directory
2. Ensure icon files exist at the specified paths
3. Update authorities array with required permissions
4. Customize the launch_path if needed
## Authority Examples
\`\`\`json
"authorities": [
"F_METADATA_IMPORT",
"F_METADATA_EXPORT",
"F_DATA_VALUE_ADD",
"F_DATAVALUE_ADD_WITHIN_ASSIGNED_ORGUNIT",
"F_TRACKED_ENTITY_INSTANCE_ADD",
"F_PROGRAM_ENROLLMENT"
]
\`\`\`
## App Type Options
- **APP**: Standard DHIS2 application
- **DASHBOARD_WIDGET**: Dashboard plugin/widget
- **TRACKER_DASHBOARD_WIDGET**: Tracker-specific dashboard widget
`;
}
export function generateBuildSystemConfig(args) {
const { appName, entryPoints, customAuthorities, pwa, publicPath, proxy } = args;
const d2Config = {
type: 'app',
name: appName,
title: appName.split('-').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' '),
...(entryPoints && { entryPoints }),
...(customAuthorities && { authorities: customAuthorities }),
...(pwa && { pwa }),
...(publicPath && { publicPath })
};
return `# DHIS2 Build System Configuration
## d2.config.js
\`\`\`javascript
const config = ${JSON.stringify(d2Config, null, 2)};
module.exports = config;
\`\`\`
## Build Commands
\`\`\`bash
# Development server
yarn start
# Production build
yarn build
# Deploy to DHIS2 instance
yarn deploy
# Build and analyze bundle
yarn build --analyze
\`\`\`
${proxy ? generateProxyConfig(proxy) : ''}
${pwa ? generatePWABuildConfig() : ''}
## Environment Configuration
\`\`\`bash
# .env.local
REACT_APP_DHIS2_BASE_URL=https://your-dhis2-instance.com
REACT_APP_DHIS2_API_VERSION=40
\`\`\`
`;
}
function generateProxyConfig(proxy) {
return `
## Proxy Configuration
\`\`\`javascript
// In d2.config.js
module.exports = {
// ... other config
proxy: {
target: '${proxy.target}',
auth: {
username: '${proxy.auth?.username || 'your-username'}',
password: '${proxy.auth?.password || 'your-password'}'
}
}
};
\`\`\`
`;
}
function generatePWABuildConfig() {
return `
## PWA Build Configuration
\`\`\`javascript
// In d2.config.js
module.exports = {
// ... other config
pwa: {
enabled: true,
caching: {
patterns: [
'api/**',
'apps/**'
],
exclude: [
'/api/me/authorization'
]
}
}
};
\`\`\`
`;
}
export function generateDevEnvironmentConfig(args) {
const { dhis2Instance, username, password, port = 3000, https = false, envFile = '.env.local' } = args;
return `# DHIS2 Development Environment Setup
## Environment File (${envFile})
\`\`\`bash
# DHIS2 Instance Configuration
REACT_APP_DHIS2_BASE_URL=${dhis2Instance}
DHIS2_CORE_HOST=${dhis2Instance}
DHIS2_CORE_USERNAME=${username}
DHIS2_CORE_PASSWORD=${password}
# Development Server Configuration
PORT=${port}
${https ? 'HTTPS=true' : '# HTTPS=false'}
# API Configuration
REACT_APP_DHIS2_API_VERSION=40
GENERATE_SOURCEMAP=true
\`\`\`
## Development Commands
\`\`\`bash
# Start with proxy
yarn start --proxy
# Start with specific port
yarn start --port ${port}
# Start with HTTPS${https ? '' : ' (if needed)'}
${https ? 'yarn start --https' : '# yarn start --https'}
\`\`\`
## Browser Configuration for CORS
### Chrome (recommended for development)
\`\`\`bash
# Start Chrome with disabled security (development only!)
google-chrome --disable-web-security --user-data-dir=/tmp/chrome-dev
\`\`\`
### Firefox
1. Open about:config
2. Set \`network.cookie.sameSite.laxByDefault\` to \`false\`
3. Set \`network.cookie.sameSite.noneRequiresSecure\` to \`false\`
## Troubleshooting Tips
- Clear browser cache if authentication fails
- Check DHIS2 CORS allowlist settings
- Verify DHIS2 instance is accessible
- Use browser dev tools Network tab for debugging
## Security Notes
⚠️ **Never commit credentials to version control!**
- Add ${envFile} to .gitignore
- Use different credentials for development
- Rotate passwords regularly
`;
}
export function generateAppRuntimeConfig(args) {
const { apiVersion = 40, appName, features = {}, errorBoundary = true, loadingMask = true } = args;
return `# DHIS2 App Runtime Configuration
## Provider Setup
\`\`\`jsx
import { DataProvider } from '@dhis2/app-runtime';
import { CssReset } from '@dhis2/ui';
const config = {
baseUrl: process.env.REACT_APP_DHIS2_BASE_URL,
apiVersion: ${apiVersion}
};
function App() {
return (
<DataProvider config={config}>
<CssReset />
${errorBoundary ? '<ErrorBoundary>' : ''}
${loadingMask ? '<LoadingMask>' : ''}
<${appName}App />
${loadingMask ? '</LoadingMask>' : ''}
${errorBoundary ? '</ErrorBoundary>' : ''}
</DataProvider>
);
}
\`\`\`
${features.dataQuery ? generateDataQueryExamples() : ''}
${features.dataMutation ? generateDataMutationExamples() : ''}
${features.alerts ? generateAlertsExample() : ''}
${features.offline ? generateOfflineExample() : ''}
${features.pwa ? generatePWAExample() : ''}
## Configuration Options
\`\`\`javascript
const config = {
baseUrl: 'https://your-dhis2-instance.com',
apiVersion: ${apiVersion},
timeout: 30000,
retries: 3,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
};
\`\`\`
`;
}
function generateDataQueryExamples() {
return `
## Data Query Examples
\`\`\`jsx
import { useDataQuery } from '@dhis2/app-runtime';
const DATA_ELEMENTS_QUERY = {
dataElements: {
resource: 'dataElements',
params: {
fields: 'id,displayName,valueType',
paging: false,
},
},
};
function DataElementsList() {
const { loading, error, data } = useDataQuery(DATA_ELEMENTS_QUERY);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.dataElements.dataElements.map(de => (
<li key={de.id}>{de.displayName} ({de.valueType})</li>
))}
</ul>
);
}
\`\`\`
`;
}
function generateDataMutationExamples() {
return `
## Data Mutation Examples
\`\`\`jsx
import { useDataMutation } from '@dhis2/app-runtime';
const CREATE_DATA_ELEMENT_MUTATION = {
resource: 'dataElements',
type: 'create',
data: ({ name, valueType }) => ({
name,
shortName: name.substring(0, 50),
valueType,
domainType: 'AGGREGATE',
aggregationType: 'SUM'
})
};
function CreateDataElementForm() {
const [mutate, { loading, error }] = useDataMutation(CREATE_DATA_ELEMENT_MUTATION);
const handleSubmit = async (formData) => {
try {
const result = await mutate(formData);
console.log('Created:', result);
} catch (err) {
console.error('Error:', err);
}
};
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
</form>
);
}
\`\`\`
`;
}
function generateAlertsExample() {
return `
## Alerts Integration
\`\`\`jsx
import { useAlert } from '@dhis2/app-runtime';
function MyComponent() {
const { show } = useAlert((message) => message, {
success: true,
duration: 3000
});
const handleSuccess = () => {
show('Operation completed successfully!');
};
return <button onClick={handleSuccess}>Save</button>;
}
\`\`\`
`;
}
function generateOfflineExample() {
return `
## Offline Configuration
\`\`\`jsx
import { OfflineProvider } from '@dhis2/app-runtime';
const offlineConfig = {
dhis2: {
baseUrl: process.env.REACT_APP_DHIS2_BASE_URL,
apiVersion: 40
},
pwa: {
enabled: true,
caching: {
patterns: ['api/dataElements', 'api/organisationUnits']
}
}
};
function App() {
return (
<OfflineProvider config={offlineConfig}>
<MyApp />
</OfflineProvider>
);
}
\`\`\`
`;
}
function generatePWAExample() {
return `
## PWA Configuration
\`\`\`jsx
import { PWAProvider } from '@dhis2/app-runtime';
function App() {
return (
<PWAProvider>
<MyApp />
</PWAProvider>
);
}
\`\`\`
`;
}
export function generateAuthenticationPatterns(args) {
const { authType, providers = [], sessionManagement = {}, securityFeatures = {}, redirectUrls = {} } = args;
return `# DHIS2 Authentication Patterns
## ${authType.toUpperCase()} Authentication Setup
${generateAuthTypeImplementation(authType)}
${providers.length > 0 ? generateProviderIntegration(providers) : ''}
## Session Management
\`\`\`javascript
const sessionConfig = {
timeout: ${sessionManagement.timeout || 30}, // minutes
refreshTokens: ${sessionManagement.refreshTokens || false},
rememberUser: ${sessionManagement.rememberUser || false}
};
\`\`\`
${Object.keys(securityFeatures).length > 0 ? generateSecurityConfiguration(securityFeatures) : ''}
## Redirect Configuration
\`\`\`javascript
const redirectConfig = {
success: '${redirectUrls.success || '/dashboard'}',
failure: '${redirectUrls.failure || '/login'}',
logout: '${redirectUrls.logout || '/login'}'
};
\`\`\`
## Testing Authentication
\`\`\`bash
# Test login endpoint
curl -X POST \\
-H "Content-Type: application/json" \\
-d '{"username":"admin","password":"district"}' \\
https://your-dhis2-instance.com/api/auth/login
# Test current user endpoint
curl -H "Cookie: JSESSIONID=your-session-id" \\
https://your-dhis2-instance.com/api/me
\`\`\`
`;
}
function generateAuthTypeImplementation(authType) {
switch (authType) {
case 'basic':
return `### Basic Authentication
\`\`\`jsx
import { useConfig } from '@dhis2/app-runtime';
const LoginForm = () => {
const { baseUrl } = useConfig();
const handleLogin = async (username, password) => {
const response = await fetch(\`\${baseUrl}/api/me\`, {
headers: {
'Authorization': \`Basic \${btoa(\`\${username}:\${password}\`)}\`
}
});
if (response.ok) {
// Store credentials securely
localStorage.setItem('dhis2.username', username);
// Navigate to app
}
};
return (
<form onSubmit={handleLogin}>
<input name="username" type="text" required />
<input name="password" type="password" required />
<button type="submit">Login</button>
</form>
);
};
\`\`\``;
case 'oauth2':
return `### OAuth2 Authentication
\`\`\`jsx
import { useConfig } from '@dhis2/app-runtime';
const OAuth2Login = () => {
const { baseUrl } = useConfig();
const handleOAuth2Login = () => {
const clientId = process.env.REACT_APP_OAUTH2_CLIENT_ID;
const redirectUri = window.location.origin + '/oauth/callback';
const oauthUrl = \`\${baseUrl}/uaa/oauth/authorize?response_type=code&client_id=\${clientId}&redirect_uri=\${redirectUri}\`;
window.location.href = oauthUrl;
};
return <button onClick={handleOAuth2Login}>Login with DHIS2</button>;
};
\`\`\``;
case 'cookie':
return `### Cookie-based Authentication
\`\`\`jsx
const CookieAuth = () => {
const handleLogin = async (username, password) => {
const response = await fetch(\`\${baseUrl}/dhis-web-commons-security/login.action\`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: \`j_username=\${username}&j_password=\${password}\`,
credentials: 'include'
});
if (response.ok) {
// Cookie is automatically handled by browser
window.location.href = '/';
}
};
return (
// Login form implementation
);
};
\`\`\``;
case 'token':
return `### Token-based Authentication
\`\`\`jsx
const TokenAuth = () => {
const handleLogin = async (username, password) => {
const response = await fetch(\`\${baseUrl}/api/auth/token\`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const { token } = await response.json();
// Store token securely
localStorage.setItem('dhis2.token', token);
// Set default header for future requests
axios.defaults.headers.common['Authorization'] = \`Bearer \${token}\`;
};
return (
// Login form implementation
);
};
\`\`\``;
default:
return '### Custom Authentication Implementation';
}
}
function generateProviderIntegration(providers) {
return `
## Identity Providers
${providers.map(provider => {
switch (provider) {
case 'google':
return `### Google SSO Integration
\`\`\`jsx
import { GoogleLogin } from '@react-oauth/google';
const GoogleSSO = () => {
const handleGoogleSuccess = (credentialResponse) => {
// Send Google token to DHIS2 for validation
fetch('/api/auth/google', {
method: 'POST',
body: JSON.stringify({ token: credentialResponse.credential })
});
};
return (
<GoogleLogin
onSuccess={handleGoogleSuccess}
onError={() => console.log('Login Failed')}
/>
);
};
\`\`\``;
case 'facebook':
return `### Facebook SSO Integration
\`\`\`jsx
import FacebookLogin from 'react-facebook-login';
const FacebookSSO = () => {
const handleFacebookResponse = (response) => {
// Send Facebook token to DHIS2
};
return (
<FacebookLogin
appId="your-facebook-app-id"
autoLoad={false}
fields="name,email,picture"
callback={handleFacebookResponse}
/>
);
};
\`\`\``;
case 'custom':
return `### Custom Identity Provider
\`\`\`jsx
const CustomSSO = () => {
const handleCustomAuth = () => {
// Implement custom authentication flow
window.location.href = '/auth/custom-provider';
};
return <button onClick={handleCustomAuth}>Login with Custom Provider</button>;
};
\`\`\``;
default:
return '';
}
}).join('\n')}
`;
}
function generateSecurityConfiguration(securityFeatures) {
return `
## Security Configuration
\`\`\`javascript
// Security middleware
const securityConfig = {
csrfProtection: ${securityFeatures.csrfProtection || false},
httpOnly: ${securityFeatures.httpOnly || true},
secure: ${securityFeatures.secure || true}, // HTTPS only
sameSite: 'strict'
};
${securityFeatures.csrfProtection ? `
// CSRF Token handling
const getCSRFToken = async () => {
const response = await fetch('/api/csrf-token');
const { token } = await response.json();
return token;
};
` : ''}
\`\`\`
`;
}
export function generateUIComponents(args) {
const { componentType, componentName, features = {}, dataIntegration = {}, styling = {} } = args;
return `# DHIS2 UI Component: ${componentName}
## Component Implementation
${generateComponentCode(componentType, componentName, features, dataIntegration, styling)}
## Usage Example
\`\`\`jsx
import { ${componentName} } from './components/${componentName}';
function App() {
return (
<div>
<${componentName} />
</div>
);
}
\`\`\`
## Features Included
${Object.entries(features).map(([feature, enabled]) => enabled ? `- ✅ ${feature.replace(/_/g, ' ').toUpperCase()}` : `- ❌ ${feature.replace(/_/g, ' ').toUpperCase()}`).join('\n')}
## Styling Options
- **Theme**: ${styling.theme || 'default'}
- **Custom CSS**: ${styling.customCss ? 'Enabled' : 'Disabled'}
${dataIntegration.apiEndpoint ? `
## API Integration
- **Endpoint**: ${dataIntegration.apiEndpoint}
- **Data Query**: ${dataIntegration.useDataQuery ? 'Enabled' : 'Disabled'}
- **Data Mutation**: ${dataIntegration.useDataMutation ? 'Enabled' : 'Disabled'}
` : ''}
`;
}
function generateComponentCode(componentType, componentName, features, dataIntegration, styling) {
const baseImports = [
"import React from 'react';",
...(dataIntegration.useDataQuery ? ["import { useDataQuery } from '@dhis2/app-runtime';"] : []),
...(dataIntegration.useDataMutation ? ["import { useDataMutation } from '@dhis2/app-runtime';"] : [])
];
switch (componentType) {
case 'form':
return generateFormComponent(componentName, features, dataIntegration, baseImports);
case 'table':
return generateTableComponent(componentName, features, dataIntegration, baseImports);
case 'dashboard':
return generateDashboardComponent(componentName, features, dataIntegration, baseImports);
case 'modal':
return generateModalComponent(componentName, features, dataIntegration, baseImports);
case 'navigation':
return generateNavigationComponent(componentName, features, dataIntegration, baseImports);
case 'chart':
return generateChartComponent(componentName, features, dataIntegration, baseImports);
case 'list':
return generateListComponent(componentName, features, dataIntegration, baseImports);
default:
return generateGenericComponent(componentName, baseImports);
}
}
function generateFormComponent(name, features, dataIntegration, imports) {
const uiImports = [
"import { Button, Field, Input, TextArea } from '@dhis2/ui';"
];
return `${[...imports, ...uiImports].join('\n')}
${dataIntegration.useDataMutation ? `
const SAVE_MUTATION = {
resource: '${dataIntegration.apiEndpoint || 'dataElements'}',
type: 'create',
data: (formData) => formData
};
` : ''}
export const ${name} = () => {
const [formData, setFormData] = React.useState({});
${features.validation ? 'const [errors, setErrors] = React.useState({});' : ''}
${dataIntegration.useDataMutation ? 'const [mutate, { loading, error }] = useDataMutation(SAVE_MUTATION);' : ''}
const handleSubmit = async (e) => {
e.preventDefault();
${features.validation ? `
const validationErrors = validateForm(formData);
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
` : ''}
${dataIntegration.useDataMutation ? `
try {
await mutate(formData);
// Handle success
} catch (err) {
console.error('Save failed:', err);
}
` : '// Handle form submission'}
};
const handleChange = (name, value) => {
setFormData(prev => ({ ...prev, [name]: value }));
${features.validation ? 'if (errors[name]) setErrors(prev => ({ ...prev, [name]: null }));' : ''}
};
return (
<form onSubmit={handleSubmit}>
<Field label="Name" error={${features.validation ? 'errors.name' : 'null'}}>
<Input
value={formData.name || ''}
onChange={({ value }) => handleChange('name', value)}
/>
</Field>
<Field label="Description">
<TextArea
value={formData.description || ''}
onChange={({ value }) => handleChange('description', value)}
rows={4}
/>
</Field>
<Button primary type="submit" ${dataIntegration.useDataMutation ? 'loading={loading}' : ''}>
Save
</Button>
</form>
);
};
${features.validation ? `
const validateForm = (data) => {
const errors = {};
if (!data.name || data.name.trim().length === 0) {
errors.name = 'Name is required';
}
if (data.name && data.name.length > 50) {
errors.name = 'Name must be less than 50 characters';
}
return errors;
};
` : ''}`;
}
function generateTableComponent(name, features, dataIntegration, imports) {
const uiImports = [
"import { DataTable, DataTableHead, DataTableRow, DataTableColumnHeader, DataTableBody, DataTableCell } from '@dhis2/ui';"
];
return `${[...imports, ...uiImports].join('\n')}
${dataIntegration.useDataQuery ? `
const DATA_QUERY = {
data: {
resource: '${dataIntegration.apiEndpoint || 'dataElements'}',
params: {
fields: 'id,displayName,valueType',
${features.pagination ? 'pageSize: 25,' : 'paging: false,'}
}
}
};
` : ''}
export const ${name} = () => {
${dataIntegration.useDataQuery ? 'const { loading, error, data } = useDataQuery(DATA_QUERY);' : ''}
${features.search ? 'const [searchTerm, setSearchTerm] = React.useState("");' : ''}
${features.pagination ? 'const [currentPage, setCurrentPage] = React.useState(1);' : ''}
${dataIntegration.useDataQuery ? `
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
const items = data?.data?.${dataIntegration.apiEndpoint?.slice(-1) === 's' ? dataIntegration.apiEndpoint : 'dataElements'} || [];
` : 'const items = [];'}
${features.search ? `
const filteredItems = items.filter(item =>
item.displayName?.toLowerCase().includes(searchTerm.toLowerCase())
);
` : 'const filteredItems = items;'}
return (
<div>
${features.search ? `
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{ marginBottom: '16px', padding: '8px' }}
/>
` : ''}
<DataTable>
<DataTableHead>
<DataTableRow>
<DataTableColumnHeader>Name</DataTableColumnHeader>
<DataTableColumnHeader>Type</DataTableColumnHeader>
<DataTableColumnHeader>Actions</DataTableColumnHeader>
</DataTableRow>
</DataTableHead>
<DataTableBody>
{filteredItems.map(item => (
<DataTableRow key={item.id}>
<DataTableCell>{item.displayName}</DataTableCell>
<DataTableCell>{item.valueType}</DataTableCell>
<DataTableCell>
<button>Edit</button>
<button>Delete</button>
</DataTableCell>
</DataTableRow>
))}
</DataTableBody>
</DataTable>
${features.pagination ? `
<div style={{ marginTop: '16px', textAlign: 'center' }}>
<button onClick={() => setCurrentPage(p => Math.max(1, p - 1))}>Previous</button>
<span style={{ margin: '0 16px' }}>Page {currentPage}</span>
<button onClick={() => setCurrentPage(p => p + 1)}>Next</button>
</div>
` : ''}
</div>
);
};`;
}
function generateDashboardComponent(name, features, dataIntegration, imports) {
return `${imports.join('\n')}
import { Card } from '@dhis2/ui';
export const ${name} = () => {
return (
<div className="dashboard-grid">
<Card>
<h3>Dashboard Widget 1</h3>
<p>Content goes here</p>
</Card>
<Card>
<h3>Dashboard Widget 2</h3>
<p>More content</p>
</Card>
</div>
);
};
// Add corresponding CSS
const styles = \`
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
padding: 16px;
}
${features.responsive ? `
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
` : ''}
\`;`;
}
function generateModalComponent(name, features, dataIntegration, imports) {
return `${imports.join('\n')}
import { Modal, ModalTitle, ModalContent, ModalActions, Button } from '@dhis2/ui';
export const ${name} = ({ open, onClose, title, children }) => {
return (
<Modal open={open} onClose={onClose}>
<ModalTitle>{title}</ModalTitle>
<ModalContent>
{children}
</ModalContent>
<ModalActions>
<Button secondary onClick={onClose}>
Cancel
</Button>
<Button primary onClick={() => console.log('Confirm')}>
Confirm
</Button>
</ModalActions>
</Modal>
);
};`;
}
function generateNavigationComponent(name, features, dataIntegration, imports) {
return `${imports.join('\n')}
import { Menu, MenuItem } from '@dhis2/ui';
export const ${name} = ({ activeItem, onItemClick }) => {
const menuItems = [
{ id: 'dashboard', label: 'Dashboard' },
{ id: 'data-entry', label: 'Data Entry' },
{ id: 'reports', label: 'Reports' },
{ id: 'settings', label: 'Settings' }
];
return (
<Menu>
{menuItems.map(item => (
<MenuItem
key={item.id}
active={activeItem === item.id}
onClick={() => onItemClick(item.id)}
label={item.label}
/>
))}
</Menu>
);
};`;
}
function generateChartComponent(name, features, dataIntegration, imports) {
return `${imports.join('\n')}
export const ${name} = ({ data, type = 'bar' }) => {
// Chart implementation would go here
// You might use Chart.js, D3.js, or DHIS2's visualization components
return (
<div className="chart-container">
<canvas id="chart" width="400" height="200"></canvas>
</div>
);
};`;
}
function generateListComponent(name, features, dataIntegration, imports) {
return `${imports.join('\n')}
import { SingleSelect, SingleSelectOption } from '@dhis2/ui';
${dataIntegration.useDataQuery ? `
const LIST_QUERY = {
items: {
resource: '${dataIntegration.apiEndpoint || 'dataElements'}',
params: {
fields: 'id,displayName',
paging: false
}
}
};
` : ''}
export const ${name} = ({ onSelectionChange }) => {
${dataIntegration.useDataQuery ? 'const { loading, error, data } = useDataQuery(LIST_QUERY);' : ''}
const [selected, setSelected] = React.useState(null);
${dataIntegration.useDataQuery ? `
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
const items = data?.items?.${dataIntegration.apiEndpoint?.slice(-1) === 's' ? dataIntegration.apiEndpoint : 'dataElements'} || [];
` : 'const items = [];'}
const handleChange = ({ selected }) => {
setSelected(selected);
onSelectionChange?.(selected);
};
return (
<SingleSelect
selected={selected}
onChange={handleChange}
placeholder="Select an item"
>
{items.map(item => (
<SingleSelectOption
key={item.id}
value={item.id}
label={item.displayName}
/>
))}
</SingleSelect>
);
};`;
}
function generateGenericComponent(name, imports) {
return `${imports.join('\n')}
export const ${name} = ({ children, ...props }) => {
return (
<div className="${name.toLowerCase()}" {...props}>
{children}
</div>
);
};`;
}
export function generateTestSetup(args) {
const { testFramework, testTypes = [], coverage = {}, mockSetup = {} } = args;
return `# DHIS2 Testing Setup
## ${testFramework.toUpperCase()} Configuration
${generateTestFrameworkConfig(testFramework, coverage)}
${testTypes.includes('unit') ? generateUnitTestExamples() : ''}
${testTypes.includes('integration') ? generateIntegrationTestExamples() : ''}
${testTypes.includes('e2e') ? generateE2ETestExamples(testFramework) : ''}
${testTypes.includes('visual') ? generateVisualTestExamples() : ''}
${Object.keys(mockSetup).length > 0 ? generateMockConfiguration(mockSetup) : ''}
## Test Commands
\`\`\`bash
# Run all tests
${testFramework === 'jest' ? 'npm test' : testFramework === 'cypress' ? 'npx cypress open' : 'npx playwright test'}
# Run with coverage
${testFramework === 'jest' ? 'npm test -- --coverage' : 'npm run test:coverage'}
# Run specific test file
${testFramework === 'jest' ? 'npm test -- MyComponent.test.js' : testFramework === 'cypress' ? 'npx cypress run --spec "cypress/integration/MyComponent.spec.js"' : 'npx playwright test tests/MyComponent.spec.ts'}
# Watch mode
${testFramework === 'jest' ? 'npm test -- --watch' : 'npm run test:watch'}
\`\`\`
`;
}
// Phase 4: UI Library Integration – Advanced Generators
export function generateUIFormPatterns(args) {
const { componentName = 'AdvancedForm', includeValidation = true, includeDatePicker = true, includeFileUpload = true, includeMultiSelect = true, includeSelects = true } = args;
return `# @dhis2/ui Form Patterns: ${componentName}
## Implementation
\`\`\`jsx
import React from 'react';
import {
Button,
Field,
InputField,
TextAreaField,
SingleSelectField,
SingleSelectOption,
${includeMultiSelect ? 'MultiSelectField, MultiSelectOption,' : ''}
${includeDatePicker ? 'DatePicker,' : ''}
${includeFileUpload ? 'FileInput,' : ''}
} from '@dhis2/ui';
${includeValidation ? "import { useState } from 'react';" : ''}
export const ${componentName} = () => {
${includeValidation ? 'const [errors, setErrors] = useState<Record<string, string | null>>({});' : ''}
const [form, setForm] = React.useState({
name: '',
description: '',
valueType: 'NUMBER',
date: '',
${includeMultiSelect ? 'options: [],' : ''}
});
const onChange = (key, value) => setForm(prev => ({ ...prev, [key]: value }));
const onSubmit = (e) => {
e.preventDefault();
${includeValidation ? `
const nextErrors: Record<string, string | null> = {};
if (!form.name?.trim()) nextErrors.name = 'Name is required';
if (form.name && form.name.length > 50) nextErrors.name = 'Max 50 characters';
setErrors(nextErrors);
if (Object.keys(nextErrors).length > 0) return;
` : ''}
// Submit logic here
console.log('Submitting form', form);
};
return (
<form onSubmit={onSubmit} style={{ display: 'grid', gap: 16 }}>
<InputField
label="Name"
name="name"
value={form.name}
onChange={({ value }) => onChange('name', value)}
${includeValidation ? 'validationText={errors.name || undefined}' : ''}
${includeValidation ? 'error={Boolean(errors.name)}' : ''}
required
/>
<TextAreaField
label="Description"
name="description"
value={form.description}
onChange={({ value }) => onChange('description', value)}
rows={4}
/>
${includeSelects ? `
<SingleSelectField
label="Value type"
selected={form.valueType}
onChange={({ selected }) => onChange('valueType', selected)}
>
<SingleSelectOption value="NUMBER" label="Number" />
<SingleSelectOption value="INTEGER" label="Integer" />
<SingleSelectOption value="TEXT" label="Text" />
</SingleSelectField>
` : ''}
${includeMultiSelect ? `
<MultiSelectField
label="Categories"
selected={form.options}
onChange={({ selected }) => onChange('options', selected)}
>
<MultiSelectOption value="male" label="Male" />
<MultiSelectOption value="female" label="Female" />
</MultiSelectField>
` : ''}
${includeDatePicker ? `
<Field label="Start date">
<DatePicker
calendar="gregorian"
date={form.date}
onDateSelect={({ date }) => onChange('date', date)}
/>
</Field>
` : ''}
${includeFileUpload ? `
<Field label="Attachment">
<FileInput onChange={({ files }) => onChange('file', files?.[0])} buttonLabel="Choose file" />
</Field>
` : ''}
<Button type="submit" primary>Save</Button>
</form>
);
};
\`\`\`
## Notes
- Includes validation, date picker, file upload, and multi-select patterns.
- Replace options with dynamic lists as needed.
`;
}
export function generateUIDataDisplayPatterns(args) {
const { componentName = 'DataDisplay', includeTable = true, includePagination = true, includeCards = true, includeLists = true, includeModal = true, includeLoading = true } = args;
return `# @dhis2/ui Data Display Patterns: ${componentName}
## Implementation
\`\`\`jsx
import React from 'react';
import {
DataTable, DataTableHead, DataTableRow, DataTableColumnHeader, DataTableBody, DataTableCell,
Card, Modal, ModalTitle, ModalContent, ModalActions, Button,
${includeLoading ? 'CircularLoader,' : ''}
} from '@dhis2/ui';
export const ${componentName} = ({ items = [], loading = false }) => {
const [open, setOpen] = React.useState(false);
if (loading) {
return ${includeLoading ? '<CircularLoader />' : '<div>Loading...</div>'};
}
return (
<div style={{ display: 'grid', gap: 16 }}>
${includeTable ? `
<DataTable>
<DataTableHead>
<DataTableRow>
<DataTableColumnHeader>Name</DataTableColumnHeader>
<DataTableColumnHeader>Type</DataTableColumnHeader>
</DataTableRow>
</DataTableHead>
<DataTableBody>
{items.map((it) => (
<DataTableRow key={it.id}>
<DataTableCell>{it.displayName}</DataTableCell>
<DataTableCell>{it.valueType}</DataTableCell>
</DataTableRow>
))}
</DataTableBody>
</DataTable>
${includePagination ? `<div style={{ textAlign: 'center' }}><Button>Prev</Button> <Button>Next</Button></div>` : ''}
` : ''}
${includeCards ? `
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 16 }}>
{items.slice(0, 4).map((it) => (
<Card key={it.id}>
<div style={{ padding: 16 }}>
<h3 style={{ margin: '0 0 8px' }}>{it.displayName}</h3>
<div>Type: {it.valueType}</div>
</div>
</Card>
))}
</div>
` : ''}
${includeLists ? `
<ul>
{items.slice(0, 5).map((it) => (
<li key={it.id}>{it.displayName}</li>
))}
</ul>
` : ''}
${includeModal ? `
<>
<Button onClick={() => setOpen(true)}>Open modal</Button>
<Modal open={open} onClose={() => setOpen(false)}>
<ModalTitle>Details</ModalTitle>
<ModalContent>Modal content here</ModalContent>
<ModalActions>
<Button secondary onClick={() => setOpen(false)}>Close</Button>
</ModalActions>
</Modal>
</>
` : ''}
</div>
);
};
\`\`\`
`;
}
export function generateUINavigationLayout(args) {
const { componentName = 'NavigationLayout', includeHeaderBar = true, includeSidebar = true, includeBreadcrumbs = true, includeTabs = true, includeResponsive = true } = args;
return `# @dhis2/ui Navigation & Layout: ${componentName}
## Implementation
\`\`\`jsx
import React from 'react';
import { Menu, MenuItem, TabBar, Tab, Breadcrumbs, BreadcrumbsItem } from '@dhis2/ui';
${includeHeaderBar ? "import { HeaderBar } from '@dhis2/ui';" : ''}
export const ${componentName} = () => {
const [active, setActive] = React.useState('dashboard');
return (
<div className="layout">
${includeHeaderBar ? '<HeaderBar appName="My DHIS2 App" />' : ''}
<div className="content">
${includeSidebar ? `
<aside className="sidebar">
<Menu>
<MenuItem label="Dashboard" active={active==='dashboard'} onClick={() => setActive('dashboard')} />
<MenuItem label="Data Entry" active={active==='data-entry'} onClick={() => setActive('data-entry')} />
<MenuItem label="Reports" active={active==='reports'} onClick={() => setActive('reports')} />
</Menu>
</aside>
` : ''}
<main className="main">
${includeBreadcrumbs ? `
<Breadcrumbs>
<BreadcrumbsItem>Home</BreadcrumbsItem>
<BreadcrumbsItem>Section</BreadcrumbsItem>
<BreadcrumbsItem>Page</BreadcrumbsItem>
</Breadcrumbs>
` : ''}
${includeTabs ? `
<TabBar>
<Tab selected>Overview</Tab>
<Tab>Details</Tab>
<Tab>Settings</Tab>
</TabBar>
` : ''}
</main>
</div>
</div>
);
};
// Basic layout styles
const styles =
\`.layout { display: flex; flex-direction: column; height: 100vh; }
.content { display: grid; grid-template-columns: ${includeSidebar ? '240px 1fr' : '1fr'}; flex: 1; min-height: 0; }
.sidebar { border-right: 1px solid #e5e7eb; padding: 12px; overflow: auto; }
.main { padding: 16px; overflow: auto; }
${includeResponsive ? '@media (max-width: 768px) { .content { grid-template-columns: 1fr; } .sidebar { display:none; } }' : ''}\`;
\`\`\`
`;
}
export function generateDesignSystemConfig(args) {
const { theme = 'default', enableDarkMode = true, palette = {
primary: '#0a6eb4',
secondary: '#4a5568',
success: '#0E7C86',
warning: '#FFA400',
danger: '#E53935'
}, typography = {
fontFamily: 'Inter, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial, "Apple Color Emoji", "Segoe UI Emoji"',
scale: [12, 14, 16, 20, 24, 32]
}, spacing = [4, 8, 12, 16, 24, 32] } = args;
return `# Design System Configuration
## Theme Tokens (CSS Variables)
\`\`\`css
:root {
--color-primary: ${palette.primary};
--color-secondary: ${palette.secondary};
--color-success: ${palette.success};
--color-warning: ${palette.warning};
--color-danger: ${palette.danger};
--font-family-base: ${typography.fontFamily};
--font-size-xs: ${typography.scale[0]}px;
--font-size-sm: ${typography.scale[1]}px;
--font-size-md: ${typography.scale[2]}px;
--font-size-lg: ${typography.scale[3]}px;
--font-size-xl: ${typography.scale[4]}px;
--font-size-2xl: ${typography.scale[5]}px;
--space-1: ${spacing[0]}px;
--space-2: ${spacing[1]}px;
--space-3: ${spacing[2]}px;
--space-4: ${spacing[3]}px;
--space-5: ${spacing[4]}px;
--space-6: ${spacing[5]}px;
}
\`\`\`
`;
}
export function generateI18nSetup() {
return `# i18n Setup for DHIS2 Apps (@dhis2/d2-i18n)
## Installation
\`\`\`bash
yarn add @dhis2/d2-i18n
\`\`\`
## Initialization (src/i18n.js)
\`\`\`javascript
import i18n from '@dhis2/d2-i18n';
i18n.addResources('en', 'translation', {});
i18n.setDefaultNamespace('translation');
export default i18n;
\`\`\`
## Usage in components
\`\`\`jsx
import i18n from '../i18n';
<Button>{i18n.t('Save')}</Button>
\`\`\`
## Translations (public/locales/<locale>/translation.json)
\`\`\`json
{ "Save": "Save", "Name": "Name" }
\`\`\`
`;
}
export function generateAccessibilityChecklist() {
return `# Accessibility Checklist
- Labels: All inputs have visible labels, associated via props
- Validation text: Use validationText with error state
- Keyboard: Tab order and ENTER/ESC in dialogs
- Focus: Focus first invalid field on submit
- Color contrast: Verify WCAG AA
- ARIA: Use role/aria-* only when needed, keep semantic HTML
`;
}
export function generateTableEnhancements(args) {
const { sorting = true, selection = true, stickyHeader = true } = args || {};
return `# DataTable Enhancements
## Features
- Sorting: column header click toggles sort
- Selection: row checkbox selection
- Sticky header: header remains visible on scroll
## Example
\`\`\`jsx
import { DataTable, DataTableHead, DataTableRow, DataTableColumnHeader, DataTableBody, DataTableCell, DataTableToolbar, Checkbox } from '@dhis2/ui';
function EnhancedTable({ items }) {
return (
<DataTable ${stickyHeader ? 'scrollHeight="60vh"' : ''}>
<DataTableHead>
<DataTableRow>
${selection ? '<DataTableColumnHeader><Checkbox /></DataTableColumnHeader>' : ''}
<DataTableColumnHeader ${sorting ? 'onClick={() => { /* toggle sort */ }}' : ''}>Name</DataTableColumnHeader>
<DataTableColumnHeader>Type</DataTableColumnHeader>
</DataTableRow>
</DataTableHead>
<DataTableBody>
{items.map((it) => (
<DataTableRow key={it.id} ${selection ? 'selected={false}' : ''}>
${selection ? '<DataTableCell><Checkbox /></DataTableCell>' : ''}
<DataTableCell>{it.displayName}</DataTableCell>
<DataTableCell>{it.valueType}</DataTableCell>
</DataTableRow>
))}
</DataTableBody>
</DataTable>
);
}
\`\`\`
`;
}
export function generateAndroidThemeScaffold(args) {
const { dynamicColor = true, lightDark = true } = args || {};
return `# Android Material 3 Theme Scaffold (Compose)
## Theme.kt
\`\`\`kotlin
@Composable
fun AppTheme(content: @Composable () -> Unit) {
${dynamicColor ? 'val dynamic = dynamicColorScheme(LocalContext.current)' : 'val scheme = lightColorScheme()'}
${lightDark ? 'val dark = isSystemInDarkTheme()' : 'val dark = false'}
MaterialTheme(
colorScheme = ${dynamicColor ? '(if (dark) dynamic.darkScheme else dynamic.lightScheme)' : 'scheme'},
typography = Typography(),
content = content
)
}
\`\`\`
## Usage
\`\`\`kotlin
@Composable
fun App() { AppTheme { /* App content */ } }
\`\`\`
`;
}
// Phase 4 (Android): UI pattern generators
export function generateAndroidMaterialForm(args) {
const { screenName = 'RegistrationForm', includeValidation = true, includeDatePicker = true, includeMultiSelect = true } = args;
return `# Android Material Form (Jetpack Compose): ${screenName}
## Implementation
\`\`\`kotlin
@Composable
fun ${screenName}(
onSubmit: (name: String, description: String, date: String, options: List<String>) -> Unit
) {
var name by remember { mutableStateOf("") }
var description by remember { mutableStateOf("") }
var date by remember { mutableStateOf("") }
var showDatePicker by remember { mutableStateOf(false) }
val selectedOptions = remember { mutableStateListOf<String>() }
${includeValidation ? 'var nameError by remember { mutableStateOf<String?>(null) }' : ''}
Column(modifier = Modifier.padding(16.dp).fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
OutlinedTextField(
value = name,
onValueChange = { value ->
name = value
${includeValidation ? 'nameError = if (value.isBlank()) "Name is required" else null' : ''}
},
label = { Text("Name") },
isError = ${includeValidation ? 'nameError != null' : 'false'},
supportingText = { ${includeValidation ? 'nameError?.let { Text(it)' : 'null'} ${includeValidation ? '}' : ''} }
)
OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text("Description") },
minLines = 3
)
${includeDatePicker ? `
OutlinedTextField(
value = date,
onValueChange = {},
label = { Text("Date") },
readOnly = true,
trailingIcon = { Icon(Icons.Default.DateRange, contentDescription = null) },
modifier = Modifier.clickable { showDatePicker = true }
)
if (showDatePicker) {
DatePickerDialog(
onDismissRequest = { showDatePicker = false },
onDateChange = { y, m, d ->
date = "%04d-%02d-%02d".format(y, m + 1, d)
showDatePicker = false
}
)
}
` : ''}
${includeMultiSelect ? `
Text("Categories")
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
listOf("male", "female", "other").forEach { option ->
FilterChip(
selected = selectedOptions.contains(option),
onClick = {
if (selectedOptions.contains(option)) selectedOptions.remove(option)
else selectedOptions.add(option)
},
label = { Text(option) }
)
}
}
` : ''}
Button(onClick = {
${includeValidation ? 'if (nameError != null) return@Button' : ''}
onSubmit(name, description, date, selectedOptions.toList())
}) {
Text("Save")
}
}
}
\`\`\`
## Notes
- Replace \`DatePickerDialog\` with your preferred implementation if needed.
- Use validation for required fields.
`;
}
export function generateAndroidListAdapter(args) {
const { adapterName = 'DataElementAdapter', itemLayout = 'item_data_element' } = args;
return `# Android RecyclerView Adapter: ${adapterName}
## Adapter (Kotlin)
\`\`\`kotlin
class ${adapterName}(private val items: MutableList<DataElement>) : RecyclerView.Adapter<${adapterName}.ViewHolder>() {
class ViewHolder(val binding: ${camelToPascal(itemLayout)}Binding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ${camelToPascal(itemLayout)}Binding.inflate(inflater, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.binding.name.text = item.displayName
holder.binding.type.text = item.valueType
}
override fun getItemCount(): Int = items.size
}
\`\`\`
## Item Layout (XML)
\`\`\`xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textStyle="bold"/>
<TextView android:id="@+id/type" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>
\`\`\`
`;
}
export function generateAndroidNavigationDrawer(args) {
const { componentName = 'AppNavigation' } = args;
return `# Android Navigation Drawer (Compose): ${componentName}
## Implementation
\`\`\`kotlin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ${componentName}() {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
NavigationDrawerItem(label = { Text("Dashboard") }, selected = true, onClick = { })
NavigationDrawerItem(label = { Text("Data Entry") }, selected = false, onClick = { })
NavigationDrawerItem(label = { Text("Reports") }, selected = false, onClick = { })
}
}
) {
Scaffold(topBar = {