profile view
This commit is contained in:
@@ -64,6 +64,8 @@ function Dashboard() {
|
||||
);
|
||||
}
|
||||
|
||||
import ProfileView from "./components/ProfileView";
|
||||
|
||||
function AdminApp() {
|
||||
const { currentUser, login, logout, loading, error } = useAuth();
|
||||
const config = React.useContext(ConfigContext);
|
||||
@@ -93,6 +95,7 @@ function AdminApp() {
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/profile" element={<ProfileView />} />
|
||||
<Route path="/:resourceName" element={<ResourceRouteWrapper />} />
|
||||
<Route path="/:resourceName/:id" element={<ResourceRouteWrapper />} />
|
||||
<Route path="/:resourceName/create" element={<ResourceRouteWrapper />} />
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
CssBaseline,
|
||||
Button,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
@@ -176,9 +177,15 @@ export default function AdminLayout({
|
||||
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1, fontWeight: 'bold' }}>
|
||||
Admin Panel
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mr: 2, fontWeight: 500, display: { xs: 'none', sm: 'block' } }}>
|
||||
<Box sx={{ display: { xs: 'none', sm: 'flex' }, alignItems: 'center', mr: 2 }}>
|
||||
<Button
|
||||
color="inherit"
|
||||
onClick={() => navigate('/profile')}
|
||||
sx={{ textTransform: 'none', fontWeight: 500 }}
|
||||
>
|
||||
{username}
|
||||
</Typography>
|
||||
</Button>
|
||||
</Box>
|
||||
<Tooltip title="Logout">
|
||||
<IconButton color="inherit" onClick={onLogout}>
|
||||
<LogoutIcon />
|
||||
|
||||
58
src_generic/components/ProfileView.tsx
Normal file
58
src_generic/components/ProfileView.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Typography, Paper, CircularProgress, Alert } from '@mui/material';
|
||||
import { useResource } from '../hooks/useResource';
|
||||
import GenericForm from './GenericForm';
|
||||
import { ConfigContext } from '../App';
|
||||
|
||||
export default function ProfileView() {
|
||||
const appConfig = React.useContext(ConfigContext);
|
||||
const profileConfig = appConfig?.profile;
|
||||
const resourceConfig = appConfig?.resources.find(r => r.name === profileConfig?.resource);
|
||||
|
||||
if (!profileConfig || !resourceConfig) {
|
||||
debugger;
|
||||
return <Alert severity="error">Profile configuration not found.</Alert>;
|
||||
}
|
||||
|
||||
const { useMe, useUpdate } = useResource(resourceConfig);
|
||||
const { data: profile, isLoading, error } = useMe();
|
||||
const updateMutation = useUpdate();
|
||||
|
||||
const handleSave = async (formData: any) => {
|
||||
try {
|
||||
const id = profile[resourceConfig.primaryKey];
|
||||
await updateMutation.mutateAsync({ id, data: formData });
|
||||
} catch (err) {
|
||||
console.error('Profile update failed:', err);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">Failed to load profile data.</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ maxWidth: 800, mx: 'auto', mt: 4 }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
My Profile
|
||||
</Typography>
|
||||
<Paper sx={{ p: 4, mt: 2 }}>
|
||||
<GenericForm
|
||||
config={resourceConfig}
|
||||
initialData={profile}
|
||||
onSave={handleSave}
|
||||
onCancel={() => window.history.back()}
|
||||
loading={updateMutation.isPending}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -40,3 +40,8 @@ export const configuration: Record<string, ResourceOverride> = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const profileConfiguration = {
|
||||
"extraFields": ['name'],
|
||||
"resource": "payors"
|
||||
};
|
||||
|
||||
@@ -81,9 +81,21 @@ export function useResource<T = any>(config: ResourceConfig) {
|
||||
},
|
||||
});
|
||||
|
||||
// --- READ ME ---
|
||||
const useMe = () =>
|
||||
useQuery({
|
||||
queryKey: [name, "me"],
|
||||
queryFn: async () => {
|
||||
// @ts-ignore
|
||||
const res = await api.get<T>(`${endpoint}/me`);
|
||||
return res.data;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
useList,
|
||||
useRead,
|
||||
useMe,
|
||||
useCreate,
|
||||
useUpdate,
|
||||
useDelete,
|
||||
|
||||
@@ -35,4 +35,8 @@ export interface AppConfig {
|
||||
baseUrl: string;
|
||||
authBaseUrl: string;
|
||||
resources: ResourceConfig[];
|
||||
profile?: {
|
||||
resource: string;
|
||||
extraFields?: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SwaggerParser from "@apidevtools/swagger-parser";
|
||||
import { AppConfig, ResourceConfig, ResourceField, FieldType } from "../types/config";
|
||||
import { configuration } from "../configuration";
|
||||
import { configuration, profileConfiguration } from "../configuration";
|
||||
|
||||
/**
|
||||
* Maps OpenAPI property types to our internal FieldType
|
||||
@@ -115,7 +115,7 @@ export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig>
|
||||
resourcePaths[base].methods.push(...methods);
|
||||
|
||||
// Identify the list endpoint for this resource
|
||||
if (!path.includes("{") && paths[path]?.get?.responses?.["200"]) {
|
||||
if (!resourcePaths[base].listPath && !path.includes("{") && paths[path]?.get?.responses?.["200"]) {
|
||||
resourcePaths[base].listPath = path;
|
||||
}
|
||||
}
|
||||
@@ -170,5 +170,6 @@ export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig>
|
||||
baseUrl: serverBaseUrl,
|
||||
authBaseUrl: authBaseUrl,
|
||||
resources,
|
||||
profile: profileConfiguration,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user