Как сделать интерактивную панель для отправки SMS

Сегодня разработаем панель, через которую сможем отправлять SMS сразу нескольким адресатам, получать их ответы и видеть статистику по отправленным сообщениям через календарь и график. Будем использовать Next.js, Shadcn для интерфейса и SMS API от МТС Exolve для отправки сообщений.

Архитектура приложения

Мы создадим приложение, где администратор сможет вводить один или несколько номеров абонентов и текст сообщения и отправлять его одной кнопкой. На сайте реализуем календарь для выбора даты и просмотра всех отправленных SMS за конкретный день, а также график с их количеством.

Подготовка окружения

Разрабатывать будем на фулстек-фреймворке Next.js, который поддерживает серверный и клиентский код на React.js. Задача — создать компонент календаря и графика. Для этого мы обратимся к библиотеке Shadcn с готовыми компонентами, которые настроим под свои нужды. Основу слоя стилей Shadcn составляет Tailwind, и рекомендуется использовать его вместе с TypeScript. 

Если вы ещё не работали с TS — не страшно. Это просто язык статического анализа данных, и, разрабатывая на нём, можно всё так же писать на JS.

Переиспользуем код из другой статьи

В предыдущей статье [ссылка на статью] (её полный код лежит на GitHub) мы уже создали часть нужной функциональности: отправку сообщений и запись данных в БД. Обратите внимание: ранее мы писали на JavaScript, а теперь будем на TypeScript. Главное — следите за расширением файлов, в целом ничего не меняется.

Разворачиваем приложение Next.js

Развернуть приложение Next.js можно с помощью документации на сайте https://nextjs.org. Но мы выбрали Shadcn, и эти ребята тесно сотрудничают с Next.js, поэтому предоставили возможность с помощью одной команды развернуть Next.js с Shadcn и настроить всё под себя. Перейдём в официальный источник https://ui.shadcn.com/docs/installation/next и воспользуемся командой, которая за нас всё сделает. Важно: выбирайте опцию с CSS-переменными и поддержкой TypeScript.

Копирование функциональности из прошлой статьи

Заходим в папку components и скачиваем файл SmsForm.js, переименовываем его в SmsForm.tsx, чтобы продолжить работать в TypeScript. Далее в папке api копируем файлы sendSms.js и messages.js и называем их sendSms.ts и messages.ts. Подключение к базе будет таким же, поэтому качаем в корне проекта файл db.js. Обратите внимание на пути подключения и настройте их под себя. После того как мы изменили расширение на ts, в скопированных файлах подсветятся ошибки TypeScript.

Сам TypeScript не вносит новой функциональности, а занимается только проверкой типов данных для лучшей отладки кода. Его можно настроить в конфигурационном файле tsconfig.json: указать, чтобы TS не подсвечивал ошибки или не проверял код. Также не забудьте скачать файл .env и заполнить его данными для подключения к БД, токеном авторизации МТС Exolve и SMS API для отправки сообщений. Если вы всё сделали правильно, то приложение уже может отправлять сообщения.

Компонент Calendar — календарь с сообщениями

53056261b56ba12273ca032fcc15b857.png

Разработаем функциональность для компонента Calendar из Shadcn. Для начала нужно его скачать: находим в библиотеке и следуем инструкции по установке через CLI. Система сама добавит Calendar к нам в проект, а также все входящие в него компоненты Shadcn.

Не удивляйтесь, если в папке components появятся новые компоненты. Уже на этом этапе можно импортировать calendar на страницу и использовать, но мы так делать не будем, чтобы не захламлять код.

Инкапсулируем логику календаря, вынеся его в свой кастомный компонент. Создадим новый компонент в папке components и назовём его CalendarMessage.tsx. Обратите внимание: по соглашению между программистами для удобства желательно называть компоненты React с большой буквы, хоть и Shadcn по умолчанию именует их с маленькой. Это можно отредактировать в конфигурационном файле. Вот документация по его настройке: https://ui.shadcn.com/docs/components-json.

Логика работы календаря

Логика записи исходящих сообщений в БД у нас уже есть в файле api messages.ts. Нам остаётся «постучаться» к этому API, достать оттуда данные о SMS, отфильтровать по дате, выбранной в нашем календаре, и вывести через map все сообщения с помощью условного рендеринга под компонентом calendar. Обратите внимание, что мы преобразовываем дату к строке для сравнения, и не забывайте, что локаль даты может быть разной, как и часовой пояс. Внимательно следите за преобразованием и часовым поясом: эта статья лишь показывает пример реализации.

Содержимое CalendarMessage.tsx

Вставьте следующий код в компонент CalendarMessage.tsx:

import React, { useEffect, useState } from "react";
import { Calendar } from "./calendar";


// Определение интерфейса для объекта сообщения
interface Message {
  received_at: string; // Дата получения сообщения в формате строки
  sender_number: string; // Номер телефона отправителя
  recipient_numbers: string; // Строка с номерами получателей
  text: string; // Текст сообщения
}


const CalendarMessage: React.FC = () => {
  // Состояние для хранения текущей выбранной даты
  const [date, setDate] = useState(new Date());
  // Состояние для хранения списка сообщений
  const [messages, setMessages] = useState([]);


  // Эта функция для загрузки сообщений из API при монтировании компонента
  useEffect(() => {
    const getMessages = async () => {
      const res = await fetch("/api/messages");
      const data = await res.json();
      setMessages(data); // Сохранение полученных данных в состояние messages
    };
    getMessages();
  }, []);


  // Эта функция обрабатывает выбор даты в календаре
  const handleDateSelect = (newDate: Date | undefined) => {
    if (newDate) {
      setDate(newDate); // Обновление состояния date, если выбрана новая дата
    }
  };


  // Фильтрация сообщений по выбранной дате
  const filteredMessages = messages.filter((message) => {
    const messageDate = new Date(message.received_at).toDateString(); // Преобразование даты сообщения в строку
    return messageDate === date.toDateString(); // Сравнение даты сообщения с выбранной датой
  });


  // Рендер компонента
  return (
    <>
      

Сообщения за {date.toLocaleDateString()}

{filteredMessages.length > 0 ? (
    {filteredMessages.map((message, index) => (
  • Номер отправителя: {message.sender_number}

    Номера получателей:{" "} {message.recipient_numbers}

    Текст сообщения: {message.text}

    Получено:{" "} {new Date(message.received_at).toLocaleString()}

  • ))}
) : (

Нет сообщений за этот день.

// Сообщение, если нет сообщений за выбранный день )}
); }; export default CalendarMessage;

Остаётся импортировать этот компонент на главную страницу index.tsx.

Компонент Chart — график с отправленными SMS

0ef52fae74180cf91cadf0f508752b34.png

Подобно установке календаря через Shadcn давайте установим компонент графика Chart. Так же вынесем логику графика в отдельный файл. Создадим в папке components компонент ChartMessage.tsx.

Логика работы графика для отображения количества SMS

Для реализации графика будем вновь использовать API messages.ts, чтобы достать данные с сообщениями. Запрограммируем график на отображение сегодняшней даты плюс два дня назад и два дня вперёд (вы можете указать любой диапазон и логику). Дальше мы должны получить данные по API: сравниваем их с датами в счётчике и при совпадении записываем в ячейку определённой даты количество сообщений. Не забывайте про часовой пояс! Рендерим компонент и наслаждаемся результатом.

Содержимое ChartMessage.tsx

Вставьте следующий код в компонент ChartMessage.tsx:

import React, { useState, useEffect } from "react";
import { Bar, BarChart, CartesianGrid, XAxis, Tooltip } from "recharts";
import { ChartContainer } from "@/components/ui/chart";


// Типы для сообщений и данных графика
interface Message {
  id: number;
  received_at: string;
}


interface ChartData {
  time: string; // дата в формате YYYY-MM-DD
  sms: number; // количество сообщений
}


// Конфигурация графика
const chartConfig = {
  sms: {
    label: "sms",
    color: "#2563eb",
  },
};


const ChartMessage: React.FC = () => {
  // Состояние для данных графика
  const [chartData, setChartData] = useState([]);


  // Получение данных при монтировании компонента
  useEffect(() => {
    const fetchData = async (): Promise => {
      try {
        const response = await fetch("/api/messages");
        if (!response.ok) {
          throw new Error(`Error fetching messages: ${response.statusText}`);
        }
        const data: Message[] = await response.json();
        updateChartData(data);
      } catch (error) {
        console.error("Error fetching messages:", error);
      }
    };
    fetchData();
  }, []);


  // Функция для обновления данных графика
  const updateChartData = (data: Message[]): void => {
    const today = new Date();
    const counts: Record = {};


    // Генерация диапазона дат для графика
    for (let i = -2; i <= 2; i++) {
      const date = new Date(today);
      date.setDate(date.getDate() + i);
      const dateString = date.toISOString().split("T")[0]; // Преобразование даты в строку YYYY-MM-DD
      counts[dateString] = 0; // Инициализация счетчика для каждой даты
    }


    // Подсчет количества сообщений по датам
    data.forEach((msg) => {
      const msgDate = msg.received_at.split("T")[0];
      if (counts.hasOwnProperty(msgDate)) {
        counts[msgDate]++;
      }
    });


    // Создание данных для графика из счетчиков
    const newChartData: ChartData[] = Object.keys(counts).map((key) => ({
      time: key,
      sms: counts[key],
    }));


    setChartData(newChartData);
  };


  // Отрисовка компонента
  return (
    
); }; export default ChartMessage;

Остаётся импортировать его на главную страницу index.tsx.

Результат

59dcce0fbc1694651ac09244dfa6f339.png

Над дизайном вы при желании поработаете сами, а основные функции мы добавили вместе — можно рассылать SMS и просматривать, что и в каком количестве было отправлено. Мы разместили весь код на GitHub.

Подводные камни компонента Chart

Если в консоли высветится ошибка — это нормально. Уже целый год разработчики серьёзной технологии Shadcn пытаются перейти с пропсов по умолчанию на пропсы кастомных компонентов. Вывод в консоли просто предупреждает программистов, что как только разрабам это удастся сделать, при обновлении, возможно, придётся залезть в код и кое-что допилить. Пожелаем им удачи.

Второй подводный камень — часовой пояс. База данных может иметь часовой пояс и преобразовывать даты по-своему. Можно следить за этим и устанавливать везде равные часовые пояса или преобразовывать дату во время выполнения кода. В компоненте календаря удалось вывести дату как строку, но в компоненте графика это оказалось сложнее: пришлось преобразовывать внутри. Как следствие, компонент календаря и компонент графика выводят сообщения в разных часовых поясах. Для продуктивной среды такой вариант не совсем подойдёт, а вот в качестве статьи очень даже.

Заключение

В статье реализована функциональность работы с абонентом через API-платформу МТС Exolve — это надёжный способ интеграции. В сочетании с готовыми компонентами задача становится вполне подъёмной. Тщательно планируйте логику работы приложения, задавайте ему стили и архитектуру, и у вас всё получится. Также не забывайте про правильную настройку и обработку даты. А если вы ещё не знаете TypeScript, то самое время его изучить — язык не сложный, а пользы от него предостаточно.

© Habrahabr.ru