DEV Community

Cover image for Create Your Easy Customizable Internal Issue Tracker With Refine and Supabase
Melih Ekinci for Pankod

Posted on • Updated on • Originally published at refine.dev

Create Your Easy Customizable Internal Issue Tracker With Refine and Supabase

In this article, we will make a project management web application with supabase and refine.

This web application will us to create issue and tasks for your team members. You will also be able to choose the priority of these tasks, their tags, and which person to assign them to.

We will use Supabase in backend. Let's start by creating our Supabase account and tables.

Create Supabase Database

We have to go to Supabase and create an organization and database. Then we can start creating our tables.

Image description

The tables we need to create for our project are as follows:

label

  • id: bigint
  • title: varchar
  • color: varchar

Image description

priority

  • id bigint
  • title varchar

Image description

status

  • id bigint
  • title varchar

Image description

users

  • email varchar
  • id uuid

Image description

tasks

  • id bigint
  • title varchar
  • description varchar
  • start_time date
  • end_time date
  • label bigint
  • priority bigint
  • statuts bigint
  • users uuid

Image description

We created our database tables. The important part here is that as you can see, in our tasks table, label, priority, status and users values ​​are defined as bigint. To summarize the reason for this, we relation the label, priority, status and users tables that we created above with the corresponding values ​​in our tasks table.

πŸ’‘ TIP: Add Foreign key relationships steps:
1.Got to a table, right click the column you wish to add a foreign key for
2.Select 'edit column'
3.Select 'Add foreign key relation'
4.Select the table and column to make a foreign key for
5.Click save

So we can take reference their id and use their value in our tasks table.

Now let's go to the Supabase Table editor and create our constant values.

Label Table

Image description

Priority Table

Image description

Status Table

Image description

Let's create a test task to better understand key relation.

Image description

As you can see, since we have defined the ids of our label, status, priority and users tables as references to our tasks table, we can now use their values.

Let's look at how we can use this data we created on the Supabase side with refine .

Refine Project Setup

Now let's refine the task-manager panel. With superplate, we can quickly create a refine project

  npx superplate-cli refine-task-manager
Enter fullscreen mode Exit fullscreen mode
βœ” Select your project type β€Ί refine
βœ” What will be the name of your app Β· refine-task-manager
βœ” Package manager: Β· npm
βœ” Do you want to customize theme?: Β· css
βœ” Data Provider: Β· supabase-data-provider
βœ” Do you want to customize layout? Β· custom-layout
βœ” i18n - Internationalization: Β· no
Enter fullscreen mode Exit fullscreen mode

After the project setup is loaded, let's start by entering our project and making the necessary changes.

Let's add our supabase url and key to our refine project.

src/utility/supabaseClient.ts

import { createClient } from "@pankod/refine-supabase";

const SUPABASE_URL = "YOUR_SUPABASE_URL";
const SUPABASE_KEY = "YOUR_SUPABASE_API_KEY";

export const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY);
Enter fullscreen mode Exit fullscreen mode

Now we can access and list the tables we created via the supabase.

  • Add custom login and signup page in App.tsx

Our purpose here is to log in if there is a registered user in the supabase. If you do not have a registered user, saving a user to the supabase with refine.

Custom Login Page

import React from "react";
import {
  Row,
  Col,
  AntdLayout,
  Card,
  Typography,
  Form,
  Input,
  Button,
  useLogin,
  useNavigation,
} from "@pankod/refine";

import "./styles.css";

const { Text, Title } = Typography;

export interface ILoginForm {
  email: string;
  password: string;
}

export const Login: React.FC = () => {
  const [form] = Form.useForm<ILoginForm>();

  const { mutate: login } = useLogin<ILoginForm>();
  const { push } = useNavigation();

  const CardTitle = (
    <Title level={3} className="title">
      Sign in your account
    </Title>
  );

  return (
    <AntdLayout className="layout">
      <Row
        justify="center"
        align="middle"
        style={{
          height: "100vh",
        }}
      >
        <Col xs={22}>
          <div className="container">
            <div className="imageContainer">
              <img src="./refine.svg" alt="Refine Logo" />
            </div>
            <Card title={CardTitle} headStyle={{ borderBottom: 0 }}>
              <Form<ILoginForm>
                layout="vertical"
                form={form}
                onFinish={(values) => {
                  login(values);
                }}
                initialValues={{
                  email: "info@refine.dev",
                  password: "refine-supabase",
                }}
              >
                <Form.Item
                  name="email"
                  label="Email"
                  rules={[{ required: true, type: "email" }]}
                >
                  <Input size="large" placeholder="Email" />
                </Form.Item>
                <Form.Item
                  name="password"
                  label="Password"
                  rules={[{ required: true }]}
                  style={{ marginBottom: "12px" }}
                >
                  <Input type="password" placeholder="●●●●●●●●" size="large" />
                </Form.Item>
                <Button type="primary" size="large" htmlType="submit" block>
                  Sign in
                </Button>
              </Form>
              <div style={{ marginTop: 8 }}>
                <Text style={{ fontSize: 12 }}>
                  Don’t have an account?
                  <a
                    href="#"
                    style={{ fontWeight: "bold", marginLeft:  12 }}
                    onClick={() => push("signup")}
                  >
                    Sign up
                  </a>
                </Text>
              </div>
            </Card>
          </div>
        </Col>
      </Row>
    </AntdLayout>
  );
};

Enter fullscreen mode Exit fullscreen mode

Custom Sign Up Page

import React from "react";
import {
  Row,
  Col,
  AntdLayout,
  Card,
  Typography,
  Form,
  Input,
  Button,
  useNavigation,
} from "@pankod/refine";
import "./styles.css";
import { supabaseClient } from "utility";

const { Text, Title } = Typography;

export interface ISignup {
  email: string;
  password: string;
}

export const Signup: React.FC = () => {
  const [form] = Form.useForm<ISignup>();
  const { push } = useNavigation();

  const CardTitle = (
    <Title level={3} className="title">
      Sign Up
    </Title>
  );

  const signup = async (email: string, password: string) => {
    const { error } = await supabaseClient.auth.signUp({
      email,
      password,
    });

    if (error) {
      return Promise.reject(error);
    }
  };

  return (
    <AntdLayout className="layout">
      <Row
        justify="center"
        align="middle"
        style={{
          height: "100vh",
        }}
      >
        <Col xs={22}>
          <div className="container">
            <div className="imageContainer">
              <img src="./refine.svg" alt="Refine Logo" />
            </div>
            <Card title={CardTitle} headStyle={{ borderBottom: 0 }}>
              <Form<ISignup>
                layout="vertical"
                form={form}
                onFinish={(values) => {
                  signup(values.email, values.password);
                  push("login");
                }}
              >
                <Form.Item
                  name="email"
                  label="Email"
                  rules={[{ required: true, type: "email" }]}
                >
                  <Input size="large" placeholder="Email" />
                </Form.Item>
                <Form.Item
                  name="password"
                  label="Password"
                  rules={[{ required: true }]}
                  style={{ marginBottom: "12px" }}
                >
                  <Input type="password" placeholder="●●●●●●●●" size="large" />
                </Form.Item>
                <Button
                  type="primary"
                  size="large"
                  htmlType="submit"
                  block
                  style={{ marginTop: 24 }}
                >
                  Create Account
                </Button>
              </Form>
              <div style={{ marginTop: 16 }}>
                <Text style={{ fontSize: 12 }}>
                  Don’t have an account?
                  <a
                    href="#"
                    style={{ fontWeight: "bold" }}
                    onClick={() => push("login")}
                  >
                    Sign in
                  </a>
                </Text>
              </div>
            </Card>
          </div>
        </Col>
      </Row>
    </AntdLayout>
  );
};

Enter fullscreen mode Exit fullscreen mode
  • App.tsx
import { Refine } from "@pankod/refine";
import routerProvider from "@pankod/refine-react-router";
import { dataProvider } from "@pankod/refine-supabase";
import authProvider from "./authProvider";
import { supabaseClient } from "utility";

import "@pankod/refine/dist/styles.min.css";

import { Login } from "./pages/login";
import { Signup } from "./pages/signup";

function App() {
  return (
    <Refine
      dataProvider={dataProvider(supabaseClient)}
      authProvider={authProvider}
      LoginPage={Login}
      routerProvider={{
        ...routerProvider,
        routes: [
          {
            exact: true,
            component: Signup,
            path: "/signup",
          },
        ] as typeof routerProvider.routes,
      }}
    />
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Here we define our login and signup pages. We then use the refine's router-provider and useNavigaton hooks to switch between login and signup.

Image description

We can now create supabase users and log in from our refine interface.

Adding resources according to the table name we created in Supabase

import { Refine } from "@pankod/refine";
import routerProvider from "@pankod/refine-react-router";
import { dataProvider } from "@pankod/refine-supabase";
import authProvider from "./authProvider";
import { supabaseClient } from "utility";

import "@pankod/refine/dist/styles.min.css";

import { Login } from "./pages/login";
import { Signup } from "./pages/signup";

function App() {
  return (
    <Refine
      dataProvider={dataProvider(supabaseClient)}
      authProvider={authProvider}
      LoginPage={Login}
      routerProvider={{
        ...routerProvider,
        routes: [
          {
            exact: true,
            component: Signup,
            path: "/signup",
          },
        ] as typeof routerProvider.routes,
      }}
      resources={[
        {
          name: "users"
        },
        {
          name: "tasks"
        },
      ]}
    />
  );
}

Enter fullscreen mode Exit fullscreen mode

Image description

We can now create lists of tasks and make changes to them.

src/pages/task/list.tsx

import React from "react";
import {
  useTable,
  useMany,
  List,
  Table,
  TextField,
  TagField,
  DateField,
  Space,
  ShowButton,
  EditButton,
  DeleteButton,
  IResourceComponentsProps,
  HttpError,
  CrudFilters,
  Row,
  Col,
  Card,
} from "@pankod/refine";

import {
  ILabel,
  IPriority,
  ITask,
  ITaskFilterVariables,
  IStatus,
  IAuthUser,
} from "interfaces";

import { Filter } from "../task";

export const TaskList: React.FC<IResourceComponentsProps> = () => {
  const { tableProps, searchFormProps } = useTable<
    ITask,
    HttpError,
    ITaskFilterVariables
  >({
    onSearch: (params) => {
      const filters: CrudFilters = [];
      const { title, label, priority, users, status, start_time, end_time } =
        params;

      filters.push(
        {
          field: "title",
          operator: "eq",
          value: title,
        },

        {
          field: "label",
          operator: "eq",
          value: label,
        },

        {
          field: "priority",
          operator: "eq",
          value: priority,
        },

        {
          field: "users",
          operator: "eq",
          value: users,
        },

        {
          field: "status",
          operator: "eq",
          value: status,
        },

        {
          field: "start_time",
          operator: "gte",
          value: start_time ? start_time[0].toISOString() : undefined,
        },

        {
          field: "start_time",
          operator: "lte",
          value: start_time ? start_time[1].toISOString() : undefined,
        },

        {
          field: "end_time",
          operator: "gte",
          value: end_time ? end_time[0].toISOString() : undefined,
        },

        {
          field: "end_time",
          operator: "lte",
          value: end_time ? end_time[1].toISOString() : undefined,
        }
      );
      return filters;
    },
  });

  const labelIds = tableProps?.dataSource?.map((item) => item.label) ?? [];
  const priorityIds = tableProps?.dataSource?.map((item) => item.priority) ?? [];
  const assignedIds = tableProps?.dataSource?.map((item) => item.users) ?? [];
  const statusIds = tableProps?.dataSource?.map((item) => item.status) ?? [];

  const { data: labels } = useMany<ILabel>({
    resource: "label",
    ids: labelIds,
  });

  const { data: priority } = useMany<IPriority>({
    resource: "priority",
    ids: priorityIds,
  });

  const { data: assigned } = useMany<IAuthUser>({
    resource: "users",
    ids: assignedIds,
  });

  const { data: status } = useMany<IStatus>({
    resource: "status",
    ids: statusIds,
  });

  return (
    <Row gutter={[16, 16]}>
      <Col lg={6} xs={24}>
        <Card title="Task Filter">
          <Filter formProps={searchFormProps} />
        </Card>
      </Col>
      <Col lg={18} xs={24}>
        <List>
          <Table {...tableProps} rowKey="id">
            <Table.Column dataIndex="title" title="Title" />
            <Table.Column
              dataIndex="label"
              title="Label"
              render={(value) => {
                return (
                  <TagField
                    color={
                      labels?.data.find((item) => item.id === value)?.color
                    }
                    value={
                      labels?.data.find((item) => item.id === value)?.title
                    }
                  />
                );
              }}
            />
            <Table.Column
              dataIndex="priority"
              title="Priority"
              render={(value) => {
                return (
                  <TextField
                    value={
                      priority?.data.find((item) => item.id === value)?.title
                    }
                  />
                );
              }}
            />
            <Table.Column
              dataIndex="users"
              title="Assigned"
              render={(value) => {
                return (
                  <TagField
                    value={
                      assigned?.data.find((item) => item.id === value)?.email
                    }
                  />
                );
              }}
            />
            <Table.Column
              dataIndex="status"
              title="Status"
              render={(value) => {
                return (
                  <TextField
                    value={
                      status?.data.find((item) => item.id === value)?.title
                    }
                  />
                );
              }}
            />
            <Table.Column
              dataIndex="start_time"
              title="Start Date"
              render={(value) => (
                <DateField format="DD/MM/YYYY" value={value} />
              )}
            />
            <Table.Column
              dataIndex="end_time"
              title="Due Date"
              render={(value) => (
                <DateField format="DD/MM/YYYY" value={value} />
              )}
            />
            <Table.Column<ITask>
              title="Actions"
              dataIndex="actions"
              render={(_, record): React.ReactNode => {
                return (
                  <Space>
                    <ShowButton
                      size="small"
                      recordItemId={record.id}
                      hideText
                    />
                    <EditButton
                      size="small"
                      recordItemId={record.id}
                      hideText
                    />
                    <DeleteButton
                      size="small"
                      recordItemId={record.id}
                      hideText
                    />
                  </Space>
                );
              }}
            />
          </Table>
        </List>
      </Col>
    </Row>
  );
};
Enter fullscreen mode Exit fullscreen mode

src/pages/task/filter.tsx

import React from "react";
import {
  Form,
  FormProps,
  Input,
  useSelect,
  Select,
  DatePicker,
  Icons,
  Button,
} from "@pankod/refine";

import { ITask, IPriority, IStatus, IAuthUser } from "interfaces";

const { RangePicker } = DatePicker

export const Filter: React.FC<{ formProps: FormProps }> = ({ formProps }) => {
  const { selectProps: labelSelectProps } = useSelect<ITask>({
    resource: "label",
  });

  const { selectProps: priorityProps } = useSelect<IPriority>({
    resource: "priority",
  });

  const { selectProps: statusProps } = useSelect<IStatus>({
    resource: "status"
  });

  const { selectProps: assigneProps } = useSelect<IAuthUser>({
    resource: "users",
    optionValue: "id",
    optionLabel: "email",
  });

  return (
    <Form layout="vertical" {...formProps}>
      <Form.Item label="Search" name="title">
        <Input
          placeholder="Title"
          prefix={<Icons.SearchOutlined />}
        />
      </Form.Item>
      <Form.Item label="Label" name="label">
        <Select {...labelSelectProps} allowClear placeholder="Seach Label" />
      </Form.Item>
      <Form.Item label="Priority" name="priority">
        <Select {...priorityProps} allowClear placeholder="Seach Priority" />
      </Form.Item>
      <Form.Item label="Status" name="status">
        <Select {...statusProps} allowClear placeholder="Search Status" />
      </Form.Item>
      <Form.Item label="Assigned" name="users">
        <Select {...assigneProps} allowClear placeholder="Search Assignee" />
      </Form.Item>
      <Form.Item label="Start Date" name="start_time">
        <RangePicker />
      </Form.Item>
      <Form.Item label="Due Date" name="end_time">
        <RangePicker />
      </Form.Item>
      <Form.Item>
        <Button htmlType="submit" type="primary">
          Filter
        </Button>
      </Form.Item>
    </Form>
  );
};
Enter fullscreen mode Exit fullscreen mode

src/interfaces

export interface IAuthUser {
  id: string;
  email: string;
}

export interface ILabel {
  id: string;
  title: string;
  color: string;
}

export interface IPriority {
  id: string;
  title: string;
}

export interface IStatus {
  id: string;
  title: string
}

export interface ITask {
  id: string;
  title: string;
  description: string;
  start_time: string;
  end_time: string;
  label: string;
  priority: string;
  status: string;
  users: string
}

export interface ITaskFilterVariables {
  title: string;
  label: string;
  priority: string;
  users: string;
  status: string;
  start_time: [Dayjs, Dayjs];
  end_time: [Dayjs, Dayjs];
}
Enter fullscreen mode Exit fullscreen mode

Image description

Using refine's tableSearch and list, we can create our list and perform filtering.

As seen in the example, we listed and showed the task table we created in supabase with refine. Now you can make changes as you want with refine.

Now how do we create task? Let's examine how we can edit them and see their details.

src/pages/task/create.tsx

import {
  useForm,
  Create,
  Form,
  Input,
  Select,
  useSelect,
  IResourceComponentsProps,
  DatePicker,
} from "@pankod/refine";

import { ITask, ILabel, IPriority, IStatus, IAuthUser } from "interfaces";

export const TaskCreate: React.FC<IResourceComponentsProps> = () => {
  const { formProps, saveButtonProps } = useForm<ITask>();

  const { selectProps: labelSelectProps } = useSelect<ILabel>({
    resource: "label",
  });

  const { selectProps: prioritySelectPorps } = useSelect<IPriority>({
    resource: "priority",
  });

  const { selectProps: assigneSelectProps } = useSelect<IAuthUser>({
    resource: "users",
    optionValue: "id",
    optionLabel: "email",
  });

  const { selectProps: statusSelectProps } = useSelect<IStatus>({
    resource: "status"
  })

  return (
    <Create saveButtonProps={saveButtonProps}>
      <Form {...formProps} wrapperCol={{ span: 12 }} layout="vertical">
        <Form.Item
          label="Title"
          name="title"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <Input />
        </Form.Item>
        <Form.Item label="Description" name="description">
          <Input />
        </Form.Item>
        <Form.Item label="Label" name="label">
          <Select {...labelSelectProps} />
        </Form.Item>
        <Form.Item label="Priority" name="priority">
          <Select {...prioritySelectPorps} />
        </Form.Item>
        <Form.Item label="Assigne To" name="users">
          <Select {...assigneSelectProps} />
        </Form.Item>
        <Form.Item label="Select Status" name="status">
          <Select {...statusSelectProps}/>
        </Form.Item>
        <Form.Item label="Start Date" name="start_time">
          <DatePicker style={{ width: "50%" }} />
        </Form.Item>
        <Form.Item label="Due Date" name="end_time">
          <DatePicker style={{ width: "50%" }} />
        </Form.Item>
      </Form>
    </Create>
  );
};

Enter fullscreen mode Exit fullscreen mode

Image description

src/pages/task/edit.tsx

import {
  useForm,
  Form,
  Input,
  Select,
  Edit,
  useSelect,
  IResourceComponentsProps,
} from "@pankod/refine";

import { ITask, IPriority,  IStatus, IAuthUser } from "interfaces";

export const EditTask: React.FC<IResourceComponentsProps> = () => {
  const { formProps, saveButtonProps } = useForm<ITask>();

  const { selectProps: labelSelectProps } = useSelect<ITask>({
    resource: "label",
  });

  const { selectProps: priorityProps } = useSelect<IPriority>({
    resource: "priority",
  });

  const { selectProps: assigneProps } = useSelect<IAuthUser>({
    resource: "users",
    optionValue: "id",
    optionLabel: "email",
  });

  const { selectProps: statusProps } = useSelect<IStatus>({
    resource: "status",
  });

  return (
    <Edit saveButtonProps={saveButtonProps}>
      <Form {...formProps} wrapperCol={{ span: 12 }} layout="vertical">
        <Form.Item label="Title" name="title">
          <Input />
        </Form.Item>
        <Form.Item label="Description" name="description">
          <Input />
        </Form.Item>
        <Form.Item label="Label" name="label">
          <Select {...labelSelectProps} />
        </Form.Item>
        <Form.Item label="Priority" name="priority">
          <Select {...priorityProps} />
        </Form.Item>
        <Form.Item label="Status" name="status">
          <Select {...statusProps} />
        </Form.Item>
        <Form.Item label="Assigne" name="users">
          <Select {...assigneProps} />
        </Form.Item>
      </Form>
    </Edit>
  );
};
Enter fullscreen mode Exit fullscreen mode

Image description

src/pages/task/show

import { useShow, Show, Typography, Tag, useOne, DateField } from "@pankod/refine";
import { ITask, ILabel, IPriority, IStatus, IAuthUser } from "interfaces";

const { Title, Text } = Typography;

export const TaskShow: React.FC = () => {
  const { queryResult } = useShow<ITask>();
  const { data, isLoading } = queryResult;
  const record = data?.data;

  const { data: assigned } = useOne<IAuthUser>({
    resource: "users",
    id: record?.users || "",
  });

  const { data: label } = useOne<ILabel>({
    resource: "label",
    id: record?.label || "",
  });

  const { data: priority } = useOne<IPriority>({
    resource: "priority",
    id: record?.priority || "",
  });

  const { data: status } = useOne<IStatus>({
    resource: "status",
    id: record?.status || "",
  });

  return (
    <Show isLoading={isLoading}>
      <Title level={5}>Task:</Title>
      <Text>{record?.title || "-"}</Text>

      <Title level={5}>Task Desciption:</Title>
      <Text>{record?.description}</Text>

      <Title level={5}>Assigned To:</Title>
      <Text>
        <Tag>{assigned?.data?.email ?? "-"}</Tag>
      </Text>

      <Title level={5}>Label:</Title>
      <Text>
        <Tag>{label?.data?.title ?? "-"}</Tag>
      </Text>

      <Title level={5}>Priority:</Title>
      <Text>{priority?.data?.title ?? "-"}</Text>

      <Title level={5}>Status:</Title>
      <Text>{status?.data?.title ?? "-"}</Text>

      <Title level={5}>Start Date:</Title>
      <DateField format="DD/MM/YYYY" value={record?.start_time ?? "-"}/>

      <Title level={5}>Due Date:</Title>
      <DateField format="DD/MM/YYYY" value={record?.end_time ?? "-"}/>
    </Show>
  );
};
Enter fullscreen mode Exit fullscreen mode

Image description

By using Refine's basic views such as create, edit and show, we can now create tasks, edit these tasks and view their details.

Let's see how to add a dashboard page to our project together.

src/components/task/pie.tsx

import React from "react";
import { Pie } from "@ant-design/charts";

interface ChartProps {
  data: {
    type: any;
    value: any;
  }[];
}

export const TaskChart: React.FC<ChartProps> = ({ data }) => {
  var config = {
    data: data,
    angleField: "value",
    colorField: "type",
    radius: 0.9,
    label: {
      type: "inner",
      offset: "-30%",
      content: function content(_ref: any) {
        var percent = _ref.percent;
        return "".concat((percent * 100).toFixed(0), "%");
      },
      style: {
        fontSize: 14,
        textAlign: "center",
      },
    },
    interactions: [{ type: "element-active" }],
  };
  return <Pie {...config} />;
};
Enter fullscreen mode Exit fullscreen mode

src/pages/dashboard/index.tsx

import React from "react";
import { useList, useMany, Row, Col, Card } from "@pankod/refine";
import { ITask, ILabel, IPriority, IStatus, IAuthUser } from "interfaces";
import { TaskChart } from "components/task/pie";
import { groupBy } from "helper";

export const Dashboard = () => {
  const taskList = useList<ITask>({
    resource: "tasks",
  });

  const labelIds = taskList.data?.data.map((item) => item.label) ?? [];
  const priorityIds = taskList.data?.data.map((item) => item.priority) ?? [];
  const assignedIds = taskList.data?.data.map((item) => item.users) ?? [];
  const statusIds = taskList.data?.data.map((item) => item.status) ?? [];

  const { data: labels } = useMany<ILabel>({
    resource: "label",
    ids: labelIds || [],
  });

  const { data: priority } = useMany<IPriority>({
    resource: "priority",
    ids: priorityIds || [],
  });

  const { data: assigned } = useMany<IAuthUser>({
    resource: "users",
    ids: assignedIds || [],
  });

  const { data: status } = useMany<IStatus>({
    resource: "status",
    ids: statusIds || [],
  });

  return (
    <Row gutter={[16, 16]}>
      <Col xl={7} lg={12} md={24} sm={24} xs={24}>
        <Card>
          <TaskChart
            data={
              labels?.data.map((i) => {
                return { type: i.title, value: groupBy(labelIds)[i.id] };
              }) ?? []
            }
          />
        </Card>
      </Col>
      <Col xl={7} lg={12} md={24} sm={24} xs={24}>
        <Card>
          <TaskChart
            data={
              priority?.data.map((i) => {
                return { type: i.title, value: groupBy(priorityIds)[i.id] };
              }) ?? []
            }
          />
        </Card>
      </Col>
      <Col xl={7} lg={12} md={24} sm={24} xs={24}>
        <Card>
          <TaskChart
            data={
              status?.data.map((i) => {
                return { type: i.title, value: groupBy(statusIds)[i.id] };
              }) ?? []
            }
          />
        </Card>
      </Col>
      <Col xl={7} lg={12} md={24} sm={24} xs={24}>
        <Card>
          <TaskChart
            data={
              assigned?.data.map((i) => {
                return { type: i.email, value: groupBy(assignedIds)[i.id] };
              }) ?? []
            }
          />
        </Card>
      </Col>
    </Row>
  );
};
Enter fullscreen mode Exit fullscreen mode

Image description

Final version of our <App.tsx/>.

import { Refine } from "@pankod/refine";
import routerProvider from "@pankod/refine-react-router";
import { dataProvider } from "@pankod/refine-supabase";
import authProvider from "./authProvider";
import { supabaseClient } from "utility";

import "@pankod/refine/dist/styles.min.css";

import { UserList } from "./pages/user";
import { TaskList, TaskShow, TaskCreate, EditTask } from "./pages/task";
import { Dashboard } from "./pages/dashboard";
import { Login } from "./pages/login";
import { Signup } from "./pages/signup";



function App() {
  return (
    <Refine
      dataProvider={dataProvider(supabaseClient)}
      authProvider={authProvider}
      DashboardPage={Dashboard}
      LoginPage={Login}
      routerProvider={{
        ...routerProvider,
        routes: [
          {
            exact: true,
            component: Signup,
            path: "/signup",
          },
        ] as typeof routerProvider.routes,
      }}
      resources={[
        {
          name: "users",
          list: UserList,
        },
        {
          name: "tasks",
          list: TaskList,
          edit: EditTask,
          create: TaskCreate,
          show: TaskShow,
        },
      ]}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Our project is done. Lets see how its look like.

Overview

Image description

Task Filter

Image description

DashboardPage

Image description

As you can see, we made a simple and short task manager application using refine on our front end and using its data-provider.

Here is repo

For more information about Refine: https://refine.dev/

For other examples and articles that will interest you with refine: https://refine.dev/blog/

Live Codesandbox Example: https://codesandbox.io/s/refine-supabase-issue-tracker-ifuh4

Discussion (0)