Делаем страницу на React с базой сотрудников при помощи Airtable и Quarkly

    Слышали про такой инструмент, как Airtable, но не знали, с чего начать? Тогда приглашаем в мир визуального программирования построения БД!


    Этим постом мы начинаем цикл обучающих статей, в которых будем давать практические примеры работы с нашим инструментом Quarkly. В этом уроке мы сделаем простое веб-приложение, которое будет показывать сотрудников компании. При создании приложения ни один сотрудник РЖД не пострадал.


    Фронт будем делать при помощи Quarkly, а данные подтягивать из базы в Airtable. На выходе получим react-приложение, синхронизированное с базой данных.



    Преамбула. Почему Airtable


    Airtable — популярный no-code инструмент, в котором вы можете делать свои базы данных большого объема. Выглядят они как таблицы, но имеют гораздо более мощный функционал. В частности, для нашего урока выбор Airtable обусловлен легким способом передачи данных по API.


    Если вы впервые слышите про Airtable, перед началом работы не будет лишним почитать официальное руководство на сайте компании. Также советуем не стесняться и задавать вопросы в чатике Airtable Chat & Community в телеграм.


    Фронтендная часть работ будет сделана в Quarkly, и для этого мы будем использовать всего два компонента:


    • Карточка сотрудника. В ней будут фото, текстовые данные и две кнопки: отправить email и позвонить. Эти данные карточка будет получать от родительского компонента — обертки.
    • Обертка. Она будет принимать данные из Airtable, генерировать карточки и передавать в них данные.

    Для тех, у кого нет времени вникать в пост в печатном формате, мы подготовили видео с субтитрами и таймкодами:




    Часть 1. Делаем визуал в Quarkly


    Создание карточки:


    1. Создадим новый проект в Quarkly, назовем его Airtable Example;
    2. Перейдем внутрь проекта;
    3. Добавим готовый блок с карточками сотрудников. Для этого кликаем на черную кнопку + посередине и выбираем блок из категории Team;



    4. Выбираем на панели слоев первую карточку (StackItem) и преобразуем её в компонент;



      Для этого нажмите на «троеточие» и выберите пункт Convert to Component. Назовем этот компонент EmployeeCard.

    5. Теперь мы можем свободно редактировать код этого react-компонента, но пока этого делать не будем и перейдем к созданию компонента-обертки.

    Создание обертки:


    1. Подготовим обертку к преобразованию в компонент. Для этого сначала удалим оставшиеся три карточки, они нам не нужны;

    2. Теперь вытащим EmployeeCard из Stack. Затем преобразуем Stack в компонент, как мы уже делали ранее с EmployeeCard: правая панель, кнопка «троеточие» и Convert to Component. Компонент назовем EmployeeTable.



      На этом пока всё, подготовительный этап завершен. Оставим компоненты и займемся базой в Airtable.


    Часть 2. Создаем базу данных в Airtable


    Переходим на сайт Airtable и регистрируемся/авторизуемся.


    1. Кликаем на кнопку Add a base, чтобы создать новую базу. Из выпадающего меню выберите пункт Start with a template;

    2. Выбираем категорию HR & Recruiting и шаблон Employee directory. Далее кликаем на кнопку Use template;

    3. Переходим в созданный проект;


    На данном этапе здесь мы ничего менять не будем, в текущем виде база уже нас устраивает.



    Часть 3. Получаем доступ к API


    Изначально Airtable интересен для нас именно за счет удобного API. При этом, что немаловажно, возможность забирать данные и отправлять их в нашу базу Airtable предоставляет бесплатно.


    1. Переходим на страницу выбора API проектов: https://airtable.com/api

    2. Выбираем наш проект Employee directory. В появившейся документации переходим на раздел AUTHENTICATION.

    3. Скопируйте две строчки, расположенные ниже заголовка EXAMPLE USING BEARER TOKEN (RECOMMENDED).

      У меня они выглядят так:

      $ curl https://api.airtable.com/v0/app2MdLITmRTBsrkg/Employee%20directory \
      -H "Authorization: Bearer YOUR_API_KEY"
    4. Теперь нам нужен YOUR_API_KEY. Это уникальный ключ доступа, который генерируется для каждого аккаунта. Найти его можно в настройках.

    5. На открывшейся странице перейдите в раздел API и нажмите на кнопку Generate API key;

    6. Скопируйте ключ. Сохраните его рядом со строчками из пункта 3. Они пригодятся нам далее.


    Часть 4. Интегрируем базу Airtable в Quarkly


    На этом этапе мы добавим в компонент EmployeeTable код, который будет забирать данные из API.


    1. Переходим в редактирование кода компонента. Для этого откроем вкладку Components и нажмем на кнопку <> у EmployeeTable (она появится при наведении курсора на компонент);

    2. Сейчас код компонента выглядит так:

    3. Заменим:

      import React from "react";

      на:

      import React, { useEffect, useState } from "react";

      Таким образом мы импортируем хуки useEffect и useState, которые помогут нам в дальнейшем;
    4. Ниже добавляем строчку, чтобы импортировать наш компонент EmployeeCard:

      import EmployeeCard from "./EmployeeCard";
    5. Заменим children (они нам пока не нужны) на override (пригодятся, чтобы выбирать элементы и стилизовать их на странице):

      const EmployeeTable = props => {
      	const {
      		children,
      		rest
      	} = useOverrides(props, overrides, defaultProps);

      на:

      const EmployeeTable = props => {
      	const {
      		override,
      		rest
      	} = useOverrides(props, overrides, defaultProps);
    6. Ниже добавим вызов хука useState, который будет следить за состоянием:

      const [employees, setEmployees] = useState([]);
    7. Далее добавим хук useEffect, который будет делать запросы к API Airtable и помещать полученные данные в наше состояние через функцию setEmployees.

      Добавляем сюда строчки, которые скопировали ранее. В fetch мы добавляем URL адрес нашей базы, добавляя параметр ?view=All%20employees. В headers мы добавляем параметры авторизации и непосредственно сам API ключ, который мы сгенерировали в 3 части этой статьи, подпункт 4.

      useEffect(() => {
      			fetch("https://api.airtable.com/v0/appWw7KBKSc9bPjZE/Employee%20directory?view=All%20employees", {
      				headers: {
      					'Authorization': 'Bearer YOUR_API_KEY'
      				}
      			})
      			.then(response => response.json())
      			.then(data => setEmployees(data.records.map(({ fields }) => fields)));
      	}, []);
    8. Теперь будем генерировать карточки из полученных данных, передавая им props с данными и override. Он нужен, чтобы выбирать и стилизовать элементы на странице.

      Меняем:

      return <Stack {...rest}>
      		{children}
      	</Stack>;
      };

      на:

      return <Stack {...rest}>
      		{
      			employees.map(employee => <EmployeeCard  {...override("employeeCard")}  employee={employee} />)
      		}
      	</Stack>;
      };
    9. Нажмите Ctrl + S (или Cmd + S на Mac). Окончательный код выглядит так:

      import React, { useEffect, useState } from "react";
      import { useOverrides, Stack } from "@quarkly/components";
      import EmployeeCard from "./EmployeeCard";
      const defaultProps = {
      	"margin-top": "40px"
      };
      const overrides = {};
      
      const EmployeeTable = props => {
      	const {
      		override,
      		rest
      	} = useOverrides(props, overrides, defaultProps);
      
      	const [employees, setEmployees] = useState([]);
      
      	useEffect(() => {
      			fetch("https://api.airtable.com/v0/appWw7KBKSc9bPjZE/Employee%20directory?view=All%20employees", {
      				headers: {
      					'Authorization': 'Bearer YOUR_API_KEY'
      				}
      			})
      			.then(response => response.json())
      			.then(data => setEmployees(data.records.map(({ fields }) => fields)));
      	}, []);
      	
      	return <Stack {...rest}>
      		{
      			employees.map(employee => <EmployeeCard  {...override("employeeCard")} employee={employee} />)
      		}
      	</Stack>;
      };
      
      Object.assign(EmployeeTable, {
      	...Stack,
      	defaultProps,
      	overrides
      });
      export default EmployeeTable;

      Важно: не забудьте вставить свой уникальный API ключ вместо текста YOUR_API_KEY.

    Готово! Теперь мы получаем данные от Airtable, помещаем их в employees и проходимся по нему методом map. На каждую запись в employees мы создаем <EmployeeCard/>, в который передаем как пропс конкретные данные.


    Осталось научить EmpolyeeCard принимать эти данные и показывать их в нужном месте.



    Часть 5. Учим EmpolyeeCard работать с БД


    Здесь мы научим карточку сотрудника принимать данные и показывать их.


    1. Откроем код компонента. Для этого заходим во вкладку Components, ищем там EmployeeCard, наводим курсор и жмем на кнопку <>.
    2. Сейчас код компонента выглядит так:

      import React from "react";
      import { useOverrides, Override, StackItem } from "@quarkly/components";
      import { Box, Text } from "@quarkly/widgets";
      const defaultProps = {
      	"width": "25%",
      	"lg-width": "50%",
      	"sm-width": "100%"
      };
      const overrides = {
      	"box": {
      		"kind": "Box",
      		"props": {
      			"height": "0",
      			"margin": "0 0 20px 0",
      			"padding-bottom": "100%",
      			"background": "url(https://images.unsplash.com/photo-1503443207922-dff7d543fd0e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=582&q=80) 50% 0/cover no-repeat"
      		}
      	},
      	"text": {
      		"kind": "Text",
      		"props": {
      			"color": "--grey",
      			"margin": "0",
      			"children": "CEO"
      		}
      	},
      	"text1": {
      		"kind": "Text",
      		"props": {
      			"as": "h3",
      			"font": "--headline3",
      			"margin": "5px 0 20px 0",
      			"children": "Nathan K. Joe"
      		}
      	},
      	"text2": {
      		"kind": "Text",
      		"props": {
      			"as": "p",
      			"margin": "20px 0 5px 0",
      			"children": "This space is 100% editable. Use it to introduce a team member, describe their work experience and role within the company. This is also a great place to highlight a team member's strong sides."
      		}
      	}
      };
      
      const EmployeeCard = props => {
      	const {
      		override,
      		children,
      		rest
      	} = useOverrides(props, overrides, defaultProps);
      	return <StackItem {...rest}>
      		<Override slot="StackItemContent" flex-direction="column" />
      		<Box {...override("box")} />
      		<Text {...override("text")} />
      		<Text {...override("text1")} />
      		<Text {...override("text2")} />
      		{children}
      	</StackItem>;
      };
      
      Object.assign(EmployeeCard, { ...StackItem,
      	defaultProps,
      	overrides
      });
      export default EmployeeCard;
    3. Ищем строчку:

      } = useOverrides(props, overrides, defaultProps);

      и добавляем ниже:

      const { employee = {} } = rest;

      В объект employee помещаем наши данные.
    4. На примере фотографии сотрудника проверим, что всё работает, как нужно. Ищем строку и меняем:

      <Box {...override("box")} />

      на:

      <Box {...override("box")} background-image={`url(${employee.Photo && employee.Photo[0] && employee.Photo[0].url})`}/>

      Также ищем:

      "background": "url(https://images.unsplash.com/photo-1503443207922-dff7d543fd0e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=582&q=80) 50% 0/cover no-repeat"

      и меняем на:

      "background-size": "cover",
      "background-position": "center",
      "background-image": "url(https://images.unsplash.com/photo-1503443207922-dff7d543fd0e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=582&q=80) 50% 0/cover no-repeat"

      Должно получится так:

    5. Смотрим, какие поля у нас есть. Документация для API в Airtable сделана очень хорошо. Название полей можно посмотреть в https://airtable.com/api, выбрав свою базу.

      Далее ищем раздел EMPLOYEE DIRECTORY TABLE.

      Итак, у нас есть:

      Name
      Department
      Home address
      Email address
      DOB
      Start date
      Phone
      Reports to
      Title
      Status
      Photo
      Location
    6. Добавим Title. Для этого заменим:

      <Text {...override("text")} />

      на:

      <Text {...override("title")} children={employee.Title} />

      И не забудем отредактировать overrides этого компонента, чтобы мы могли его выбирать и редактировать на странице.

      Меняем:

      "text": {
      	"kind": "Text",
      	"props": {
      		"color": "--grey",
      		"margin": "0",
      		"children": "CEO"
      	}
      },

      на:

      "title": {
      	"kind": "Text",
      	"props": {
      		"color": "--grey",
      		"margin": "0",
      		"children": "Title"
      	}
      },

      Сохраняем и проверяем:



      Результат: в карточки добавилась строка с профессией.
    7. Повторим такие же действия для Name и Home address.

      Заменим:

      <Text {...override("text1")} />
      <Text {...override("text2")} />

      на:

      <Text {...override("name")} children={employee.Name} />
      <Text {...override("address")} children={employee['Home address']} />

      И поправим их overrides. Для этого заменим:

      "text1": {
      	"kind": "Text",
      	"props": {
      		"as": "h3",
      		"font": "--headline3",
      		"margin": "5px 0 20px 0",
      		"children": "Nathan K. Joe"
      	}
      },
      "text2": {
      	"kind": "Text",
      	"props": {
      		"as": "p",
      		"margin": "20px 0 5px 0",
      		"children": "This space is 100% editable. Use it to introduce a team member, describe their work experience and role within the company. This is also a great place to highlight a team member's strong sides."
      	}
      }

      на:

      "name": {
      	"kind": "Text",
      	"props": {
      		"as": "h3",
      		"font": "--headline3",
      		"margin": "5px 0 5px 0",
      		"children": "Name"
      	}
      },
      "address": {
      	"kind": "Text",
      	"props": {
      		"as": "p",
      		"margin": "10px 0 5px 0",
      		"children": "Home address"
      	}
      },

      Сохраняем и снова проверяем:

    8. Добавим ещё несколько Text по аналогии. Для простоты мы не будем брать Department и Reports to, потому что эти данные находятся в другой базе DEPARTMENTS TABLE.

      Добавляем:

      <Text {...override("address")} children={employee['Home address']} />
      <Text {...override("Start date")} children={`Start date: ${employee['Start date']}`} />
      <Text {...override("Status")} children={employee['Status']} />
      <Text {...override("DOB")} children={`Birth date: ${employee['DOB']}`} />

      и

      "address": {
      	"kind": "Text",
      	"props": {
      		"as": "p",
      		"margin": "10px 0 5px 0",
      		"children": "Home address"
      	}
      },
      "Start date": {
      	"kind": "Text",
      	"props": {
      		"as": "p",
      		"margin": "10px 0 5px 0",
      		"children": "Start date"
      	
      	}
      },
      "Status": {
      	"kind": "Text",
      	"props": {
      		"as": "p",
      		"margin": "10px 0 5px 0",
      		"children": "Status"
      	}
      },
      "DOB": {
      	"kind": "Text",
      	"props": {
      		"as": "p",
      		"margin": "10px 0 5px 0",
      		"children": "Birth date"
      	}
      },

      Проверяем результат:

    9. Теперь добавим два компонента Link, в которых у нас будут Phone и Email:

      import { Box, Text } from "@quarkly/widgets";

      меняем на:

      import { Box, Text, Link } from "@quarkly/widgets";

      И добавляем следующие строки:

      <Link {...override("Email address")} children={employee['Email address']} href={`mailto:${employee['Email address']}`} />
      <Link {...override("Phone")} children={employee['Phone']} href={`tel:${employee['Phone']}`}/>

      Не забыв про их overrides:

      "Email address": {
      	"kind": "Link",
      	"props": {
      		"margin": "10px 0 5px 0",
      		"color": "--primary",
      		"text-decoration": "none",
      		"children": "Email"
      	}
      },
      "Phone": {
      	"kind": "Link",
      	"props": {
      		"margin": "10px 0 5px 0",
      		"color": "--primary",
      		"text-decoration": "none",
      		"children": "Phone"
      	}
      },

      Проверяем результат:


    Финально наш код выглядит так:

    import React from "react";
    import { useOverrides, Override, StackItem } from "@quarkly/components";
    import { Box, Text, Link } from "@quarkly/widgets";
    const defaultProps = {
    	"width": "25%",
    	"lg-width": "50%",
    	"sm-width": "100%"
    };
    const overrides = {
    	"box": {
    		"kind": "Box",
    		"props": {
    			"height": "0",
    			"margin": "0 0 20px 0",
    			"padding-bottom": "100%",
    			"background-size": "cover",
    			"background-position": "center",
    			"background-image": "url(https://images.unsplash.com/photo-1503443207922-dff7d543fd0e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=582&q=80) 50% 0/cover no-repeat"
    		}
    	},
    	"title": {
    		"kind": "Text",
    		"props": {
    			"color": "--grey",
    			"margin": "0",
    			"children": "title"
    		}
    	},
    	"name": {
    		"kind": "Text",
    		"props": {
    			"as": "h3",
    			"font": "--headline3",
    			"margin": "5px 0 5px 0",
    			"children": "Name"
    		}
    	},
    	"address": {
    		"kind": "Text",
    		"props": {
    			"as": "p",
    			"margin": "10px 0 5px 0",
    			"children": "Home address"
    		}
    	},
    	"Start date": {
    		"kind": "Text",
    		"props": {
    			"as": "p",
    			"margin": "10px 0 5px 0",
    			"children": "Start date"
    		}
    	},
    	"Status": {
    		"kind": "Text",
    		"props": {
    			"as": "p",
    			"margin": "10px 0 5px 0",
    			"children": "Status"
    		}
    	},
    	"DOB": {
    		"kind": "Text",
    		"props": {
    			"as": "p",
    			"margin": "10px 0 5px 0",
    			"children": "Birth date"
    		}
    	},
    	"Email address": {
    		"kind": "Link",
    		"props": {
    			"margin": "10px 0 5px 0",
    			"color": "--primary",
    			"text-decoration": "none",
    			"children": "Email"
    		}
    	},
    	"Phone": {
    		"kind": "Link",
    		"props": {
    			"margin": "10px 0 5px 0",
    			"color": "--primary",
    			"text-decoration": "none",
    			"children": "Phone"
    		}
    	},
    };
    
    const EmployeeCard = props => {
    	const {
    		override,
    		children,
    		rest
    	} = useOverrides(props, overrides, defaultProps);
    	
    		const { employee = {} } = rest;
    
    	
    	return <StackItem {...rest}>
    		<Override slot="StackItemContent" flex-direction="column" />
    		<Box {...override("box")} background-image={`url(${employee.Photo[0].url})`}/>
    		<Text {...override("title")} children={employee.Title} />
    		<Text {...override("name")} children={employee.Name} />
    		<Text {...override("address")} children={employee['Home address']} />
    		<Text {...override("Start date")} children={`Start date: ${employee['Start date']}`} />
    		<Text {...override("Status")} children={employee['Status']} />
    		<Text {...override("DOB")} children={`Birth date: ${employee['DOB']}`} />
    		<Link {...override("Email address")} children={employee['Email address']} href={`mailto:${employee['Email address']}`} />
    		<Link {...override("Phone")} children={employee['Phone']} href={`tel:${employee['Phone']}`}/>
    		{children}
    	</StackItem>;
    };
    
    Object.assign(EmployeeCard, { ...StackItem,
    	defaultProps,
    	overrides
    });
    export default EmployeeCard;

    Делаем коммит в GitHub и публикуем на Netlify:





    Ждем несколько минут и проверяем: https://keen-varahamihira-c54ae1.netlify.app/



    Для проверки синхронизации меняем данные в базе:


    Теперь они появятся в приложении:


    В дальнейшем мы можем как угодно стилизовать наши элементы с карточками, не нарушая настроенный импорт из Airtable. Пример можно посмотреть здесь.


    Репозиторий на GitHub: https://github.com/quarkly-dev/Getting-data-from-Airtable-tutorial


    Спасибо за внимание!


    Если у вас остались вопросы — не стесняйтесь задавать их в комментариях. В следующем уроке рассмотрим ещё один пример работы с данными, покажем как интерактивно их визуализировать, дав возможность пользователю менять фильтры прямо в приложении.

    Quarkly
    Make building your site or app a piece of cake

    Comments 3

      0
      А как служба информационной безопасности относится к хранению данных сотрудников на сторонних ресурсах?
        0
        Я не знаю как ваша служба безопасности относится к хранению данных где-либо и каких-либо.
        Все, очевидно, зависит от внутренних требований компании, от юридических норм вашей страны.
        Если это предложение откатываться в каменный век, отказ от любого SaaS решения в т.ч, то с трудом могу вас понять.
        Если вы говорите, что конкретно для вас такой кейс не применим т.к. данные сотрудников — это супер прайваси и так нельзя. Где-то нельзя, где-то можно. Очевидно, пример интеграции условный. Можете использовать в своих нуждах и хранить в аэиртейблс, Гугл докс и пр только те данные, которые не так чувствительны к прайваси.
        Мы, все же, обсуждаем технологические подходы и реализации в первую и во вторую очередь.
          +1

          СБ Какой страны?


          Думаю у airtable есть свои документы/гарантии по секьюрити/приватности данных. Ну или например, вы же не боитесь хранить данные в amazon rds? И сервера у вас наверное не в кладовке а на амазоне? Так в чем же разница?

        Only users with full accounts can post comments. Log in, please.