kirin-ri / memo

0 stars 0 forks source link

mock #28

Open kirin-ri opened 3 months ago

kirin-ri commented 3 months ago
(base) q_li@vm-I-DNA-daas-2:~/Desktop/work/catalog-web-app-demo/client$ git push -u origin master
To https://github.com/qmonus-test/mfgmock-catalog-web-app
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'https://github.com/qmonus-test/mfgmock-catalog-web-app'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, ScriptableContext, Title, Tooltip } from 'chart.js';
import { useEffect, useRef, useState } from "react";
import { Chart } from 'react-chartjs-2';

ChartJS.register(CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend);

const CollapsiblePanel = ({ title, money, details }: { title: string; money: string; details: { month: string, amount: string, alert?: boolean }[] }) => {
  const [isOpen, setIsOpen] = useState(false);

  const togglePanel = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div className="collapsible-panel">
      <div className="panel-header" onClick={togglePanel}>
        <div className="panel-title">{title}</div>
        <div className="panel-money">{money}</div>
      </div>
      {isOpen && (
        <div className="panel-content">
          <div className="details-container">
            {details.map((detail, index) => (
              <div key={index} className="detail-item" style={{ color: detail.alert ? 'red' : 'black' }}>
                <span>{detail.month}</span>
                <span>{detail.alert && <i style={{ color: 'red', marginRight: '5px' }} />}</span>
                <span>{detail.amount}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const EmptyPage = () => {
  const test = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true); // 控制警告弹窗的显示
  const chartRef = useRef<ChartJS | null>(null);

  useEffect(() => {
    const chart = chartRef.current;
    if (chart) {
      const ctx = chart.ctx;
      if (ctx && chart.data.datasets[2].data) {
        (chart.data.datasets[2].data as number[]).forEach((value, index) => {
          if (value < 0) {
            const meta = chart.getDatasetMeta(2).data[index];
            if (meta) {
              const x = meta.x;
              const y = meta.y;
              ctx.fillStyle = 'red';
              ctx.font = 'bold 12px Arial';
              ctx.fillText('!', x - 5, y - 10);
            }
          }
        });
      }
    }
  }, []);

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        type: 'bar' as const,
        label: '収入',
        data: [20, 30, 25, 15, 30, 25, 20, 30, 25, 30, 20, 25],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      },
      {
        type: 'bar' as const,
        label: '支出',
        data: [-25, -35, -30, -20, -25, -30, -35, -25, -30, -20, -25, -30],
        backgroundColor: 'rgba(54, 162, 235, 0.5)',
      },
      {
        type: 'line' as const,
        label: '資金繰り',
        data: [10, -5, -10, 5, 10, -15, 20, -10, 15, 5, -5, 10],
        borderColor: 'black',
        backgroundColor: 'black',
        fill: false,
        tension: 0.1,
        borderWidth: 2,
        pointStyle: 'rectRot',
        pointRadius: 6,
        pointHoverRadius: 8,
        pointBackgroundColor: (context: ScriptableContext<'line'>) => {
          const index = context.dataIndex;
          const value = context.dataset.data[index] as number;
          return value < 0 ? 'red' : 'black';
        }
      }
    ],
  };

  const details = [
    { month: '4月', amount: 'XXXXX円' },
    { month: '5月', amount: 'XXXXX円' },
    { month: '6月', amount: 'XXXXX円' },
    { month: '7月', amount: '▲XXXXX円', alert: true },
    { month: '8月', amount: 'XXXXX円' },
    { month: '9月', amount: 'XXXXX円' },
    { month: '10月', amount: 'XXXXX円' },
    { month: '11月', amount: '▲XXXXX円', alert: true },
    { month: '12月', amount: 'XXXXX円' },
    { month: '1月', amount: 'XXXXX円' },
    { month: '2月', amount: '▲XXXXX円', alert: true },
    { month: '3月', amount: 'XXXXX円' }
  ];

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
        labels: {
          font: {
            size: 16 // 修改图例文字大小
          }
        }
      },
      title: {
        display: true,
        text: '期中資金繰り表',
        font: {
          size: 24 // 修改标题文字大小
        }
      },
      tooltip: {
        callbacks: {
          label: function(context: any) {
            const label = context.dataset.label || '';
            const value = context.raw;
            return `${label}: ${value}`;
          }
        }
      },
    },
    scales: {
      y: {
        beginAtZero: true,
      }
    }
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{test}</h1>
        </div>
      </section>
      {/* 警告弹窗 */}
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="期中に資金がマイナスとなる期間があります"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="main-content">
        {/* 資金繰り表のグラフ */}
        <div className="graph-container">
          <Chart ref={chartRef} type="bar" data={data} options={options} />
          {/* データ絞り込み と データ比較 */}
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ絞り込み</h2>
              <div className="filter-group">
                <button className="filter-btn">収入</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <button className="comparison-btn active">時系列比較</button>
              <button className="comparison-btn">製品別比較</button>
              <button className="comparison-btn">顧客別比較</button>
            </div>
          </div>
        </div>
        {/* 可折叠面板 */}
        <div className="collapsible-panels">
          <CollapsiblePanel title="営業キャッシュフロー" money="343,564円" details={details} />
          <CollapsiblePanel title="投資キャッシュフロー" money="435,435円" details={details} />
          <CollapsiblePanel title="財務キャッシュフロー" money="567,232円" details={details} />
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;
kirin-ri commented 3 months ago
.content-wrapper {
  padding: 0; /* 确保没有内边距 */
}

.page-cover {
  margin: 0; /* 确保没有外边距 */
}

.page-cover-title-frame {
  margin: 0; /* 确保没有外边距 */
  padding: 0; /* 确保没有内边距 */
}

.alert-container {
  padding: 0;
  margin: 0;
}

.alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  margin: 0; /* 确保没有外边距 */
  display: flex;
  justify-content: space-between; /* 确保按钮在右侧 */
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: relative;
}

.alert-content {
  display: flex;
  align-items: center;
  justify-content: center; /* 确保内容居中 */
  flex-grow: 1;
}

.alert-icon {
  color: red;
  font-size: 2rem; /* 调整感叹号的大小 */
  margin-right: 10px; /* 感叹号与文字之间的间距 */
}

.alert-message {
  text-align: center; /* 文本居中对齐 */
  font-size: 1.2rem;
}

.close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px; /* 椭圆形 */
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
  margin-left: 10px;
}

.main-content {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  width: 100%;
  margin-top: 20px;
  padding-left: 20px; /* 增加左侧边距,避免图表贴边 */
  box-sizing: border-box; /* 确保内外边距包含在宽度内 */
}

.graph-container {
  flex: 1;
  max-width: 48%; /* 图表宽度 */
  height: auto; /* 图表高度 */
  padding: 20px;
  margin-left: 2%;
  background-color: white;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.collapsible-panels {
  flex: 1;
  max-width: 40%;
  padding: 20px;
  background-color: #f9f9f9;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-right: 5%;
}

.collapsible-panel {
  margin-bottom: 10px;
  width: 100%; /* 确保折叠面板宽度填满容器 */
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  background-color: #f1f1f1;
  cursor: pointer;
  border-radius: 5px;
}

.panel-title {
  font-weight: bold;
  font-size: 1.2rem;
}

.panel-money {
  font-weight: bold;
  margin-left: auto;
  font-size: 1.2rem;
}

.panel-content {
  padding: 10px;
  background-color: #ffffff;
  border: 1px solid #f1f1f1;
  border-radius: 0 0 5px 5px;
}

.details-container {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

.detail-item {
  display: flex;
  align-items: center;
}

.detail-item span {
  margin-right: 5px;
}

/* 新增样式 */
.additional-section {
  display: flex;
  margin-top: 10px;
}

.data-filter, .data-comparison {
  margin-left: 2%;
  height: auto;
  width: 50%;
  padding: 20px;
}

.data-filter h2, .data-comparison h2 {
  margin-bottom: 20px;
  text-align: center;
  font-size: 1.5rem;
}

.filter-group, .comparison-group {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.filter-btn, .comparison-btn {
  width: 45%;
  padding: 10px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 1rem;
}

.filter-select {
  width: 45%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-size: 1rem;
}

.comparison-btn {
  width: 100%;
  margin-bottom: 10px;
}

.comparison-btn.active {
  background-color: #2980b9;
}
kirin-ri commented 3 months ago
.data-filter h2, .data-comparison h2 {
  margin-bottom: 20px;
  text-align: center;
  font-size: 1.2rem; /* 调整字体大小 */
  color: white; /* 白色字体 */
  background-color: #00008B; /* 深蓝色背景 */
  padding: 10px; /* 增加内边距以确保背景颜色显示为长方形 */
  border-radius: 5px; /* 圆角 */
}
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, ScriptableContext, Title, Tooltip } from 'chart.js';
import { useEffect, useRef, useState } from "react";
import { Chart } from 'react-chartjs-2';

ChartJS.register(CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend);

const CollapsiblePanel = ({ title, money, details }: { title: string; money: string; details: { month: string, amount: string, alert?: boolean }[] }) => {
  const [isOpen, setIsOpen] = useState(false);

  const togglePanel = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div className="collapsible-panel">
      <div className="panel-header" onClick={togglePanel}>
        <div className="panel-title">{title}</div>
        <div className="panel-money">{money}</div>
      </div>
      {isOpen && (
        <div className="panel-content">
          <div className="details-container">
            {details.map((detail, index) => (
              <div key={index} className="detail-item" style={{ color: detail.alert ? 'red' : 'black' }}>
                <span>{detail.month}</span>
                <span>{detail.alert && <i style={{ color: 'red', marginRight: '5px' }} />}</span>
                <span>{detail.amount}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const EmptyPage = () => {
  const test = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true); // 控制警告弹窗的显示
  const chartRef = useRef<ChartJS | null>(null);
  const [activeComparison, setActiveComparison] = useState('時系列比較');

  useEffect(() => {
    const chart = chartRef.current;
    if (chart) {
      const ctx = chart.ctx;
      if (ctx && chart.data.datasets[2].data) {
        (chart.data.datasets[2].data as number[]).forEach((value, index) => {
          if (value < 0) {
            const meta = chart.getDatasetMeta(2).data[index];
            if (meta) {
              const x = meta.x;
              const y = meta.y;
              ctx.fillStyle = 'red';
              ctx.font = 'bold 12px Arial';
              ctx.fillText('!', x - 5, y - 10);
            }
          }
        });
      }
    }
  }, []);

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        type: 'bar' as const,
        label: '収入',
        data: [20, 30, 25, 15, 30, 25, 20, 30, 25, 30, 20, 25],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      },
      {
        type: 'bar' as const,
        label: '支出',
        data: [-25, -35, -30, -20, -25, -30, -35, -25, -30, -20, -25, -30],
        backgroundColor: 'rgba(54, 162, 235, 0.5)',
      },
      {
        type: 'line' as const,
        label: '資金繰り',
        data: [10, -5, -10, 5, 10, -15, 20, -10, 15, 5, -5, 10],
        borderColor: 'black',
        backgroundColor: 'black',
        fill: false,
        tension: 0.1,
        borderWidth: 2,
        pointStyle: 'rectRot',
        pointRadius: 6,
        pointHoverRadius: 8,
        pointBackgroundColor: (context: ScriptableContext<'line'>) => {
          const index = context.dataIndex;
          const value = context.dataset.data[index] as number;
          return value < 0 ? 'red' : 'black';
        }
      }
    ],
  };

  const details = [
    { month: '4月', amount: 'XXXXX円' },
    { month: '5月', amount: 'XXXXX円' },
    { month: '6月', amount: 'XXXXX円' },
    { month: '7月', amount: '▲XXXXX円', alert: true },
    { month: '8月', amount: 'XXXXX円' },
    { month: '9月', amount: 'XXXXX円' },
    { month: '10月', amount: 'XXXXX円' },
    { month: '11月', amount: '▲XXXXX円', alert: true },
    { month: '12月', amount: 'XXXXX円' },
    { month: '1月', amount: 'XXXXX円' },
    { month: '2月', amount: '▲XXXXX円', alert: true },
    { month: '3月', amount: 'XXXXX円' }
  ];

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
        labels: {
          font: {
            size: 16 // 修改图例文字大小
          }
        }
      },
      title: {
        display: true,
        text: '期中資金繰り表',
        font: {
          size: 24 // 修改标题文字大小
        }
      },
      tooltip: {
        callbacks: {
          label: function(context: any) {
            const label = context.dataset.label || '';
            const value = context.raw;
            return `${label}: ${value}`;
          }
        }
      },
    },
    scales: {
      y: {
        beginAtZero: true,
      }
    }
  };

  const handleComparisonClick = (type: string) => {
    setActiveComparison(type);
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{test}</h1>
        </div>
      </section>
      {/* 警告弹窗 */}
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="期中に資金がマイナスとなる期間があります"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="main-content">
        {/* 資金繰り表のグラフ */}
        <div className="graph-container">
          <Chart ref={chartRef} type="bar" data={data} options={options} />
          {/* データ絞り込み と データ比較 */}
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ絞り込み</h2>
              <div className="filter-group">
                <button className="filter-btn">収入</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <button
                className={`comparison-btn ${activeComparison === '時系列比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '製品別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('製品別比較')}
              >
                製品別比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '顧客別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('顧客別比較')}
              >
                顧客別比較
              </button>
            </div>
          </div>
        </div>
        {/* 可折叠面板 */}
        <div className="collapsible-panels">
          <CollapsiblePanel title="営業キャッシュフロー" money="343,564円" details={details} />
          <CollapsiblePanel title="投資キャッシュフロー" money="435,435円" details={details} />
          <CollapsiblePanel title="財務キャッシュフロー" money="567,232円" details={details} />
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;
kirin-ri commented 3 months ago
.content-wrapper {
  padding: 0; /* 确保没有内边距 */
}

.page-cover {
  margin: 0; /* 确保没有外边距 */
}

.page-cover-title-frame {
  margin: 0; /* 确保没有外边距 */
  padding: 0; /* 确保没有内边距 */
}

.alert-container {
  padding: 0;
  margin: 0;
}

.alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  margin: 0; /* 确保没有外边距 */
  display: flex;
  justify-content: space-between; /* 确保按钮在右侧 */
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: relative;
}

.alert-content {
  display: flex;
  align-items: center;
  justify-content: center; /* 确保内容居中 */
  flex-grow: 1;
}

.alert-icon {
  color: red;
  font-size: 2rem; /* 调整感叹号的大小 */
  margin-right: 10px; /* 感叹号与文字之间的间距 */
}

.alert-message {
  text-align: center; /* 文本居中对齐 */
  font-size: 1.2rem;
}

.close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px; /* 椭圆形 */
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
  margin-left: 10px;
}

.main-content {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  width: 100%;
  margin-top: 20px;
  padding-left: 20px; /* 增加左侧边距,避免图表贴边 */
  box-sizing: border-box; /* 确保内外边距包含在宽度内 */
}

.graph-container {
  flex: 1;
  max-width: 48%; /* 图表宽度 */
  height: auto; /* 图表高度 */
  padding: 20px;
  margin-left: 2%;
  background-color: white;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.collapsible-panels {
  flex: 1;
  max-width: 40%;
  padding: 20px;
  background-color: #f9f9f9;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-right: 5%;
}

.collapsible-panel {
  margin-bottom: 10px;
  width: 100%; /* 确保折叠面板宽度填满容器 */
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  background-color: #f1f1f1;
  cursor: pointer;
  border-radius: 5px;
}

.panel-title {
  font-weight: bold;
  font-size: 1.2rem;
}

.panel-money {
  font-weight: bold;
  margin-left: auto;
  font-size: 1.2rem;
}

.panel-content {
  padding: 10px;
  background-color: #ffffff;
  border: 1px solid #f1f1f1;
  border-radius: 0 0 5px 5px;
}

.details-container {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

.detail-item {
  display: flex;
  align-items: center;
}

.detail-item span {
  margin-right: 5px;
}

/* 新增样式 */
.additional-section {
  display: flex;
  flex-direction: column; /* 垂直排列 */
  margin-top: 10px;
}

.data-filter, .data-comparison {
  margin-left: 2%;
  height: auto;
  width: 100%; /* 修改为占满整个宽度 */
  padding: 20px;
}

.data-filter h2, .data-comparison h2 {
  margin-bottom: 20px;
  text-align: center;
  font-size: 1.2rem; /* 调整字体大小 */
  color: white; /* 白色字体 */
  background-color: #00008B; /* 深蓝色背景 */
  padding: 10px; /* 增加内边距以确保背景颜色显示为长方形 */
  border-radius: 5px; /* 圆角 */
}

.filter-group, .comparison-group {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.filter-btn, .comparison-btn {
  width: 45%;
  padding: 10px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 1rem;
}

.filter-select {
  width: 45%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-size: 1rem;
}

.comparison-btn {
  width: 100%;
  margin-bottom: 10px;
  background-color: white;
  color: black;
}

.comparison-btn.active {
  background-color: #2980b9;
  color: white;
}
kirin-ri commented 3 months ago
.details-container {
  display: grid;
  grid-template-columns: 1fr 1fr; /* 定义两列布局 */
  gap: 10px;
}

.detail-item {
  display: grid;
  grid-template-columns: 1fr 1fr; /* 定义两列布局 */
  align-items: center;
}

.detail-item span {
  margin-right: 5px;
  text-align: left; /* 确保文本靠左对齐 */
}
kirin-ri commented 3 months ago
import { useEffect } from "react";
import { useLocation } from "react-router-dom";

const ScrollToTop = () => {
  const { pathname } = useLocation();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
};

export default ScrollToTop;
kirin-ri commented 3 months ago
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import ScrollToTop from "./ScrollToTop";
import EmptyPage from "./EmptyPage"; // 你的页面组件

const App = () => {
  return (
    <Router>
      <ScrollToTop />
      <Switch>
        <Route path="/empty" component={EmptyPage} />
        {/* 你的其他路由 */}
      </Switch>
    </Router>
  );
};

export default App;
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, ScriptableContext, Title, Tooltip } from 'chart.js';
import { useEffect, useRef, useState } from "react";
import { Chart } from 'react-chartjs-2';

ChartJS.register(CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend);

const CollapsiblePanel = ({ title, money, details }: { title: string; money: string; details: { month: string, amount: string, alert?: boolean }[] }) => {
  const [isOpen, setIsOpen] = useState(false);

  const togglePanel = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div className="collapsible-panel">
      <div className="panel-header" onClick={togglePanel}>
        <div className="panel-title">{title}</div>
        <div className="panel-money">{money}</div>
      </div>
      {isOpen && (
        <div className="panel-content">
          <div className="details-container">
            {details.map((detail, index) => (
              <div key={index} className="detail-item" style={{ color: detail.alert ? 'red' : 'black' }}>
                <span>{detail.month}</span>
                <span>{detail.alert && <i style={{ color: 'red', marginRight: '5px' }} />}</span>
                <span>{detail.amount}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const EmptyPage = () => {
  const test = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const chartRef = useRef<ChartJS | null>(null);
  const [activeComparison, setActiveComparison] = useState('時系列比較');

  useEffect(() => {
    const chart = chartRef.current;
    if (chart) {
      const ctx = chart.ctx;
      if (ctx && chart.data.datasets[2].data) {
        (chart.data.datasets[2].data as number[]).forEach((value, index) => {
          if (value < 0) {
            const meta = chart.getDatasetMeta(2).data[index];
            if (meta) {
              const x = meta.x;
              const y = meta.y;
              ctx.fillStyle = 'red';
              ctx.font = 'bold 12px Arial';
              ctx.fillText('!', x - 5, y - 10);
            }
          }
        });
      }
    }
  }, []);

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        type: 'bar' as const,
        label: '収入',
        data: [20, 30, 25, 15, 30, 25, 20, 30, 25, 30, 20, 25],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      },
      {
        type: 'bar' as const,
        label: '支出',
        data: [-25, -35, -30, -20, -25, -30, -35, -25, -30, -20, -25, -30],
        backgroundColor: 'rgba(54, 162, 235, 0.5)',
      },
      {
        type: 'line' as const,
        label: '資金繰り',
        data: [10, -5, -10, 5, 10, -15, 20, -10, 15, 5, -5, 10],
        borderColor: 'black',
        backgroundColor: 'black',
        fill: false,
        tension: 0.1,
        borderWidth: 2,
        pointStyle: 'rectRot',
        pointRadius: 6,
        pointHoverRadius: 8,
        pointBackgroundColor: (context: ScriptableContext<'line'>) => {
          const index = context.dataIndex;
          const value = context.dataset.data[index] as number;
          return value < 0 ? 'red' : 'black';
        }
      }
    ],
  };

  const details = [
    { month: '4月', amount: 'XXXXX円' },
    { month: '5月', amount: 'XXXXX円' },
    { month: '6月', amount: 'XXXXX円' },
    { month: '7月', amount: '▲XXXXX円', alert: true },
    { month: '8月', amount: 'XXXXX円' },
    { month: '9月', amount: 'XXXXX円' },
    { month: '10月', amount: 'XXXXX円' },
    { month: '11月', amount: '▲XXXXX円', alert: true },
    { month: '12月', amount: 'XXXXX円' },
    { month: '1月', amount: 'XXXXX円' },
    { month: '2月', amount: '▲XXXXX円', alert: true },
    { month: '3月', amount: 'XXXXX円' }
  ];

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
        labels: {
          font: {
            size: 16
          }
        }
      },
      title: {
        display: true,
        text: '期中資金繰り表',
        font: {
          size: 20
        }
      },
      tooltip: {
        callbacks: {
          label: function(context: any) {
            const label = context.dataset.label || '';
            const value = context.raw;
            return `${label}: ${value}`;
          }
        }
      },
    },
    scales: {
      y: {
        beginAtZero: true,
      }
    }
  };

  const handleComparisonClick = (type: string) => {
    setActiveComparison(type);
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{test}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="期中に資金がマイナスとなる期間があります"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="main-content">
        <div className="left-container">
          <div className="graph-container">
            <Chart ref={chartRef} type="bar" data={data} options={options} />
          </div>
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ絞り込み</h2>
              <div className="filter-group">
                <button className="filter-btn">収入</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <button
                className={`comparison-btn ${activeComparison === '時系列比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '製品別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('製品別比較')}
              >
                製品別比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '顧客別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('顧客別比較')}
              >
                顧客別比較
              </button>
            </div>
          </div>
        </div>
        <div className="collapsible-panels">
          <CollapsiblePanel title="営業キャッシュフロー" money="343,564円" details={details} />
          <CollapsiblePanel title="投資キャッシュフロー" money="435,435円" details={details} />
          <CollapsiblePanel title="財務キャッシュフロー" money="567,232円" details={details} />
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;
kirin-ri commented 3 months ago
.content-wrapper {
  padding: 0; /* 确保没有内边距 */
}

.page-cover {
  margin: 0; /* 确保没有外边距 */
}

.page-cover-title-frame {
  margin: 0; /* 确保没有外边距 */
  padding: 0; /* 确保没有内边距 */
}

.alert-container {
  padding: 0;
  margin: 0;
}

.alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  margin: 0; /* 确保没有外边距 */
  display: flex;
  justify-content: space-between; /* 确保按钮在右侧 */
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: relative;
}

.alert-content {
  display: flex;
  align-items: center;
  justify-content: center; /* 确保内容居中 */
  flex-grow: 1;
}

.alert-icon {
  color: red;
  font-size: 2rem; /* 调整感叹号的大小 */
  margin-right: 10px; /* 感叹号与文字之间的间距 */
}

.alert-message {
  text-align: center; /* 文本居中对齐 */
  font-size: 1.2rem;
}

.close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px; /* 椭圆形 */
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
  margin-left: 10px;
}

.main-content {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  width: 100%;
  margin-top: 20px;
  padding-left: 20px; /* 增加左侧边距,避免图表贴边 */
  box-sizing: border-box; /* 确保内外边距包含在宽度内 */
}

.left-container {
  flex: 1;
  max-width: 48%; /* 图表宽度 */
  height: auto; /* 图表高度 */
  padding: 20px;
  margin-left: 2%;
  border-radius: 5px;
}

.graph-container {
  background-color: #f9f9f9;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 30px;
  border: 1px solid #000;
}

.collapsible-panels {
  flex: 1;
  max-width: 40%;
  padding: 20px;
  background-color: #f9f9f9;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-right: 5%;
  margin-top: 1.5%;
}

.collapsible-panel {
  margin-bottom: 10px;
  width: 100%; /* 确保折叠面板宽度填满容器 */
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  background-color: #f1f1f1;
  cursor: pointer;
  border-radius: 5px;
}

.panel-title {
  font-weight: bold;
  font-size: 1.2rem;
}

.panel-money {
  font-weight: bold;
  margin-left: auto;
  font-size: 1.2rem;
}

.panel-content {
  padding: 10px;
  background-color: #ffffff;
  border: 1px solid #f1f1f1;
  border-radius: 0 0 5px 5px;
}

.details-container {
  display: grid;
  grid-template-columns: 1fr 1fr; /* 定义两列布局 */
  gap: 10px;
}

.detail-item {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr; /* 定义两列布局 */
  align-items: center;
}

.detail-item span {
  margin-right: 5px;
  text-align: left; /* 确保文本靠左对齐 */
  white-space: nowrap;
}

/* 新增样式 */
.additional-section {
  display: flex;
  margin-top: 10px;
}

.data-filter, .data-comparison {
  margin-left: 2%;
  height: auto;
  width: 50%;
  padding: 20px;
}

.data-filter h2, .data-comparison h2 {
  margin-bottom: 20px;
  text-align: center;
  font-size: 1rem; /* 调整字体大小 */
  color: white; /* 白色字体 */
  background-color: var(--sidebar-theme-color); /* 深蓝色背景 */
  padding: 10px; /* 增加内边距以确保背景颜色显示为长方形 */
  border-radius: 5px; /* 圆角 */
}

.filter-group, .comparison-group {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.filter-btn, .comparison-btn {
  width: 30%;
  padding: 10px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 1rem;
}

.filter-select {
  width: 60%;
  padding: 10px;
  border: 1px solid #ccc;
  background-color: white;
  border-radius: 20px;
  font-size: 1rem;
  text-align: center;
}

.comparison-btn {
  width: 100%;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: #f1f1f1;
  color: black;
}

.comparison-btn.active {
  background-color: #3498db;
}
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, ScriptableContext, Title, Tooltip } from 'chart.js';
import { useEffect, useRef, useState } from "react";
import { Chart } from 'react-chartjs-2';

ChartJS.register(CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend);

const CollapsiblePanel = ({ title, money, details }: { title: string; money: string; details: { month: string, amount: string, alert?: boolean }[] }) => {
  const [isOpen, setIsOpen] = useState(false);

  const togglePanel = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div className="collapsible-panel">
      <div className="panel-header" onClick={togglePanel}>
        <div className="panel-title">{title}</div>
        <div className="panel-money">{money}</div>
      </div>
      {isOpen && (
        <div className="panel-content">
          <div className="details-container">
            {details.map((detail, index) => (
              <div key={index} className="detail-item" style={{ color: detail.alert ? 'red' : 'black' }}>
                <span>{detail.month}</span>
                <span>{detail.alert && <i style={{ color: 'red', marginRight: '5px' }} />}</span>
                <span>{detail.amount}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const EmptyPage = () => {
  const test = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const chartRef = useRef<ChartJS | null>(null);
  const [activeComparison, setActiveComparison] = useState('時系列比較');

  useEffect(() => {
    const chart = chartRef.current;
    if (chart) {
      const ctx = chart.ctx;
      if (ctx && chart.data.datasets[2].data) {
        (chart.data.datasets[2].data as number[]).forEach((value, index) => {
          if (value < 0) {
            const meta = chart.getDatasetMeta(2).data[index];
            if (meta) {
              const x = meta.x;
              const y = meta.y;
              ctx.fillStyle = 'red';
              ctx.font = 'bold 12px Arial';
              ctx.fillText('!', x - 5, y - 10);
            }
          }
        });
      }
    }
  }, []);

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        type: 'bar' as const,
        label: '収入',
        data: [20, 30, 25, 15, 30, 25, 20, 30, 25, 30, 20, 25],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      },
      {
        type: 'bar' as const,
        label: '支出',
        data: [-25, -35, -30, -20, -25, -30, -35, -25, -30, -20, -25, -30],
        backgroundColor: 'rgba(54, 162, 235, 0.5)',
      },
      {
        type: 'line' as const,
        label: '資金繰り',
        data: [10, -5, -10, 5, 10, -15, 20, -10, 15, 5, -5, 10],
        borderColor: 'black',
        backgroundColor: 'black',
        fill: false,
        tension: 0.1,
        borderWidth: 2,
        pointStyle: 'rectRot',
        pointRadius: 6,
        pointHoverRadius: 8,
        pointBackgroundColor: (context: ScriptableContext<'line'>) => {
          const index = context.dataIndex;
          const value = context.dataset.data[index] as number;
          return value < 0 ? 'red' : 'black';
        }
      }
    ],
  };

  const details = [
    { month: '4月', amount: 'XXXXX円' },
    { month: '5月', amount: 'XXXXX円' },
    { month: '6月', amount: 'XXXXX円' },
    { month: '7月', amount: '▲XXXXX円', alert: true },
    { month: '8月', amount: 'XXXXX円' },
    { month: '9月', amount: 'XXXXX円' },
    { month: '10月', amount: 'XXXXX円' },
    { month: '11月', amount: '▲XXXXX円', alert: true },
    { month: '12月', amount: 'XXXXX円' },
    { month: '1月', amount: 'XXXXX円' },
    { month: '2月', amount: '▲XXXXX円', alert: true },
    { month: '3月', amount: 'XXXXX円' }
  ];

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
        labels: {
          font: {
            size: 16
          }
        }
      },
      title: {
        display: true,
        text: '期中資金繰り表',
        font: {
          size: 20
        }
      },
      tooltip: {
        callbacks: {
          label: function(context: any) {
            const label = context.dataset.label || '';
            const value = context.raw;
            return `${label}: ${value}`;
          }
        }
      },
    },
    scales: {
      y: {
        beginAtZero: true,
      }
    }
  };

  const handleComparisonClick = (type: string) => {
    setActiveComparison(type);
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{test}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="期中に資金がマイナスとなる期間があります"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="main-content">
        <div className="left-container">
          <div className="graph-container">
            <Chart ref={chartRef} type="bar" data={data} options={options} />
          </div>
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ絞り込み</h2>
              <div className="filter-group">
                <button className="filter-btn">収入</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <button
                className={`comparison-btn ${activeComparison === '時系列比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '製品別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('製品別比較')}
              >
                製品別比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '顧客別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('顧客別比較')}
              >
                顧客別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="title-box">
            <h2>test</h2>
          </div>
          <div className="collapsible-panels">
            <CollapsiblePanel title="営業キャッシュフロー" money="343,564円" details={details} />
            <CollapsiblePanel title="投資キャッシュフロー" money="435,435円" details={details} />
            <CollapsiblePanel title="財務キャッシュフロー" money="567,232円" details={details} />
          </div>
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;

// CSS
.content-wrapper {
  padding: 0; /* 确保没有内边距 */
}

.page-cover {
  margin: 0; /* 确保没有外边距 */
}

.page-cover-title-frame {
  margin: 0; /* 确保没有外边距 */
  padding: 0; /* 确保没有内边距 */
}

.alert-container {
  padding: 0;
  margin: 0;
}

.alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  margin: 0; /* 确保没有外边距 */
  display: flex;
  justify-content: space-between; /* 确保按钮在右侧 */
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: relative;
}

.alert-content {
  display: flex;
  align-items: center;
  justify-content: center; /* 确保内容居中 */
  flex-grow: 1;
}

.alert-icon {
  color: red;
  font-size: 2rem; /* 调整感叹号的大小 */
  margin-right: 10px; /* 感叹号与文字之间的间距 */
}

.alert-message {
  text-align: center; /* 文本居中对齐 */
  font-size: 1.2rem;
}

.close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px; /* 椭圆形 */
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
  margin-left: 10px;
}

.main-content {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  width: 100%;
  margin-top: 20px;
  padding-left: 20px; /* 增加左侧边距,避免图表贴边 */
  box-sizing: border-box; /* 确保内外边距包含在宽度内 */
}

.left-container {
  flex: 1;
  max-width: 48%; /* 图表宽度 */
  height: auto; /* 图表高度 */
  padding: 20px;
  margin-left: 2%;
  border-radius: 5px;
}

.graph-container {
  background-color: #f9f9f9;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 30px;
  border: 1px solid #000;
}

.right-container {
  flex: 1;
  max-width: 48%;
  padding: 20px;
  background-color: #f9f9f9;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-right: 5%;
  margin-top: 1.5%;
}

.title-box {
  text-align: center;
  background-color: #3498db;
  color: white;
  padding: 10px;
  border-radius: 5px;
  margin-bottom: 20px;
}

.collapsible-panels {
  margin-top: 20px;
}

.collapsible-panel {
  margin-bottom: 10px;
  width: 100%; /* 确保折叠面板宽度填满容器 */
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  background-color: #f1f1f1;
  cursor: pointer;
  border-radius: 5px;
}

.panel-title {
  font-weight: bold;
  font-size: 1.2rem;
}

.panel-money {
  font-weight: bold;
  margin-left: auto;
  font-size: 1.2rem;
}

.panel-content {
  padding: 10px;
  background-color: #ffffff;
  border: 1px solid #f1f1f1;
  border-radius: 0 0 5px 5px;
}

.details-container {
  display: grid;
  grid-template-columns: 1fr 1fr; /* 定义两列布局 */
  gap: 10px;
}

.detail-item {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr; /* 定义两列布局 */
  align-items: center;
}

.detail-item span {
  margin-right: 5px;
  text-align: left; /* 确保文本靠左对齐 */
  white-space: nowrap;
}

/* 新增样式 */
.additional-section {
  display: flex;
  margin-top: 10px;
}

.data-filter, .data-comparison {
  margin-left: 2%;
  height: auto;
  width: 50%;
  padding: 20px;
}

.data-filter h2, .data-comparison h2 {
  margin-bottom: 20px;
  text-align: center;
  font-size: 1rem; /* 调整字体大小 */
  color: white; /* 白色字体 */
  background-color: var(--sidebar-theme-color); /* 深蓝色背景 */
  padding: 10px; /* 增加内边距以确保背景颜色显示为长方形 */
  border-radius: 5px; /* 圆角 */
}

.filter-group, .comparison-group {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.filter-btn, .comparison-btn {
  width: 30%;
  padding: 10px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 1rem;
}

.filter-select {
  width: 60%;
  padding: 10px;
  border: 1px solid #ccc;
  background-color: white;
  border-radius: 20px;
  font-size: 1rem;
  text-align: center;
}

.comparison-btn {
  width: 100%;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: #f1f1f1;
  color: black;
}

.comparison-btn.active {
  background-color: #3498db;
}
kirin-ri commented 3 months ago
1. 期間中の資金不足
問題点: 7月、11月、2月の期間において、資金が顕著なマイナスを示しており、これらの月には支出が収入を大幅に上回っています。この状況は会社のキャッシュフローに深刻な影響を与える可能性があります。

対策:

支出管理の強化: これらの月には非必須の支出を延期し、必須支出のみを優先します。また、各部署に支出削減を指示し、コスト削減のための具体的なアクションプランを策定します。

短期借入の検討: 一時的な資金不足を補うために、短期借入を検討します。これにより、資金繰りの安定性を確保します。

収入の増加: 収入の予測と計画を強化し、売上を増加させるためのプロモーション活動や新規顧客の開拓を行います。また、既存顧客からの追加受注を促進するためのインセンティブプランを導入します。

資金フローの予測: 定期的に資金フローを予測し、問題が発生する前に早期に対策を講じることができるようにします。これにより、予期せぬ資金不足を防ぎます。

2. 資金警告システムの導入
問題点: 資金が不足する期間に対して、事前に警告を発するシステムが不足しているため、問題が発生した際に迅速な対応ができていません。

対策:

リアルタイム資金監視: リアルタイムで資金フローを監視するシステムを導入します。これにより、資金不足が発生する前に警告を受け取り、迅速に対応することができます。

定期的な資金レビュー: 月次で資金フローのレビューを行い、予想外の支出や収入の変動に対して早期に対応策を講じます。

これらの対策を実施することで、期間中の資金不足問題を効果的に緩和し、会社の財務健全性と持続可能な発展を確保することができます。
kirin-ri commented 3 months ago
        <div className="right-container">
          <div className="actionbox-title">
            <div>推奨アクション</div>
            <div className='actionbox-message'>message</div>
          </div>
          <div className="collapsible-panels">
            <CollapsiblePanel title="営業キャッシュフロー" money="343,564円" details={details} />
            <CollapsiblePanel title="投資キャッシュフロー" money="435,435円" details={details} />
            <CollapsiblePanel title="財務キャッシュフロー" money="567,232円" details={details} />
          </div>
        </div>
kirin-ri commented 3 months ago
.content-wrapper {
  padding: 0; /* 确保没有内边距 */
}

.page-cover {
  margin: 0; /* 确保没有外边距 */
}

.page-cover-title-frame {
  margin: 0; /* 确保没有外边距 */
  padding: 0; /* 确保没有内边距 */
}

.alert-container {
  padding: 0;
  margin: 0;
}

.alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  margin: 0; /* 确保没有外边距 */
  display: flex;
  justify-content: space-between; /* 确保按钮在右侧 */
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: relative;
}

.alert-content {
  display: flex;
  align-items: center;
  justify-content: center; /* 确保内容居中 */
  flex-grow: 1;
}

.alert-icon {
  color: red;
  font-size: 2rem; /* 调整感叹号的大小 */
  margin-right: 10px; /* 感叹号与文字之间的间距 */
}

.alert-message {
  text-align: center; /* 文本居中对齐 */
  font-size: 1.2rem;
}

.close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px; /* 椭圆形 */
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
  margin-left: 10px;
}

.main-content {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  width: 100%;
  margin-top: 20px;
  padding-left: 20px; /* 增加左侧边距,避免图表贴边 */
  box-sizing: border-box; /* 确保内外边距包含在宽度内 */
}

.left-container {
  flex: 1;
  max-width: 48%; /* 图表宽度 */
  height: auto; /* 图表高度 */
  padding: 20px;
  margin-left: 2%;
  border-radius: 5px;
}

.graph-container {
  background-color: #f9f9f9;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 30px;
  border: 1px solid #000;
}

.right-container {
  flex: 1;
  max-width: 48%;
  padding: 20px;
  border-radius: 5px;
  margin-right: 2%;
}

.actionbox-title {
  text-align: center;
  background-color: var(--sidebar-theme-color); /* 深蓝色背景 */
  border-radius: 5px;
  font-size: 1.2rem;
  color: white;
}

.actionbox-message{
  background-color:  #f1f1f1;
  color: #000;
  font-size: 1rem;
}

.collapsible-panels {
  margin-top: 20px;
  padding: 20px;
  background-color: #f9f9f9;
}

.collapsible-panel {
  margin-bottom: 10px;
  width: 100%; /* 确保折叠面板宽度填满容器 */
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  background-color: #f1f1f1;
  cursor: pointer;
  border-radius: 5px;
}

.panel-title {
  font-weight: bold;
  font-size: 1.2rem;
}

.panel-money {
  font-weight: bold;
  margin-left: auto;
  font-size: 1.2rem;
}

.panel-content {
  padding: 10px;
  background-color: #ffffff;
  border: 1px solid #f1f1f1;
  border-radius: 0 0 5px 5px;
}

.details-container {
  display: grid;
  grid-template-columns: 1fr 1fr; /* 定义两列布局 */
  gap: 10px;
}

.detail-item {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr; /* 定义两列布局 */
  align-items: center;
}

.detail-item span {
  margin-right: 5px;
  text-align: left; /* 确保文本靠左对齐 */
  white-space: nowrap;
}

/* 新增样式 */
.additional-section {
  display: flex;
  margin-top: 10px;
}

.data-filter, .data-comparison {
  margin-left: 2%;
  height: auto;
  width: 50%;
  padding: 20px;
}

.data-filter h2, .data-comparison h2 {
  margin-bottom: 20px;
  text-align: center;
  font-size: 1rem; /* 调整字体大小 */
  color: white; /* 白色字体 */
  background-color: var(--sidebar-theme-color); /* 深蓝色背景 */
  padding: 10px; /* 增加内边距以确保背景颜色显示为长方形 */
  border-radius: 5px; /* 圆角 */
}

.filter-group, .comparison-group {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.filter-btn, .comparison-btn {
  width: 30%;
  padding: 10px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 1rem;
}

.filter-select {
  width: 60%;
  padding: 10px;
  border: 1px solid #ccc;
  background-color: white;
  border-radius: 20px;
  font-size: 1rem;
  text-align: center;
}

.comparison-btn {
  width: 100%;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: #f1f1f1;
  color: black;
}

.comparison-btn.active {
  background-color: #3498db;
}
kirin-ri commented 3 months ago
.actionbox-message::-webkit-scrollbar {
  width: 12px; /* 滚动条宽度 */
}

.actionbox-message::-webkit-scrollbar-track {
  background: #f1f1f1; /* 滚动条轨道颜色 */
  border-radius: 10px;
}

.actionbox-message::-webkit-scrollbar-thumb {
  background-color: #888; /* 滚动条颜色 */
  border-radius: 10px; /* 滚动条圆角 */
  border: 3px solid #f1f1f1; /* 滚动条周围的空白和颜色 */
}

.actionbox-message::-webkit-scrollbar-thumb:hover {
  background: #555; /* 鼠标悬停时滚动条颜色 */
}``
kirin-ri commented 3 months ago

在庫回転率設計

kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const NewPage = () => {
  const [comparisonType, setComparisonType] = useState('時系列比較');

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        label: '全体',
        data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      }
    ],
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top',
      },
      title: {
        display: true,
        text: '全体'
      }
    }
  };

  return (
    <div className="content-wrapper">
      <div className="alert-container">
        <div className="alert-box">
          <i className="alert-icon">!</i>
          <div className="alert-message">B製品の在庫回転率が悪化しています</div>
          <button className="close-btn">非表示</button>
        </div>
      </div>
      <div className="main-content">
        <div className="left-container">
          <div className="graph-container">
            <Chart type="bar" data={data} options={options} />
          </div>
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ絞り込み</h2>
              <div className="filter-group">
                <button className="filter-btn">製品</button>
                <select className="filter-select">
                  <option>全体</option>
                  {/* Add more options as needed */}
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">期間</button>
                <div className="date-range">
                  <input type="date" className="filter-date" />
                  <span>〜</span>
                  <input type="date" className="filter-date" />
                </div>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <button
                className={`comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            7月にキャッシュがマイナスとなっております。在庫の増加が想定されるため・・・
          </div>
        </div>
      </div>
      <div className="metrics-section">
        <div className="metrics-item">
          <div className="metrics-label">期間在庫回転率</div>
          <div className="metrics-value">XX回</div>
        </div>
        <div className="metrics-item">
          <div className="metrics-label">期間売上原価</div>
          <div className="metrics-value">XXXX万円</div>
        </div>
        <div className="metrics-item">
          <div className="metrics-label">期間平均在庫金額</div>
          <div className="metrics-value">XXXX万円</div>
        </div>
      </div>
    </div>
  );
};

export default NewPage;
kirin-ri commented 3 months ago
ERROR in src/components/pages/inventoryTurnsPage.tsx:43:43
TS2322: Type '{ responsive: boolean; plugins: { legend: { position: string; }; title: { display: boolean; text: string; }; }; }' is not assignable to type '_DeepPartialObject<CoreChartOptions<"bar"> & ElementChartOptions<"bar"> & PluginChartOptions<"bar"> & DatasetChartOptions<"bar"> & ScaleChartOptions<...> & BarControllerChartOptions>'.
  The types of 'plugins.legend.position' are incompatible between these types.
    Type 'string' is not assignable to type '"top" | "left" | "right" | "bottom" | "center" | "chartArea" | _DeepPartialObject<{ [scaleId: string]: number; }> | undefined'.
    41 |         <div className="left-container">
    42 |           <div className="graph-container">
  > 43 |             <Chart type="bar" data={data} options={options} />
       |                                           ^^^^^^^
    44 |           </div>
    45 |           <div className="additional-section">
    46 |             <div className="data-filter">
kirin-ri commented 3 months ago
.content-wrapper {
  padding: 0;
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.alert-container {
  padding: 10px;
}

.alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.alert-icon {
  color: red;
  font-size: 2rem;
  margin-right: 10px;
}

.alert-message {
  text-align: center;
  font-size: 1.2rem;
}

.close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px;
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
}

.main-content {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  width: 100%;
  flex-grow: 1;
  padding: 20px;
  box-sizing: border-box;
}

.left-container {
  flex: 1;
  max-width: 48%;
  padding: 20px;
  border-radius: 5px;
}

.graph-container {
  background-color: #f9f9f9;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 30px;
  border: 1px solid #000;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.right-container {
  flex: 1;
  max-width: 48%;
  padding: 20px;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  background-color: #f9f9f9;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.actionbox-title {
  text-align: center;
  background-color: var(--sidebar-theme-color);
  border-radius: 5px;
  font-size: 1.2rem;
  color: white;
  padding: 10px;
}

.actionbox-message {
  background-color: #f1f1f1;
  color: #000;
  font-size: 1rem;
  flex-grow: 1;
  overflow-y: auto;
  padding: 10px;
  border-radius: 5px;
  margin-top: 10px;
}

.metrics-section {
  display: flex;
  justify-content: space-around;
  padding: 20px;
  background-color: #f9f9f9;
}

.metrics-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 30%;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  background-color: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.metrics-label {
  font-weight: bold;
}

.metrics-value {
  font-size: 1.2rem;
}
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const NewPage = () => {
  const [comparisonType, setComparisonType] = useState('時系列比較');

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        label: '全体',
        data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      }
    ],
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const, // 将 position 指定为 'top'
      },
      title: {
        display: true,
        text: '全体'
      }
    }
  };

  return (
    <div className="content-wrapper">
      <div className="alert-container">
        <div className="alert-box">
          <i className="alert-icon">!</i>
          <div className="alert-message">B製品の在庫回転率が悪化しています</div>
          <button className="close-btn">非表示</button>
        </div>
      </div>
      <div className="main-content">
        <div className="left-container">
          <div className="graph-container">
            <Chart type="bar" data={data} options={options} />
          </div>
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ絞り込み</h2>
              <div className="filter-group">
                <button className="filter-btn">製品</button>
                <select className="filter-select">
                  <option>全体</option>
                  {/* Add more options as needed */}
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">期間</button>
                <div className="date-range">
                  <input type="date" className="filter-date" />
                  <span>〜</span>
                  <input type="date" className="filter-date" />
                </div>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <button
                className={`comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            7月にキャッシュがマイナスとなっております。在庫の増加が想定されるため・・・
          </div>
        </div>
      </div>
      <div className="metrics-section">
        <div className="metrics-item">
          <div className="metrics-label">期間在庫回転率</div>
          <div className="metrics-value">XX回</div>
        </div>
        <div className="metrics-item">
          <div className="metrics-label">期間売上原価</div>
          <div className="metrics-value">XXXX万円</div>
        </div>
        <div className="metrics-item">
          <div className="metrics-label">期間平均在庫金額</div>
          <div className="metrics-value">XXXX万円</div>
        </div>
      </div>
    </div>
  );
};

export default NewPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const [comparisonType, setComparisonType] = useState('時系列比較');

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        label: '全体',
        data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      }
    ],
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      },
      title: {
        display: true,
        text: '全体'
      }
    }
  };

  return (
    <div className="inventory-wrapper">
      <div className="inventory-alert-container">
        <div className="inventory-alert-box">
          <i className="inventory-alert-icon">!</i>
          <div className="inventory-alert-message">B製品の在庫回転率が悪化しています</div>
          <button className="inventory-close-btn">非表示</button>
        </div>
      </div>
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={data} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select className="inventory-filter-select">
                  <option>全体</option>
                  {/* Add more options as needed */}
                </select>
              </div>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input type="date" className="inventory-filter-date" />
                  <span>〜</span>
                  <input type="date" className="inventory-filter-date" />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="inventory-right-container">
          <div className="inventory-actionbox-title">推奨アクション</div>
          <div className="inventory-actionbox-message">
            7月にキャッシュがマイナスとなっております。在庫の増加が想定されるため・・・
          </div>
        </div>
      </div>
      <div className="inventory-metrics-section">
        <div className="inventory-metrics-item">
          <div className="inventory-metrics-label">期間在庫回転率</div>
          <div className="inventory-metrics-value">XX回</div>
        </div>
        <div className="inventory-metrics-item">
          <div className="inventory-metrics-label">期間売上原価</div>
          <div className="inventory-metrics-value">XXXX万円</div>
        </div>
        <div className="inventory-metrics-item">
          <div className="inventory-metrics-label">期間平均在庫金額</div>
          <div className="inventory-metrics-value">XXXX万円</div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
.inventory-wrapper {
  padding: 0;
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.inventory-alert-container {
  padding: 10px;
}

.inventory-alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.inventory-alert-icon {
  color: red;
  font-size: 2rem;
  margin-right: 10px;
}

.inventory-alert-message {
  text-align: center;
  font-size: 1.2rem;
}

.inventory-close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px;
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
}

.inventory-main-content {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  width: 100%;
  flex-grow: 1;
  padding: 20px;
  box-sizing: border-box;
}

.inventory-left-container {
  flex: 1;
  max-width: 48%;
  padding: 20px;
  border-radius: 5px;
}

.inventory-graph-container {
  background-color: #f9f9f9;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 30px;
  border: 1px solid #000;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.inventory-right-container {
  flex: 1;
  max-width: 48%;
  padding: 20px;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  background-color: #f9f9f9;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.inventory-actionbox-title {
  text-align: center;
  background-color: var(--sidebar-theme-color);
  border-radius: 5px;
  font-size: 1.2rem;
  color: white;
  padding: 10px;
}

.inventory-actionbox-message {
  background-color: #f1f1f1;
  color: #000;
  font-size: 1rem;
  flex-grow: 1;
  overflow-y: auto;
  padding: 10px;
  border-radius: 5px;
  margin-top: 10px;
}

.inventory-metrics-section {
  display: flex;
  justify-content: space-around;
  padding: 20px;
  background-color: #f9f9f9;
}

.inventory-metrics-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 30%;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  background-color: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.inventory-metrics-label {
  font-weight: bold;
}

.inventory-metrics-value {
  font-size: 1.2rem;
}

.inventory-additional-section {
  display: flex;
  margin-top: 10px;
}

.inventory-data-filter, .inventory-data-comparison {
  margin-left: 2%;
  height: auto;
  width: 50%;
  padding: 20px;
}

.inventory-data-filter h2, .inventory-data-comparison h2 {
  margin-bottom: 20px;
  text-align: center;
  font-size: 1rem;
  color: white;
  background-color: var(--sidebar-theme-color);
  padding: 10px;
  border-radius: 5px;
}

.inventory-filter-group, .inventory-comparison-group {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.inventory-filter-btn, .inventory-comparison-btn {
  width: 30%;
  padding: 10px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 1rem;
}

.inventory-filter-select {
  width: 60%;
  padding: 10px;
  border: 1px solid #ccc;
  background-color: white;
  border-radius: 20px;
  font-size: 1rem;
  text-align: center;
}

.inventory-filter-date {
  width: 45%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

.inventory-comparison-btn {
  width: 100%;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: #f1f1f1;
  color: black;
}

.inventory-comparison-btn.active {
  background-color: #3498db;
}
kirin-ri commented 3 months ago
backgroundColor: [
          'rgba(153, 102, 255, 0.5)', // 4月
          'rgba(153, 102, 255, 0.5)', // 5月
          'rgba(153, 102, 255, 0.5)', // 6月
          'rgba(153, 102, 255, 0.5)', // 7月
          'rgba(255, 206, 86, 0.5)',  // 8月 黄色
          'rgba(153, 102, 255, 0.5)', // 9月
          'rgba(153, 102, 255, 0.5)', // 10月
          'rgba(153, 102, 255, 0.5)', // 11月
          'rgba(153, 102, 255, 0.5)', // 12月
          'rgba(255, 206, 86, 0.5)',  // 1月 黄色
          'rgba(153, 102, 255, 0.5)', // 2月
          'rgba(153, 102, 255, 0.5)', // 3月
        ],
kirin-ri commented 3 months ago
問題点:
B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と1月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。

対策:

需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。
販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。
サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。
在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。
これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。

可以将这段话插入到页面的适当位置。以下是更新后的 JSX 代码,包含这段话:

JSX:

jsx
复制代码
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const [comparisonType, setComparisonType] = useState('時系列比較');

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        label: '全体',
        data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
        backgroundColor: [
          'rgba(153, 102, 255, 0.5)', // 4月
          'rgba(153, 102, 255, 0.5)', // 5月
          'rgba(153, 102, 255, 0.5)', // 6月
          'rgba(153, 102, 255, 0.5)', // 7月
          'rgba(255, 206, 86, 0.5)',  // 8月 黄色
          'rgba(153, 102, 255, 0.5)', // 9月
          'rgba(153, 102, 255, 0.5)', // 10月
          'rgba(153, 102, 255, 0.5)', // 11月
          'rgba(153, 102, 255, 0.5)', // 12月
          'rgba(255, 206, 86, 0.5)',  // 1月 黄色
          'rgba(153, 102, 255, 0.5)', // 2月
          'rgba(153, 102, 255, 0.5)', // 3月
        ],
      }
    ],
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      },
      title: {
        display: true,
        text: '全体'
      }
    }
  };

  return (
    <div className="inventory-wrapper">
      <div className="inventory-alert-container">
        <div className="inventory-alert-box">
          <i className="inventory-alert-icon">!</i>
          <div className="inventory-alert-message">B製品の在庫回転率が悪化しています</div>
          <button className="inventory-close-btn">非表示</button>
        </div>
      </div>
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={data} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select className="inventory-filter-select">
                  <option>全体</option>
                  {/* Add more options as needed */}
                </select>
              </div>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input type="date" className="inventory-filter-date" />
                  <span>〜</span>
                  <input type="date" className="inventory-filter-date" />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="inventory-right-container">
          <div className="inventory-actionbox-title">推奨アクション</div>
          <div className="inventory-actionbox-message">
            7月にキャッシュがマイナスとなっております。在庫の増加が想定されるため・・・
          </div>
        </div>
      </div>
      <div className="inventory-details-section">
        <div className="inventory-details-title">B製品の在庫回転率が悪化している問題と対策</div>
        <div className="inventory-details-content">
          <p><strong>問題点:</strong></p>
          <p>B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と1月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。</p>
          <p><strong>対策:</strong></p>
          <ol>
            <li>需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。</li>
            <li>販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。</li>
            <li>サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。</li>
            <li>在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。</li>
          </ol>
          <p>これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。</p>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
CSS:

css
复制代码
.inventory-wrapper {
  padding: 0;
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.inventory-alert-container {
  padding: 10px;
}

.inventory-alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.inventory-alert-icon {
  color: red;
  font-size: 2rem;
  margin-right: 10px;
}

.inventory-alert-message {
  text-align: center;
  font-size: 1.2rem;
}

.inventory-close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px;
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
}

.inventory-main-content {
  display: flex;
  justify-content
kirin-ri commented 3 months ago
問題点:
B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と1月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。

対策:

需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。
販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。
サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。
在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。
これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。

可以将这段话插入到页面的适当位置。以下是更新后的 JSX 代码,包含这段话:

JSX:

jsx
复制代码
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const [comparisonType, setComparisonType] = useState('時系列比較');

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        label: '全体',
        data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
        backgroundColor: [
          'rgba(153, 102, 255, 0.5)', // 4月
          'rgba(153, 102, 255, 0.5)', // 5月
          'rgba(153, 102, 255, 0.5)', // 6月
          'rgba(153, 102, 255, 0.5)', // 7月
          'rgba(255, 206, 86, 0.5)',  // 8月 黄色
          'rgba(153, 102, 255, 0.5)', // 9月
          'rgba(153, 102, 255, 0.5)', // 10月
          'rgba(153, 102, 255, 0.5)', // 11月
          'rgba(153, 102, 255, 0.5)', // 12月
          'rgba(255, 206, 86, 0.5)',  // 1月 黄色
          'rgba(153, 102, 255, 0.5)', // 2月
          'rgba(153, 102, 255, 0.5)', // 3月
        ],
      }
    ],
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      },
      title: {
        display: true,
        text: '全体'
      }
    }
  };

  return (
    <div className="inventory-wrapper">
      <div className="inventory-alert-container">
        <div className="inventory-alert-box">
          <i className="inventory-alert-icon">!</i>
          <div className="inventory-alert-message">B製品の在庫回転率が悪化しています</div>
          <button className="inventory-close-btn">非表示</button>
        </div>
      </div>
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={data} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select className="inventory-filter-select">
                  <option>全体</option>
                  {/* Add more options as needed */}
                </select>
              </div>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input type="date" className="inventory-filter-date" />
                  <span>〜</span>
                  <input type="date" className="inventory-filter-date" />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="inventory-right-container">
          <div className="inventory-actionbox-title">推奨アクション</div>
          <div className="inventory-actionbox-message">
            7月にキャッシュがマイナスとなっております。在庫の増加が想定されるため・・・
          </div>
        </div>
      </div>
      <div className="inventory-details-section">
        <div className="inventory-details-title">B製品の在庫回転率が悪化している問題と対策</div>
        <div className="inventory-details-content">
          <p><strong>問題点:</strong></p>
          <p>B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と1月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。</p>
          <p><strong>対策:</strong></p>
          <ol>
            <li>需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。</li>
            <li>販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。</li>
            <li>サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。</li>
            <li>在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。</li>
          </ol>
          <p>これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。</p>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
CSS:

css
复制代码
.inventory-wrapper {
  padding: 0;
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.inventory-alert-container {
  padding: 10px;
}

.inventory-alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.inventory-alert-icon {
  color: red;
  font-size: 2rem;
  margin-right: 10px;
}

.inventory-alert-message {
  text-align: center;
  font-size: 1.2rem;
}

.inventory-close-btn {
  background-color: transparent;
  border: 1px solid red;
  border-radius: 15px;
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px 10px;
}

.inventory-main-content {
  display: flex;
  justify-content
kirin-ri commented 3 months ago
問題点:
B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と1月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。

対策:

需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。
販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。
サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。
在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。
これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。
kirin-ri commented 3 months ago
                  <input 
                    type="date" 
                    className="inventory-filter-date" 
                    value={startDate} 
                    onChange={handleStartDateChange} 
                    placeholder="-" 
                  />
                  <span>〜</span>
                  <input 
                    type="date" 
                    className="inventory-filter-date" 
                    value={endDate} 
                    onChange={handleEndDateChange} 
                    placeholder="-" 
                  />
kirin-ri commented 3 months ago
 const [startDate, setStartDate] = useState('');
  const [endDate, setEndDate] = useState('');

  const handleStartDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setStartDate(e.target.value);
  };

  const handleEndDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setEndDate(e.target.value);
  };
kirin-ri commented 3 months ago
import { useState } from 'react';

const CollapsiblePanel = ({ title, money, details, isOpen, onToggle }: { title: string; money: string; details: { month: string, amount: string, alert?: boolean }[], isOpen: boolean, onToggle: () => void }) => {
  return (
    <div className="collapsible-panel">
      <div className="panel-header" onClick={onToggle}>
        <div className="panel-title">{title}</div>
        <div className="panel-money">{money}</div>
      </div>
      {isOpen && (
        <div className="panel-content">
          <div className="details-container">
            {details.map((detail, index) => (
              <div key={index} className="detail-item" style={{ color: detail.alert ? 'red' : 'black' }}>
                <span>{detail.month}</span>
                <span>{detail.alert && <i style={{ color: 'red', marginRight: '5px' }} />}</span>
                <span>{detail.amount}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const InventoryTurnsPage = () => {
  const [openPanel, setOpenPanel] = useState<number | null>(null);

  const handleTogglePanel = (panelIndex: number) => {
    setOpenPanel(openPanel === panelIndex ? null : panelIndex);
  };

  const details = [
    { month: '4月', amount: 'XXXXX円' },
    { month: '5月', amount: 'XXXXX円' },
    { month: '6月', amount: 'XXXXX円' },
    { month: '7月', amount: '▲XXXXX円', alert: true },
    { month: '8月', amount: 'XXXXX円' },
    { month: '9月', amount: 'XXXXX円' },
    { month: '10月', amount: 'XXXXX円' },
    { month: '11月', amount: '▲XXXXX円', alert: true },
    { month: '12月', amount: 'XXXXX円' },
    { month: '1月', amount: 'XXXXX円' },
    { month: '2月', amount: '▲XXXXX円', alert: true },
    { month: '3月', amount: 'XXXXX円' }
  ];

  return (
    <div className="inventory-main-content">
      <div className="inventory-right-container">
        <div className="actionbox-title">推奨アクション</div>
        <div className="actionbox-message">
          7月にキャッシュがマイナスとなっております。在庫の増加が想定されるため・・・
        </div>
        <div className="collapsible-panels">
          <CollapsiblePanel title="営業キャッシュフロー" money="343,564円" details={details} isOpen={openPanel === 0} onToggle={() => handleTogglePanel(0)} />
          <CollapsiblePanel title="投資キャッシュフロー" money="435,435円" details={details} isOpen={openPanel === 1} onToggle={() => handleTogglePanel(1)} />
          <CollapsiblePanel title="財務キャッシュフロー" money="567,232円" details={details} isOpen={openPanel === 2} onToggle={() => handleTogglePanel(2)} />
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, ScriptableContext, Title, Tooltip } from 'chart.js';
import { useEffect, useRef, useState } from "react";
import { Chart } from 'react-chartjs-2';

ChartJS.register(CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend);

const CollapsiblePanel = ({ title, money, details }: { title: string; money: string; details: { month: string, amount: string, alert?: boolean }[] }) => {
  const [isOpen, setIsOpen] = useState(false);

  const togglePanel = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div className="collapsible-panel">
      <div className="panel-header" onClick={togglePanel}>
        <div className="panel-title">{title}</div>
        <div className="panel-money">{money}</div>
      </div>
      {isOpen && (
        <div className="panel-content">
          <div className="details-container">
            {details.map((detail, index) => (
              <div key={index} className="detail-item" style={{ color: detail.alert ? 'red' : 'black' }}>
                <span>{detail.month}</span>
                <span>{detail.alert && <i style={{ color: 'red', marginRight: '5px' }} />}</span>
                <span>{detail.amount}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const EmptyPage = () => {
  const pageName = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const chartRef = useRef<ChartJS | null>(null);
  const [activeComparison, setActiveComparison] = useState('時系列比較');

  useEffect(() => {
    const chart = chartRef.current;
    if (chart) {
      const ctx = chart.ctx;
      if (ctx && chart.data.datasets[2].data) {
        (chart.data.datasets[2].data as number[]).forEach((value, index) => {
          if (value < 0) {
            const meta = chart.getDatasetMeta(2).data[index];
            if (meta) {
              const x = meta.x;
              const y = meta.y;
              ctx.fillStyle = 'red';
              ctx.font = 'bold 12px Arial';
              ctx.fillText('!', x - 5, y - 10);
            }
          }
        });
      }
    }
  }, []);

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        type: 'bar' as const,
        label: '収入',
        data: [20, 30, 25, 15, 30, 25, 20, 30, 25, 30, 20, 25],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      },
      {
        type: 'bar' as const,
        label: '支出',
        data: [-25, -35, -30, -20, -25, -30, -35, -25, -30, -20, -25, -30],
        backgroundColor: 'rgba(54, 162, 235, 0.5)',
      },
      {
        type: 'line' as const,
        label: '資金繰り',
        data: [10, -5, -10, 5, 10, -15, 20, -10, 15, 5, -5, 10],
        borderColor: 'black',
        backgroundColor: 'black',
        fill: false,
        tension: 0.1,
        borderWidth: 2,
        pointStyle: 'rectRot',
        pointRadius: 6,
        pointHoverRadius: 8,
        pointBackgroundColor: (context: ScriptableContext<'line'>) => {
          const index = context.dataIndex;
          const value = context.dataset.data[index] as number;
          return value < 0 ? 'red' : 'black';
        }
      }
    ],
  };

  const details = [
    { month: '4月', amount: 'XXXXX円' },
    { month: '5月', amount: '▲XXXXX円', alert: true },
    { month: '6月', amount: '▲XXXXX円', alert: true },
    { month: '7月', amount: 'XXXXX円' },
    { month: '8月', amount: 'XXXXX円' },
    { month: '9月', amount: '▲XXXXX円', alert: true },
    { month: '10月', amount: 'XXXXX円' },
    { month: '11月', amount: '▲XXXXX円', alert: true },
    { month: '12月', amount: 'XXXXX円' },
    { month: '1月', amount: 'XXXXX円' },
    { month: '2月', amount: 'XXXXX円' },
    { month: '3月', amount: '▲XXXXX円', alert: true }
  ];

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
        labels: {
          font: {
            size: 16
          }
        }
      },
      title: {
        display: true,
        text: '期中資金繰り表',
        font: {
          size: 20
        }
      },
      tooltip: {
        callbacks: {
          label: function(context: any) {
            const label = context.dataset.label || '';
            const value = context.raw;
            return `${label}: ${value}`;
          }
        }
      },
    },
    scales: {
      y: {
        beginAtZero: true,
      }
    }
  };

  const handleComparisonClick = (type: string) => {
    setActiveComparison(type);
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="期中に資金がマイナスとなる期間があります"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="main-content">
        <div className="left-container">
          <div className="graph-container">
            <Chart ref={chartRef} type="bar" data={data} options={options} />
          </div>
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ絞り込み</h2>
              <div className="filter-group">
                <button className="filter-btn">収入</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <button
                className={`comparison-btn ${activeComparison === '時系列比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '製品別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('製品別比較')}
              >
                製品別比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '顧客別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('顧客別比較')}
              >
                顧客別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">
            <div>推奨アクション</div>
          </div>
          <div className='actionbox-message'>
            <h6>問題点:</h6> 5月、6月、9月、11月、3月の期間において、資金が顕著なマイナスを示しており、これらの月には支出が収入を大幅に上回っています。この状況は会社のキャッシュフローに深刻な影響を与える可能性があります。<br></br><br></br>
            <h6>対策:</h6> 支出管理の強化: これらの月には非必須の支出を延期し、必須支出のみを優先します。また、各部署に支出削減を指示し、コスト削減のための具体的なアクションプランを策定します。<br></br><br></br>
            ①短期借入の検討: 一時的な資金不足を補うために、短期借入を検討します。これにより、資金繰りの安定性を確保します。<br></br>
            ②収入の増加: 収入の予測と計画を強化し、売上を増加させるためのプロモーション活動や新規顧客の開拓を行います。また、既存顧客からの追加受注を促進するためのインセンティブプランを導入します。<br></br>
            ③資金フローの予測: 定期的に資金フローを予測し、問題が発生する前に早期に対策を講じることができるようにします。これにより、予期せぬ資金不足を防ぎます。</div>
          <div className="collapsible-panels">
            <CollapsiblePanel title="営業キャッシュフロー" money="343,564円" details={details} />
            <CollapsiblePanel title="投資キャッシュフロー" money="435,435円" details={details} />
            <CollapsiblePanel title="財務キャッシュフロー" money="567,232円" details={details} />
          </div>
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, LineElement, PointElement, ScriptableContext, Title, Tooltip } from 'chart.js';
import { useEffect, useRef, useState } from "react";
import { Chart } from 'react-chartjs-2';

ChartJS.register(CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend);

const CollapsiblePanel = ({ title, money, details, isOpen, onToggle }: { title: string; money: string; details: { month: string, amount: string, alert?: boolean }[], isOpen: boolean, onToggle: () => void }) => {
  return (
    <div className="collapsible-panel">
      <div className="panel-header" onClick={onToggle}>
        <div className="panel-title">{title}</div>
        <div className="panel-money">{money}</div>
      </div>
      {isOpen && (
        <div className="panel-content">
          <div className="details-container">
            {details.map((detail, index) => (
              <div key={index} className="detail-item" style={{ color: detail.alert ? 'red' : 'black' }}>
                <span>{detail.month}</span>
                <span>{detail.alert && <i style={{ color: 'red', marginRight: '5px' }} />}</span>
                <span>{detail.amount}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const EmptyPage = () => {
  const pageName = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const chartRef = useRef<ChartJS | null>(null);
  const [activeComparison, setActiveComparison] = useState('時系列比較');
  const [openPanel, setOpenPanel] = useState<number | null>(null);

  useEffect(() => {
    const chart = chartRef.current;
    if (chart) {
      const ctx = chart.ctx;
      if (ctx && chart.data.datasets[2].data) {
        (chart.data.datasets[2].data as number[]).forEach((value, index) => {
          if (value < 0) {
            const meta = chart.getDatasetMeta(2).data[index];
            if (meta) {
              const x = meta.x;
              const y = meta.y;
              ctx.fillStyle = 'red';
              ctx.font = 'bold 12px Arial';
              ctx.fillText('!', x - 5, y - 10);
            }
          }
        });
      }
    }
  }, []);

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        type: 'bar' as const,
        label: '収入',
        data: [20, 30, 25, 15, 30, 25, 20, 30, 25, 30, 20, 25],
        backgroundColor: 'rgba(153, 102, 255, 0.5)',
      },
      {
        type: 'bar' as const,
        label: '支出',
        data: [-25, -35, -30, -20, -25, -30, -35, -25, -30, -20, -25, -30],
        backgroundColor: 'rgba(54, 162, 235, 0.5)',
      },
      {
        type: 'line' as const,
        label: '資金繰り',
        data: [10, -5, -10, 5, 10, -15, 20, -10, 15, 5, -5, 10],
        borderColor: 'black',
        backgroundColor: 'black',
        fill: false,
        tension: 0.1,
        borderWidth: 2,
        pointStyle: 'rectRot',
        pointRadius: 6,
        pointHoverRadius: 8,
        pointBackgroundColor: (context: ScriptableContext<'line'>) => {
          const index = context.dataIndex;
          const value = context.dataset.data[index] as number;
          return value < 0 ? 'red' : 'black';
        }
      }
    ],
  };

  const details = [
    { month: '4月', amount: 'XXXXX円' },
    { month: '5月', amount: '▲XXXXX円', alert: true },
    { month: '6月', amount: '▲XXXXX円', alert: true },
    { month: '7月', amount: 'XXXXX円' },
    { month: '8月', amount: 'XXXXX円' },
    { month: '9月', amount: '▲XXXXX円', alert: true },
    { month: '10月', amount: 'XXXXX円' },
    { month: '11月', amount: '▲XXXXX円', alert: true },
    { month: '12月', amount: 'XXXXX円' },
    { month: '1月', amount: 'XXXXX円' },
    { month: '2月', amount: 'XXXXX円' },
    { month: '3月', amount: '▲XXXXX円', alert: true }
  ];

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
        labels: {
          font: {
            size: 16
          }
        }
      },
      title: {
        display: true,
        text: '期中資金繰り表',
        font: {
          size: 20
        }
      },
      tooltip: {
        callbacks: {
          label: function(context: any) {
            const label = context.dataset.label || '';
            const value = context.raw;
            return `${label}: ${value}`;
          }
        }
      },
    },
    scales: {
      y: {
        beginAtZero: true,
      }
    }
  };

  const handleComparisonClick = (type: string) => {
    setActiveComparison(type);
  };

  const handleTogglePanel = (panelIndex: number) => {
    setOpenPanel(openPanel === panelIndex ? null : panelIndex);
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="期中に資金がマイナスとなる期間があります"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="main-content">
        <div className="left-container">
          <div className="graph-container">
            <Chart ref={chartRef} type="bar" data={data} options={options} />
          </div>
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ絞り込み</h2>
              <div className="filter-group">
                <button className="filter-btn">収入</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select">
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <button
                className={`comparison-btn ${activeComparison === '時系列比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '製品別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('製品別比較')}
              >
                製品別比較
              </button>
              <button
                className={`comparison-btn ${activeComparison === '顧客別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonClick('顧客別比較')}
              >
                顧客別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">
            <div>推奨アクション</div>
          </div>
          <div className='actionbox-message'>
            <h6>問題点:</h6> 5月、6月、9月、11月、3月の期間において、資金が顕著なマイナスを示しており、これらの月には支出が収入を大幅に上回っています。この状況は会社のキャッシュフローに深刻な影響を与える可能性があります。<br></br><br></br>
            <h6>対策:</h6> 支出管理の強化: これらの月には非必須の支出を延期し、必須支出のみを優先します。また、各部署に支出削減を指示し、コスト削減のための具体的なアクションプランを策定します。<br></br><br></br>
            ①短期借入の検討: 一時的な資金不足を補うために、短期借入を検討します。これにより、資金繰りの安定性を確保します。<br></br>
            ②収入の増加: 収入の予測と計画を強化し、売上を増加させるためのプロモーション活動や新規顧客の開拓を行います。また、既存顧客からの追加受注を促進するためのインセンティブプランを導入します。<br></br>
            ③資金フローの予測: 定期的に資金フローを予測し、問題が発生する前に早期に対策を講じることができるようにします。これにより、予期せぬ資金不足を防ぎます。</div>
          <div className="collapsible-panels">
            <CollapsiblePanel title="営業キャッシュフロー" money="343,564円" details={details} isOpen={openPanel === 0} onToggle={() => handleTogglePanel(0)} />
            <CollapsiblePanel title="投資キャッシュフロー" money="435,435円" details={details} isOpen={openPanel === 1} onToggle={() => handleTogglePanel(1)} />
            <CollapsiblePanel title="財務キャッシュフロー" money="567,232円" details={details} isOpen={openPanel === 2} onToggle={() => handleTogglePanel(2)} />
          </div>
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "在庫回転率"
  const [showAlert, setShowAlert] = useState(true);
  const [comparisonType, setComparisonType] = useState('時系列比較');

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        label: '回数',
        data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
        backgroundColor: [
          'rgba(153, 102, 255, 0.5)', // 4月
          'rgba(153, 102, 255, 0.5)', // 5月
          'rgba(153, 102, 255, 0.5)', // 6月
          'rgba(153, 102, 255, 0.5)', // 7月
          'rgba(255, 206, 86, 0.5)',  // 8月
          'rgba(153, 102, 255, 0.5)', // 9月
          'rgba(153, 102, 255, 0.5)', // 10月
          'rgba(255, 206, 86, 0.5)', // 11月
          'rgba(153, 102, 255, 0.5)', // 12月
          'rgba(153, 102, 255, 0.5)',  // 1月
          'rgba(153, 102, 255, 0.5)', // 2月
          'rgba(153, 102, 255, 0.5)', // 3月
        ],
      }
    ],
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={data} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select className="inventory-filter-select">
                  <option>全体</option>
                  <option>A製品</option>
                  <option>B製品</option>
                  <option>C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input type="date" className="inventory-filter-date" />
                  <span>~</span>
                  <input type="date" className="inventory-filter-date" />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">期間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">期間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">期間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率"
  const [showAlert, setShowAlert] = useState(true);
  const [comparisonType, setComparisonType] = useState('時系列比較');

  const data = {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        label: '回数',
        data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
        backgroundColor: [
          'rgba(153, 102, 255, 0.5)', // 4月
          'rgba(153, 102, 255, 0.5)', // 5月
          'rgba(153, 102, 255, 0.5)', // 6月
          'rgba(153, 102, 255, 0.5)', // 7月
          'rgba(255, 206, 86, 0.5)',  // 8月
          'rgba(153, 102, 255, 0.5)', // 9月
          'rgba(153, 102, 255, 0.5)', // 10月
          'rgba(255, 206, 86, 0.5)', // 11月
          'rgba(153, 102, 255, 0.5)', // 12月
          'rgba(153, 102, 255, 0.5)',  // 1月
          'rgba(153, 102, 255, 0.5)', // 2月
          'rgba(153, 102, 255, 0.5)', // 3月
        ],
      }
    ],
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        display:false,
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={data} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select className="inventory-filter-select">
                  <option>全体</option>
                  <option>A製品</option>
                  <option>B製品</option>
                  <option>C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input type="date" className="inventory-filter-date" />
                  <span>~</span>
                  <input type="date" className="inventory-filter-date" />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [showAlert, setShowAlert] = useState(true);
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [chartData, setChartData] = useState(getTimeSeriesData());

  function getTimeSeriesData() {
    return {
      labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ],
        }
      ],
    };
  }

  function getProductComparisonData() {
    return {
      labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
      datasets: [
        {
          label: 'A製品',
          data: [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
          backgroundColor: 'rgba(75, 192, 192, 0.5)',
        },
        {
          label: 'B製品',
          data: [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
          backgroundColor: 'rgba(255, 159, 64, 0.5)',
        },
        {
          label: 'C製品',
          data: [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2],
          backgroundColor: 'rgba(153, 102, 255, 0.5)',
        }
      ],
    };
  }

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  const handleComparisonTypeChange = (type: string) => {
    setComparisonType(type);
    if (type === '製品別比較') {
      setChartData(getProductComparisonData());
    } else {
      setChartData(getTimeSeriesData());
    }
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={chartData} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select className="inventory-filter-select">
                  <option>全体</option>
                  <option>A製品</option>
                  <option>B製品</option>
                  <option>C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input type="date" className="inventory-filter-date" />
                  <span>~</span>
                  <input type="date" className="inventory-filter-date" />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => handleComparisonTypeChange('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => handleComparisonTypeChange('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
ERROR in src/components/pages/inventoryTurnsPage.tsx:83:20
TS2345: Argument of type '{ labels: string[]; datasets: { label: string; data: number[]; backgroundColor: string; }[]; }' is not assignable to parameter of type 'SetStateAction<{ labels: string[]; datasets: { label: string; data: number[]; backgroundColor: string[]; }[]; }>'.
  Type '{ labels: string[]; datasets: { label: string; data: number[]; backgroundColor: string; }[]; }' is not assignable to type '{ labels: string[]; datasets: { label: string; data: number[]; backgroundColor: string[]; }[]; }'.
    Types of property 'datasets' are incompatible.
      Type '{ label: string; data: number[]; backgroundColor: string; }[]' is not assignable to type '{ label: string; data: number[]; backgroundColor: string[]; }[]'.
        Type '{ label: string; data: number[]; backgroundColor: string; }' is not assignable to type '{ label: string; data: number[]; backgroundColor: string[]; }'.
          Types of property 'backgroundColor' are incompatible.
            Type 'string' is not assignable to type 'string[]'.
    81 |     setComparisonType(type);
    82 |     if (type === '製品別比較') {
  > 83 |       setChartData(getProductComparisonData());
       |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
    84 |     } else {
    85 |       setChartData(getTimeSeriesData());
    86 |     }
kirin-ri commented 3 months ago
function getProductComparisonData() {
  return {
    labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
    datasets: [
      {
        label: 'A製品',
        data: [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
        backgroundColor: Array(12).fill('rgba(75, 192, 192, 0.5)'),
      },
      {
        label: 'B製品',
        data: [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
        backgroundColor: Array(12).fill('rgba(255, 159, 64, 0.5)'),
      },
      {
        label: 'C製品',
        data: [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2],
        backgroundColor: Array(12).fill('rgba(153, 102, 255, 0.5)'),
      }
    ],
  };
}
kirin-ri commented 3 months ago
<div className="inventory-date-range">
  <select className="inventory-filter-month">
    <option value="1">1月</option>
    <option value="2">2月</option>
    <option value="3">3月</option>
    <option value="4">4月</option>
    <option value="5">5月</option>
    <option value="6">6月</option>
    <option value="7">7月</option>
    <option value="8">8月</option>
    <option value="9">9月</option>
    <option value="10">10月</option>
    <option value="11">11月</option>
    <option value="12">12月</option>
  </select>
  <span>~</span>
  <select className="inventory-filter-month">
    <option value="1">1月</option>
    <option value="2">2月</option>
    <option value="3">3月</option>
    <option value="4">4月</option>
    <option value="5">5月</option>
    <option value="6">6月</option>
    <option value="7">7月</option>
    <option value="8">8月</option>
    <option value="9">9月</option>
    <option value="10">10月</option>
    <option value="11">11月</option>
    <option value="12">12月</option>
  </select>
</div>
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');

  // 数据准备(示例数据)
  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: Object.keys(productData).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => setSelectedProduct(e.target.value)}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    onChange={(e) => setSelectedStartMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    onChange={(e) => setSelectedEndMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
              </div>
            </div>
          </div>
        </div>
        <div className="right-container">
          {/* 右边容器的内容可以保持不变 */}
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [showAlert, setShowAlert] = useState(true);

  // 数据准备(示例数据)
  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: Object.keys(productData).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => setSelectedProduct(e.target.value)}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    onChange={(e) => setSelectedStartMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    onChange={(e) => setSelectedEndMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
              </div>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
ERROR in src/components/pages/inventoryTurnsPage.tsx:25:28
TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ A製品: number[]; B製品: number[]; C製品: number[]; }'.
  No index signature with a parameter of type 'string' was found on type '{ A製品: number[]; B製品: number[]; C製品: number[]; }'.
    23 |
    24 |     if (selectedProduct !== '全体') {
  > 25 |       const filteredData = productData[selectedProduct].slice(startIndex, endIndex + 1);
       |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    26 |       return {
    27 |         labels: allLabels.slice(startIndex, endIndex + 1),
    28 |         datasets: [

ERROR in src/components/pages/inventoryTurnsPage.tsx:42:15
TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ A製品: number[]; B製品: number[]; C製品: number[]; }'.
  No index signature with a parameter of type 'string' was found on type '{ A製品: number[]; B製品: number[]; C製品: number[]; }'.
    40 |       datasets: Object.keys(productData).map(product => ({
    41 |         label: product,
  > 42 |         data: productData[product].slice(startIndex, endIndex + 1),
       |               ^^^^^^^^^^^^^^^^^^^^
    43 |         backgroundColor: getColorForProduct(product),
    44 |       })),
    45 |     };
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  // 数据准备(示例数据)
  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => setSelectedProduct(e.target.value)}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    onChange={(e) => setSelectedStartMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    onChange={(e) => setSelectedEndMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  // 数据准备(示例数据)
  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getTimeSeriesData = () => {
    return {
      labels: allLabels,
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ],
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => setSelectedProduct(e.target.value)}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    onChange={(e) => setSelectedStartMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    onChange={(e) => setSelectedEndMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  const resetDates = () => {
    setSelectedStartMonth('2023-04');
    setSelectedEndMonth('2024-03');
  };

  // 数据准备(示例数据)
  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getTimeSeriesData = () => {
    return {
      labels: allLabels,
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ],
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => setSelectedProduct(e.target.value)}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    onChange={(e) => setSelectedStartMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    onChange={(e) => setSelectedEndMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
                <button onClick={resetDates} className="reset-btn">削除</button>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState, useRef } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  const startMonthRef = useRef<HTMLInputElement>(null);
  const endMonthRef = useRef<HTMLInputElement>(null);

  const resetDates = () => {
    setSelectedStartMonth('2023-04');
    setSelectedEndMonth('2024-03');
    if (startMonthRef.current) startMonthRef.current.value = '2023-04';
    if (endMonthRef.current) endMonthRef.current.value = '2024-03';
  };

  // 数据准备(示例数据)
  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getTimeSeriesData = () => {
    return {
      labels: allLabels,
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ],
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => setSelectedProduct(e.target.value)}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    ref={startMonthRef}
                    onChange={(e) => setSelectedStartMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    ref={endMonthRef}
                    onChange={(e) => setSelectedEndMonth(e.target.value)}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <button onClick={resetDates} className="reset-btn">削除</button>
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState, useRef } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  const startMonthRef = useRef<HTMLInputElement>(null);
  const endMonthRef = useRef<HTMLInputElement>(null);

  const resetDates = () => {
    setSelectedStartMonth('2023-04');
    setSelectedEndMonth('2024-03');
    if (startMonthRef.current) startMonthRef.current.value = '2023-04';
    if (endMonthRef.current) endMonthRef.current.value = '2024-03';
  };

  // 数据准备(示例数据)
  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getTimeSeriesData = () => {
    return {
      labels: allLabels,
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ],
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => {
                    setSelectedProduct(e.target.value);
                    setComparisonType('製品別比較');
                  }}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <button className="inventory-filter-btn">期間</button>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    ref={startMonthRef}
                    onChange={(e) => {
                      setSelectedStartMonth(e.target.value);
                      setComparisonType('製品別比較');
                    }}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    ref={endMonthRef}
                    onChange={(e) => {
                      setSelectedEndMonth(e.target.value);
                      setComparisonType('製品別比較');
                    }}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <button onClick={resetDates} className="reset-btn">削除</button>
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState, useRef } from 'react';
import { Chart } from 'react-chartjs-2';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  const startMonthRef = useRef<HTMLInputElement>(null);
  const endMonthRef = useRef<HTMLInputElement>(null);

  const resetDates = () => {
    setSelectedStartMonth('2023-04');
    setSelectedEndMonth('2024-03');
    if (startMonthRef.current) startMonthRef.current.value = '2023-04';
    if (endMonthRef.current) endMonthRef.current.value = '2024-03';
  };

  // 数据准备(示例数据)
  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getTimeSeriesData = () => {
    return {
      labels: allLabels,
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ],
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => {
                    setSelectedProduct(e.target.value);
                    setComparisonType('製品別比較');
                  }}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <div className="inventory-period-title">
                  <button className="inventory-filter-btn">期間</button>
                  <FontAwesomeIcon 
                    icon={faTrashAlt} 
                    onClick={resetDates} 
                    className="reset-icon"
                    style={{ cursor: 'pointer', marginLeft: '10px' }} 
                  />
                </div>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    ref={startMonthRef}
                    onChange={(e) => {
                      setSelectedStartMonth(e.target.value);
                      setComparisonType('製品別比較');
                    }}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    ref={endMonthRef}
                    onChange={(e) => {
                      setSelectedEndMonth(e.target.value);
                      setComparisonType('製品別比較');
                    }}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState, useRef } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  const handleStartMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    if (value === '') {
      setSelectedStartMonth('2023-04');
    } else {
      setSelectedStartMonth(value);
      setComparisonType('製品別比較');
    }
  };

  const handleEndMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    if (value === '') {
      setSelectedEndMonth('2024-03');
    } else {
      setSelectedEndMonth(value);
      setComparisonType('製品別比較');
    }
  };

  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const getTimeSeriesData = () => {
    return {
      labels: allLabels,
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14],
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ],
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => {
                    setSelectedProduct(e.target.value);
                    setComparisonType('製品別比較');
                  }}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <div className="inventory-period-title">
                  <button className="inventory-filter-btn">期間</button>
                </div>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    onChange={handleStartMonthChange}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    onChange={handleEndMonthChange}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const handleStartMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSelectedStartMonth(e.target.value);
  };

  const handleEndMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSelectedEndMonth(e.target.value);
  };

  const getTimeSeriesData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);
    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14].slice(startIndex, endIndex + 1),
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ].slice(startIndex, endIndex + 1),
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => {
                    setSelectedProduct(e.target.value);
                    setComparisonType('製品別比較');
                  }}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <div className="inventory-period-title">
                  <button className="inventory-filter-btn">期間</button>
                </div>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    onChange={handleStartMonthChange}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    onChange={handleEndMonthChange}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
import { useState } from 'react';
import { Chart } from 'react-chartjs-2';

const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const handleStartMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value || '2023-04'; // 默认值为 2023-04
    setSelectedStartMonth(value);
  };

  const handleEndMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value || '2024-03'; // 默认值为 2024-03
    setSelectedEndMonth(value);
  };

  const getTimeSeriesData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);
    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14].slice(startIndex, endIndex + 1),
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ].slice(startIndex, endIndex + 1),
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: 'rgba(75, 192, 192, 0.5)',
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';
      case 'C製品': return 'rgba(153, 102, 255, 0.5)';
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{pageName}</h1>
        </div>
      </section>
      {showAlert && (
        <div className="alert-container">
          <AlertBox
            message="B製品の在庫回転率が悪化しています"
            onClose={() => setShowAlert(false)}
          />
        </div>
      )}
      <div className="inventory-main-content">
        <div className="inventory-left-container">
          <div className="inventory-graph-container">
            <Chart type="bar" data={getFilteredData()} options={options} />
          </div>
          <div className="inventory-additional-section">
            <div className="inventory-data-filter">
              <h2>データ絞り込み</h2>
              <div className="inventory-filter-group">
                <button className="inventory-filter-btn">製品</button>
                <select 
                  className="inventory-filter-select" 
                  value={selectedProduct} 
                  onChange={(e) => {
                    setSelectedProduct(e.target.value);
                    setComparisonType('製品別比較');
                  }}
                >
                  <option value="全体">全体</option>
                  <option value="A製品">A製品</option>
                  <option value="B製品">B製品</option>
                  <option value="C製品">C製品</option>
                </select>
              </div>
              <div className="inventory-filter-group2">
                <div className="inventory-period-title">
                  <button className="inventory-filter-btn">期間</button>
                </div>
                <div className="inventory-date-range">
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedStartMonth} 
                    onChange={handleStartMonthChange}
                    min="2023-04" 
                    max="2024-03" 
                  />
                  <span>~</span>
                  <input 
                    type="month" 
                    className="inventory-filter-date" 
                    value={selectedEndMonth} 
                    onChange={handleEndMonthChange}
                    min="2023-04" 
                    max="2024-03" 
                  />
                </div>
              </div>
            </div>
            <div className="inventory-data-comparison">
              <h2>データ比較</h2>
              <button
                className={`inventory-comparison-btn ${comparisonType === '時系列比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('時系列比較')}
              >
                時系列比較
              </button>
              <button
                className={`inventory-comparison-btn ${comparisonType === '製品別比較' ? 'active' : ''}`}
                onClick={() => setComparisonType('製品別比較')}
              >
                製品別比較
              </button>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">推奨アクション</div>
          <div className="actionbox-message">
            <h6>問題点:</h6> B製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に7月と11月に在庫が急増しています。このままでは在庫管理コストの増加や資金繰りの悪化を招く可能性があります。<br></br><br></br>
            <h6>対策:</h6> 需要予測の精度向上: B製品の需要を正確に予測し、生産計画を見直すことで過剰在庫を防ぎます。<br></br><br></br>
            ①販売促進: 在庫削減のため、特別割引やキャンペーンを実施し、B製品の販売を促進します。<br></br>
            ②サプライチェーンの改善: 供給元との連携を強化し、必要な時に必要な量だけを迅速に調達できる体制を整えます。<br></br>
            ③在庫管理の強化: 定期的な在庫チェックと迅速な対応を行い、在庫の最適化を図ります。<br></br>
            ④これらの対策を実施することで、B製品の在庫回転率を改善し、企業全体の効率的な在庫管理を実現することが期待されます。<br></br>
          </div>
          <div className='inventory-metrics'>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間在庫回転率</div>
              <div className="inventory-metrics-value">XX回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">XXXX万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
const InventoryTurnsPage = () => {
  const pageName = "年間平均在庫回転率";
  const [selectedProduct, setSelectedProduct] = useState('全体');
  const [selectedStartMonth, setSelectedStartMonth] = useState('2023-04');
  const [selectedEndMonth, setSelectedEndMonth] = useState('2024-03');
  const [comparisonType, setComparisonType] = useState('時系列比較');
  const [showAlert, setShowAlert] = useState(true);

  const productData = {
    'A製品': [5, 6, 4, 7, 8, 5, 5, 9, 7, 8, 6, 7],
    'B製品': [3, 4, 2, 5, 6, 3, 3, 7, 5, 6, 4, 5],
    'C製品': [2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 2, 2]
  };

  const allLabels = ['2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03'];

  const handleStartMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value || '2023-04'; // 默认值为 2023-04
    setSelectedStartMonth(value);
  };

  const handleEndMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value || '2024-03'; // 默认值为 2024-03
    setSelectedEndMonth(value);
  };

  const getTimeSeriesData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);
    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: [
        {
          label: '回数',
          data: [10, 12, 8, 14, 16, 11, 10, 18, 14, 15, 12, 14].slice(startIndex, endIndex + 1),
          backgroundColor: [
            'rgba(153, 102, 255, 0.5)', // 4月
            'rgba(153, 102, 255, 0.5)', // 5月
            'rgba(153, 102, 255, 0.5)', // 6月
            'rgba(153, 102, 255, 0.5)', // 7月
            'rgba(255, 206, 86, 0.5)',  // 8月
            'rgba(153, 102, 255, 0.5)', // 9月
            'rgba(153, 102, 255, 0.5)', // 10月
            'rgba(255, 206, 86, 0.5)', // 11月
            'rgba(153, 102, 255, 0.5)', // 12月
            'rgba(153, 102, 255, 0.5)',  // 1月
            'rgba(153, 102, 255, 0.5)', // 2月
            'rgba(153, 102, 255, 0.5)', // 3月
          ].slice(startIndex, endIndex + 1),
        }
      ],
    };
  };

  const getFilteredData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);

    if (comparisonType === '時系列比較') {
      return getTimeSeriesData();
    }

    if (selectedProduct !== '全体') {
      const filteredData = productData[selectedProduct as keyof typeof productData].slice(startIndex, endIndex + 1);
      return {
        labels: allLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: getColorForProduct(selectedProduct),
          }
        ],
      };
    }

    return {
      labels: allLabels.slice(startIndex, endIndex + 1),
      datasets: (Object.keys(productData) as (keyof typeof productData)[]).map(product => ({
        label: product,
        data: productData[product].slice(startIndex, endIndex + 1),
        backgroundColor: getColorForProduct(product),
      })),
    };
  };

  const getColorForProduct = (product: string) => {
    switch (product) {
      case 'A製品': return 'rgba(75, 192, 192, 0.5)';  // 蓝色
      case 'B製品': return 'rgba(255, 159, 64, 0.5)';  // 橙色
      case 'C製品': return 'rgba(153, 102, 255, 0.5)'; // 紫色
      default: return 'rgba(153, 102, 255, 0.5)';
    }
  };

  const options = {
    responsive: true,
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: '回数', // 纵轴单位
        },
      },
    },
    plugins: {
      legend: {
        position: 'top' as const,
      }
    }
  };

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <div className="alert-content">
          <i className="fa fa-exclamation-circle alert-icon" aria-hidden="true"></i>
          <span className="alert-message">{message}</span>
        </div>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };