init refine
app
This commit is contained in:
120
src/pages/blog-posts/create.tsx
Normal file
120
src/pages/blog-posts/create.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import { Autocomplete, Box, MenuItem, Select, TextField } from "@mui/material";
|
||||
import { Create, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import React from "react";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
export const BlogPostCreate = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
const { autocompleteProps: categoryAutocompleteProps } = useAutocomplete({
|
||||
resource: "categories",
|
||||
});
|
||||
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("title", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.title}
|
||||
helperText={(errors as any)?.title?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Title"}
|
||||
name="title"
|
||||
/>
|
||||
<TextField
|
||||
{...register("content", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.content}
|
||||
helperText={(errors as any)?.content?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
multiline
|
||||
label={"Content"}
|
||||
name="content"
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"category.id"}
|
||||
rules={{ required: "This field is required" }}
|
||||
// eslint-disable-next-line
|
||||
defaultValue={null as any}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...categoryAutocompleteProps}
|
||||
{...field}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value.id);
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return (
|
||||
categoryAutocompleteProps?.options?.find((p) => {
|
||||
const itemId =
|
||||
typeof item === "object"
|
||||
? item?.id?.toString()
|
||||
: item?.toString();
|
||||
const pId = p?.id?.toString();
|
||||
return itemId === pId;
|
||||
})?.title ?? ""
|
||||
);
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
const optionId = option?.id?.toString();
|
||||
const valueId =
|
||||
typeof value === "object"
|
||||
? value?.id?.toString()
|
||||
: value?.toString();
|
||||
return value === undefined || optionId === valueId;
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={"Category"}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!(errors as any)?.category?.id}
|
||||
helperText={(errors as any)?.category?.id?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="status"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Select
|
||||
{...field}
|
||||
value={field?.value || "draft"}
|
||||
label={"Status"}
|
||||
>
|
||||
<MenuItem value="draft">Draft</MenuItem>
|
||||
<MenuItem value="published">Published</MenuItem>
|
||||
<MenuItem value="rejected">Rejected</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Create>
|
||||
);
|
||||
};
|
125
src/pages/blog-posts/edit.tsx
Normal file
125
src/pages/blog-posts/edit.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import { Autocomplete, Box, Select, TextField } from "@mui/material";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import React from "react";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
export const BlogPostEdit = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { queryResult, formLoading },
|
||||
register,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
const blogPostsData = queryResult?.data?.data;
|
||||
|
||||
const { autocompleteProps: categoryAutocompleteProps } = useAutocomplete({
|
||||
resource: "categories",
|
||||
defaultValue: blogPostsData?.category?.id,
|
||||
});
|
||||
|
||||
return (
|
||||
<Edit isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("title", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.title}
|
||||
helperText={(errors as any)?.title?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Title"}
|
||||
name="title"
|
||||
/>
|
||||
<TextField
|
||||
{...register("content", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.content}
|
||||
helperText={(errors as any)?.content?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
multiline
|
||||
label={"Content"}
|
||||
name="content"
|
||||
rows={4}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"category.id"}
|
||||
rules={{ required: "This field is required" }}
|
||||
// eslint-disable-next-line
|
||||
defaultValue={null as any}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...categoryAutocompleteProps}
|
||||
{...field}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value.id);
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return (
|
||||
categoryAutocompleteProps?.options?.find((p) => {
|
||||
const itemId =
|
||||
typeof item === "object"
|
||||
? item?.id?.toString()
|
||||
: item?.toString();
|
||||
const pId = p?.id?.toString();
|
||||
return itemId === pId;
|
||||
})?.title ?? ""
|
||||
);
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
const optionId = option?.id?.toString();
|
||||
const valueId =
|
||||
typeof value === "object"
|
||||
? value?.id?.toString()
|
||||
: value?.toString();
|
||||
return value === undefined || optionId === valueId;
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={"Category"}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!(errors as any)?.category?.id}
|
||||
helperText={(errors as any)?.category?.id?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="status"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Select
|
||||
{...field}
|
||||
value={field?.value || "draft"}
|
||||
label={"Status"}
|
||||
>
|
||||
<MenuItem value="draft">Draft</MenuItem>
|
||||
<MenuItem value="published">Published</MenuItem>
|
||||
<MenuItem value="rejected">Rejected</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Edit>
|
||||
);
|
||||
};
|
4
src/pages/blog-posts/index.ts
Normal file
4
src/pages/blog-posts/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./create";
|
||||
export * from "./edit";
|
||||
export * from "./list";
|
||||
export * from "./show";
|
124
src/pages/blog-posts/list.tsx
Normal file
124
src/pages/blog-posts/list.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import { DataGrid, type GridColDef } from "@mui/x-data-grid";
|
||||
import { useMany } from "@refinedev/core";
|
||||
import {
|
||||
DateField,
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import { Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
export const BlogPostList = () => {
|
||||
const { dataGridProps } = useDataGrid({});
|
||||
|
||||
const { data: categoryData, isLoading: categoryIsLoading } = useMany({
|
||||
resource: "categories",
|
||||
ids:
|
||||
dataGridProps?.rows
|
||||
?.map((item: any) => item?.category?.id)
|
||||
.filter(Boolean) ?? [],
|
||||
queryOptions: {
|
||||
enabled: !!dataGridProps?.rows,
|
||||
},
|
||||
});
|
||||
|
||||
const columns = React.useMemo<GridColDef[]>(
|
||||
() => [
|
||||
{
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "number",
|
||||
minWidth: 50,
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: "title",
|
||||
headerName: "Title",
|
||||
minWidth: 200,
|
||||
display: "flex",
|
||||
},
|
||||
{
|
||||
field: "content",
|
||||
flex: 1,
|
||||
headerName: "Content",
|
||||
minWidth: 250,
|
||||
display: "flex",
|
||||
renderCell: function render({ value }) {
|
||||
if (!value) return "-";
|
||||
return (
|
||||
<Typography
|
||||
component="p"
|
||||
whiteSpace="pre"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "category",
|
||||
headerName: "Category",
|
||||
minWidth: 160,
|
||||
display: "flex",
|
||||
valueGetter: (_, row) => {
|
||||
const value = row?.category;
|
||||
return value;
|
||||
},
|
||||
renderCell: function render({ value }) {
|
||||
return categoryIsLoading ? (
|
||||
<>Loading...</>
|
||||
) : (
|
||||
categoryData?.data?.find((item) => item.id === value?.id)?.title
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "status",
|
||||
headerName: "Status",
|
||||
minWidth: 80,
|
||||
display: "flex",
|
||||
},
|
||||
{
|
||||
field: "createdAt",
|
||||
headerName: "Created at",
|
||||
minWidth: 120,
|
||||
display: "flex",
|
||||
renderCell: function render({ value }) {
|
||||
return <DateField value={value} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
align: "right",
|
||||
headerAlign: "right",
|
||||
minWidth: 120,
|
||||
sortable: false,
|
||||
display: "flex",
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<ShowButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText recordItemId={row.id} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[categoryData, categoryIsLoading]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<DataGrid {...dataGridProps} columns={columns} />
|
||||
</List>
|
||||
);
|
||||
};
|
59
src/pages/blog-posts/show.tsx
Normal file
59
src/pages/blog-posts/show.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useOne, useShow } from "@refinedev/core";
|
||||
import {
|
||||
DateField,
|
||||
MarkdownField,
|
||||
NumberField,
|
||||
Show,
|
||||
TextFieldComponent as TextField,
|
||||
} from "@refinedev/mui";
|
||||
|
||||
export const BlogPostShow = () => {
|
||||
const { query } = useShow({});
|
||||
|
||||
const { data, isLoading } = query;
|
||||
|
||||
const record = data?.data;
|
||||
|
||||
const { data: categoryData, isLoading: categoryIsLoading } = useOne({
|
||||
resource: "categories",
|
||||
id: record?.category?.id || "",
|
||||
queryOptions: {
|
||||
enabled: !!record,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Show isLoading={isLoading}>
|
||||
<Stack gap={1}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"ID"}
|
||||
</Typography>
|
||||
<TextField value={record?.id} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Title"}
|
||||
</Typography>
|
||||
<TextField value={record?.title} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Content"}
|
||||
</Typography>
|
||||
<MarkdownField value={record?.content} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Category"}
|
||||
</Typography>
|
||||
{categoryIsLoading ? <>Loading...</> : <>{categoryData?.data?.title}</>}
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Status"}
|
||||
</Typography>
|
||||
<TextField value={record?.status} />
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"CreatedAt"}
|
||||
</Typography>
|
||||
<DateField value={record?.createdAt} />
|
||||
</Stack>
|
||||
</Show>
|
||||
);
|
||||
};
|
36
src/pages/categories/create.tsx
Normal file
36
src/pages/categories/create.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Box, TextField } from "@mui/material";
|
||||
import { Create } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
|
||||
export const CategoryCreate = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("title", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.title}
|
||||
helperText={(errors as any)?.title?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Title"}
|
||||
name="title"
|
||||
/>
|
||||
</Box>
|
||||
</Create>
|
||||
);
|
||||
};
|
35
src/pages/categories/edit.tsx
Normal file
35
src/pages/categories/edit.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { Box, TextField } from "@mui/material";
|
||||
import { Edit } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
|
||||
export const CategoryEdit = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
return (
|
||||
<Edit saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("title", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.title}
|
||||
helperText={(errors as any)?.title?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Title"}
|
||||
name="title"
|
||||
/>
|
||||
</Box>
|
||||
</Edit>
|
||||
);
|
||||
};
|
4
src/pages/categories/index.ts
Normal file
4
src/pages/categories/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./create";
|
||||
export * from "./edit";
|
||||
export * from "./list";
|
||||
export * from "./show";
|
59
src/pages/categories/list.tsx
Normal file
59
src/pages/categories/list.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { DataGrid, type GridColDef } from "@mui/x-data-grid";
|
||||
import {
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import React from "react";
|
||||
|
||||
export const CategoryList = () => {
|
||||
const { dataGridProps } = useDataGrid({});
|
||||
|
||||
const columns = React.useMemo<GridColDef[]>(
|
||||
() => [
|
||||
{
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "number",
|
||||
minWidth: 50,
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: "title",
|
||||
flex: 1,
|
||||
headerName: "Title",
|
||||
minWidth: 200,
|
||||
display: "flex",
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
align: "right",
|
||||
headerAlign: "right",
|
||||
minWidth: 120,
|
||||
sortable: false,
|
||||
display: "flex",
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<ShowButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText recordItemId={row.id} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<DataGrid {...dataGridProps} columns={columns} />
|
||||
</List>
|
||||
);
|
||||
};
|
29
src/pages/categories/show.tsx
Normal file
29
src/pages/categories/show.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useShow } from "@refinedev/core";
|
||||
import {
|
||||
NumberField,
|
||||
Show,
|
||||
TextFieldComponent as TextField,
|
||||
} from "@refinedev/mui";
|
||||
|
||||
export const CategoryShow = () => {
|
||||
const { query } = useShow({});
|
||||
const { data, isLoading } = query;
|
||||
|
||||
const record = data?.data;
|
||||
|
||||
return (
|
||||
<Show isLoading={isLoading}>
|
||||
<Stack gap={1}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"ID"}
|
||||
</Typography>
|
||||
<TextField value={record?.id} />
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Title"}
|
||||
</Typography>
|
||||
<TextField value={record?.title} />
|
||||
</Stack>
|
||||
</Show>
|
||||
);
|
||||
};
|
5
src/pages/forgotPassword/index.tsx
Normal file
5
src/pages/forgotPassword/index.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { AuthPage } from "@refinedev/mui";
|
||||
|
||||
export const ForgotPassword = () => {
|
||||
return <AuthPage type="forgotPassword" />;
|
||||
};
|
12
src/pages/login/index.tsx
Normal file
12
src/pages/login/index.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { AuthPage } from "@refinedev/mui";
|
||||
|
||||
export const Login = () => {
|
||||
return (
|
||||
<AuthPage
|
||||
type="login"
|
||||
formProps={{
|
||||
defaultValues: { email: "demo@refine.dev", password: "demodemo" },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
5
src/pages/register/index.tsx
Normal file
5
src/pages/register/index.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { AuthPage } from "@refinedev/mui";
|
||||
|
||||
export const Register = () => {
|
||||
return <AuthPage type="register" />;
|
||||
};
|
Reference in New Issue
Block a user