技術検証用サンプルサイト


このサイトは?お問い合わせフォームの例CSVデータを型安全に扱う

実装ソースコード
import { ArrowDownIcon } from "@yamada-ui/lucide";
import { Markdown } from "@yamada-ui/markdown";
import { Box, Container, Heading, HStack } from "@yamada-ui/react";
import { type Column, Table, type TableProps } from "@yamada-ui/table";
import { readFileSync } from "node:fs";
import path from "node:path";
import papa from "papaparse";
import type { FC } from "react";
import * as v from "valibot";

export const CSVPage = () => {
  const csvFilePath = path.join(process.cwd(), "src/views/csv/sales_data.csv");
  const csvString = readFileSync(csvFilePath, "utf-8");
  const papaResult = papa.parse(csvString, {
    header: true,
    skipEmptyLines: true,
    dynamicTyping: true,
  });
  const validationResult = v.safeParse(v.array(salesDataSchema), papaResult.data);

  return (
    <Container bg="white" roundedTop={0} p={4}>
      <Heading>CSVデータを型安全に扱う</Heading>
      <Markdown>{`\`\`\`csv\n${csvString}\n\`\`\``}</Markdown>
      <HStack justifyContent="center">
        <ArrowDownIcon fontSize="xxx-large" />
      </HStack>
      {validationResult.success ? (
        <Box overflowX="auto" w="full">
          <CSVTable data={validationResult.output} whiteSpace="nowrap" />
        </Box>
      ) : (
        <pre>{JSON.stringify(validationResult.issues, null, 2)}</pre>
      )}
    </Container>
  );
};

const salesDataSchema = v.object({
  itemId: v.string(),
  productName: v.string(),
  category: v.string(),
  unitPrice: v.number(),
  salesCount: v.number(),
  saleDate: v.string(),
});

type SalesData = v.InferOutput<typeof salesDataSchema>;

const CSVTable: FC<{ data: SalesData[] } & Omit<TableProps<SalesData>, "columns">> = ({
  data,
  ...props
}) => {
  const columns: Column<SalesData>[] = [
    {
      header: "商品ID",
      accessorKey: "itemId",
    },
    {
      header: "商品名",
      accessorKey: "productName",
    },
    {
      header: "カテゴリ",
      accessorKey: "category",
    },
    {
      header: "単価",
      accessorKey: "unitPrice",
    },
    {
      header: "販売数",
      accessorKey: "salesCount",
    },
    {
      header: "販売日",
      accessorKey: "saleDate",
    },
  ];
  return <Table {...{ data, columns }} {...props} />;
};