profile view
This commit is contained in:
@@ -64,6 +64,8 @@ function Dashboard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import ProfileView from "./components/ProfileView";
|
||||||
|
|
||||||
function AdminApp() {
|
function AdminApp() {
|
||||||
const { currentUser, login, logout, loading, error } = useAuth();
|
const { currentUser, login, logout, loading, error } = useAuth();
|
||||||
const config = React.useContext(ConfigContext);
|
const config = React.useContext(ConfigContext);
|
||||||
@@ -93,6 +95,7 @@ function AdminApp() {
|
|||||||
>
|
>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Dashboard />} />
|
<Route path="/" element={<Dashboard />} />
|
||||||
|
<Route path="/profile" element={<ProfileView />} />
|
||||||
<Route path="/:resourceName" element={<ResourceRouteWrapper />} />
|
<Route path="/:resourceName" element={<ResourceRouteWrapper />} />
|
||||||
<Route path="/:resourceName/:id" element={<ResourceRouteWrapper />} />
|
<Route path="/:resourceName/:id" element={<ResourceRouteWrapper />} />
|
||||||
<Route path="/:resourceName/create" element={<ResourceRouteWrapper />} />
|
<Route path="/:resourceName/create" element={<ResourceRouteWrapper />} />
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
ListItemIcon,
|
ListItemIcon,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
CssBaseline,
|
CssBaseline,
|
||||||
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
@@ -176,9 +177,15 @@ export default function AdminLayout({
|
|||||||
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1, fontWeight: 'bold' }}>
|
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1, fontWeight: 'bold' }}>
|
||||||
Admin Panel
|
Admin Panel
|
||||||
</Typography>
|
</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}
|
{username}
|
||||||
</Typography>
|
</Button>
|
||||||
|
</Box>
|
||||||
<Tooltip title="Logout">
|
<Tooltip title="Logout">
|
||||||
<IconButton color="inherit" onClick={onLogout}>
|
<IconButton color="inherit" onClick={onLogout}>
|
||||||
<LogoutIcon />
|
<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 {
|
return {
|
||||||
useList,
|
useList,
|
||||||
useRead,
|
useRead,
|
||||||
|
useMe,
|
||||||
useCreate,
|
useCreate,
|
||||||
useUpdate,
|
useUpdate,
|
||||||
useDelete,
|
useDelete,
|
||||||
|
|||||||
@@ -35,4 +35,8 @@ export interface AppConfig {
|
|||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
authBaseUrl: string;
|
authBaseUrl: string;
|
||||||
resources: ResourceConfig[];
|
resources: ResourceConfig[];
|
||||||
|
profile?: {
|
||||||
|
resource: string;
|
||||||
|
extraFields?: Record<string, any>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import SwaggerParser from "@apidevtools/swagger-parser";
|
import SwaggerParser from "@apidevtools/swagger-parser";
|
||||||
import { AppConfig, ResourceConfig, ResourceField, FieldType } from "../types/config";
|
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
|
* 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);
|
resourcePaths[base].methods.push(...methods);
|
||||||
|
|
||||||
// Identify the list endpoint for this resource
|
// 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;
|
resourcePaths[base].listPath = path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,5 +170,6 @@ export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig>
|
|||||||
baseUrl: serverBaseUrl,
|
baseUrl: serverBaseUrl,
|
||||||
authBaseUrl: authBaseUrl,
|
authBaseUrl: authBaseUrl,
|
||||||
resources,
|
resources,
|
||||||
|
profile: profileConfiguration,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user