prototype 1.0
This commit is contained in:
111
src/App.tsx
111
src/App.tsx
@@ -1,82 +1,39 @@
|
|||||||
import { Admin, Resource, ListGuesser, fetchUtils } from 'react-admin';
|
import React from 'react';
|
||||||
import simpleRestProvider from 'ra-data-simple-rest';
|
import { Admin, Resource } from 'react-admin';
|
||||||
|
import { authProvider } from './authProvider';
|
||||||
// Кастомный httpClient, добавляющий X-Total-Count, если его нет
|
import { dataProvider } from './dataProvider';
|
||||||
const httpClient = (url: string, options: any = {}) => {
|
import Dashboard from './resources/dashboard/Dashboard';
|
||||||
// Добавляем JWT токен
|
import { UserList, UserEdit } from './resources/users';
|
||||||
const token = localStorage.getItem('token');
|
import { ReportList, ReportEdit } from './resources/reports';
|
||||||
if (token) {
|
import { TicketList, TicketEdit } from './resources/tickets';
|
||||||
options.headers = new Headers({
|
import { AdminList, AdminEdit } from './resources/admins';
|
||||||
...options.headers,
|
import { BannedWordList, BannedWordEdit } from './resources/banned-words';
|
||||||
Authorization: `Bearer ${token}`,
|
import { SubscriptionList, SubscriptionEdit } from './resources/subscriptions';
|
||||||
});
|
import { ReviewList, ReviewEdit } from './resources/reviews';
|
||||||
}
|
import { AuditList } from './resources/audit';
|
||||||
|
|
||||||
return fetchUtils.fetchJson(url, options).then((response) => {
|
|
||||||
const { headers, json } = response;
|
|
||||||
// Если это GET-запрос и ответ - массив, добавляем X-Total-Count
|
|
||||||
if (
|
|
||||||
!options.method || options.method === 'GET'
|
|
||||||
) {
|
|
||||||
if (Array.isArray(json)) {
|
|
||||||
// Создаём новый объект Response с добавленным заголовком
|
|
||||||
const newHeaders = new Headers(headers);
|
|
||||||
if (!newHeaders.has('X-Total-Count')) {
|
|
||||||
newHeaders.set('X-Total-Count', json.length.toString());
|
|
||||||
}
|
|
||||||
return Promise.resolve({
|
|
||||||
status: response.status,
|
|
||||||
headers: newHeaders,
|
|
||||||
body: json,
|
|
||||||
json: json,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataProvider = simpleRestProvider('/api', httpClient);
|
|
||||||
|
|
||||||
const authProvider = {
|
|
||||||
login: ({ username, password }: any) => {
|
|
||||||
return fetch('/v1/admin/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ email: username, password }),
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res.ok) return res.json();
|
|
||||||
throw new Error(res.statusText);
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
localStorage.setItem('token', data.token);
|
|
||||||
return Promise.resolve();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
logout: () => {
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
checkAuth: () =>
|
|
||||||
localStorage.getItem('token') ? Promise.resolve() : Promise.reject(),
|
|
||||||
checkError: (error: any) => {
|
|
||||||
if (error.status === 401) {
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
return Promise.reject();
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getPermissions: () => Promise.resolve(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Admin dataProvider={dataProvider} authProvider={authProvider}>
|
<Admin
|
||||||
<Resource name="users" list={ListGuesser} />
|
dashboard={Dashboard}
|
||||||
<Resource name="events" list={ListGuesser} />
|
authProvider={authProvider}
|
||||||
<Resource name="complaints" list={ListGuesser} />
|
dataProvider={dataProvider}
|
||||||
<Resource name="bugs" list={ListGuesser} />
|
requireAuth
|
||||||
</Admin>
|
>
|
||||||
|
{(permissions) => [
|
||||||
|
// Доступно всем администраторам
|
||||||
|
<Resource name="users" list={UserList} edit={UserEdit} />,
|
||||||
|
<Resource name="reports" list={ReportList} edit={ReportEdit} />,
|
||||||
|
<Resource name="tickets" list={TicketList} edit={TicketEdit} />,
|
||||||
|
<Resource name="banned-words" list={BannedWordList} edit={BannedWordEdit} />,
|
||||||
|
<Resource name="subscriptions" list={SubscriptionList} edit={SubscriptionEdit} />,
|
||||||
|
<Resource name="reviews" list={ReviewList} edit={ReviewEdit} />,
|
||||||
|
<Resource name="audit" list={AuditList} />,
|
||||||
|
// Только superadmin
|
||||||
|
permissions === 'superadmin' ? (
|
||||||
|
<Resource name="admins" list={AdminList} edit={AdminEdit} />
|
||||||
|
) : null,
|
||||||
|
]}
|
||||||
|
</Admin>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
44
src/authProvider.ts
Normal file
44
src/authProvider.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const base = import.meta.env.VITE_ADMIN_API_BASE_URL || '';
|
||||||
|
|
||||||
|
export const authProvider = {
|
||||||
|
login: async ({ username, password }: { username: string; password: string }) => {
|
||||||
|
const req = new Request(`${base}/v1/admin/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ email: username, password }),
|
||||||
|
headers: new Headers({ 'Content-Type': 'application/json' }),
|
||||||
|
});
|
||||||
|
const res = await fetch(req);
|
||||||
|
if (res.status < 200 || res.status >= 300) {
|
||||||
|
throw new Error(res.statusText);
|
||||||
|
}
|
||||||
|
const auth = await res.json();
|
||||||
|
localStorage.setItem('auth', JSON.stringify(auth));
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
logout: () => {
|
||||||
|
localStorage.removeItem('auth');
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
checkAuth: () =>
|
||||||
|
localStorage.getItem('auth')
|
||||||
|
? Promise.resolve()
|
||||||
|
: Promise.reject(new Error('Not authenticated')),
|
||||||
|
checkError: (error: any) => {
|
||||||
|
const status = error.status;
|
||||||
|
if (status === 401 || status === 403) {
|
||||||
|
localStorage.removeItem('auth');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
getPermissions: () => {
|
||||||
|
const auth = JSON.parse(localStorage.getItem('auth') || '{}');
|
||||||
|
// Используем роль из вложенного объекта user, если есть
|
||||||
|
return Promise.resolve(auth.user?.role || auth.role);
|
||||||
|
},
|
||||||
|
getIdentity: () => {
|
||||||
|
const auth = JSON.parse(localStorage.getItem('auth') || '{}');
|
||||||
|
const user = auth.user || {};
|
||||||
|
return Promise.resolve({ id: user.id || '', fullName: user.email || auth.email });
|
||||||
|
},
|
||||||
|
};
|
||||||
80
src/dataProvider.ts
Normal file
80
src/dataProvider.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// src/dataProvider.ts
|
||||||
|
import { fetchUtils } from 'react-admin';
|
||||||
|
|
||||||
|
const base = import.meta.env.VITE_ADMIN_API_BASE_URL || '';
|
||||||
|
|
||||||
|
const httpClient = (url: string, options: any = {}) => {
|
||||||
|
if (!options.headers) {
|
||||||
|
options.headers = new Headers({ Accept: 'application/json' });
|
||||||
|
}
|
||||||
|
const auth = JSON.parse(localStorage.getItem('auth') || '{}');
|
||||||
|
if (auth.token) {
|
||||||
|
(options.headers as Headers).set('Authorization', `Bearer ${auth.token}`);
|
||||||
|
}
|
||||||
|
return fetchUtils.fetchJson(url, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dataProvider = {
|
||||||
|
getList: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}`;
|
||||||
|
const { json } = await httpClient(url);
|
||||||
|
const data = Array.isArray(json) ? json : json.data || json.items || [];
|
||||||
|
const total = data.length;
|
||||||
|
return { data, total };
|
||||||
|
},
|
||||||
|
getOne: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}/${params.id}`;
|
||||||
|
const { json } = await httpClient(url);
|
||||||
|
return { data: json };
|
||||||
|
},
|
||||||
|
getMany: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}`;
|
||||||
|
const { json } = await httpClient(url);
|
||||||
|
const data = (Array.isArray(json) ? json : json.data || []).filter((item: any) =>
|
||||||
|
params.ids.includes(item.id)
|
||||||
|
);
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
getManyReference: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}`;
|
||||||
|
const { json } = await httpClient(url);
|
||||||
|
return { data: (Array.isArray(json) ? json : []), total: (Array.isArray(json) ? json : []).length };
|
||||||
|
},
|
||||||
|
update: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}/${params.id}`;
|
||||||
|
const { json } = await httpClient(url, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(params.data),
|
||||||
|
});
|
||||||
|
return { data: json };
|
||||||
|
},
|
||||||
|
updateMany: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}`;
|
||||||
|
await httpClient(url, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({ ids: params.ids, data: params.data }),
|
||||||
|
});
|
||||||
|
return { data: [] };
|
||||||
|
},
|
||||||
|
create: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}`;
|
||||||
|
const { json } = await httpClient(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(params.data),
|
||||||
|
});
|
||||||
|
return { data: json };
|
||||||
|
},
|
||||||
|
delete: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}/${params.id}`;
|
||||||
|
await httpClient(url, { method: 'DELETE' });
|
||||||
|
return { data: { id: params.id } };
|
||||||
|
},
|
||||||
|
deleteMany: async (resource, params) => {
|
||||||
|
const url = `${base}/v1/admin/${resource}`;
|
||||||
|
await httpClient(url, {
|
||||||
|
method: 'DELETE',
|
||||||
|
body: JSON.stringify({ ids: params.ids }),
|
||||||
|
});
|
||||||
|
return { data: [] };
|
||||||
|
},
|
||||||
|
};
|
||||||
14
src/resources/admins/AdminEdit.tsx
Normal file
14
src/resources/admins/AdminEdit.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edit, SimpleForm, SelectInput, required } from 'react-admin';
|
||||||
|
|
||||||
|
export const AdminEdit = () => (
|
||||||
|
<Edit>
|
||||||
|
<SimpleForm>
|
||||||
|
<SelectInput source="role" choices={[
|
||||||
|
{ id: 'admin', name: 'Admin' },
|
||||||
|
{ id: 'moderator', name: 'Moderator' },
|
||||||
|
{ id: 'support', name: 'Support' },
|
||||||
|
]} validate={required()} />
|
||||||
|
</SimpleForm>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
22
src/resources/admins/AdminList.tsx
Normal file
22
src/resources/admins/AdminList.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, EmailField, DateField, EditButton, SelectInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const AdminList = () => (
|
||||||
|
<List filters={[
|
||||||
|
<SelectInput source="role" choices={[
|
||||||
|
{ id: 'admin', name: 'Admin' },
|
||||||
|
{ id: 'superadmin', name: 'Superadmin' },
|
||||||
|
{ id: 'moderator', name: 'Moderator' },
|
||||||
|
{ id: 'support', name: 'Support' },
|
||||||
|
]} alwaysOn />,
|
||||||
|
]}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="id" />
|
||||||
|
<EmailField source="email" />
|
||||||
|
<TextField source="role" />
|
||||||
|
<TextField source="status" />
|
||||||
|
<DateField source="created_at" />
|
||||||
|
<EditButton />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
2
src/resources/admins/index.ts
Normal file
2
src/resources/admins/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { AdminList } from './AdminList';
|
||||||
|
export { AdminEdit } from './AdminEdit';
|
||||||
15
src/resources/audit/AuditList.tsx
Normal file
15
src/resources/audit/AuditList.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, DateField, TextInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const AuditList = () => (
|
||||||
|
<List filters={[<TextInput source="admin_id" alwaysOn />]}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="admin_id" />
|
||||||
|
<TextField source="action" />
|
||||||
|
<TextField source="entity_type" />
|
||||||
|
<TextField source="entity_id" />
|
||||||
|
<DateField source="timestamp" />
|
||||||
|
<TextField source="ip" />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
1
src/resources/audit/index.ts
Normal file
1
src/resources/audit/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { AuditList } from './AuditList';
|
||||||
10
src/resources/banned-words/BannedWordEdit.tsx
Normal file
10
src/resources/banned-words/BannedWordEdit.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edit, SimpleForm, TextInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const BannedWordEdit = () => (
|
||||||
|
<Edit>
|
||||||
|
<SimpleForm>
|
||||||
|
<TextInput source="word" />
|
||||||
|
</SimpleForm>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
13
src/resources/banned-words/BannedWordList.tsx
Normal file
13
src/resources/banned-words/BannedWordList.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, DateField, EditButton, TextInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const BannedWordList = () => (
|
||||||
|
<List filters={[<TextInput source="word" alwaysOn />]}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="word" />
|
||||||
|
<DateField source="added_at" />
|
||||||
|
<TextField source="added_by" />
|
||||||
|
<EditButton />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
2
src/resources/banned-words/index.ts
Normal file
2
src/resources/banned-words/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { BannedWordList } from './BannedWordList';
|
||||||
|
export { BannedWordEdit } from './BannedWordEdit';
|
||||||
64
src/resources/dashboard/Dashboard.tsx
Normal file
64
src/resources/dashboard/Dashboard.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// src/resources/dashboard/Dashboard.tsx
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Card, CardContent, Typography, Box, CircularProgress } from '@mui/material';
|
||||||
|
import { useDataProvider } from 'react-admin';
|
||||||
|
|
||||||
|
const Dashboard = () => {
|
||||||
|
const [stats, setStats] = useState<Record<string, number> | null>(null);
|
||||||
|
const [ticketsStats, setTicketsStats] = useState<{ total: number } | null>(null);
|
||||||
|
const dataProvider = useDataProvider();
|
||||||
|
|
||||||
|
const fetchJson = async (url: string) => {
|
||||||
|
const auth = JSON.parse(localStorage.getItem('auth') || '{}');
|
||||||
|
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||||
|
if (auth.token) {
|
||||||
|
headers['Authorization'] = `Bearer ${auth.token}`;
|
||||||
|
}
|
||||||
|
const res = await fetch(url, { headers });
|
||||||
|
if (!res.ok) throw new Error(res.statusText);
|
||||||
|
return res.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Promise.all([
|
||||||
|
fetchJson('/v1/admin/stats'),
|
||||||
|
fetchJson('/v1/admin/tickets/stats'),
|
||||||
|
])
|
||||||
|
.then(([statsData, ticketsData]) => {
|
||||||
|
setStats(statsData);
|
||||||
|
setTicketsStats(ticketsData);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!stats || !ticketsStats) return <CircularProgress />;
|
||||||
|
|
||||||
|
const cards = [
|
||||||
|
{ label: 'Users', value: stats.users },
|
||||||
|
{ label: 'Calendars', value: stats.calendars },
|
||||||
|
{ label: 'Events', value: stats.events },
|
||||||
|
{ label: 'Bookings', value: stats.bookings },
|
||||||
|
{ label: 'Reviews', value: stats.reviews },
|
||||||
|
{ label: 'Subscriptions', value: stats.subscriptions },
|
||||||
|
{ label: 'Tickets', value: ticketsStats.total },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box display="flex" flexWrap="wrap" gap={3}>
|
||||||
|
{cards.map(({ label, value }) => (
|
||||||
|
<Box key={label} flex="1 1 200px">
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Typography color="textSecondary" gutterBottom>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h5">{value}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
14
src/resources/events/EventEdit.tsx
Normal file
14
src/resources/events/EventEdit.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edit, SimpleForm, SelectInput, required } from 'react-admin';
|
||||||
|
|
||||||
|
export const EventEdit = () => (
|
||||||
|
<Edit>
|
||||||
|
<SimpleForm>
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'active', name: 'Active' },
|
||||||
|
{ id: 'cancelled', name: 'Cancelled' },
|
||||||
|
{ id: 'completed', name: 'Completed' },
|
||||||
|
]} validate={required()} />
|
||||||
|
</SimpleForm>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
21
src/resources/events/EventList.tsx
Normal file
21
src/resources/events/EventList.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, DateField, EditButton, TextInput, SelectInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const EventList = () => (
|
||||||
|
<List filters={[
|
||||||
|
<TextInput source="title" alwaysOn />,
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'active', name: 'Active' },
|
||||||
|
{ id: 'cancelled', name: 'Cancelled' },
|
||||||
|
{ id: 'completed', name: 'Completed' },
|
||||||
|
]} alwaysOn />,
|
||||||
|
]}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="id" />
|
||||||
|
<TextField source="title" />
|
||||||
|
<TextField source="status" />
|
||||||
|
<DateField source="created_at" />
|
||||||
|
<EditButton />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
2
src/resources/events/index.ts
Normal file
2
src/resources/events/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { EventList } from './EventList';
|
||||||
|
export { EventEdit } from './EventEdit';
|
||||||
14
src/resources/reports/ReportEdit.tsx
Normal file
14
src/resources/reports/ReportEdit.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edit, SimpleForm, SelectInput, required } from 'react-admin';
|
||||||
|
|
||||||
|
export const ReportEdit = () => (
|
||||||
|
<Edit>
|
||||||
|
<SimpleForm>
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'pending', name: 'Pending' },
|
||||||
|
{ id: 'reviewed', name: 'Reviewed' },
|
||||||
|
{ id: 'dismissed', name: 'Dismissed' },
|
||||||
|
]} validate={required()} />
|
||||||
|
</SimpleForm>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
22
src/resources/reports/ReportList.tsx
Normal file
22
src/resources/reports/ReportList.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, DateField, EditButton, SelectInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const ReportList = () => (
|
||||||
|
<List filters={[
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'pending', name: 'Pending' },
|
||||||
|
{ id: 'reviewed', name: 'Reviewed' },
|
||||||
|
{ id: 'dismissed', name: 'Dismissed' },
|
||||||
|
]} alwaysOn />,
|
||||||
|
]}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="id" />
|
||||||
|
<TextField source="target_type" />
|
||||||
|
<TextField source="target_id" />
|
||||||
|
<TextField source="reason" />
|
||||||
|
<TextField source="status" />
|
||||||
|
<DateField source="created_at" />
|
||||||
|
<EditButton />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
2
src/resources/reports/index.ts
Normal file
2
src/resources/reports/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { ReportList } from './ReportList';
|
||||||
|
export { ReportEdit } from './ReportEdit';
|
||||||
14
src/resources/reviews/ReviewEdit.tsx
Normal file
14
src/resources/reviews/ReviewEdit.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edit, SimpleForm, SelectInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const ReviewEdit = () => (
|
||||||
|
<Edit>
|
||||||
|
<SimpleForm>
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'visible', name: 'Visible' },
|
||||||
|
{ id: 'hidden', name: 'Hidden' },
|
||||||
|
{ id: 'deleted', name: 'Deleted' },
|
||||||
|
]} />
|
||||||
|
</SimpleForm>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
17
src/resources/reviews/ReviewList.tsx
Normal file
17
src/resources/reviews/ReviewList.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, DateField, EditButton, TextInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const ReviewList = () => (
|
||||||
|
<List filters={[<TextInput source="target_id" alwaysOn />]}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="id" />
|
||||||
|
<TextField source="user_id" />
|
||||||
|
<TextField source="target_type" />
|
||||||
|
<TextField source="target_id" />
|
||||||
|
<TextField source="rating" />
|
||||||
|
<TextField source="status" />
|
||||||
|
<DateField source="created_at" />
|
||||||
|
<EditButton />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
2
src/resources/reviews/index.ts
Normal file
2
src/resources/reviews/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { ReviewList } from './ReviewList';
|
||||||
|
export { ReviewEdit } from './ReviewEdit';
|
||||||
15
src/resources/subscriptions/SubscriptionEdit.tsx
Normal file
15
src/resources/subscriptions/SubscriptionEdit.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edit, SimpleForm, TextInput, SelectInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const SubscriptionEdit = () => (
|
||||||
|
<Edit>
|
||||||
|
<SimpleForm>
|
||||||
|
<TextInput source="user_id" disabled />
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'active', name: 'Active' },
|
||||||
|
{ id: 'expired', name: 'Expired' },
|
||||||
|
{ id: 'cancelled', name: 'Cancelled' },
|
||||||
|
]} />
|
||||||
|
</SimpleForm>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
19
src/resources/subscriptions/SubscriptionList.tsx
Normal file
19
src/resources/subscriptions/SubscriptionList.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, DateField, EditButton, SelectInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const SubscriptionList = () => (
|
||||||
|
<List filters={[<SelectInput source="status" choices={[
|
||||||
|
{ id: 'active', name: 'Active' },
|
||||||
|
{ id: 'expired', name: 'Expired' },
|
||||||
|
{ id: 'cancelled', name: 'Cancelled' },
|
||||||
|
]} alwaysOn />]}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="user_id" />
|
||||||
|
<TextField source="plan" />
|
||||||
|
<TextField source="status" />
|
||||||
|
<DateField source="started_at" />
|
||||||
|
<DateField source="expires_at" />
|
||||||
|
<EditButton />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
2
src/resources/subscriptions/index.ts
Normal file
2
src/resources/subscriptions/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { SubscriptionList } from './SubscriptionList';
|
||||||
|
export { SubscriptionEdit } from './SubscriptionEdit';
|
||||||
18
src/resources/tickets/TicketEdit.tsx
Normal file
18
src/resources/tickets/TicketEdit.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edit, SimpleForm, TextInput, SelectInput, required } from 'react-admin';
|
||||||
|
|
||||||
|
export const TicketEdit = () => (
|
||||||
|
<Edit>
|
||||||
|
<SimpleForm>
|
||||||
|
<TextInput source="error_message" disabled />
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'open', name: 'Open' },
|
||||||
|
{ id: 'in_progress', name: 'In Progress' },
|
||||||
|
{ id: 'resolved', name: 'Resolved' },
|
||||||
|
{ id: 'closed', name: 'Closed' },
|
||||||
|
]} validate={required()} />
|
||||||
|
<TextInput source="assigned_to" />
|
||||||
|
<TextInput source="resolution_note" multiline />
|
||||||
|
</SimpleForm>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
22
src/resources/tickets/TicketList.tsx
Normal file
22
src/resources/tickets/TicketList.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, NumberField, DateField, EditButton, SelectInput } from 'react-admin';
|
||||||
|
|
||||||
|
export const TicketList = () => (
|
||||||
|
<List filters={[
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'open', name: 'Open' },
|
||||||
|
{ id: 'in_progress', name: 'In Progress' },
|
||||||
|
{ id: 'resolved', name: 'Resolved' },
|
||||||
|
{ id: 'closed', name: 'Closed' },
|
||||||
|
]} alwaysOn />,
|
||||||
|
]}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="id" />
|
||||||
|
<TextField source="error_message" />
|
||||||
|
<NumberField source="count" />
|
||||||
|
<TextField source="status" />
|
||||||
|
<DateField source="last_seen" />
|
||||||
|
<EditButton />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
2
src/resources/tickets/index.ts
Normal file
2
src/resources/tickets/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { TicketList } from './TicketList';
|
||||||
|
export { TicketEdit } from './TicketEdit';
|
||||||
15
src/resources/users/UserEdit.tsx
Normal file
15
src/resources/users/UserEdit.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edit, SimpleForm, TextInput, SelectInput, required } from 'react-admin';
|
||||||
|
|
||||||
|
export const UserEdit = () => (
|
||||||
|
<Edit>
|
||||||
|
<SimpleForm>
|
||||||
|
<TextInput source="email" disabled />
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'active', name: 'Active' },
|
||||||
|
{ id: 'blocked', name: 'Blocked' },
|
||||||
|
{ id: 'deleted', name: 'Deleted' },
|
||||||
|
]} validate={required()} />
|
||||||
|
</SimpleForm>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
24
src/resources/users/UserList.tsx
Normal file
24
src/resources/users/UserList.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List, Datagrid, TextField, EmailField, DateField, EditButton, TextInput, SelectInput } from 'react-admin';
|
||||||
|
|
||||||
|
const userFilters = [
|
||||||
|
<TextInput source="email" label="Email" alwaysOn />,
|
||||||
|
<SelectInput source="status" choices={[
|
||||||
|
{ id: 'active', name: 'Active' },
|
||||||
|
{ id: 'blocked', name: 'Blocked' },
|
||||||
|
{ id: 'deleted', name: 'Deleted' },
|
||||||
|
]} alwaysOn />,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const UserList = () => (
|
||||||
|
<List filters={userFilters}>
|
||||||
|
<Datagrid rowClick="edit">
|
||||||
|
<TextField source="id" />
|
||||||
|
<EmailField source="email" />
|
||||||
|
<TextField source="role" />
|
||||||
|
<TextField source="status" />
|
||||||
|
<DateField source="created_at" />
|
||||||
|
<EditButton />
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
2
src/resources/users/index.ts
Normal file
2
src/resources/users/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { UserList } from './UserList';
|
||||||
|
export { UserEdit } from './UserEdit';
|
||||||
Reference in New Issue
Block a user