[Actual Combat] Eleven. Kanban page and task group page development (2) —— React17+React Hook+TS4 best practice, imitating Jira enterprise-level projects (24)


Source of learning content: React + React Hook + TS Best Practice - MOOC


Compared with the original tutorial, I used the latest version at the beginning of my study (2023.03):

item Version
react & react-dom ^18.2.0
react-router & react-router-dom ^6.11.2
antd ^4.24.8
@commitlint/cli & @commitlint/config-conventional ^17.4.4
eslint-config-prettier ^8.6.0
husky ^8.0.3
lint-staged ^13.1.2
prettier 2.8.4
json-server 0.17.2
craco-less ^2.0.0
@craco/craco ^7.1.0
qs ^6.11.0
dayjs ^1.11.7
react-helmet ^6.1.0
@types/react-helmet ^6.1.6
react-query ^6.1.0
@welldone-software/why-did-you-render ^7.0.1
@emotion/react & @emotion/styled ^11.10.6

The specific configuration, operation and content will be different, and the "pit" will also be different. . .


1. Project launch: project initialization and configuration

2. React and Hook application: implement the project list

3. TS Application: JS God Assist - Strong Type

4. JWT, user authentication and asynchronous request


5. CSS is actually very simple - add styles with CSS-in-JS


6. User experience optimization - loading and error state handling



7. Hook, routing, and URL state management



8. User selector and item editing function


9. In-depth React state management and Redux mechanism





10. Use react-query to get data and manage cache


11. Kanban page and task group page development

1~3

4. Add task search function

Next, add a search function to the task board

Edit src\screens\ViewBoard\utils.ts(added to prepare useTasksSearchParamsfor data linkage in the follow-up ):SearchPanel

import {
    
     useMemo } from "react";
import {
    
     useLocation } from "react-router";
import {
    
     useProject } from "utils/project";
import {
    
     useUrlQueryParam } from "utils/url";

...
export const useTasksSearchParams = () => {
    
    
  const [param, setParam] = useUrlQueryParam([
    "name",
    "typeId",
    "processorId",
    "tagId",
  ]);
  const projectId = useProjectIdInUrl();
  return useMemo(
    () => ({
    
    
      projectId,
      typeId: Number(param.typeId) || undefined,
      processorId: Number(param.processorId) || undefined,
      tagId: Number(param.tagId) || undefined,
      name: param.name,
    }),
    [projectId, param]
  );
};
...

Create a new one src\components\task-type-select.tsx(modeled UserSelectafter transforming one TaskTypeSelect):

import {
    
     useTaskTypes } from "utils/task-type";
import {
    
     IdSelect } from "./id-select";

export const TaskTypeSelect = (props: React.ComponentProps<typeof IdSelect>) => {
    
    
  const {
    
     data: taskTypes } = useTaskTypes();
  return <IdSelect options={
    
    taskTypes || []} {
    
    ...props} />;
};

New src\screens\ViewBoard\components\SearchPanel.tsx:

import {
    
     useSetUrlSearchParam } from "utils/url"
import {
    
     useTasksSearchParams } from "../utils"
import {
    
     Row } from "components/lib"
import {
    
     Button, Input } from "antd"
import {
    
     UserSelect } from "components/user-select"
import {
    
     TaskTypeSelect } from "components/task-type-select"

export const SearchPanel = () => {
    
    
  const searchParams = useTasksSearchParams()
  const setSearchParams = useSetUrlSearchParam()
  const reset = () => {
    
    
    setSearchParams({
    
    
      typeId: undefined,
      processorId: undefined,
      tagId: undefined,
      name: undefined
    })
  }
  return <Row marginBottom={
    
    4} gap={
    
    true}>
    <Input style={
    
    {
    
    width: '20rem'}} placeholder='任务名' value={
    
    searchParams.name}
      onChange={
    
    e => setSearchParams({
    
    name: e.target.value})}/>
    <UserSelect defaultOptionName="经办人" value={
    
    searchParams.processorId}
      onChange={
    
    val => setSearchParams({
    
    processorId: val})}/>
    <TaskTypeSelect defaultOptionName="类型" value={
    
    searchParams.typeId}
      onChange={
    
    val => setSearchParams({
    
    typeId: val})}/>
    <Button onClick={
    
    reset}>清除筛选器</Button>
  </Row>
}

EDIT src\screens\ViewBoard\index.tsx(introduced SearchPanel):

...
import {
    
     SearchPanel } from "./components/SearchPanel";

export const ViewBoard = () => {
    
    
  ...
  return (
    <div>
      <h1>{
    
    currentProject?.name}看板</h1>
      <SearchPanel/>
      <ColumnsContainer>...</ColumnsContainer>
    </div>
  );
};
...

View functions and effects:
renderings

5. Optimize Kanban style

Part of the function is realized, and then the style is optimized

EDIT src\components\lib.tsx(added ViewContainerto handle padding):

export const ViewContainer = styled.div`
  padding: 3.2rem;
  width: 100%;
  display: flex;
  flex-direction: column;
`

EDIT src\authenticated-app.tsx(adjust Mainstyle, fill vertically):

...
const Main = styled.main`
  display: flex;
  /* overflow: hidden; */
`;

EDIT src\screens\ViewBoard\index.tsx(applied ViewContainer, added Loadingadjust ColumnsContainerstyle and exposed so it bottoms out):

...
import {
    
     useProjectInUrl, useTasksSearchParams, useViewBoardSearchParams } from "./utils";
...
import {
    
     ViewContainer } from "components/lib";
import {
    
     useTasks } from "utils/task";
import {
    
     Spin } from "antd";

export const ViewBoard = () => {
    
    
  ...
  const {
    
     data: viewboards, isLoading: viewBoardIsLoading } = useViewboards(useViewBoardSearchParams());
  const {
    
     isLoading: taskIsLoading } = useTasks(useTasksSearchParams())
  const isLoading = taskIsLoading || viewBoardIsLoading
  return (
    <ViewContainer>
      <h1>{
    
    currentProject?.name}看板</h1>
      <SearchPanel />
      {
    
    
        isLoading ? <Spin/> : <ColumnsContainer>
        ...
        </ColumnsContainer>
      }
    </ViewContainer>
  );
};

const ColumnsContainer = styled.div`
  display: flex;
  overflow-x: scroll;
  flex: 1;
`;

EDIT src\screens\ProjectDetail\index.tsx(introduced Menuand adjusted the entire component style, Menuhighlight state is taken from the route):

import {
    
     Link, Navigate } from "react-router-dom";
import {
    
     Route, Routes, useLocation } from "react-router";
import {
    
     TaskGroup } from "screens/TaskGroup";
import {
    
     ViewBoard } from "screens/ViewBoard";
import styled from "@emotion/styled";
import {
    
     Menu } from "antd";

const useRouteType = () => {
    
    
  const pathEnd = useLocation().pathname.split('/')
  return pathEnd[pathEnd.length - 1]
}

export const ProjectDetail = () => {
    
    
  const routeType = useRouteType()

  return (
    <Container>
      <Aside>
        <Menu mode="inline" selectedKeys={
    
    [routeType]}>
          <Menu.Item key='viewboard'>
            <Link to="viewboard">看板</Link>
          </Menu.Item>
          <Menu.Item key='taskgroup'>
            <Link to="taskgroup">任务组</Link>
          </Menu.Item>
        </Menu>
      </Aside>
      <Main>
        <Routes>
          <Route path="/viewboard" element={
    
    <ViewBoard />} />
          <Route path="/taskgroup" element={
    
    <TaskGroup />} />
          <Route index element={
    
    <Navigate to="viewboard" replace />} />
        </Routes>
      </Main>
    </Container>
  );
};

const Aside = styled.aside`
  background-color: rgb(244, 245, 247);
  display: flex;
`

const Main = styled.div`
  display: flex;
  box-shadow: -5px 0 5px -5px rgbs(0, 0, 0, 0.1);
  overflow: hidden;
`

const Container = styled.div`
  display: grid;
  grid-template-columns: 16rem 1fr;
  width: 100%;
`

View functions and effects:
renderings

6. Create boards and tasks

Next, create a new component that creates a kanban:

Get ready to call the newly added Kanban interface Hook, edit src\utils\viewboard.ts:

...
export const useAddViewboard = (queryKey: QueryKey) => {
    
    
  const client = useHttp();
  return useMutation(
    (params: Partial<Viewboard>) =>
      client(`kanbans`, {
    
    
        method: "POST",
        data: params,
      }),
    useAddConfig(queryKey)
  );
};

New component: src\screens\ViewBoard\components\CreateViewboard.tsx:

import {
    
     useState } from "react"
import {
    
     useProjectIdInUrl, useViewBoardQueryKey } from "../utils"
import {
    
     useAddViewboard } from "utils/viewboard"
import {
    
     Input } from "antd"
import {
    
     Container } from "./ViewboardCloumn"

export const CreateViewBoard = () => {
    
    
  const [name, setName] = useState('')
  const projectId = useProjectIdInUrl()
  const {
    
     mutateAsync: addViewBoard } = useAddViewboard(useViewBoardQueryKey())

  const submit = async () => {
    
    
    await addViewBoard({
    
    name, projectId})
    setName('')
  }

  return <Container>
    <Input
      size="large"
      placeholder="新建看板名称"
      onPressEnter={
    
    submit}
      value={
    
    name}
      onChange={
    
    evt => setName(evt.target.value)}
    />
  </Container>
}

EDIT: src\screens\ViewBoard\index.tsx(introduced CreateViewBoard):

...
import {
    
     CreateViewBoard } from "./components/CreateViewboard";

export const ViewBoard = () => {
    
    
  ...
  return (
    <ViewContainer>
      ...
      {
    
    
        isLoading ? <Spin/> : <ColumnsContainer>
          {
    
    viewboards?.map((vbd) => (
            <ViewboardColumn viewboard={
    
    vbd} key={
    
    vbd.id} />
          ))}
          <CreateViewBoard/>
        </ColumnsContainer>
      }
    </ViewContainer>
  );
};
...

To view the functions and effects, enter the name of the new board and press Enter to see the new board:
renderings

Next, create a new component that creates tasks:

Prepare to call the new task interface first Hook, edit src\utils\task.ts:

...
import {
    
     QueryKey, useMutation, useQuery } from "react-query";
import {
    
     useAddConfig } from "./use-optimistic-options";

...
export const useAddTask = (queryKey: QueryKey) => {
    
    
  const client = useHttp();
  return useMutation(
    (params: Partial<Task>) =>
      client(`tasks`, {
    
    
        method: "POST",
        data: params,
      }),
    useAddConfig(queryKey)
  );
};

New component: src\screens\ViewBoard\components\CreateTask.tsx:

import {
    
     useEffect, useState } from "react";
import {
    
     useProjectIdInUrl, useTasksQueryKey } from "../utils";
import {
    
     Card, Input } from "antd";
import {
    
     useAddTask } from "utils/task";

export const CreateTask = ({
     
     kanbanId}: {
     
     kanbanId: number}) => {
    
    
  const [name, setName] = useState("");
  const {
    
     mutateAsync: addTask } = useAddTask(useTasksQueryKey());
  const projectId = useProjectIdInUrl();
  const [inputMode, setInputMode] = useState(false)

  const submit = async () => {
    
    
    await addTask({
    
     name, projectId, kanbanId });
    setName("");
    setInputMode(false)
  };

  const toggle = () => setInputMode(mode => !mode)

  useEffect(() => {
    
    
    if (!inputMode) {
    
    
      setName('')
    }
  }, [inputMode])
  
  if (!inputMode) {
    
    
    return <div onClick={
    
    toggle}>+创建任务</div>
  }

  return (
    <Card>
      <Input
        onBlur={
    
    toggle}
        placeholder="需要做些什么"
        autoFocus={
    
    true}
        onPressEnter={
    
    submit}
        value={
    
    name}
        onChange={
    
    (evt) => setName(evt.target.value)}
      />
    </Card>
  );
};

EDIT: src\screens\ViewBoard\components\ViewboardCloumn.tsx(introduced CreateTask):

...
import {
    
     CreateTask } from "./CreateTask";

...
export const ViewboardColumn = ({
     
      viewboard }: {
     
      viewboard: Viewboard }) => {
    
    
  ...
  return (
    <Container>
      <h3>{
    
    viewboard.name}</h3>
      <TasksContainer>
        ...
        <CreateTask kanbanId={
    
    viewboard.id}/>
      </TasksContainer>
    </Container>
  );
};
...

Check the functions and effects, click +创建任务the input box to appear, click outside the input box to hide the input box, enter the new task name and press Enter, you can see the new task:
renderings


Some reference notes are still in draft stage, so stay tuned. . .

Guess you like

Origin blog.csdn.net/qq_32682301/article/details/132378725