Airtable、Gatsby和React如何构建交互式甘特图?

2021年11月30日01:30:07 发表评论 1,001 次浏览

使用 Gatsby,可以很容易地将不同的数据源集成到一个应用程序中。在本文中,我们将开发一个任务管理工具,其数据是从 Airtable 中获取的。我们将在前端使用 React,并采用混合渲染策略。

这是一个常见的场景:你想要开发一个应用程序,该应用程序连接到来自电子表格应用程序或某些其他数据源的数据。在本文中,我将向你展示如何使用 Gatsby 框架实现这种类型的应用程序。在我们的示例应用程序中,任务将从 Airtable 工作区导入并可视化为甘特图。用户可以通过拖放来移动任务,之后所有更改将与 Airtable 同步。你可以将该项目用作各种日程安排应用程序的模板。

Airtable、Gatsby和React如何构建交互式甘特图?
交互式甘特图构建实例

你可以在我的Gatsby Cloud 网站上实时试用结果。该项目的 src 文件可以在我的GitHub 存储库中找到。

交互式甘特图构建教程:设置项目

如何构建交互式甘特图?Gatsby 是一个静态站点生成器。这意味着你使用 React 编写应用程序,Gatsby 会将你的代码转换为浏览器可以理解的 HTML 文件。这个构建过程在服务器端定期执行,这与传统的 Web 应用程序不同,传统的 Web 应用程序首先在客户端的用户浏览器中组装 HTML 代码。因此,HTML 文件在服务器上是静态可用的(因此称为静态站点生成器),并且可以在请求时直接发送到客户端。这减少了用户应用程序的加载时间。

SitePoint 的Gatsby 教程提供了使用此框架开发应用程序所需的所有信息。如果你想一步一步地开发我的示例应用程序,你应该从下面的概述开始。

首先,你应该下载并安装Node.js的。你可以通过node -v在控制台上键入来检查它是否安装正确。应显示 Node 的当前版本:

node -v
> v14.16.0

使用 Node 我们还获得了 npm,即 Node 包管理器。使用这个工具,我们现在可以安装 Gatsby CLI:

npm install -g gatsby-cli

我们已准备好使用 Gatsby CLI 创建一个新项目。我将其命名为“甘特图-盖茨比”:

gatsby new gantt-chart-gatsby

然后使用命令进入项目文件夹并使用命令cd gantt-chart-gatsby构建项目gatsby develop。现在你可以在http://localhost:8000上的浏览器中打开项目的索引页面。起初,你应该只看到 Gatsby 为我们准备的欢迎页面。

在下一步中,你应该检查src项目的文件夹。该子文件夹src/pages包含项目各个页面的 React 组件。现在,保留index.js索引页的文件就足够了,因为在我们的示例应用程序中,我们只需要一页。你可以删除此文件夹中的其他文件,除了404.js(如果有人输入错误地址,这会很有用)。

交互式甘特图构建实例:如果你index.js用以下代码覆盖现有代码,这是一个很好的起点:

import * as React from 'react'

const IndexPage = () => {
  return (
   <main>
      <title>Gantt Chart</title>
      <h1>Welcome to my Gatsby Gantt Chart</h1> 

    </main>
  )
}

export default IndexPage;

你可以gatsby develop在命令行中使用命令再次构建项目,并在浏览器中打开索引页面。现在你应该看到一个空白页面,标题为“欢迎使用我的 Gatsby 甘特图”。

使用 React 构建前端

第一版索引页

我们将甘特图实现为可重用的 React 组件。在接下来的部分中详细解释该组件的实现之前,我首先想展示它是如何初始化并嵌入到索引页面中的。所以我建议你在gatsby develop我们完成组件的第一个版本之前不要使用该命令。(当我们准备好时,我会告诉你的!)

在这个示例项目中,我使用了“工作”和“资源”的概念。作业是绘制到图表单元格中并且可以通过拖放移动的任务。资源包含可以移动作业的行的标签。这些可以是任务的名称,但在其他用例中也是执行任务的人员、车辆或机器的名称。

作业和资源作为属性传递给甘特图组件。在将任务管理工具连接到 Airtable 之前,我们用一些 JSON 格式的硬编码测试数据填充列表:

import * as React from "react";
import {GanttChart} from "../GanttChart";
import "../styles/index.css";

let j = [
  {id: "j1", start: new Date("2021/6/1"), end: new Date("2021/6/4"), resource: "r1"},
  {id: "j2", start: new Date("2021/6/4"), end: new Date("2021/6/13"), resource: "r2"},
  {id: "j3", start: new Date("2021/6/13"), end: new Date("2021/6/21"), resource: "r3"},
];

let r = [{id:"r1", name: "Task 1"}, {id:"r2", name: "Task 2"}, {id:"r3", name: "Task 3"}, {id:"r4", name: "Task 4"}];

const IndexPage = () => {
  return (
    <main>
      <title>Gantt Chart</title>
      <h1>Welcome to my Gatsby Gantt Chart</h1> 
      <GanttChart jobs={j} resources={r}/>
    </main>
  )
};

export default IndexPage;

甘特图的 CSS 样式

交互式甘特图构建实例:在下一步中,我们index.cssstyles文件夹中创建一个新文件。(如果该文件夹不存在,则在项目styles的文件夹中新建一个文件夹src。)以下 CSS 设置控制甘特图的布局和外观:

body{
  font-family: Arial, Helvetica, sans-serif;
}

#gantt-container{
  display: grid;     
}

.gantt-row-resource{
  background-color:whitesmoke;
  color:rgba(0, 0, 0, 0.726);
  border:1px solid rgb(133, 129, 129);
  text-align: center;
  padding: 15px;
}

.gantt-row-period{
  background-color:whitesmoke;
  color:rgba(0, 0, 0, 0.726);
  border:1px solid rgb(133, 129, 129);
  text-align: center;

  display:grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(40px, 1fr);
}

.period{
  padding: 10px 0 10px 0;
}

.gantt-row-item{
  border: 1px solid rgb(214, 214, 214);
  padding: 10px 0 10px 0;
  position: relative;
  background-color:white;
}

.job{
  position: absolute;
  height:38px;
  top:5px;
  z-index: 100;
  background-color:rgb(167, 171, 245);
  cursor: pointer;
}

实现GanttChart组件

如何构建交互式甘特图?现在我GanttChart将更详细地解释该组件的实现。首先,我们需要一个GanttChart.jssrc文件夹中命名的文件。在本教程中,我仅使用了GanttChart一个月(2021 年 6 月)的简化版。可以在 GitHub 上以GanttChart_extended.js的名称找到带有开始月份和结束月份的选择字段的扩展版本。

图表表分三步构建,分别由函数initFirstRowinitSecondRow和 表示initGanttRows

import React from 'react';

export class GanttChart extends React.Component {

    names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

    constructor(props) {

        super(props);       

        this.state = {
            dateFrom: new Date(2021,5,1),
            dateTo: new Date(2021,5,30),
        };
    }

    render(){

        let month = new Date(this.state.dateFrom.getFullYear(), this.state.dateFrom.getMonth(), 1);

        let grid_style = "100px 1fr";

        let firstRow = this.initFirstRow(month);
        let secondRow = this.initSecondRow(month);
        let ganttRows = this.initGanttRows(month);

        return (

            <div className="gantt-chart">
                <div id="gantt-container" style={{gridTemplateColumns : grid_style}}>
                    {firstRow}
                    {secondRow}
                    {ganttRows}
                </div>
            </div>
        );
     }


    initFirstRow(month){...}

    initSecondRow(month){...}

    initGanttRows(month){...}


    //helper functions:

    formatDate(d){ 
        return d.getFullYear()+"-"+this.zeroPad(d.getMonth()+1)+"-"+this.zeroPad(d.getDate());  
    }

    zeroPad(n){
        return n<10 ? "0"+n : n;
    }

    monthDiff(d1, d2) {
        let months;
        months = (d2.getFullYear() - d1.getFullYear()) * 12;
        months -= d1.getMonth();
        months += d2.getMonth();
        return months <= 0 ? 0 : months;
    }

    dayDiff(d1, d2){   
        let diffTime = Math.abs(d2 - d1);
        let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); 
        return diffDays;
    }

}

initFirstRow函数中,生成图表表格的第一行。从上图可以看出,第一行由两个网格单元组成。它们作为 div 生成,这些 div 又作为子元素插入到“甘特容器”中(参见上面的列表)。第二个 div 还包含当前月份的标签。

React 需要为枚举中的所有元素提供唯一的“key”属性。这有助于优化渲染性能:

 initFirstRow(month){

    let elements = []; let i = 0;

    elements.push(<div key={"fr"+(i++)} className="gantt-row-resource"></div>);

    elements.push(<div key={"fr"+(i++)} className="gantt-row-period"><div className="period">{this.names[month.getMonth()] + " " + month.getFullYear()}</div></div>);

    return elements;
 }

图表表格的下一行在initSecondRow函数中生成。我们再次使用相同的原则:为每个表格单元格创建一个 div。你必须确保 div 正确嵌套(行中的第二个 div 包含一个月中每一天的单独 div),以便 CSS 网格设置(请参阅index.css文件)生成所需的布局:

initSecondRow(month){

    let elements = []; let i=0;

    //first div
    elements.push(<div key={"sr"+(i++)} style={{borderTop : 'none'}} className="gantt-row-resource"></div>);

    let days = [];

    let f_om = new Date(month); //first day of month
    let l_om = new Date(month.getFullYear(), month.getMonth()+1, 0); //last day of month

    let date = new Date(f_om);

    for(date; date <= l_om; date.setDate(date.getDate()+1)){

        days.push(<div key={"sr"+(i++)} style={{borderTop: 'none'}} className="gantt-row-period period">{date.getDate()}</div>);
    }

    //second div in the row with child divs for the individual days
    elements.push(<div key={"sr"+(i++)} style={{border: 'none'}} className="gantt-row-period">{days}</div>);

    return elements;

}

图表表的其余行在initGanttRows函数中生成。它们包含绘制作业的网格单元。同样,渲染是逐行完成的:对于每一行,我们首先放置资源的名称,然后迭代一个月的各个天。每个网格单元都被初始化为ChartCell特定日期和资源的组件。使用cell_jobs列表,单个单元格被分配了需要绘制到其中的作业(通常这恰好是一项作业):

initGanttRows(month){

    let elements = []; let i=0;

    this.props.resources.forEach(resource => {

        elements.push(<div key={"gr"+(i++)} style={{borderTop : 'none'}} className="gantt-row-resource">{resource.name}</div>);

        let cells = [];

        let f_om = new Date(month);
        let l_om = new Date(month.getFullYear(), month.getMonth()+1, 0);

        let date = new Date(f_om);

        for(date; date <= l_om; date.setDate(date.getDate()+1)){

            let cell_jobs = this.props.jobs.filter((job) => job.resource == resource.id && job.start.getTime() == date.getTime());

            cells.push(<ChartCell key={"gr"+(i++)} resource={resource} date={new Date(date)} jobs={cell_jobs}/>);
        }

        elements.push(<div key={"gr"+(i++)} style={{border: 'none'}} className="gantt-row-period">{cells}</div>);

    });

    return elements;
}

现在ChartCellGanttChart.js. 该组件将图表的单个表格单元格呈现为包含一个或多个作业作为子元素的 div。用于显示作业的 HTML 代码由getJobElement函数提供:

class ChartCell extends React.Component {

    constructor(props) {

      super(props);

      this.state = {
        jobs: props.jobs
      }
    }

    render(){

      let jobElements = this.props.jobs.map((job) => this.getJobElement(job));

      return (
        <div 
            style={{borderTop: 'none', borderRight: 'none', backgroundColor: (this.props.date.getDay()==0 || this.props.date.getDay()==6) ? "whitesmoke" : "white" }} 
            className="gantt-row-item">
            {jobElements}
        </div>
      );
    }

    getJobElement(job){

        let d = this.dayDiff(job.start, job.end);

        //Example: a job with a duration of 2 days covers exactly two grid cells, so the width is 2*100% and we have to add up 2px for the width of the grid lines
        return (
        <div    style={{width: "calc("+(d*100)+"% + "+ d + "px)"}} 
                className="job" 
                id={job.id} 
                key={job.id}
        >

        </div>
        );
    }

    dayDiff(d1, d2){   
        let diffTime = Math.abs(d2 - d1);
        let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); 
        return diffDays;
    }
}

此时,你可以使用gatsby develop命令从根文件夹构建项目。索引页中的硬编码作业应该在甘特图中可见。它们还不能被拖放,但我们稍后会处理。

交互式甘特图构建教程:集成来自 Airtable 的数据

是时候将我们的应用程序连接到 Airtable,以便我们可以从那里导入作业和资源。首先,在Airtable创建一个免费帐户。登录后,你将看到一个“Untitled Base”(见下图)。单击Add a base,然后单击 Start from scratch,并为你的 base 输入一个名称。我进入了“任务管理器”。

Airtable、Gatsby和React如何构建交互式甘特图?

使用“Jobs”和“Resources”表设置Airtable base

现在你可以按照以下步骤为你的基地定义表:

  1. 使用字段id(字段类型:文本)、start(字段类型:日期)和end(字段类型:日期)定义表“作业” 。
  2. 使用字段id(字段类型:文本)和name(字段类型:文本)定义表“资源” 。
  3. 转到“工作”表,添加resource字段类型为“链接到另一条记录”的字段,然后选择该字段id作为“资源”表的查找字段。

完成这些步骤后,你的表格应如下图所示。

Airtable、Gatsby和React如何构建交互式甘特图?
Airtable、Gatsby和React如何构建交互式甘特图?

使用 GraphQL 和 Gatsby 的 Airtable 插件从 Airtable 导入数据

交互式甘特图构建实例:接下来,我们要将数据从 Airtable 导入到我们的应用程序中。为此,请安装插件“gatsby-source-airtable” npm install --save gatsby-source-airtable。然后,修改gatsby-config.js项目文件夹中的文件,如下表所示:

module.exports = {
  siteMetadata: {
    siteUrl: "https://www.yourdomain.tld",
    title: "Gatsby Gantt Chart",
  },
  plugins: [

  "gatsby-plugin-gatsby-cloud",

  {
    resolve: "gatsby-source-airtable",
    options: {
      apiKey: "XXX", // may instead specify via env, see below
      concurrency: 5, // default, see using markdown and attachments for more information
      tables: [
        {
          baseId: "YYY",
          tableName: "Jobs",    
        },
        {
          baseId: "YYY",
          tableName: "Resources",
        }
      ]
    }
  }
  ],
};

现在我们可以尝试从 Airtable 中获取数据。使用 启动你的应用程序gatsby develop,然后在浏览器中打开 GraphiQL 编辑器,http://localhost:8000/___graphql并将以下查询粘贴到左侧区域中:

{
  jobs: allAirtable(filter: {table: {eq: "Jobs"}, data: {}}) {
    edges {
      node {
        data {
          id
          start
          end
          id__from_resource_
          resource
        }
        recordId
      }
    }
  }
  resources: allAirtable(
    filter: {table: {eq: "Resources"}}
    sort: {fields: [data___name], order: ASC}
  ) {
    edges {
      node {
        data {
          id
          name
        }
      }
    }
  }
}

单击箭头符号以运行查询。查询结果应出现在右侧。

Airtable、Gatsby和React如何构建交互式甘特图?

现在是时候删除包含作业和资源的硬编码列表了index.js。更新代码,index.js如下面的清单所示。这里发生了什么事?首先,在文件末尾,你可以看到一个所谓的“页面查询”,它请求所有作业和资源。查询的结果会自动分配给data组件的属性IndexPage。因此,该data属性准确地存储了你在右侧的 GraphiQL 编辑器中看到的查询结果。我们可以使用该map函数将jobsresources数组转换为我们喜欢的格式。

即使看起来有点麻烦,我们也必须为所有作业保留由 Airtable 自动创建的属性recordIDid__from_resource。这是必要的,以便我们稍后可以通过 Airtable REST API 保存对作业的更改:

import * as React from "react"
import { useStaticQuery, graphql } from "gatsby"
import {GanttChart} from "../GanttChart"
import '../styles/index.css';

// markup
const IndexPage = (data) => {

  let j = data.data.jobs.edges.map(edge => {

    let s = new Date(edge.node.data.start);
    s.setHours(0);

    let e = new Date(edge.node.data.end);
    e.setHours(0);

    return {
      airtable_id: edge.node.recordId,
      id:edge.node.data.id,
      start: s,
      end: e,
      resource: edge.node.data.id__from_resource_[0],
      resource_airtable_id: edge.node.data.resource[0]
    };
  });

  let r = data.data.resources.edges.map(edge => {

    return{
      id: edge.node.data.id,
      name: edge.node.data.name
    }
  });

  if(r && j){
    return (
      <main>
        <title>Gantt Chart</title>
        <h1>Welcome to my Gatsby Gantt Chart</h1> 
        <GanttChart jobs={j} resources={r}/> 
      </main>
    )
  }else{
    return (
      <main>
        <title>Gantt Chart</title>
        <h1>Welcome to my Gatsby Gantt Chart</h1> 
        <p>Missing data...</p> 
      </main>
    )
  }
}

export const query = graphql`
      query{
        jobs: allAirtable(filter: {table: {eq: "Jobs"}, data: {}}) {
          edges {
            node {
              data {
                id
                start
                end
                id__from_resource_
                resource
              }
              recordId
            }
          }
        }
        resources: allAirtable(
          filter: {table: {eq: "Resources"}}
          sort: {fields: [data___name], order: ASC}
        ) {
          edges {
            node {
              data {
                id
                name
              }
            }
          }
        }
      }
  `
export default IndexPage;

如果你在本地构建和启动你的应用程序gatsby develop,数据将从 Airtable 获取并显示在你的甘特图中。如果你已根据Gatsby 教程设置了 Gatsby Cloud 站点,则只要你将代码更改推送到关联的 GitHub 帐户,该站点就会更新。但是,你会注意到 Airtable 查询仅在项目构建时执行(无论发生在本地还是在 Gatsby Cloud 站点上)。如果你修改了 Airtable 库中的数据,除非你重新构建项目,否则这些更改不会反映在甘特图中。这对于 Gatsby 的服务器端渲染过程来说是典型的。

在下一节中,我们将讨论如何处理数据的变化。

交互式甘特图构建教程:实现 Gatsby 和 Airtable 的双向同步

在我们的示例中,可以在 Airtable 中(通过编辑表格单元格)或在甘特图中(通过拖放)对数据进行更改。为了同步这些部分,我采用了一种混合策略,包括服务器端和客户端更新操作。

1. 将Airtable的变化转移到甘特图(服务器端)

Gatsby 提供了webhooks来远程触发服务器端构建过程。可以将 Airtable 配置为在某些事件(例如创建或更改记录)上自动触发构建挂钩,前提是你在那里拥有专业会员资格。(你可以在此处找到有关为此目的所需的设置的更多详细信息)。

2. 将 Airtable 的更改传输到甘特图(客户端)

如何构建交互式甘特图?当应用程序在浏览器中使用时,甘特图应该从 Airtable 动态加载更新(例如,以特定时间间隔)。为了使过程简单,我们只想在指定的时间间隔重新下载完整的作业和资源列表。为此,我们将使用官方的 Airtable API。

IndexPage组件中,我们使用React 的 useState 钩子将作业和资源列表设置为组件的状态。然后我们应用useEffect 钩子来设置一个间隔,loadDataFromAirtable一旦组件初始化,该函数应该被调用:

const IndexPage = (data) => {

  let j = data.data.jobs.edges.map(edge => {...});
  let r = data.data.resources.edges.map(edge => {...});

  const [resources, setResources] = useState(r);
  const [jobs, setJobs] = useState(j);

  useEffect(() => {
    const interval = setInterval(() => { 

      let jobsLoaded = (j) => { setJobs(j) };
      let resourcesLoaded = (r) => { setResources(r) };

      loadDataFromAirtable(jobsLoaded, resourcesLoaded);

    }, 60000);  

    return () => clearInterval(interval);
  }, []);

  if(resources && jobs){
    return (
      <main>
        <title>Gantt Chart</title>
        <h1>Welcome to my Gatsby Gantt Chart</h1> 
        <GanttChart jobs={jobs} resources={resources}/> 
      </main>
    )
  }else{
    return (
      <main>
        <title>Gantt Chart</title>
        <h1>Welcome to my Gatsby Gantt Chart</h1> 
        <p>Missing data...</p> 
      </main>
    )
  }
}

对于loadDataFromAirtable函数的实现,我们看一下Airtable API的文档。该文档适用于选定的基础(在我们的例子中为“任务管理器”)。如果你单击左侧的Jobs TableList 记录,你将在“curl”区域看到用于检索所有作业数据的 GET 请求的确切结构。可以使用“fetch”方法在 JavaScript 中轻松实现此请求。

因此,为了下载所有作业和资源的数据,我们依次向 Airtable 执行两个异步 GET 请求。我屏蔽了确切的 URL,因为它们包含我的个人 API 密钥:

function loadDataFromAirtable(onJobsLoaded, onResourcesLoaded){

  let j,r;

  let url_j= "XXXX";
  let url_r= "YYYY";

  fetch(url_j, {headers: {"Authorization": "ZZZZ"}})
  .then(response => response.json())
  .then(data => {

    j = data.records.map(record => {

      let s = new Date(record.fields.start);
      s.setHours(0);

      let e = new Date(record.fields.end);
      e.setHours(0);

       return {
        airtable_id: record.id,
        id: record.fields.id,
        start: s,
        end: e,
        resource: record.fields['id (from resource)'][0],
        resource_airtable_id: record.fields.resource[0]
       };
    });

    onJobsLoaded(j);
  });


  fetch(url_r, {headers: {"Authorization": "ZZZZ"}})
  .then(response => response.json())
  .then(data => {

    r = data.records.map(record => {

       return {
        id: record.fields.id,
        name: record.fields.name
       };
    });

    onResourcesLoaded(r);
  });
}

作为测试,你可以对 Airtable 库中的作业数据进行一些更改。在给定的间隔时间(这里是一分钟)之后,甘特图应该会在你的浏览器中自动更新。

3. 将甘特图的变化转移到Airtable base(客户端)

交互式甘特图构建实例:在用户可以修改甘特图之前,我们必须首先使作业可拖动。为此,请ChartCell按如下方式更新组件:

class ChartCell extends React.Component {

    constructor(props) {

      super(props);
    }

    render(){

      let jobElements = this.props.jobs.map((job) => this.getJobElement(job));

      let dragOver = (ev) => {ev.preventDefault()};

      let drop = (ev) => {

        ev.preventDefault(); 

        let job_id = ev.dataTransfer.getData("job");  

        this.props.onDropJob(job_id, this.props.resource.id, this.props.date)

      };

      return (
        <div 
            style={{borderTop: 'none', borderRight: 'none', backgroundColor: (this.props.date.getDay()==0 || this.props.date.getDay()==6) ? "whitesmoke" : "white" }} 
            className="gantt-row-item" onDragOver={dragOver} onDrop={drop}>
            {jobElements}
        </div>
      );
    }

    getJobElement(job){

        let d = this.dayDiff(job.start, job.end);

        return (
        <div    style={{width: "calc("+(d*100)+"% + "+ d + "px)"}} 
                className="job" 
                id={job.id} 
                key={job.id}
                draggable="true"
                onDragStart={this.dragStart}>
        </div>
        );
    }

    dragStart(ev){ ev.dataTransfer.setData("job", ev.target.id);}

    dayDiff(d1, d2){

        let diffTime = Math.abs(d2 - d1);
        let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); 
        return diffDays;
    }
}

使用 JavaScript 实现拖放并不是特别复杂。你必须为事件onDragStart(对于可拖动元素)onDragOveronDrop(对于放置目标)实现处理程序,如本教程中所示。

我们需要指定在onDropJob事件上调用哪个处理程序函数,这是由drop处理程序触发的。在initGanttRows函数中,更新以下行:

cells.push(<ChartCell key={"gr"+(i++)} resource={resource} date={new Date(date)} jobs={cell_jobs} onDropJob={this.dropJob}/>);

GanttChart组件中,添加函数dropJob

dropJob(id, newResource, newDate){

      let job = this.props.jobs.find(j => j.id == id );

      let newJob = {};
      newJob.resource = newResource;

      let d = this.dayDiff(job.start, job.end); 
      let end = new Date(newDate);
      end.setDate(newDate.getDate()+d);

      newJob.start = newDate;
      newJob.end = end;

      this.props.onUpdateJob(id, newJob);
  };

作业列表的实际修改是IndexPageindex.js. 该slice方法用于创建作业列表的副本。使用拖放移动的作业根据其 ID 位于列表中,并被赋予新的属性。之后,IndexPage通过调用更新组件的状态setJobs。请注意,现在触发了甘特图组件的重新渲染,现在作业元素出现在其新位置:

const IndexPage = (data) => {

  ...

  let updateJob = (id, newJob) => {

    let new_jobs = jobs.slice();

    let job = new_jobs.find(j => j.id == id );

    job.resource = newJob.resource;
    job.start = newJob.start;
    job.end = newJob.end;

    setJobs(new_jobs);
    updateJobToAirtable(job);
  }

  if(resources && jobs){
    return (
      <main>
        <title>Gantt Chart</title>
        <h1>Welcome to my Gatsby Gantt Chart</h1> 
        <GanttChart jobs={jobs} resources={resources} onUpdateJob={updateJob}/> 
      </main>
    )
  }else{
    ...
  }
}

在最后一步中,我们必须实现该updateJobToAirtable功能。同样,我们遵循 Airtable API 文档,这次是在更新记录部分:

function updateJobToAirtable(job){

    let data = {
      records: [
      {
        id: job.airtable_id,
        fields: {
          id: job.id,
          start: formatDate(job.start),
          end: formatDate(job.end),
          resource: [
            job.resource_airtable_id
          ]
        }
      }
    ]};

 fetch("XXX", {
    method: "PATCH", 
    headers: {"Authorization": "ZZZ", "Content-Type": "application/json"},
    body: JSON.stringify(data)
  });
}

现在,你可以在甘特图中移动作业,并在你的 Airtable 基础上查看“作业”表如何实时更新。

交互式甘特图构建教程总结

如何构建交互式甘特图?本文中的简单任务管理应用程序表明,服务器端渲染也可用于具有丰富客户端交互的应用程序。主要优点是初始加载时间快,因为 DOM 是在服务器上准备好的。特别是对于具有非常复杂用户界面的应用程序(例如,用于规划任务的仪表板),这可能至关重要。在客户端定期获取新数据通常不会导致重大的性能问题,因为 React 使用复杂的算法来确定哪些 DOM 更改实际上是必要的。

Gatsby 框架通过提供对服务器端渲染的无缝支持以及用于从外部源导入数据的众多插件,极大地简化了开发此类混合应用程序的过程。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: