Browse Source

项目添加和编辑

master
YuJian920 3 years ago
parent
commit
ca24a2ff41
  1. 6
      src/components/IdSelect.tsx
  2. 93
      src/components/ProjectModal.tsx
  3. 4
      src/components/ProjectPopover.tsx
  4. 33
      src/hook/useProjectModal.ts
  5. 49
      src/hook/useProjects.ts
  6. 15
      src/hook/useSetUrlSearchParam.ts
  7. 25
      src/hook/useUrlQueryParams.ts
  8. 16
      src/pages/Home/index.tsx
  9. 9
      src/pages/ProjectList/List/index.tsx
  10. 4
      src/pages/ProjectList/index.tsx
  11. 21
      src/utils/index.ts

6
src/components/IdSelect.tsx

@ -7,8 +7,8 @@ type SelectProps = React.ComponentProps<typeof Select>; @@ -7,8 +7,8 @@ type SelectProps = React.ComponentProps<typeof Select>;
interface IdSelectProps
extends Omit<SelectProps, "value" | "onChange" | "options"> {
value: number | string | null | undefined;
onChange: (value?: number) => void;
value?: number | string | null | undefined;
onChange?: (value?: number) => void;
defaultOptionName?: string;
options?: { name: string; id: number }[];
}
@ -20,7 +20,7 @@ const IdSelect = (props: IdSelectProps) => { @@ -20,7 +20,7 @@ const IdSelect = (props: IdSelectProps) => {
return (
<Select
value={options?.length ? toNumber(value) : 0}
onChange={(value) => onChange(toNumber(value) || undefined)}
onChange={(value) => onChange?.(toNumber(value) || undefined)}
{...restProps}
>
{defaultOptionName ? (

93
src/components/ProjectModal.tsx

@ -1,20 +1,93 @@ @@ -1,20 +1,93 @@
import React from "react";
import { Button, Drawer } from "antd";
import React, { useEffect } from "react";
import { Button, Drawer, Form, Input, Spin, Typography } from "antd";
import useProjectModal from "../hook/useProjectModal";
import UserSelect from "./UserSelect";
import { useAddProject, useEditProject } from "../hook/useProjects";
import { useForm } from "antd/es/form/Form";
import styled from "@emotion/styled";
const ProjectModal = () => {
const { projectModalOpen, close, editeingProject, isLoading } =
useProjectModal();
const useMutateProject = editeingProject ? useEditProject : useAddProject;
const { mutateAsync, error, isLoading: mutateLoading } = useMutateProject();
const [form] = useForm();
const onFinish = (values: any) => {
mutateAsync({ ...editeingProject, ...values }).then(() => {
form.resetFields();
close();
});
};
const title = editeingProject ? "编辑项目" : "创建项目";
useEffect(() => {
form.setFieldsValue(editeingProject);
}, [editeingProject, form]);
const ProjectModal = (props: {
projectModalOpen: boolean;
onClose: () => void;
}) => {
return (
<Drawer
onClose={props.onClose}
visible={props.projectModalOpen}
forceRender={true}
onClose={close}
visible={projectModalOpen}
width="100%"
>
<h1>Project Modal</h1>
<Button onClick={props.onClose}></Button>
<Container>
{isLoading ? (
<Spin size="large" />
) : (
<>
<h1>{title}</h1>
{/* {error ? (
<Typography.Text type="danger">{error.message}</Typography.Text>
) : null} */}
<Form
form={form}
layout="vertical"
style={{ width: "40rem" }}
onFinish={onFinish}
>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: "请输入项目名" }]}
>
<Input placeholder="请输入项目名称" />
</Form.Item>
<Form.Item
label="部门"
name="organization"
rules={[{ required: true, message: "请输入部门名" }]}
>
<Input placeholder="请输入部门名称" />
</Form.Item>
<Form.Item label="负责人" name="personId">
<UserSelect defaultOptionName="负责人" />
</Form.Item>
<Form.Item style={{ textAlign: 'right' }}>
<Button
loading={mutateLoading}
type="primary"
htmlType="submit"
>
</Button>
</Form.Item>
</Form>
</>
)}
</Container>
</Drawer>
);
};
export default React.memo(ProjectModal);
const Container = styled.div`
height: 80vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;

4
src/components/ProjectPopover.tsx

@ -2,9 +2,11 @@ import React from "react"; @@ -2,9 +2,11 @@ import React from "react";
import { Button, Divider, List, Popover, Typography } from "antd";
import { useProjects } from "../hook/useProjects";
import styled from "@emotion/styled";
import useProjectModal from "../hook/useProjectModal";
const ProjectPopover = () => {
const { data: projects, isLoading } = useProjects();
const { open } = useProjectModal();
const pinnedProjects = projects?.filter((projects) => projects.pin);
const content = (
@ -18,7 +20,7 @@ const ProjectPopover = () => { @@ -18,7 +20,7 @@ const ProjectPopover = () => {
))}
</List>
<Divider />
<Button style={{ padding: 0 }} type="link">
<Button onClick={open} style={{ padding: 0 }} type="link">
</Button>
</ContentContainer>

33
src/hook/useProjectModal.ts

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
import { useProject } from "./useProjects";
import useSetUrlSearchParam from "./useSetUrlSearchParam";
import useUrlQueryParams from "./useUrlQueryParams";
const useProjectModal = () => {
const [{ projectCreate }, setProjectCreate] = useUrlQueryParams([
"projectCreate",
]);
const [{ editeingProjectId }, setEditingProjectId] = useUrlQueryParams([
"editeingProjectId",
]);
const setUrlParams = useSetUrlSearchParam();
const { data: editeingProject, isLoading } = useProject(
Number(editeingProjectId)
);
const open = () => setProjectCreate({ projectCreate: "true" });
const close = () =>
setUrlParams({ projectCreate: undefined, editeingProjectId: undefined });
const startEdit = (id: number) =>
setEditingProjectId({ editeingProjectId: id });
return {
projectModalOpen: projectCreate === "true" || Boolean(editeingProject),
open,
close,
startEdit,
editeingProject,
isLoading,
};
};
export default useProjectModal;

49
src/hook/useProjects.ts

@ -1,38 +1,45 @@ @@ -1,38 +1,45 @@
import { useEffect } from "react";
import { Project } from "../type";
import { cleanObject } from "../utils";
import useRequest from "./useRequest";
import useAsync from "./useAsync";
import { useMutation, useQuery, useQueryClient } from "react-query";
const useProjects = (param?: Partial<Project>) => {
const request = useRequest();
const { run, ...result } = useAsync<Project[]>();
useEffect(() => {
run(request("/projects", { data: cleanObject(param || {}) }));
}, [param]);
return result;
return useQuery<Project[], Error>(["projects", param], () =>
request("/projects", { data: param })
);
};
const useEditProject = () => {
const { run, ...asyncResult } = useAsync();
const request = useRequest();
const mutate = (params: Partial<Project>) => {
return run(request(`/projects/${params.id}`, { data: params, method: "PATCH" }));
};
return { mutate, ...asyncResult };
const queryClient = useQueryClient();
return useMutation(
(params: Partial<Project>) =>
request(`/projects/${params.id}`, { method: "PATCH", data: params }),
{ onSuccess: () => queryClient.invalidateQueries("projects") }
);
};
const useAddProject = () => {
const { run, ...asyncResult } = useAsync();
const request = useRequest();
const mutate = (params: Partial<Project>) => {
return run(request(`/projects/${params.id}`, { data: params, method: "POST" }));
};
const queryClient = useQueryClient();
return { mutate, ...asyncResult };
return useMutation(
(params: Partial<Project>) =>
request(`/projects`, { method: "POST", data: params }),
{ onSuccess: () => queryClient.invalidateQueries("projects") }
);
};
const useProject = (id?: number) => {
const request = useRequest();
return (
useQuery<Project>(
["project", { id }],
() => request(`/projects/${id}`, {}),
{ enabled: !!id }
)
);
};
export { useProjects, useEditProject, useAddProject };
export { useProjects, useEditProject, useAddProject, useProject };

15
src/hook/useSetUrlSearchParam.ts

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
import { URLSearchParamsInit, useSearchParams } from "react-router-dom";
import { cleanObject } from "../utils";
const useSetUrlSearchParam = () => {
const [searchParams, setSearchParam] = useSearchParams();
return (params: { [key in string]: unknown }) => {
const o = cleanObject({
...Object.fromEntries(searchParams),
...params,
}) as URLSearchParamsInit;
return setSearchParam(o);
};
};
export default useSetUrlSearchParam;

25
src/hook/useUrlQueryParams.ts

@ -1,24 +1,23 @@ @@ -1,24 +1,23 @@
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { URLSearchParamsInit, useSearchParams } from "react-router-dom";
import { cleanObject } from "../utils";
import { cleanObject, subset } from "../utils";
import useSetUrlSearchParam from "./useSetUrlSearchParam";
const useUrlQueryParams = <K extends string>(keys: K[]) => {
const [searchParams, setSearchParams] = useSearchParams();
const [searchParams] = useSearchParams();
const setSearchParams = useSetUrlSearchParam();
const [stateKeys] = useState(keys);
return [
useMemo(
() =>
keys.reduce(
(prev, key) => ({
...prev,
[key]: searchParams.get(key) || "",
}),
{} as { [key in K]: string }
),
[searchParams]
subset(Object.fromEntries(searchParams), stateKeys) as {
[key in K]: string;
},
[searchParams, stateKeys]
),
(params: Partial<{ [key in K]: unknown }>) => {
const newParams = cleanObject({ ...Object.fromEntries(searchParams), ...params }) as URLSearchParamsInit
setSearchParams(newParams)
return setSearchParams(params);
},
] as const;
};

16
src/pages/Home/index.tsx

@ -12,23 +12,19 @@ import ProjectModal from "../../components/ProjectModal"; @@ -12,23 +12,19 @@ import ProjectModal from "../../components/ProjectModal";
import ProjectPopover from "../../components/ProjectPopover";
const Home = () => {
const [projectModalOpen, setProjectModalOpen] = useState(false);
return (
<Container>
<PageHeader />
<Main>
<BrowserRouter>
<BrowserRouter>
<PageHeader />
<Main>
<Routes>
<Route path="/projects" element={<ProjectList />} />
<Route path="/projects/:projectId/*" element={<Project />} />
<Route path="*" element={<Navigate to="/projects" />} />
</Routes>
</BrowserRouter>
</Main>
<ProjectModal
projectModalOpen={projectModalOpen}
onClose={() => setProjectModalOpen(false)}
/>
</Main>
<ProjectModal />
</BrowserRouter>
</Container>
);
};

9
src/pages/ProjectList/List/index.tsx

@ -5,13 +5,17 @@ import dayjs from "dayjs"; @@ -5,13 +5,17 @@ import dayjs from "dayjs";
import { Project, User } from "../../../type";
import Pin from "../../../components/Pin";
import { useEditProject } from "../../../hook/useProjects";
import useProjectModal from "../../../hook/useProjectModal";
interface ListProps extends TableProps<Project> {
users: User[];
}
const ProjectList = ({ users, ...props }: ListProps) => {
const { mutate } = useEditProject();
const { startEdit } = useProjectModal()
const pinProject = (id: number) => (pin: boolean) => mutate({ id, pin });
const editProject = (id: number) => startEdit(id);
return (
<Table
pagination={false}
@ -67,9 +71,8 @@ const ProjectList = ({ users, ...props }: ListProps) => { @@ -67,9 +71,8 @@ const ProjectList = ({ users, ...props }: ListProps) => {
<Dropdown
overlay={
<Menu>
<Menu.Item key="edit">
<Button type="link" style={{ padding: 0 }}></Button>
</Menu.Item>
<Menu.Item onClick={() => editProject(project.id)} key="edit"></Menu.Item>
<Menu.Item onClick={() => {}} key="delete"></Menu.Item>
</Menu>
}
>

4
src/pages/ProjectList/index.tsx

@ -9,11 +9,13 @@ import useUsers from "../../hook/useUsers"; @@ -9,11 +9,13 @@ import useUsers from "../../hook/useUsers";
import useDocumentTitle from "../../hook/useDocumentTitle";
import useProjectsSearchParams from "../../hook/useProjectsSearchParams";
import { Row } from "../../components/lib";
import useProjectModal from "../../hook/useProjectModal";
const ProjectList = () => {
useDocumentTitle("项目列表", false);
const [params, setParam] = useProjectsSearchParams();
const { open } = useProjectModal();
const {
isLoading,
error,
@ -25,7 +27,7 @@ const ProjectList = () => { @@ -25,7 +27,7 @@ const ProjectList = () => {
<Container>
<Row between={true}>
<h1></h1>
<Button></Button>
<Button onClick={open}></Button>
</Row>
<SearchPanel users={users || []} param={params} setParam={setParam} />
{error ? (

21
src/utils/index.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
export const isFalsy = (value: unknown) => (value === 0 ? false : !value);
export const isVoid = (value: unknown) => value === undefined || value === null || value === "";
export const isVoid = (value: unknown) =>
value === undefined || value === null || value === "";
// 筛选对象空值
export const cleanObject = (object: { [key: string]: unknown }) => {
@ -14,4 +15,22 @@ export const cleanObject = (object: { [key: string]: unknown }) => { @@ -14,4 +15,22 @@ export const cleanObject = (object: { [key: string]: unknown }) => {
return result;
};
/**
*
* @param obj
* @param keys
*/
export const subset = <
O extends { [key in string]: unknown },
K extends keyof O
>(
obj: O,
keys: K[]
) => {
const filteredEntries = Object.entries(obj).filter(([key]) =>
keys.includes(key as K)
);
return Object.fromEntries(filteredEntries) as Pick<O, K>;
};
export const resetRoute = () => (window.location.href = window.location.origin);

Loading…
Cancel
Save