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
{
  income: [31.2, 32.8, 30.5, 34.7, 33.2, 34.1, 35.3, 36.0, 35.6, 36.5, 34.9, 35.4],
  expense: [-26.7, -28.3, -27.5, -29.1, -28.4, -29.0, -29.7, -30.2, -29.8, -30.5, -29.3, -29.7],
  action: '4月から6月の実績データを見ると、収益は安定した増加を示し、支出も効果的に管理されています。この傾向が続くと、7月以降の収益はさらに伸び、支出もコントロールされると予測されます。この状況を踏まえ、現行の戦略を維持しつつ、新たな成長機会を探ることが推奨されます。例えば、新しい市場への参入や革新的な製品開発を通じて、さらなる収益の拡大が期待できます。一方で、予期せぬリスクに備えるため、継続的なリスク管理と資金繰りのモニタリングを強化する必要があります。このような戦略を取ることで、長期的な安定成長が見込まれます。これにより、会社全体の成長力が向上し、競争力が強化されるでしょう。'
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 33.5, 32.0, 33.0, 33.5, 34.0, 33.7, 34.5, 33.2, 33.8],
  expense: [-26.7, -28.3, -27.5, -29.7, -28.9, -29.8, -30.2, -30.7, -30.4, -31.0, -29.8, -30.3],
  action: '4月から6月の実績データに基づき、収益は好調であり、支出も安定しています。しかし、7月以降の予測では、収益の増加が続くものの、支出も同様に増加する見込みです。このような状況では、特に支出の管理を強化することが求められます。具体的には、コスト削減のための戦略的アプローチが必要です。特定のプロジェクトや部門で無駄な支出がないかを確認し、必要でない支出を削減することが重要です。また、収益性の高い事業に注力し、限られたリソースを効率的に配分することで、会社全体の利益率を向上させることが期待されます。この戦略を実行することで、今後の成長を安定的に維持することが可能になります。'
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 32.9, 31.7, 32.4, 32.6, 33.2, 32.8, 33.5, 32.0, 32.4],
  expense: [-26.7, -28.3, -27.5, -31.1, -30.3, -31.4, -32.0, -32.8, -32.5, -33.1, -31.8, -32.3],
  action: '4月から6月の実績データでは、収益が堅調に推移していますが、支出も増加しており、予測ではさらに支出が増加する見込みです。このままでは、会社のキャッシュフローに悪影響を及ぼす可能性があります。まず、直ちに支出の削減策を講じる必要があります。無駄な支出を排除し、コスト削減を徹底することが重要です。さらに、収益性の高い事業に重点を置き、低収益な活動を見直すことも検討すべきです。加えて、短期的な資金繰りの改善を図るために、財務戦略を再評価し、必要に応じて資金調達の手段を検討することが求められます。このような取り組みにより、予測される財務リスクを最小限に抑え、持続可能な成長を実現することができます。'
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 33.2, 32.3, 33.0, 33.4, 34.0, 33.6, 34.4, 33.0, 33.6],
  expense: [-26.7, -28.3, -27.5, -29.0, -28.5, -29.3, -29.7, -30.0, -29.8, -30.3, -29.4, -29.9],
  action: '4月から6月までの収益と支出は安定しており、会社の財務状況は健全です。7月以降の予測では、収益は増加する見込みで、支出も管理可能な範囲で推移すると予想されています。このような状況では、効率的な経営を維持しつつ、収益性のさらなる向上を目指すことが推奨されます。具体的には、生産性を高めるためのプロセス改善や、収益性の高い事業へのリソース集中が有効です。また、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整えることで、長期的な成長を確保することが可能になります。このような戦略を展開することで、今後の市場競争力を高め、安定した成長を実現することができるでしょう。'
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 32.6, 31.8, 32.3, 32.7, 33.3, 32.9, 33.6, 32.2, 32.8],
  expense: [-26.7, -28.3, -27.5, -29.5, -29.0, -29.6, -30.1, -30.5, -30.2, -30.8, -29.9, -30.4],
  action: '4月から6月までの実績データによると、収益と支出のバランスは取れており、財務状況は安定しています。7月以降の予測では、収益と支出が同程度に増加すると見込まれ、大きな変動は予測されていません。このような安定した状況を維持するためには、現行の戦略を維持しつつ、外部環境の変化に柔軟に対応できる体制を整えることが重要です。また、収益性の向上を目指して、マーケティング戦略の再評価や、プロセスの効率化を進めることが求められます。加えて、リスク管理の強化を図り、予期せぬ事態に迅速に対応するための準備を行うことで、長期的な成長を確保することができるでしょう。この戦略により、安定した経営基盤を築き、持続的な成長を実現することが可能になります。'
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 31.9, 31.2, 31.8, 32.0, 32.7, 32.2, 32.9, 31.7, 32.2],
  expense: [-26.7, -28.3, -27.5, -30.4, -30.0, -30.6, -31.2, -31.7, -31.5, -32.1, -31.2, -31.8],
  action: '4月から6月までの実績データでは、収益と支出が均衡しており、健全な財務状況を維持しています。しかし、7月以降の予測では、支出が増加傾向にあるため、財務状況に悪影響を及ぼす可能性があります。この状況を踏まえ、まずは支出の見直しと削減が必要です。特に、無駄な経費を削減し、コスト効率を高める施策を講じることが求められます。また、収益の向上に向けた取り組みとして、新規市場の開拓や既存顧客の深耕を図ることで、収益基盤の強化を目指すことが重要です。さらに、予期せぬリスクに備えたリスク管理の強化も必要です。これにより、会社の財務健全性を維持し、安定した成長を続けるための基盤を築くことができるでしょう。'
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 31.5, 30.9, 31.2, 31.8, 32.4, 32.1, 32.8, 31.4, 31.9],
  expense: [-26.7, -28.3, -27.5, -29.2, -28.7, -29.3, -29.8, -30.3, -30.0, -30.6, -29.7, -30.1],
  action: '4月から6月までの実績データに基づき、収益と支出は安定していますが、収益の増加は限られています。7月以降の予測では、収益はやや増加する見込みで、支出も管理可能な範囲で推移すると考えられます。この状況では、収益性を高めるために、効率的な経営が求められます。具体的には、コスト削減を図りながら、収益性の高いプロジェクトにリソースを集中させることが重要です。また、現行の戦略を見直し、リスク管理を強化することで、予期せぬ事態にも柔軟に対応できる体制を整えることが必要です。さらに、市場の動向を注視し、適切なタイミングでの投資を行うことで、将来的な成長を促進することが期待されます。'
}
{
  income: [31.2, 32.8, 30.5, 30.9, 30.2, 30.7, 31.1, 31.7, 31.3, 31.9, 30.9, 31.3],
  expense: [-26.7, -28.3, -27.5, -29.9, -29.3, -30.0, -30.5, -31.0, -30.8, -31.4, -30.5, -31.0],
  action: '4月から6月までの実績データによると、収益はやや伸び悩んでいますが、支出は比較的安定して管理されています。7月以降の予測では、支出が増加傾向にあり、収益の成長は限定的と見込まれます。このような状況下では、支出管理の徹底が不可欠です。特に、コスト削減策の強化と、低収益プロジェクトの見直しが求められます。さらに、収益の増加を図るため、新規事業の展開や既存顧客の深耕に注力することが必要です。また、リスク管理を強化し、市場の変化に迅速に対応できる体制を整えることで、将来的なリスクを回避し、持続的な成長を実現することが期待されます。この戦略により、財務健全性を維持しながら、安定した成長を続けることが可能です。'
}
{
  income: [31.2, 32.8, 30.5, 30.2, 29.6, 30.1, 30.4, 31.0, 30.7, 31.3, 30.3, 30.8],
  expense: [-26.7, -28.3, -27.5, -30.7, -30.1, -30.8, -31.3, -31.8, -31.5, -32.1, -31.2, -31.7],
  action: '4月から6月の実績データによると、収益は伸び悩み、支出は増加傾向にあります。7月以降の予測では、収益がさらに減少し、支出は大幅に増加する見込みです。このままでは、会社の財務健全性に深刻な影響を及ぼす可能性があります。まず、緊急のコスト削減策を講じ、無駄な支出を徹底的に削減する必要があります。また、収益性の低い事業やプロジェクトを再評価し、必要に応じて中止やリソースの再配分を検討することが求められます。さらに、短期的な資金繰りの安定化を図るため、財務戦略を再構築し、必要に応じて資金調達の手段を検討することが重要です。このような対応を取ることで、予測されるリスクを最小限に抑え、持続可能な経営を目指すことが可能となります。'
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 37.5, 38.2, 39.6, 40.1, 41.5, 42.0, 43.2, 41.8, 42.7],
  expense: [-26.7, -28.3, -27.5, -24.9, -25.4, -26.1, -25.7, -26.3, -25.9, -26.8, -25.3, -26.0],
  action: ' 4月から6月までの実績データによると、収益は非常に良好で、支出も効果的に管理されています。この傾向が続くと予測されるため、7月以降の収益は大幅に増加し、支出は引き続き低い水準で推移すると期待されます。\n\n このような状況下では、新たな市場への積極的な投資や、技術革新を推進するための資金投入が推奨されます。また、企業の成長を加速させるために、人材育成やグローバル展開に向けた戦略的投資も検討する価値があります。\n\n 一方で、予期せぬリスクに備えた継続的なリスク管理と資金繰りのモニタリングも怠らないようにする必要があります。これにより、企業の競争力がさらに強化され、持続的な成長が見込まれます。'
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 37.5, 38.2, 39.6, 40.1, 41.5, 42.0, 43.2, 41.8, 42.7],
  expense: [-26.7, -28.3, -27.5, -24.9, -25.4, -26.1, -25.7, -26.3, -25.9, -26.8, -25.3, -26.0],
  action: ` 4月から6月までの実績データによると、収益は非常に良好で、支出も効果的に管理されています。

  この傾向が続くと予測されるため、7月以降の収益は大幅に増加し、支出は引き続き低い水準で推移すると期待されます。

  このような状況下では、新たな市場への積極的な投資や、技術革新を推進するための資金投入が推奨されます。また、企業の成長を加速させるために、人材育成やグローバル展開に向けた戦略的投資も検討する価値があります。

  一方で、予期せぬリスクに備えた継続的なリスク管理と資金繰りのモニタリングも怠らないようにする必要があります。これにより、企業の競争力がさらに強化され、持続的な成長が見込まれます。`
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 37.5, 38.2, 39.6, 40.1, 41.5, 42.0, 43.2, 41.8, 42.7],
  expense: [-26.7, -28.3, -27.5, -24.9, -25.4, -26.1, -25.7, -26.3, -25.9, -26.8, -25.3, -26.0],
  action: `&nbsp;&nbsp;&nbsp;&nbsp;4月から6月までの実績データによると、収益は非常に良好で、支出も効果的に管理されています。<br><br>

  この傾向が続くと予測されるため、7月以降の収益は大幅に増加し、支出は引き続き低い水準で推移すると期待されます。<br><br>

  このような状況下では、新たな市場への積極的な投資や、技術革新を推進するための資金投入が推奨されます。また、企業の成長を加速させるために、人材育成やグローバル展開に向けた戦略的投資も検討する価値があります。<br><br>

  一方で、予期せぬリスクに備えた継続的なリスク管理と資金繰りのモニタリングも怠らないようにする必要があります。これにより、企業の競争力がさらに強化され、持続的な成長が見込まれます。`
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 37.5, 38.2, 39.6, 40.1, 41.5, 42.0, 43.2, 41.8, 42.7],
  expense: [-26.7, -28.3, -27.5, -24.9, -25.4, -26.1, -25.7, -26.3, -25.9, -26.8, -25.3, -26.0],
  action: ' 4月から6月までの実績データによると、収益は非常に良好で、支出も効果的に管理されています。 この傾向が続くと予測されるため、7月以降の収益は大幅に増加し、支出は引き続き低い水準で推移すると期待されます。 このような状況下では、新たな市場への積極的な投資や、技術革新を推進するための資金投入が推奨されます。また、企業の成長を加速させるために、人材育成やグローバル展開に向けた戦略的投資も検討する価値があります。 一方で、予期せぬリスクに備えた継続的なリスク管理と資金繰りのモニタリングも怠らないようにする必要があります。これにより、企業の競争力がさらに強化され、持続的な成長が見込まれます。'
}
kirin-ri commented 3 months ago
  <div className='actionbox-message'>
    {actionText.map((paragraph, index) => (
      <p key={index}>{paragraph}</p>
    ))}
  </div>
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 37.5, 38.2, 39.6, 40.1, 41.5, 42.0, 43.2, 41.8, 42.7],
  expense: [-26.7, -28.3, -27.5, -24.9, -25.4, -26.1, -25.7, -26.3, -25.9, -26.8, -25.3, -26.0],
  action: [
    "4月から6月までの実績データによると、収益は非常に良好で、支出も効果的に管理されています。",
    "この傾向が続くと予測されるため、7月以降の収益は大幅に増加し、支出は引き続き低い水準で推移すると期待されます。",
    "このような状況下では、新たな市場への積極的な投資や、技術革新を推進するための資金投入が推奨されます。また、企業の成長を加速させるために、人材育成やグローバル展開に向けた戦略的投資も検討する価値があります。",
    "一方で、予期せぬリスクに備えた継続的なリスク管理と資金繰りのモニタリングも怠らないようにする必要があります。これにより、企業の競争力がさらに強化され、持続的な成長が見込まれます。"
  ]
}
kirin-ri commented 3 months ago
ERROR in src/components/pages/financingPage.tsx:170:22
TS2345: Argument of type 'string | string[]' is not assignable to parameter of type 'SetStateAction<string>'.
  Type 'string[]' is not assignable to type 'SetStateAction<string>'.
    168 |     }
    169 |
  > 170 |     setActionMessage(selectedData.action);
        |                      ^^^^^^^^^^^^^^^^^^^
    171 |   };
    172 |
    173 |   useEffect(() => {

ERROR in src/components/pages/financingPage.tsx:339:28
TS2339: Property 'map' does not exist on type 'string'.
    337 |           </div>
    338 |           <div className='actionbox-message'>
  > 339 |             {actionMessage.map((paragraph, index) => (
        |                            ^^^
    340 |               <p key={index}>{paragraph}</p>
    341 |             ))}
    342 |           </div>

ERROR in src/components/pages/financingPage.tsx:339:33
TS7006: Parameter 'paragraph' implicitly has an 'any' type.
    337 |           </div>
    338 |           <div className='actionbox-message'>
  > 339 |             {actionMessage.map((paragraph, index) => (
        |                                 ^^^^^^^^^
    340 |               <p key={index}>{paragraph}</p>
    341 |             ))}
    342 |           </div>

ERROR in src/components/pages/financingPage.tsx:339:44
TS7006: Parameter 'index' implicitly has an 'any' type.
    337 |           </div>
    338 |           <div className='actionbox-message'>
  > 339 |             {actionMessage.map((paragraph, index) => (
        |                                            ^^^^^
    340 |               <p key={index}>{paragraph}</p>
    341 |             ))}
    342 |           </div>
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, ChartTypeRegistry, Legend, LegendItem, 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 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 EmptyPage = () => {
  const pageName = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const [activeComparison, setActiveComparison] = useState('時系列比較');
  const [actionMessage, setActionMessage] = useState<string | string[]>(''); // 更新类型
  const chartRef = useRef<ChartJS | null>(null);

  const dataSets = {
    '楽観-楽観': {
      income: [31.2, 32.8, 30.5, 37.5, 38.2, 39.6, 40.1, 41.5, 42.0, 43.2, 41.8, 42.7],
      expense: [-26.7, -28.3, -27.5, -24.9, -25.4, -26.1, -25.7, -26.3, -25.9, -26.8, -25.3, -26.0],
      action: [
        "4月から6月までの実績データによると、収益は非常に良好で、支出も効果的に管理されています。",
        "この傾向が続くと予測されるため、7月以降の収益は大幅に増加し、支出は引き続き低い水準で推移すると期待されます。",
        "このような状況下では、新たな市場への積極的な投資や、技術革新を推進するための資金投入が推奨されます。また、企業の成長を加速させるために、人材育成やグローバル展開に向けた戦略的投資も検討する価値があります。",
        "一方で、予期せぬリスクに備えた継続的なリスク管理と資金繰りのモニタリングも怠らないようにする必要があります。これにより、企業の競争力がさらに強化され、持続的な成長が見込まれます。"
      ]
    },
    // 其他数据集...
  };

  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: function(context: ScriptableContext<'bar'>) {
          const index = context.dataIndex;
          return index < 3 ? 'rgba(153, 102, 255, 0.5)' : 'rgba(153, 102, 255, 0.2)';
        },
      },
      {
        type: 'bar' as const,
        label: '支出',
        data: [-25, -35, -30, -20, -25, -30, -35, -25, -30, -20, -25, -30],
        backgroundColor: function(context: ScriptableContext<'bar'>) {
          const index = context.dataIndex;
          return index < 3 ? 'rgba(54, 162, 235, 0.5)' : 'rgba(54, 162, 235, 0.2)';
        },
      },
      {
        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,
        segment: {
          borderDash: (ctx: { p0DataIndex: number }) => {
            return ctx.p0DataIndex < 2 ? [] : [5, 5]; // 4月到6月实线,7月到3月虚线
          },
        },
        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,
    layout: {
      padding: {
        top: 0,
      },
    },
    plugins: {
      legend: {
        display: true,
        labels:{
          color : 'white',
          boxWidth : 0,
          boxHeight : 0,
      }
      }
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: '百万円', // 纵轴单位
        }
      }
    }
  };

  // 自定义图例绘制插件
  const customLegendPlugin = {
    id: 'customLegend',
    afterDraw: function(chart: ChartJS<keyof ChartTypeRegistry, unknown[], unknown>) {
      const legend = chart?.legend;
      if (!legend || !legend.legendItems) return;

      const ctx = chart.ctx;
      const itemWidth = 100; // 每个图例项的宽度(包括图标和文本)
      const startX = (chart.width - legend.legendItems.length * itemWidth) / 2; // 图例起始X坐标(居中)
      let currentX = startX;

      const y = 10; // 在图例下方添加额外的距离,以确保与图表内容不重叠

      legend.legendItems.forEach((legendItem: LegendItem, i: number) => {
        if (legendItem.text === '残高') {
          ctx.save();
          ctx.strokeStyle = legendItem.strokeStyle as string;
          ctx.lineWidth = 2; // 设置线条的宽度
          ctx.beginPath();
          ctx.moveTo(currentX, y);
          ctx.lineTo(currentX + 40, y); // 假设线条宽度为 40
          ctx.stroke();
          ctx.restore();
        } else {
          ctx.save();
          ctx.fillStyle = legendItem.fillStyle as string;
          ctx.fillRect(currentX, y - 5, 40, 10); // 假设图标宽度为 40, 高度为 10
          ctx.restore();
        }

        ctx.textBaseline = 'middle';
        ctx.fillStyle = 'black'
        ctx.fillText(legendItem.text, currentX + 50, y); // 图标和文本之间的间距为 50

        currentX += itemWidth; // 移动到下一个图例项
      });
    }
  };

  useEffect(() => {
    ChartJS.register(customLegendPlugin);
    return () => {
      ChartJS.unregister(customLegendPlugin);
    };
  },[]);

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

  const updateChartAndAction = () => {
    const key = `${activeComparison}-楽観`;  // Example key generation
    const selectedData = dataSets[key];

    if (chartRef.current) {
      chartRef.current.data.datasets[0].data = selectedData.income;
      chartRef.current.data.datasets[1].data = selectedData.expense;
      chartRef.current.data.datasets[2].data = selectedData.income.map((income, i) => income + selectedData.expense[i]);
      chartRef.current.update();
    }

    setActionMessage(selectedData.action);
  };

  useEffect(() => {
    updateChartAndAction();
  }, [activeComparison]);

  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>
            </div>
          </div>
        </div>
        <div className="right-container">
          <div className="actionbox-title">
            <div>推奨アクション</div>
          </div>
          <div className='actionbox-message'>
            {Array.isArray(actionMessage) ? (
              actionMessage.map((paragraph, index) => (
                <p key={index}>{paragraph}</p>
              ))
            ) : (
              <p>{actionMessage}</p>
            )}
          </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
{
  income: [31.2, 32.8, 30.5, 37.5, 38.2, 39.6, 40.1, 41.5, 42.0, 43.2, 41.8, 42.7],
  expense: [-26.7, -28.3, -31.5, -24.9, -25.4, -26.1, -25.7, -26.3, -25.9, -26.8, -25.3, -26.0],
  action: `4月から6月の実績データを見ると、5月の残高が一時的にマイナスになっていますが、全体としては収益が安定しており、健全な財務状況が保たれています。7月以降の予測では、収益がさらに増加し、支出も適度に抑えられる見込みです。このような状況下では、企業はさらなる成長を目指して積極的に投資を行うべきです。特に、新興市場への進出や技術革新に対する投資が推奨されます。これにより、企業の競争力を高め、長期的な成長を実現することができます。また、収益性の高いプロジェクトに焦点を当て、リソースを効率的に配分することで、全体の収益性を向上させることが重要です。さらに、将来の不確実性に備えるため、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整える必要があります。このような戦略を実行することで、企業は安定した成長を続けることができ、長期的な財務健全性を維持することが可能となります。`
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 37.5, 38.2, 39.6, 40.1, 41.5, 42.0, 43.2, 41.8, 42.7],
  expense: [-26.7, -28.3, -36.0, -24.9, -25.4, -26.1, -25.7, -26.3, -25.9, -26.8, -25.3, -26.0],
  action: `4月から6月の実績データを見ると、特に6月において残高が大幅にマイナスになっていますが、全体的には収益が安定しており、健全な財務状況が保たれています。7月以降の予測では、収益がさらに増加し、支出も適度に抑えられる見込みです。このような状況下では、企業はさらなる成長を目指して積極的に投資を行うべきです。特に、新興市場への進出や技術革新に対する投資が推奨されます。これにより、企業の競争力を高め、長期的な成長を実現することができます。また、収益性の高いプロジェクトに焦点を当て、リソースを効率的に配分することで、全体の収益性を向上させることが重要です。さらに、将来の不確実性に備えるため、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整える必要があります。このような戦略を実行することで、企業は安定した成長を続けることができ、長期的な財務健全性を維持することが可能となります。`
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 35.2, 36.0, 36.7, 37.1, 38.0, 38.4, 39.2, 38.0, 38.8],
  expense: [-26.7, -28.3, -36.0, -27.6, -28.1, -28.9, -29.2, -29.8, -29.5, -30.3, -29.0, -29.6],
  action: `4月から6月の実績データによれば、6月の残高が大幅にマイナスとなっていますが、全体的には収益は安定しており、支出も管理可能な範囲で抑えられています。7月以降の予測では、収益は引き続き増加する見込みですが、支出もわずかに増加することが予想されます。この状況下で、企業はコスト管理を強化し、収益性の向上を図ることが求められます。特に、無駄なコストを削減し、効率的な運営を実現するための内部プロセスの見直しが重要です。また、収益性の高いプロジェクトに集中投資し、低収益のプロジェクトについては再評価を行い、必要に応じてリソースを再配分することが推奨されます。さらに、市場の変化に対応するための柔軟な戦略を採用し、リスク管理を強化することで、企業の持続的な成長を確保することが可能となります。これにより、企業は安定した財務状況を維持しつつ、将来の成長に向けた準備を整えることができます。`
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 33.0, 33.6, 34.1, 34.5, 35.2, 35.7, 36.0, 34.8, 35.3],
  expense: [-26.7, -28.3, -36.0, -31.5, -32.2, -33.0, -33.7, -34.3, -34.0, -35.0, -33.8, -34.6],
  action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、財務状況に不安が生じています。7月以降の予測では、収益の増加が続くものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ効果的な対応が必要です。まず、コスト削減を徹底し、無駄な支出を削減することで、キャッシュフローの改善を図ることが求められます。また、低収益プロジェクトの見直しや、必要に応じたリソースの再配分を行い、収益性の高い事業に集中投資することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて資金調達の手段を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。`
}
{
  income: [31.2, 32.8, 30.5, 33.8, 34.3, 35.0, 35.4, 36.2, 36.7, 37.3, 36.0, 36.8],
  expense: [-26.7, -28.3, -36.0, -28.6, -29.2, -29.8, -30.3, -30.7, -30.5, -31.2, -30.0, -30.6],
  action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなっていますが、収益と支出のバランスはおおむね取れており、安定した財務状況が保たれています。7月以降の予測では、収益がやや増加し、支出も管理可能な範囲で推移する見込みです。このような状況では、企業は収益性の向上を目指し、効率的な経営を推進することが求められます。特に、コスト削減を図りつつ、収益性の高いプロジェクトに重点的にリソースを配分することで、会社全体の利益を最大化する戦略が重要です。また、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整えることで、長期的な成長を確保することができます。このような戦略により、競争力を強化し、持続的な成長を実現することが可能となるでしょう。さらに、将来の市場変化に対応するための柔軟な経営戦略を採用し、企業の競争力を維持し続けることが重要です。`
}
{
  income: [31.2, 32.8, 30.5, 32.2, 32.7, 33.3, 33.7, 34.2, 34.0, 34.8, 33.5, 34.0],
  expense: [-26.7, -28.3, -36.0, -29.8, -30.3, -31.0, -31.5, -31.9, -31.7, -32.5, -31.4, -32.0],
  action: `4月から6月の実績データでは、6月の残高が大幅にマイナスになっていることが確認されていますが、収益と支出のバランスは全体的に均衡しています。7月以降の予測では、収益と支出の双方がわずかに増加する見込みです。このような安定した状況を維持するためには、現在の戦略を継続し、効率的な運営を推進することが重要です。また、コスト削減の余地を見直し、無駄を排除することで、財務状況をさらに強化することが求められます。さらに、外部環境の変化に迅速に対応できる柔軟な経営体制を整え、リスク管理を強化することが必要です。これにより、企業は安定した成長を続けることができ、長期的な財務の健全性を確保することが可能となります。また、将来的な投資機会を慎重に評価し、必要に応じて戦略的な投資を行うことで、企業の競争力を維持し、成長を促進することが可能です。`
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 31.9, 32.3, 32.9, 33.3, 33.8, 33.5, 34.2, 33.0, 33.5],
  expense: [-26.7, -28.3, -36.0, -33.0, -33.5, -34.2, -34.8, -35.4, -35.1, -36.0, -34.6, -35.2],
  action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなり、企業の財務状況に懸念が生じています。7月以降の予測では、収益はわずかに増加するものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ徹底したコスト管理が求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトに焦点を当てることで、キャッシュフローを改善する必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じてリソースの再配分を検討することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて追加の資金調達を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に対応するため、常に柔軟な経営を心がけることが、将来の成功の鍵となります。`
}
{
  income: [31.2, 32.8, 30.5, 30.5, 31.0, 31.6, 32.0, 32.5, 32.2, 32.8, 31.5, 32.0],
  expense: [-26.7, -28.3, -36.0, -29.0, -29.5, -30.2, -30.7, -31.1, -30.9, -31.5, -30.3, -30.9],
  action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなっていることが確認されていますが、7月以降の収益予測はやや改善される見込みです。支出も管理可能な範囲で推移すると見込まれています。この状況では、企業はコスト管理を強化しつつ、収益性の向上を目指して効率的な経営を推進することが重要です。特に、コスト削減の取り組みを進め、無駄な支出を排除することで、財務の安定を図ることが求められます。また、収益性の高いプロジェクトに重点的にリソースを配分し、リスクの高いプロジェクトについては慎重に再評価することが必要です。さらに、外部環境の変化に柔軟に対応するため、企業は常にリスク管理を強化し、迅速に対応できる体制を整えることが重要です。このような戦略を実行することで、企業は持続可能な成長を実現し、長期的な財務健全性を確保することが可能となります。また、将来の市場変化に対応するための戦略的な計画を策定し、企業の競争力を維持することが求められます。`
}
{
  income: [31.2, 32.8, 30.5, 29.8, 30.3, 30.8, 31.2, 31.7, 31.4, 32.0, 30.8, 31.3],
  expense: [-26.7, -28.3, -36.0, -30.8, -31.3, -31.9, -32.5, -33.0, -32.7, -33.4, -32.2, -32.8],
  action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、企業の財務状況に深刻な懸念が生じています。7月以降の予測でも、収益は僅かに増加するものの、支出はそれ以上に増加する見込みです。この状況下で、企業は迅速かつ徹底的なコスト管理を行うことが求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトにリソースを集中させることで、キャッシュフローの改善を図る必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じて戦略的なリソース配分を検討することが重要です。さらに、短期的な資金繰りを安定させるために、財務戦略を再構築し、追加の資金調達や資金の再配分を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に迅速に対応するため、企業は常に柔軟な経営を心がけ、リスク管理を強化することが必要です。`
}
{
  income: [31.2, 32.8, 30.5, 28.5, 29.0, 29.6, 30.0, 30.4, 30.1, 30.7, 29.5, 30.0],
  expense: [-26.7, -28.3, -36.0, -33.8, -34.3, -34.9, -35.5, -36.0, -35.7, -36.5, -35.3, -35.9],
  action: `4月から6月の実績データによれば、特に6月の残高が大幅にマイナスになっていることが確認されています。7月以降の予測でも、収益は緩やかに減少し、支出がそれ以上に増加する見込みです。この非常に厳しい状況下で、企業は財務の安定を確保するために、緊急かつ徹底的な対策が必要です。まず、全社的にコスト削減を徹底し、無駄な支出を即座に削減する必要があります。さらに、低収益のプロジェクトや非戦略的な投資については、早急に見直しを行い、必要に応じて停止や縮小を検討することが求められます。また、短期的な資金繰りの安定を図るために、財務戦略を抜本的に見直し、追加の資金調達やコスト削減計画を策定することも重要です。さらに、リスクの高いプロジェクトについては、慎重に再評価を行い、必要に応じて戦略の修正を行うことが求められます。このような対応を迅速に行うことで、企業は予測されるリスクを最小限に抑え、最悪のシナリオを回避するための基盤を築くことができます。また、外部環境の変化に対応するため、常に柔軟な経営を心がけ、危機に備えることが将来の成功の鍵となります。`
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 40.0, 41.2, 42.8, 44.1, 45.5, 46.0, 47.2, 45.8, 46.7],
  expense: [-26.7, -28.3, -36.0, -22.0, -22.5, -23.1, -23.7, -24.3, -24.0, -25.0, -24.3, -25.0],
  action: `4月から6月の実績データを見ると、特に6月において残高が大幅にマイナスになっていますが、全体的には収益が安定しており、健全な財務状況が保たれています。7月以降の予測では、収益がさらに増加し、支出も適度に抑えられる見込みです。このような状況下では、企業はさらなる成長を目指して積極的に投資を行うべきです。特に、新興市場への進出や技術革新に対する投資が推奨されます。これにより、企業の競争力を高め、長期的な成長を実現することができます。また、収益性の高いプロジェクトに焦点を当て、リソースを効率的に配分することで、全体の収益性を向上させることが重要です。さらに、将来の不確実性に備えるため、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整える必要があります。このような戦略を実行することで、企業は安定した成長を続けることができ、長期的な財務健全性を維持することが可能となります。`
}
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 40.0, 41.2, 42.8, 44.1, 45.5, 46.0, 47.2, 45.8, 46.7],
  expense: [-26.7, -28.3, -36.0, -22.0, -22.5, -23.1, -23.7, -24.3, -24.0, -25.0, -24.3, -25.0],
  action: `4月から6月の実績データを見ると、特に6月において残高が大幅にマイナスになっていますが、全体的には収益が安定しており、健全な財務状況が保たれています。7月以降の予測では、収益がさらに増加し、支出も適度に抑えられる見込みです。このような状況下では、企業はさらなる成長を目指して積極的に投資を行うべきです。特に、新興市場への進出や技術革新に対する投資が推奨されます。これにより、企業の競争力を高め、長期的な成長を実現することができます。また、収益性の高いプロジェクトに焦点を当て、リソースを効率的に配分することで、全体の収益性を向上させることが重要です。さらに、将来の不確実性に備えるため、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整える必要があります。このような戦略を実行することで、企業は安定した成長を続けることができ、長期的な財務健全性を維持することが可能となります。`
}
{
  income: [31.2, 32.8, 30.5, 38.0, 39.0, 40.2, 41.1, 42.5, 43.0, 43.8, 42.5, 43.2],
  expense: [-26.7, -28.3, -36.0, -26.0, -27.0, -27.8, -28.3, -29.0, -28.7, -29.5, -28.5, -29.0],
  action: `4月から6月の実績データによれば、6月の残高が大幅にマイナスとなっていますが、全体的には収益は安定しており、支出も管理可能な範囲で抑えられています。7月以降の予測では、収益は引き続き増加する見込みですが、支出もわずかに増加することが予想されます。この状況下で、企業はコスト管理を強化し、収益性の向上を図ることが求められます。特に、無駄なコストを削減し、効率的な運営を実現するための内部プロセスの見直しが重要です。また、収益性の高いプロジェクトに集中投資し、低収益のプロジェクトについては再評価を行い、必要に応じてリソースを再配分することが推奨されます。さらに、市場の変化に対応するための柔軟な戦略を採用し、リスク管理を強化することで、企業の持続的な成長を確保することが可能となります。これにより、企業は安定した財務状況を維持しつつ、将来の成長に向けた準備を整えることができます。`
}
{
  income: [31.2, 32.8, 30.5, 36.0, 37.0, 38.0, 38.5, 39.2, 39.5, 40.0, 39.0, 39.5],
  expense: [-26.7, -28.3, -36.0, -33.0, -34.0, -34.8, -35.5, -36.3, -36.0, -37.0, -36.0, -36.5],
  action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、財務状況に不安が生じています。7月以降の予測では、収益の増加が続くものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ効果的な対応が必要です。まず、コスト削減を徹底し、無駄な支出を削減することで、キャッシュフローの改善を図ることが求められます。また、低収益プロジェクトの見直しや、必要に応じたリソースの再配分を行い、収益性の高い事業に集中投資することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて資金調達の手段を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。`
}
{
  income: [31.2, 32.8, 30.5, 34.5, 35.2, 36.0, 36.8, 37.5, 38.0, 38.5, 37.8, 38.2],
  expense: [-26.7, -28.3, -36.0, -28.0, -28.5, -29.2, -29.8, -30.5, -30.2, -31.0, -30.0, -30.5],
  action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなっていますが、収益と支出のバランスはおおむね取れており、安定した財務状況が保たれています。7月以降の予測では、収益がやや増加し、支出も管理可能な範囲で推移する見込みです。このような状況では、企業は収益性の向上を目指し、効率的な経営を推進することが求められます。特に、コスト削減を図りつつ、収益性の高いプロジェクトに重点的にリソースを配分することで、会社全体の利益を最大化する戦略が重要です。また、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整えることで、長期的な成長を確保することができます。このような戦略により、競争力を強化し、持続的な成長を実現することが可能となるでしょう。さらに、将来の市場変化に対応するための柔軟な経営戦略を採用し、企業の競争力を維持し続けることが重要です。`
}
{
  income: [31.2, 32.8, 30.5, 32.5, 33.2, 34.0, 34.8, 35.2, 35.5, 36.0, 35.0, 35.5],
  expense: [-26.7, -28.3, -36.0, -30.5, -31.0, -31.7, -32.5, -33.0, -32.7, -33.5, -32.5, -33.0],
  action: `4月から6月の実績データでは、6月の残高が大幅にマイナスになっていることが確認されていますが、収益と支出のバランスは全体的に均衡しています。7月以降の予測では、収益と支出の双方がわずかに増加する見込みです。このような安定した状況を維持するためには、現在の戦略を継続し、効率的な運営を推進することが重要です。また、コスト削減の余地を見直し、無駄を排除することで、財務状況をさらに強化することが求められます。さらに、外部環境の変化に迅速に対応できる柔軟な経営体制を整え、リスク管理を強化することが必要です。これにより、企業は安定した成長を続けることができ、長期的な財務の健全性を確保することが可能となります。また、将来的な投資機会を慎重に評価し、必要に応じて戦略的な投資を行うことで、企業の競争力を維持し、成長を促進することが可能です。`
}
{
  income: [31.2, 32.8, 30.5, 31.5, 32.0, 32.5, 33.0, 33.5, 33.0, 33.8, 32.8, 33.3],
  expense: [-26.7, -28.3, -36.0, -33.8, -34.3, -35.0, -35.8, -36.5, -36.0, -37.0, -36.0, -36.5],
  action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなり、企業の財務状況に懸念が生じています。7月以降の予測では、収益はわずかに増加するものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ徹底したコスト管理が求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトに焦点を当てることで、キャッシュフローを改善する必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じてリソースの再配分を検討することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて追加の資金調達を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に対応するため、常に柔軟な経営を心がけることが、将来の成功の鍵となります。`
}
{
  income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
  expense: [-26.7, -28.3, -36.0, -30.0, -30.5, -31.0, -31.5, -32.0, -31.7, -32.5, -31.5, -32.0],
  action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなっていることが確認されていますが、7月以降の収益予測はやや改善される見込みです。支出も管理可能な範囲で推移すると見込まれています。この状況では、企業はコスト管理を強化しつつ、収益性の向上を目指して効率的な経営を推進することが重要です。特に、コスト削減の取り組みを進め、無駄な支出を排除することで、財務の安定を図ることが求められます。また、収益性の高いプロジェクトに重点的にリソースを配分し、リスクの高いプロジェクトについては慎重に再評価することが必要です。さらに、外部環境の変化に柔軟に対応するため、企業は常にリスク管理を強化し、迅速に対応できる体制を整えることが重要です。このような戦略を実行することで、企業は持続可能な成長を実現し、長期的な財務健全性を確保することが可能となります。また、将来の市場変化に対応するための戦略的な計画を策定し、企業の競争力を維持することが求められます。`
}
{
  income: [31.2, 32.8, 30.5, 29.5, 30.0, 30.5, 31.0, 31.5, 31.0, 31.7, 30.8, 31.2],
  expense: [-26.7, -28.3, -36.0, -31.2, -31.7, -32.3, -33.0, -33.5, -33.0, -34.0, -33.0, -33.5],
  action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、企業の財務状況に深刻な懸念が生じています。7月以降の予測でも、収益は僅かに増加するものの、支出はそれ以上に増加する見込みです。この状況下で、企業は迅速かつ徹底的なコスト管理を行うことが求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトにリソースを集中させることで、キャッシュフローの改善を図る必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じて戦略的なリソース配分を検討することが重要です。さらに、短期的な資金繰りを安定させるために、財務戦略を再構築し、追加の資金調達や資金の再配分を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に迅速に対応するため、企業は常に柔軟な経営を心がけ、リスク管理を強化することが必要です。`
}
{
  income: [31.2, 32.8, 30.5, 28.0, 28.5, 29.0, 29.5, 30.0, 29.5, 30.0, 28.8, 29.2],
  expense: [-26.7, -28.3, -36.0, -35.0, -35.5, -36.2, -36.8, -37.5, -37.0, -38.0, -37.0, -37.5],
  action: `4月から6月の実績データによれば、特に6月の残高が大幅にマイナスになっていることが確認されています。7月以降の予測でも、収益は緩やかに減少し、支出がそれ以上に増加する見込みです。この非常に厳しい状況下で、企業は財務の安定を確保するために、緊急かつ徹底的な対策が必要です。まず、全社的にコスト削減を徹底し、無駄な支出を即座に削減する必要があります。さらに、低収益のプロジェクトや非戦略的な投資については、早急に見直しを行い、必要に応じて停止や縮小を検討することが求められます。また、短期的な資金繰りの安定を図るために、財務戦略を抜本的に見直し、追加の資金調達やコスト削減計画を策定することも重要です。さらに、リスクの高いプロジェクトについては、慎重に再評価を行い、必要に応じて戦略の修正を行うことが求められます。このような対応を迅速に行うことで、企業は予測されるリスクを最小限に抑え、最悪のシナリオを回避するための基盤を築くことができます。また、外部環境の変化に対応するため、常に柔軟な経営を心がけ、危機に備えることが将来の成功の鍵となります。`
}
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, ChartTypeRegistry, Legend, LegendItem, 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 dataSets = {
  '楽観-楽観': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -22.0, -22.5, -23.1, -23.7, -24.3, -24.0, -25.0, -24.3, -25.0],
    action: `4月から6月の実績データを見ると、特に6月において残高が大幅にマイナスになっていますが、全体的には収益が安定しており、健全な財務状況が保たれています。7月以降の予測では、収益がさらに増加し、支出も適度に抑えられる見込みです。このような状況下では、企業はさらなる成長を目指して積極的に投資を行うべきです。特に、新興市場への進出や技術革新に対する投資が推奨されます。これにより、企業の競争力を高め、長期的な成長を実現することができます。また、収益性の高いプロジェクトに焦点を当て、リソースを効率的に配分することで、全体の収益性を向上させることが重要です。さらに、将来の不確実性に備えるため、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整える必要があります。このような戦略を実行することで、企業は安定した成長を続けることができ、長期的な財務健全性を維持することが可能となります。`
  },
  '楽観-中立': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -26.0, -27.0, -27.8, -28.3, -29.0, -28.7, -29.5, -28.5, -29.0],
    action: `4月から6月の実績データによれば、6月の残高が大幅にマイナスとなっていますが、全体的には収益は安定しており、支出も管理可能な範囲で抑えられています。7月以降の予測では、収益は引き続き増加する見込みですが、支出もわずかに増加することが予想されます。この状況下で、企業はコスト管理を強化し、収益性の向上を図ることが求められます。特に、無駄なコストを削減し、効率的な運営を実現するための内部プロセスの見直しが重要です。また、収益性の高いプロジェクトに集中投資し、低収益のプロジェクトについては再評価を行い、必要に応じてリソースを再配分することが推奨されます。さらに、市場の変化に対応するための柔軟な戦略を採用し、リスク管理を強化することで、企業の持続的な成長を確保することが可能となります。これにより、企業は安定した財務状況を維持しつつ、将来の成長に向けた準備を整えることができます。`
  },
  '楽観-悲観': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -33.0, -34.0, -34.8, -35.5, -36.3, -36.0, -37.0, -36.0, -36.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、財務状況に不安が生じています。7月以降の予測では、収益の増加が続くものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ効果的な対応が必要です。まず、コスト削減を徹底し、無駄な支出を削減することで、キャッシュフローの改善を図ることが求められます。また、低収益プロジェクトの見直しや、必要に応じたリソースの再配分を行い、収益性の高い事業に集中投資することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて資金調達の手段を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。`
  },
  '中立-楽観': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -28.0, -28.5, -29.2, -29.8, -30.5, -30.2, -31.0, -30.0, -30.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなっていますが、収益と支出のバランスはおおむね取れており、安定した財務状況が保たれています。7月以降の予測では、収益がやや増加し、支出も管理可能な範囲で推移する見込みです。このような状況では、企業は収益性の向上を目指し、効率的な経営を推進することが求められます。特に、コスト削減を図りつつ、収益性の高いプロジェクトに重点的にリソースを配分することで、会社全体の利益を最大化する戦略が重要です。また、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整えることで、長期的な成長を確保することができます。このような戦略により、競争力を強化し、持続的な成長を実現することが可能となるでしょう。さらに、将来の市場変化に対応するための柔軟な経営戦略を採用し、企業の競争力を維持し続けることが重要です。`
  },
  '中立-中立': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -30.5, -31.0, -31.7, -32.5, -33.0, -32.7, -33.5, -32.5, -33.0],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスになっていることが確認されていますが、収益と支出のバランスは全体的に均衡しています。7月以降の予測では、収益と支出の双方がわずかに増加する見込みです。このような安定した状況を維持するためには、現在の戦略を継続し、効率的な運営を推進することが重要です。また、コスト削減の余地を見直し、無駄を排除することで、財務状況をさらに強化することが求められます。さらに、外部環境の変化に迅速に対応できる柔軟な経営体制を整え、リスク管理を強化することが必要です。これにより、企業は安定した成長を続けることができ、長期的な財務の健全性を確保することが可能となります。また、将来的な投資機会を慎重に評価し、必要に応じて戦略的な投資を行うことで、企業の競争力を維持し、成長を促進することが可能です。`
  },
  '中立-悲観': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -33.8, -34.3, -35.0, -35.8, -36.5, -36.0, -37.0, -36.0, -36.5],
    action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなり、企業の財務状況に懸念が生じています。7月以降の予測では、収益はわずかに増加するものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ徹底したコスト管理が求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトに焦点を当てることで、キャッシュフローを改善する必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じてリソースの再配分を検討することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて追加の資金調達を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に対応するため、常に柔軟な経営を心がけることが、将来の成功の鍵となります。`
  },
  '悲観-楽観': {
    income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
    expense: [-26.7, -28.3, -36.0, -30.0, -30.5, -31.0, -31.5, -32.0, -31.7, -32.5, -31.5, -32.0],
    action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなっていることが確認されていますが、7月以降の収益予測はやや改善される見込みです。支出も管理可能な範囲で推移すると見込まれています。この状況では、企業はコスト管理を強化しつつ、収益性の向上を目指して効率的な経営を推進することが重要です。特に、コスト削減の取り組みを進め、無駄な支出を排除することで、財務の安定を図ることが求められます。また、収益性の高いプロジェクトに重点的にリソースを配分し、リスクの高いプロジェクトについては慎重に再評価することが必要です。さらに、外部環境の変化に柔軟に対応するため、企業は常にリスク管理を強化し、迅速に対応できる体制を整えることが重要です。このような戦略を実行することで、企業は持続可能な成長を実現し、長期的な財務健全性を確保することが可能となります。また、将来の市場変化に対応するための戦略的な計画を策定し、企業の競争力を維持することが求められます。`
  },
  '悲観-中立':{
    income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
    expense: [-26.7, -28.3, -36.0, -31.2, -31.7, -32.3, -33.0, -33.5, -33.0, -34.0, -33.0, -33.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、企業の財務状況に深刻な懸念が生じています。7月以降の予測でも、収益は僅かに増加するものの、支出はそれ以上に増加する見込みです。この状況下で、企業は迅速かつ徹底的なコスト管理を行うことが求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトにリソースを集中させることで、キャッシュフローの改善を図る必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じて戦略的なリソース配分を検討することが重要です。さらに、短期的な資金繰りを安定させるために、財務戦略を再構築し、追加の資金調達や資金の再配分を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に迅速に対応するため、企業は常に柔軟な経営を心がけ、リスク管理を強化することが必要です。`
  },
  '悲観-悲観': {
    income: [31.2, 32.8, 30.5, 26.0, 26.8, 27.5, 28.0, 28.5, 28.0, 28.7, 27.8, 28.2],
    expense: [-26.7, -28.3, -36.0, -35.0, -35.5, -36.2, -36.8, -37.5, -37.0, -38.0, -37.0, -37.5],
    action: `4月から6月の実績データによれば、特に6月の残高が大幅にマイナスになっていることが確認されています。7月以降の予測でも、収益は緩やかに減少し、支出がそれ以上に増加する見込みです。この非常に厳しい状況下で、企業は財務の安定を確保するために、緊急かつ徹底的な対策が必要です。まず、全社的にコスト削減を徹底し、無駄な支出を即座に削減する必要があります。さらに、低収益のプロジェクトや非戦略的な投資については、早急に見直しを行い、必要に応じて停止や縮小を検討することが求められます。また、短期的な資金繰りの安定を図るために、財務戦略を抜本的に見直し、追加の資金調達やコスト削減計画を策定することも重要です。さらに、リスクの高いプロジェクトについては、慎重に再評価を行い、必要に応じて戦略の修正を行うことが求められます。このような対応を迅速に行うことで、企業は予測されるリスクを最小限に抑え、最悪のシナリオを回避するための基盤を築くことができます。また、外部環境の変化に対応するため、常に柔軟な経営を心がけ、危機に備えることが将来の成功の鍵となります。`
  }
  ,
};

// 定义其他必要的组件和函数
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>
  );
};

// 定义 AlertBox 组件
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 EmptyPage = () => {
  const pageName = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const [activeComparison, setActiveComparison] = useState('時系列比較');
  const [incomeLevel, setIncomeLevel] = useState('楽観');
  const [expenseLevel, setExpenseLevel] = useState('楽観');
  const [actionMessage, setActionMessage] = useState('');
  const chartRef = useRef<ChartJS | null>(null);

  const updateChartAndAction = () => {
    const key = `${incomeLevel}-${expenseLevel}` as keyof typeof dataSets;
    const selectedData = dataSets[key];

    if (chartRef.current) {
        // 更新图表数据对象
        chartRef.current.data = {
            labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
            datasets: [
                {
                    type: 'bar' as const,
                    label: '収入',
                    data: selectedData.income,
                    backgroundColor: function(context: ScriptableContext<'bar'>) {
                        const index = context.dataIndex;
                        return index < 3 ? 'rgba(153, 102, 255, 0.5)' : 'rgba(153, 102, 255, 0.2)';
                    },
                },
                {
                    type: 'bar' as const,
                    label: '支出',
                    data: selectedData.expense,
                    backgroundColor: function(context: ScriptableContext<'bar'>) {
                        const index = context.dataIndex;
                        return index < 3 ? 'rgba(54, 162, 235, 0.5)' : 'rgba(54, 162, 235, 0.2)';
                    },
                },
                {
                    type: 'line' as const,
                    label: '残高',
                    data: selectedData.income.map((income, i) => income + selectedData.expense[i]),
                    borderColor: 'black',
                    backgroundColor: 'black',
                    fill: false,
                    tension: 0.1,
                    borderWidth: 2,
                    pointStyle: 'rectRot',
                    pointRadius: 6,
                    pointHoverRadius: 8,
                    segment: {
                        borderDash: (ctx: { p0DataIndex: number }) => {
                            return ctx.p0DataIndex < 2 ? [] : [5, 5]; // 4月到6月实线,7月到3月虚线
                        },
                    },
                    pointBackgroundColor: (context: ScriptableContext<'line'>) => {
                        const index = context.dataIndex;
                        const value = context.dataset.data[index] as number;
                        return value < 0 ? 'red' : 'black';
                    }
                }
            ],
        };

        chartRef.current.update(); // 强制触发图表更新
    }

    setActionMessage(selectedData.action);
  };

  useEffect(() => {
    updateChartAndAction();
  }, [incomeLevel, expenseLevel]);

  const options = {
    responsive: true,
    layout: {
      padding: {
        top: 0,
      },
    },
    plugins: {
      legend: {
        display: true,
        labels: {
          color: 'white',
          boxWidth: 0,
          boxHeight: 0,
        },
      },
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: '百万円', // 纵轴单位
        },
      },
    },
  };

  const handleIncomeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setIncomeLevel(e.target.value);
  };

  const handleExpenseChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setExpenseLevel(e.target.value);
  };

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

  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 }
  ];

  useEffect(() => {
    ChartJS.register(customLegendPlugin);
    return () => {
      ChartJS.unregister(customLegendPlugin);
    };
  }, []);

  const customLegendPlugin = {
    id: 'customLegend',
    afterDraw: function (chart: ChartJS<keyof ChartTypeRegistry, unknown[], unknown>) {
      const legend = chart?.legend;
      if (!legend || !legend.legendItems) return;

      const ctx = chart.ctx;
      const itemWidth = 100; // 每个图例项的宽度(包括图标和文本)
      const startX = (chart.width - legend.legendItems.length * itemWidth) / 2; // 图例起始X坐标(居中)
      let currentX = startX;

      const y = 10; // 在图例下方添加额外的距离,以确保与图表内容不重叠

      legend.legendItems.forEach((legendItem: LegendItem, i: number) => {
        if (legendItem.text === '残高') {
          ctx.save();
          ctx.strokeStyle = legendItem.strokeStyle as string;
          ctx.lineWidth = 2; // 设置线条的宽度
          ctx.beginPath();
          ctx.moveTo(currentX, y);
          ctx.lineTo(currentX + 40, y); // 假设线条宽度为 40
          ctx.stroke();
          ctx.restore();
        } else {
          ctx.save();
          ctx.fillStyle = legendItem.fillStyle as string;
          ctx.fillRect(currentX, y - 5, 40, 10); // 假设图标宽度为 40, 高度为 10
          ctx.restore();
        }

        ctx.textBaseline = 'middle';
        ctx.fillStyle = 'black';
        ctx.fillText(legendItem.text, currentX + 50, y); // 图标和文本之间的间距为 50

        currentX += itemWidth; // 移动到下一个图例项
      });
    },
  };

  const defaultData = {
    labels:[],
    datasets:[]
  }

  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={chartRef.current?.data || defaultData} 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" onChange={handleIncomeChange} value={incomeLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select" onChange={handleExpenseChange} value={expenseLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <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'>
            {actionMessage}
          </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 { 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: 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>
    );
  };

  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}
                    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製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に8月と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">12.8回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">86,912万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">6,790万円</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('4月');
  const [selectedEndMonth, setSelectedEndMonth] = useState('3月');
  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 = ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'];

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

  const handleEndMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value || '3月'; // 默认值为 3月
    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>
    );
  };

  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}
                    onChange={handleStartMonthChange}
                    min="4月"
                    max="3月"
                  />
                  <span>~</span>
                  <input
                    type="month"
                    className="inventory-filter-date"
                    value={selectedEndMonth}
                    onChange={handleEndMonthChange}
                    min="4月"
                    max="3月"
                  />
                </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製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に8月と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">12.8回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">86,912万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">6,790万円</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 displayLabels = ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'];

  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: displayLabels.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: displayLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: getColorForProduct(selectedProduct),
          }
        ],
      };
    }

    return {
      labels: displayLabels.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>
    );
  };

  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}
                    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製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に8月と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">12.8回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">86,912万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">6,790万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
  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 }
  ];
kirin-ri commented 3 months ago
const operatingCashFlow = [
  { month: '4月', amount: '1,200,000円' },
  { month: '5月', amount: '▲800,000円', alert: true },
  { month: '6月', amount: '▲500,000円', alert: true },
  { month: '7月', amount: '1,500,000円' },
  { month: '8月', amount: '1,300,000円' },
  { month: '9月', amount: '▲700,000円', alert: true },
  { month: '10月', amount: '1,400,000円' },
  { month: '11月', amount: '▲600,000円', alert: true },
  { month: '12月', amount: '1,600,000円' },
  { month: '1月', amount: '1,800,000円' },
  { month: '2月', amount: '1,700,000円' },
  { month: '3月', amount: '▲900,000円', alert: true }
];
const investingCashFlow = [
  { month: '4月', amount: '▲3,000,000円', alert: true },
  { month: '5月', amount: '▲2,500,000円', alert: true },
  { month: '6月', amount: '▲4,000,000円', alert: true },
  { month: '7月', amount: '▲1,500,000円', alert: true },
  { month: '8月', amount: '▲2,000,000円', alert: true },
  { month: '9月', amount: '▲3,500,000円', alert: true },
  { month: '10月', amount: '▲2,200,000円', alert: true },
  { month: '11月', amount: '▲1,800,000円', alert: true },
  { month: '12月', amount: '▲2,600,000円', alert: true },
  { month: '1月', amount: '▲2,700,000円', alert: true },
  { month: '2月', amount: '▲3,100,000円', alert: true },
  { month: '3月', amount: '▲2,900,000円', alert: true }
];
const financingCashFlow = [
  { month: '4月', amount: '500,000円' },
  { month: '5月', amount: '▲300,000円', alert: true },
  { month: '6月', amount: '200,000円' },
  { month: '7月', amount: '▲100,000円', alert: true },
  { month: '8月', amount: '600,000円' },
  { month: '9月', amount: '▲200,000円', alert: true },
  { month: '10月', amount: '700,000円' },
  { month: '11月', amount: '▲150,000円', alert: true },
  { month: '12月', amount: '800,000円' },
  { month: '1月', amount: '900,000円' },
  { month: '2月', amount: '1,000,000円' },
  { month: '3月', amount: '▲250,000円', alert: true }
];
kirin-ri commented 3 months ago
const operatingCashFlow = [
  { month: '4月', amount: '1,234,567円' },
  { month: '5月', amount: '▲876,543円', alert: true },
  { month: '6月', amount: '▲567,890円', alert: true },
  { month: '7月', amount: '1,456,789円' },
  { month: '8月', amount: '1,234,890円' },
  { month: '9月', amount: '▲789,123円', alert: true },
  { month: '10月', amount: '1,345,678円' },
  { month: '11月', amount: '▲654,321円', alert: true },
  { month: '12月', amount: '1,567,234円' },
  { month: '1月', amount: '1,789,012円' },
  { month: '2月', amount: '1,678,901円' },
  { month: '3月', amount: '▲901,234円', alert: true }
];
const investingCashFlow = [
  { month: '4月', amount: '▲3,123,456円', alert: true },
  { month: '5月', amount: '▲2,456,789円', alert: true },
  { month: '6月', amount: '▲4,234,567円', alert: true },
  { month: '7月', amount: '▲1,567,890円', alert: true },
  { month: '8月', amount: '▲2,345,678円', alert: true },
  { month: '9月', amount: '▲3,678,901円', alert: true },
  { month: '10月', amount: '▲2,234,567円', alert: true },
  { month: '11月', amount: '▲1,890,123円', alert: true },
  { month: '12月', amount: '▲2,678,901円', alert: true },
  { month: '1月', amount: '▲2,789,012円', alert: true },
  { month: '2月', amount: '▲3,234,567円', alert: true },
  { month: '3月', amount: '▲2,901,234円', alert: true }
];
const financingCashFlow = [
  { month: '4月', amount: '523,456円' },
  { month: '5月', amount: '▲345,678円', alert: true },
  { month: '6月', amount: '278,901円' },
  { month: '7月', amount: '▲198,765円', alert: true },
  { month: '8月', amount: '612,345円' },
  { month: '9月', amount: '▲234,567円', alert: true },
  { month: '10月', amount: '734,567円' },
  { month: '11月', amount: '▲156,789円', alert: true },
  { month: '12月', amount: '812,345円' },
  { month: '1月', amount: '923,456円' },
  { month: '2月', amount: '1,034,567円' },
  { month: '3月', amount: '▲289,012円', alert: true }
];
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, ChartTypeRegistry, Legend, LegendItem, 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 dataSets = {
  '楽観-楽観': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -22.0, -22.5, -23.1, -23.7, -24.3, -24.0, -25.0, -24.3, -25.0],
    action: `4月から6月の実績データを見ると、特に6月において残高が大幅にマイナスになっていますが、全体的には収益が安定しており、健全な財務状況が保たれています。7月以降の予測では、収益がさらに増加し、支出も適度に抑えられる見込みです。このような状況下では、企業はさらなる成長を目指して積極的に投資を行うべきです。特に、新興市場への進出や技術革新に対する投資が推奨されます。これにより、企業の競争力を高め、長期的な成長を実現することができます。また、収益性の高いプロジェクトに焦点を当て、リソースを効率的に配分することで、全体の収益性を向上させることが重要です。さらに、将来の不確実性に備えるため、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整える必要があります。このような戦略を実行することで、企業は安定した成長を続けることができ、長期的な財務健全性を維持することが可能となります。`
  },
  '楽観-中立': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -26.0, -27.0, -27.8, -28.3, -29.0, -28.7, -29.5, -28.5, -29.0],
    action: `4月から6月の実績データによれば、6月の残高が大幅にマイナスとなっていますが、全体的には収益は安定しており、支出も管理可能な範囲で抑えられています。7月以降の予測では、収益は引き続き増加する見込みですが、支出もわずかに増加することが予想されます。この状況下で、企業はコスト管理を強化し、収益性の向上を図ることが求められます。特に、無駄なコストを削減し、効率的な運営を実現するための内部プロセスの見直しが重要です。また、収益性の高いプロジェクトに集中投資し、低収益のプロジェクトについては再評価を行い、必要に応じてリソースを再配分することが推奨されます。さらに、市場の変化に対応するための柔軟な戦略を採用し、リスク管理を強化することで、企業の持続的な成長を確保することが可能となります。これにより、企業は安定した財務状況を維持しつつ、将来の成長に向けた準備を整えることができます。`
  },
  '楽観-悲観': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -33.0, -34.0, -34.8, -35.5, -36.3, -36.0, -37.0, -36.0, -36.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、財務状況に不安が生じています。7月以降の予測では、収益の増加が続くものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ効果的な対応が必要です。まず、コスト削減を徹底し、無駄な支出を削減することで、キャッシュフローの改善を図ることが求められます。また、低収益プロジェクトの見直しや、必要に応じたリソースの再配分を行い、収益性の高い事業に集中投資することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて資金調達の手段を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。`
  },
  '中立-楽観': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -28.0, -28.5, -29.2, -29.8, -30.5, -30.2, -31.0, -30.0, -30.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなっていますが、収益と支出のバランスはおおむね取れており、安定した財務状況が保たれています。7月以降の予測では、収益がやや増加し、支出も管理可能な範囲で推移する見込みです。このような状況では、企業は収益性の向上を目指し、効率的な経営を推進することが求められます。特に、コスト削減を図りつつ、収益性の高いプロジェクトに重点的にリソースを配分することで、会社全体の利益を最大化する戦略が重要です。また、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整えることで、長期的な成長を確保することができます。このような戦略により、競争力を強化し、持続的な成長を実現することが可能となるでしょう。さらに、将来の市場変化に対応するための柔軟な経営戦略を採用し、企業の競争力を維持し続けることが重要です。`
  },
  '中立-中立': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -30.5, -31.0, -31.7, -32.5, -33.0, -32.7, -33.5, -32.5, -33.0],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスになっていることが確認されていますが、収益と支出のバランスは全体的に均衡しています。7月以降の予測では、収益と支出の双方がわずかに増加する見込みです。このような安定した状況を維持するためには、現在の戦略を継続し、効率的な運営を推進することが重要です。また、コスト削減の余地を見直し、無駄を排除することで、財務状況をさらに強化することが求められます。さらに、外部環境の変化に迅速に対応できる柔軟な経営体制を整え、リスク管理を強化することが必要です。これにより、企業は安定した成長を続けることができ、長期的な財務の健全性を確保することが可能となります。また、将来的な投資機会を慎重に評価し、必要に応じて戦略的な投資を行うことで、企業の競争力を維持し、成長を促進することが可能です。`
  },
  '中立-悲観': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -33.8, -34.3, -35.0, -35.8, -36.5, -36.0, -37.0, -36.0, -36.5],
    action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなり、企業の財務状況に懸念が生じています。7月以降の予測では、収益はわずかに増加するものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ徹底したコスト管理が求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトに焦点を当てることで、キャッシュフローを改善する必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じてリソースの再配分を検討することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて追加の資金調達を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に対応するため、常に柔軟な経営を心がけることが、将来の成功の鍵となります。`
  },
  '悲観-楽観': {
    income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
    expense: [-26.7, -28.3, -36.0, -30.0, -30.5, -31.0, -31.5, -32.0, -31.7, -32.5, -31.5, -32.0],
    action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなっていることが確認されていますが、7月以降の収益予測はやや改善される見込みです。支出も管理可能な範囲で推移すると見込まれています。この状況では、企業はコスト管理を強化しつつ、収益性の向上を目指して効率的な経営を推進することが重要です。特に、コスト削減の取り組みを進め、無駄な支出を排除することで、財務の安定を図ることが求められます。また、収益性の高いプロジェクトに重点的にリソースを配分し、リスクの高いプロジェクトについては慎重に再評価することが必要です。さらに、外部環境の変化に柔軟に対応するため、企業は常にリスク管理を強化し、迅速に対応できる体制を整えることが重要です。このような戦略を実行することで、企業は持続可能な成長を実現し、長期的な財務健全性を確保することが可能となります。また、将来の市場変化に対応するための戦略的な計画を策定し、企業の競争力を維持することが求められます。`
  },
  '悲観-中立':{
    income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
    expense: [-26.7, -28.3, -36.0, -31.2, -31.7, -32.3, -33.0, -33.5, -33.0, -34.0, -33.0, -33.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、企業の財務状況に深刻な懸念が生じています。7月以降の予測でも、収益は僅かに増加するものの、支出はそれ以上に増加する見込みです。この状況下で、企業は迅速かつ徹底的なコスト管理を行うことが求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトにリソースを集中させることで、キャッシュフローの改善を図る必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じて戦略的なリソース配分を検討することが重要です。さらに、短期的な資金繰りを安定させるために、財務戦略を再構築し、追加の資金調達や資金の再配分を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に迅速に対応するため、企業は常に柔軟な経営を心がけ、リスク管理を強化することが必要です。`
  },
  '悲観-悲観': {
    income: [31.2, 32.8, 30.5, 26.0, 26.8, 27.5, 28.0, 28.5, 28.0, 28.7, 27.8, 28.2],
    expense: [-26.7, -28.3, -36.0, -35.0, -35.5, -36.2, -36.8, -37.5, -37.0, -38.0, -37.0, -37.5],
    action: `4月から6月の実績データによれば、特に6月の残高が大幅にマイナスになっていることが確認されています。7月以降の予測でも、収益は緩やかに減少し、支出がそれ以上に増加する見込みです。この非常に厳しい状況下で、企業は財務の安定を確保するために、緊急かつ徹底的な対策が必要です。まず、全社的にコスト削減を徹底し、無駄な支出を即座に削減する必要があります。さらに、低収益のプロジェクトや非戦略的な投資については、早急に見直しを行い、必要に応じて停止や縮小を検討することが求められます。また、短期的な資金繰りの安定を図るために、財務戦略を抜本的に見直し、追加の資金調達やコスト削減計画を策定することも重要です。さらに、リスクの高いプロジェクトについては、慎重に再評価を行い、必要に応じて戦略の修正を行うことが求められます。このような対応を迅速に行うことで、企業は予測されるリスクを最小限に抑え、最悪のシナリオを回避するための基盤を築くことができます。また、外部環境の変化に対応するため、常に柔軟な経営を心がけ、危機に備えることが将来の成功の鍵となります。`
  }
  ,
};

// 定义其他必要的组件和函数
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>
  );
};

// 定义 AlertBox 组件
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 EmptyPage = () => {
  const pageName = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const [activeComparison, setActiveComparison] = useState('時系列比較');
  const [incomeLevel, setIncomeLevel] = useState('楽観');
  const [expenseLevel, setExpenseLevel] = useState('楽観');
  const [actionMessage, setActionMessage] = useState('');
  const chartRef = useRef<ChartJS | null>(null);

  const updateChartAndAction = () => {
    const key = `${incomeLevel}-${expenseLevel}` as keyof typeof dataSets;
    const selectedData = dataSets[key];

    if (chartRef.current) {
        // 更新图表数据对象
        chartRef.current.data = {
            labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
            datasets: [
                {
                    type: 'bar' as const,
                    label: '収入',
                    data: selectedData.income,
                    backgroundColor: function(context: ScriptableContext<'bar'>) {
                        const index = context.dataIndex;
                        return index < 3 ? 'rgba(153, 102, 255, 0.5)' : 'rgba(153, 102, 255, 0.2)';
                    },
                },
                {
                    type: 'bar' as const,
                    label: '支出',
                    data: selectedData.expense,
                    backgroundColor: function(context: ScriptableContext<'bar'>) {
                        const index = context.dataIndex;
                        return index < 3 ? 'rgba(54, 162, 235, 0.5)' : 'rgba(54, 162, 235, 0.2)';
                    },
                },
                {
                    type: 'line' as const,
                    label: '残高',
                    data: selectedData.income.map((income, i) => income + selectedData.expense[i]),
                    borderColor: 'black',
                    backgroundColor: 'black',
                    fill: false,
                    tension: 0.1,
                    borderWidth: 2,
                    pointStyle: 'rectRot',
                    pointRadius: 6,
                    pointHoverRadius: 8,
                    segment: {
                        borderDash: (ctx: { p0DataIndex: number }) => {
                            return ctx.p0DataIndex < 2 ? [] : [5, 5]; // 4月到6月实线,7月到3月虚线
                        },
                    },
                    pointBackgroundColor: (context: ScriptableContext<'line'>) => {
                        const index = context.dataIndex;
                        const value = context.dataset.data[index] as number;
                        return value < 0 ? 'red' : 'black';
                    }
                }
            ],
        };

        chartRef.current.update(); // 强制触发图表更新
    }

    setActionMessage(selectedData.action);
  };

  useEffect(() => {
    updateChartAndAction();
  }, [incomeLevel, expenseLevel]);

  const options = {
    responsive: true,
    layout: {
      padding: {
        top: 0,
      },
    },
    plugins: {
      legend: {
        display: true,
        labels: {
          color: 'white',
          boxWidth: 0,
          boxHeight: 0,
        },
      },
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: '百万円', // 纵轴单位
        },
      },
    },
  };

  const handleIncomeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setIncomeLevel(e.target.value);
  };

  const handleExpenseChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setExpenseLevel(e.target.value);
  };

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

  const operatingCashFlow = [
    { month: '4月', amount: '1,234,567円' },
    { month: '5月', amount: '▲876,543円', alert: true },
    { month: '6月', amount: '▲567,890円', alert: true },
    { month: '7月', amount: '1,456,789円' },
    { month: '8月', amount: '1,234,890円' },
    { month: '9月', amount: '▲789,123円', alert: true },
    { month: '10月', amount: '1,345,678円' },
    { month: '11月', amount: '▲654,321円', alert: true },
    { month: '12月', amount: '1,567,234円' },
    { month: '1月', amount: '1,789,012円' },
    { month: '2月', amount: '1,678,901円' },
    { month: '3月', amount: '▲901,234円', alert: true }
  ];
  const investingCashFlow = [
    { month: '4月', amount: '▲3,123,456円', alert: true },
    { month: '5月', amount: '▲2,456,789円', alert: true },
    { month: '6月', amount: '▲4,234,567円', alert: true },
    { month: '7月', amount: '▲1,567,890円', alert: true },
    { month: '8月', amount: '▲2,345,678円', alert: true },
    { month: '9月', amount: '▲3,678,901円', alert: true },
    { month: '10月', amount: '▲2,234,567円', alert: true },
    { month: '11月', amount: '▲1,890,123円', alert: true },
    { month: '12月', amount: '▲2,678,901円', alert: true },
    { month: '1月', amount: '▲2,789,012円', alert: true },
    { month: '2月', amount: '▲3,234,567円', alert: true },
    { month: '3月', amount: '▲2,901,234円', alert: true }
  ];

  const financingCashFlow = [
    { month: '4月', amount: '523,456円' },
    { month: '5月', amount: '▲345,678円', alert: true },
    { month: '6月', amount: '278,901円' },
    { month: '7月', amount: '▲198,765円', alert: true },
    { month: '8月', amount: '612,345円' },
    { month: '9月', amount: '▲234,567円', alert: true },
    { month: '10月', amount: '734,567円' },
    { month: '11月', amount: '▲156,789円', alert: true },
    { month: '12月', amount: '812,345円' },
    { month: '1月', amount: '923,456円' },
    { month: '2月', amount: '1,034,567円' },
    { month: '3月', amount: '▲289,012円', alert: true }
  ];

  useEffect(() => {
    ChartJS.register(customLegendPlugin);
    return () => {
      ChartJS.unregister(customLegendPlugin);
    };
  }, []);

  const customLegendPlugin = {
    id: 'customLegend',
    afterDraw: function (chart: ChartJS<keyof ChartTypeRegistry, unknown[], unknown>) {
      const legend = chart?.legend;
      if (!legend || !legend.legendItems) return;

      const ctx = chart.ctx;
      const itemWidth = 100; // 每个图例项的宽度(包括图标和文本)
      const startX = (chart.width - legend.legendItems.length * itemWidth) / 2; // 图例起始X坐标(居中)
      let currentX = startX;

      const y = 10; // 在图例下方添加额外的距离,以确保与图表内容不重叠

      legend.legendItems.forEach((legendItem: LegendItem, i: number) => {
        if (legendItem.text === '残高') {
          ctx.save();
          ctx.strokeStyle = legendItem.strokeStyle as string;
          ctx.lineWidth = 2; // 设置线条的宽度
          ctx.beginPath();
          ctx.moveTo(currentX, y);
          ctx.lineTo(currentX + 40, y); // 假设线条宽度为 40
          ctx.stroke();
          ctx.restore();
        } else {
          ctx.save();
          ctx.fillStyle = legendItem.fillStyle as string;
          ctx.fillRect(currentX, y - 5, 40, 10); // 假设图标宽度为 40, 高度为 10
          ctx.restore();
        }

        ctx.textBaseline = 'middle';
        ctx.fillStyle = 'black';
        ctx.fillText(legendItem.text, currentX + 50, y); // 图标和文本之间的间距为 50

        currentX += itemWidth; // 移动到下一个图例项
      });
    },
  };

  const defaultData = {
    labels:[],
    datasets:[]
  }

  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={chartRef.current?.data || defaultData} 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" onChange={handleIncomeChange} value={incomeLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select" onChange={handleExpenseChange} value={expenseLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <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'>
            {actionMessage}
          </div>
          <div className="collapsible-panels">
            <CollapsiblePanel title="営業キャッシュフロー" money="543,163円" details={operatingCashFlow} />
            <CollapsiblePanel title="投資キャッシュフロー" money="-2,761,307円" details={investingCashFlow} />
            <CollapsiblePanel title="財務キャッシュフロー" money="307,902円" details={financingCashFlow} />
          </div>
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;
kirin-ri commented 3 months ago
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);
  };

  const formatAmount = (amount: string) => {
    return amount.split(/(\d[\d,]*)/).map((part, index) =>
      /\d/.test(part) ? <span key={index} style={{ color: 'red' }}>{part}</span> : part
    );
  };

  return (
    <div className="collapsible-panel">
      <div className="panel-header" onClick={togglePanel}>
        <div className="panel-title">{title}</div>
        <div className="panel-money">{formatAmount(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.amount.split(/(\d[\d,]*)/).map((part, idx) =>
                  /\d/.test(part) ? <span key={idx} style={{ color: 'red' }}>{part}</span> : part
                )}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};
kirin-ri commented 3 months ago
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: 'black' }}>
                <span>{detail.month}</span>
                <span style={{ color: detail.alert ? 'red' : 'black', marginLeft: '5px' }}>
                  {detail.amount}
                </span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};
kirin-ri commented 3 months ago
import { BarElement, CategoryScale, Chart as ChartJS, ChartTypeRegistry, Legend, LegendItem, 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 dataSets = {
  '楽観-楽観': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -22.0, -22.5, -23.1, -23.7, -24.3, -24.0, -25.0, -24.3, -25.0],
    action: `4月から6月の実績データを見ると、特に6月において残高が大幅にマイナスになっていますが、全体的には収益が安定しており、健全な財務状況が保たれています。7月以降の予測では、収益がさらに増加し、支出も適度に抑えられる見込みです。このような状況下では、企業はさらなる成長を目指して積極的に投資を行うべきです。特に、新興市場への進出や技術革新に対する投資が推奨されます。これにより、企業の競争力を高め、長期的な成長を実現することができます。また、収益性の高いプロジェクトに焦点を当て、リソースを効率的に配分することで、全体の収益性を向上させることが重要です。さらに、将来の不確実性に備えるため、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整える必要があります。このような戦略を実行することで、企業は安定した成長を続けることができ、長期的な財務健全性を維持することが可能となります。`
  },
  '楽観-中立': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -26.0, -27.0, -27.8, -28.3, -29.0, -28.7, -29.5, -28.5, -29.0],
    action: `4月から6月の実績データによれば、6月の残高が大幅にマイナスとなっていますが、全体的には収益は安定しており、支出も管理可能な範囲で抑えられています。7月以降の予測では、収益は引き続き増加する見込みですが、支出もわずかに増加することが予想されます。この状況下で、企業はコスト管理を強化し、収益性の向上を図ることが求められます。特に、無駄なコストを削減し、効率的な運営を実現するための内部プロセスの見直しが重要です。また、収益性の高いプロジェクトに集中投資し、低収益のプロジェクトについては再評価を行い、必要に応じてリソースを再配分することが推奨されます。さらに、市場の変化に対応するための柔軟な戦略を採用し、リスク管理を強化することで、企業の持続的な成長を確保することが可能となります。これにより、企業は安定した財務状況を維持しつつ、将来の成長に向けた準備を整えることができます。`
  },
  '楽観-悲観': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -33.0, -34.0, -34.8, -35.5, -36.3, -36.0, -37.0, -36.0, -36.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、財務状況に不安が生じています。7月以降の予測では、収益の増加が続くものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ効果的な対応が必要です。まず、コスト削減を徹底し、無駄な支出を削減することで、キャッシュフローの改善を図ることが求められます。また、低収益プロジェクトの見直しや、必要に応じたリソースの再配分を行い、収益性の高い事業に集中投資することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて資金調達の手段を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。`
  },
  '中立-楽観': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -28.0, -28.5, -29.2, -29.8, -30.5, -30.2, -31.0, -30.0, -30.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなっていますが、収益と支出のバランスはおおむね取れており、安定した財務状況が保たれています。7月以降の予測では、収益がやや増加し、支出も管理可能な範囲で推移する見込みです。このような状況では、企業は収益性の向上を目指し、効率的な経営を推進することが求められます。特に、コスト削減を図りつつ、収益性の高いプロジェクトに重点的にリソースを配分することで、会社全体の利益を最大化する戦略が重要です。また、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整えることで、長期的な成長を確保することができます。このような戦略により、競争力を強化し、持続的な成長を実現することが可能となるでしょう。さらに、将来の市場変化に対応するための柔軟な経営戦略を採用し、企業の競争力を維持し続けることが重要です。`
  },
  '中立-中立': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -30.5, -31.0, -31.7, -32.5, -33.0, -32.7, -33.5, -32.5, -33.0],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスになっていることが確認されていますが、収益と支出のバランスは全体的に均衡しています。7月以降の予測では、収益と支出の双方がわずかに増加する見込みです。このような安定した状況を維持するためには、現在の戦略を継続し、効率的な運営を推進することが重要です。また、コスト削減の余地を見直し、無駄を排除することで、財務状況をさらに強化することが求められます。さらに、外部環境の変化に迅速に対応できる柔軟な経営体制を整え、リスク管理を強化することが必要です。これにより、企業は安定した成長を続けることができ、長期的な財務の健全性を確保することが可能となります。また、将来的な投資機会を慎重に評価し、必要に応じて戦略的な投資を行うことで、企業の競争力を維持し、成長を促進することが可能です。`
  },
  '中立-悲観': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -33.8, -34.3, -35.0, -35.8, -36.5, -36.0, -37.0, -36.0, -36.5],
    action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなり、企業の財務状況に懸念が生じています。7月以降の予測では、収益はわずかに増加するものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ徹底したコスト管理が求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトに焦点を当てることで、キャッシュフローを改善する必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じてリソースの再配分を検討することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて追加の資金調達を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に対応するため、常に柔軟な経営を心がけることが、将来の成功の鍵となります。`
  },
  '悲観-楽観': {
    income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
    expense: [-26.7, -28.3, -36.0, -30.0, -30.5, -31.0, -31.5, -32.0, -31.7, -32.5, -31.5, -32.0],
    action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなっていることが確認されていますが、7月以降の収益予測はやや改善される見込みです。支出も管理可能な範囲で推移すると見込まれています。この状況では、企業はコスト管理を強化しつつ、収益性の向上を目指して効率的な経営を推進することが重要です。特に、コスト削減の取り組みを進め、無駄な支出を排除することで、財務の安定を図ることが求められます。また、収益性の高いプロジェクトに重点的にリソースを配分し、リスクの高いプロジェクトについては慎重に再評価することが必要です。さらに、外部環境の変化に柔軟に対応するため、企業は常にリスク管理を強化し、迅速に対応できる体制を整えることが重要です。このような戦略を実行することで、企業は持続可能な成長を実現し、長期的な財務健全性を確保することが可能となります。また、将来の市場変化に対応するための戦略的な計画を策定し、企業の競争力を維持することが求められます。`
  },
  '悲観-中立':{
    income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
    expense: [-26.7, -28.3, -36.0, -31.2, -31.7, -32.3, -33.0, -33.5, -33.0, -34.0, -33.0, -33.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、企業の財務状況に深刻な懸念が生じています。7月以降の予測でも、収益は僅かに増加するものの、支出はそれ以上に増加する見込みです。この状況下で、企業は迅速かつ徹底的なコスト管理を行うことが求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトにリソースを集中させることで、キャッシュフローの改善を図る必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じて戦略的なリソース配分を検討することが重要です。さらに、短期的な資金繰りを安定させるために、財務戦略を再構築し、追加の資金調達や資金の再配分を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に迅速に対応するため、企業は常に柔軟な経営を心がけ、リスク管理を強化することが必要です。`
  },
  '悲観-悲観': {
    income: [31.2, 32.8, 30.5, 26.0, 26.8, 27.5, 28.0, 28.5, 28.0, 28.7, 27.8, 28.2],
    expense: [-26.7, -28.3, -36.0, -35.0, -35.5, -36.2, -36.8, -37.5, -37.0, -38.0, -37.0, -37.5],
    action: `4月から6月の実績データによれば、特に6月の残高が大幅にマイナスになっていることが確認されています。7月以降の予測でも、収益は緩やかに減少し、支出がそれ以上に増加する見込みです。この非常に厳しい状況下で、企業は財務の安定を確保するために、緊急かつ徹底的な対策が必要です。まず、全社的にコスト削減を徹底し、無駄な支出を即座に削減する必要があります。さらに、低収益のプロジェクトや非戦略的な投資については、早急に見直しを行い、必要に応じて停止や縮小を検討することが求められます。また、短期的な資金繰りの安定を図るために、財務戦略を抜本的に見直し、追加の資金調達やコスト削減計画を策定することも重要です。さらに、リスクの高いプロジェクトについては、慎重に再評価を行い、必要に応じて戦略の修正を行うことが求められます。このような対応を迅速に行うことで、企業は予測されるリスクを最小限に抑え、最悪のシナリオを回避するための基盤を築くことができます。また、外部環境の変化に対応するため、常に柔軟な経営を心がけ、危機に備えることが将来の成功の鍵となります。`
  }
  ,
};

// 定义其他必要的组件和函数
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: 'black' }}>
                <span>{detail.month}</span>
                <span style={{ color: detail.alert ? 'red' : 'black', marginLeft: '5px' }}>
                  {detail.amount}
                </span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

// 定义 AlertBox 组件
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 EmptyPage = () => {
  const pageName = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const [activeComparison, setActiveComparison] = useState('時系列比較');
  const [incomeLevel, setIncomeLevel] = useState('楽観');
  const [expenseLevel, setExpenseLevel] = useState('楽観');
  const [actionMessage, setActionMessage] = useState('');
  const chartRef = useRef<ChartJS | null>(null);

  const updateChartAndAction = () => {
    const key = `${incomeLevel}-${expenseLevel}` as keyof typeof dataSets;
    const selectedData = dataSets[key];

    if (chartRef.current) {
        // 更新图表数据对象
        chartRef.current.data = {
            labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
            datasets: [
                {
                    type: 'bar' as const,
                    label: '収入',
                    data: selectedData.income,
                    backgroundColor: function(context: ScriptableContext<'bar'>) {
                        const index = context.dataIndex;
                        return index < 3 ? 'rgba(153, 102, 255, 0.5)' : 'rgba(153, 102, 255, 0.2)';
                    },
                },
                {
                    type: 'bar' as const,
                    label: '支出',
                    data: selectedData.expense,
                    backgroundColor: function(context: ScriptableContext<'bar'>) {
                        const index = context.dataIndex;
                        return index < 3 ? 'rgba(54, 162, 235, 0.5)' : 'rgba(54, 162, 235, 0.2)';
                    },
                },
                {
                    type: 'line' as const,
                    label: '残高',
                    data: selectedData.income.map((income, i) => income + selectedData.expense[i]),
                    borderColor: 'black',
                    backgroundColor: 'black',
                    fill: false,
                    tension: 0.1,
                    borderWidth: 2,
                    pointStyle: 'rectRot',
                    pointRadius: 6,
                    pointHoverRadius: 8,
                    segment: {
                        borderDash: (ctx: { p0DataIndex: number }) => {
                            return ctx.p0DataIndex < 2 ? [] : [5, 5]; // 4月到6月实线,7月到3月虚线
                        },
                    },
                    pointBackgroundColor: (context: ScriptableContext<'line'>) => {
                        const index = context.dataIndex;
                        const value = context.dataset.data[index] as number;
                        return value < 0 ? 'red' : 'black';
                    }
                }
            ],
        };

        chartRef.current.update(); // 强制触发图表更新
    }

    setActionMessage(selectedData.action);
  };

  useEffect(() => {
    updateChartAndAction();
  }, [incomeLevel, expenseLevel]);

  const options = {
    responsive: true,
    layout: {
      padding: {
        top: 0,
      },
    },
    plugins: {
      legend: {
        display: true,
        labels: {
          color: 'white',
          boxWidth: 0,
          boxHeight: 0,
        },
      },
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: '百万円', // 纵轴单位
        },
      },
    },
  };

  const handleIncomeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setIncomeLevel(e.target.value);
  };

  const handleExpenseChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setExpenseLevel(e.target.value);
  };

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

  const operatingCashFlow = [
    { month: '4月', amount: '1,234,567円' },
    { month: '5月', amount: '▲876,543円', alert: true },
    { month: '6月', amount: '▲567,890円', alert: true },
    { month: '7月', amount: '1,456,789円' },
    { month: '8月', amount: '1,234,890円' },
    { month: '9月', amount: '▲789,123円', alert: true },
    { month: '10月', amount: '1,345,678円' },
    { month: '11月', amount: '▲654,321円', alert: true },
    { month: '12月', amount: '1,567,234円' },
    { month: '1月', amount: '1,789,012円' },
    { month: '2月', amount: '1,678,901円' },
    { month: '3月', amount: '▲901,234円', alert: true }
  ];
  const investingCashFlow = [
    { month: '4月', amount: '▲3,123,456円', alert: true },
    { month: '5月', amount: '▲2,456,789円', alert: true },
    { month: '6月', amount: '▲4,234,567円', alert: true },
    { month: '7月', amount: '▲1,567,890円', alert: true },
    { month: '8月', amount: '▲2,345,678円', alert: true },
    { month: '9月', amount: '▲3,678,901円', alert: true },
    { month: '10月', amount: '▲2,234,567円', alert: true },
    { month: '11月', amount: '▲1,890,123円', alert: true },
    { month: '12月', amount: '▲2,678,901円', alert: true },
    { month: '1月', amount: '▲2,789,012円', alert: true },
    { month: '2月', amount: '▲3,234,567円', alert: true },
    { month: '3月', amount: '▲2,901,234円', alert: true }
  ];

  const financingCashFlow = [
    { month: '4月', amount: '523,456円' },
    { month: '5月', amount: '▲345,678円', alert: true },
    { month: '6月', amount: '278,901円' },
    { month: '7月', amount: '▲198,765円', alert: true },
    { month: '8月', amount: '612,345円' },
    { month: '9月', amount: '▲234,567円', alert: true },
    { month: '10月', amount: '734,567円' },
    { month: '11月', amount: '▲156,789円', alert: true },
    { month: '12月', amount: '812,345円' },
    { month: '1月', amount: '923,456円' },
    { month: '2月', amount: '1,034,567円' },
    { month: '3月', amount: '▲289,012円', alert: true }
  ];

  useEffect(() => {
    ChartJS.register(customLegendPlugin);
    return () => {
      ChartJS.unregister(customLegendPlugin);
    };
  }, []);

  const customLegendPlugin = {
    id: 'customLegend',
    afterDraw: function (chart: ChartJS<keyof ChartTypeRegistry, unknown[], unknown>) {
      const legend = chart?.legend;
      if (!legend || !legend.legendItems) return;

      const ctx = chart.ctx;
      const itemWidth = 100; // 每个图例项的宽度(包括图标和文本)
      const startX = (chart.width - legend.legendItems.length * itemWidth) / 2; // 图例起始X坐标(居中)
      let currentX = startX;

      const y = 10; // 在图例下方添加额外的距离,以确保与图表内容不重叠

      legend.legendItems.forEach((legendItem: LegendItem, i: number) => {
        if (legendItem.text === '残高') {
          ctx.save();
          ctx.strokeStyle = legendItem.strokeStyle as string;
          ctx.lineWidth = 2; // 设置线条的宽度
          ctx.beginPath();
          ctx.moveTo(currentX, y);
          ctx.lineTo(currentX + 40, y); // 假设线条宽度为 40
          ctx.stroke();
          ctx.restore();
        } else {
          ctx.save();
          ctx.fillStyle = legendItem.fillStyle as string;
          ctx.fillRect(currentX, y - 5, 40, 10); // 假设图标宽度为 40, 高度为 10
          ctx.restore();
        }

        ctx.textBaseline = 'middle';
        ctx.fillStyle = 'black';
        ctx.fillText(legendItem.text, currentX + 50, y); // 图标和文本之间的间距为 50

        currentX += itemWidth; // 移动到下一个图例项
      });
    },
  };

  const defaultData = {
    labels:[],
    datasets:[]
  }

  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={chartRef.current?.data || defaultData} 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" onChange={handleIncomeChange} value={incomeLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select" onChange={handleExpenseChange} value={expenseLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <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'>
            {actionMessage}
          </div>
          <div className="collapsible-panels">
            <CollapsiblePanel title="営業キャッシュフロー" money="543,163円" details={operatingCashFlow} />
            <CollapsiblePanel title="投資キャッシュフロー" money="-2,761,307円" details={investingCashFlow} />
            <CollapsiblePanel title="財務キャッシュフロー" money="307,902円" details={financingCashFlow} />
          </div>
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;
kirin-ri commented 3 months ago
const EmptyPage = () => {
  const pageName = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const [activeComparison, setActiveComparison] = useState('時系列比較');
  const [incomeLevel, setIncomeLevel] = useState('楽観');
  const [expenseLevel, setExpenseLevel] = useState('楽観');
  const [actionMessage, setActionMessage] = useState('');
  const chartRef = useRef<ChartJS | null>(null);

  const updateChartAndAction = () => {
    const key = `${incomeLevel}-${expenseLevel}` as keyof typeof dataSets;
    const selectedData = dataSets[key];

    if (chartRef.current) {
      chartRef.current.data = {
        labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
        datasets: [
          {
            type: 'bar' as const,
            label: '収入',
            data: selectedData.income,
            backgroundColor: function(context: ScriptableContext<'bar'>) {
              const index = context.dataIndex;
              return index < 3 ? 'rgba(153, 102, 255, 0.5)' : 'rgba(153, 102, 255, 0.2)';
            },
          },
          {
            type: 'bar' as const,
            label: '支出',
            data: selectedData.expense,
            backgroundColor: function(context: ScriptableContext<'bar'>) {
              const index = context.dataIndex;
              return index < 3 ? 'rgba(54, 162, 235, 0.5)' : 'rgba(54, 162, 235, 0.2)';
            },
          },
          {
            type: 'line' as const,
            label: '残高',
            data: selectedData.income.map((income, i) => income + selectedData.expense[i]),
            borderColor: 'black',
            backgroundColor: 'black',
            fill: false,
            tension: 0.1,
            borderWidth: 2,
            pointStyle: 'rectRot',
            pointRadius: 6,
            pointHoverRadius: 8,
            segment: {
              borderDash: (ctx: { p0DataIndex: number }) => {
                return ctx.p0DataIndex < 2 ? [] : [5, 5];
              },
            },
            pointBackgroundColor: (context: ScriptableContext<'line'>) => {
              const index = context.dataIndex;
              const value = context.dataset.data[index] as number;
              return value < 0 ? 'red' : 'black';
            },
          },
        ],
      };

      chartRef.current.update();
    }

    // ここで改行を追加
    const formattedActionMessage = selectedData.action.replace(/。/g, '。<br>');
    setActionMessage(formattedActionMessage);
  };

  useEffect(() => {
    updateChartAndAction();
  }, [incomeLevel, expenseLevel]);

  const options = {
    responsive: true,
    layout: {
      padding: {
        top: 0,
      },
    },
    plugins: {
      legend: {
        display: true,
        labels: {
          color: 'white',
          boxWidth: 0,
          boxHeight: 0,
        },
      },
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: '百万円',
        },
      },
    },
  };

  const handleIncomeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setIncomeLevel(e.target.value);
  };

  const handleExpenseChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setExpenseLevel(e.target.value);
  };

  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={chartRef.current?.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" onChange={handleIncomeChange} value={incomeLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <button className="filter-btn">支出</button>
                <select className="filter-select" onChange={handleExpenseChange} value={expenseLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <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" dangerouslySetInnerHTML={{ __html: actionMessage }} />
          <div className="collapsible-panels">
            <CollapsiblePanel title="営業キャッシュフロー" money="543,163円" details={operatingCashFlow} />
            <CollapsiblePanel title="投資キャッシュフロー" money="-2,761,307円" details={investingCashFlow} />
            <CollapsiblePanel title="財務キャッシュフロー" money="307,902円" details={financingCashFlow} />
          </div>
        </div>
      </div>
    </div>
  );
};

export default EmptyPage;
kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 40.0, 41.2, 42.8, 44.1, 45.5, 46.0, 47.2, 45.8, 46.7],
  expense: [-26.7, -28.3, -36.0, -22.0, -22.5, -23.1, -23.7, -24.3, -24.0, -25.0, -24.3, -25.0],
  action: `4月から6月の実績データを見ると、収益は非常に安定しており、特に6月の一時的なマイナスを除けば、健全な財務状況が維持されています。7月以降の予測では、収益がさらに増加し、支出も適切に管理される見込みです。このような状況下では、新たな市場への進出や技術革新への投資が推奨され、企業の長期的な成長が期待されます。全体として、リスクは低く、持続的な成長が見込めます。`
}
kirin-ri commented 3 months ago

action:4月から6月の実績データでは、収益は安定しており、支出も適切に管理されていますが、6月に一時的な残高のマイナスが発生しました。7月以降の予測では、収益の増加が見込まれるものの、支出の増加も予測されています。このため、企業はさらなるコスト削減と効率的な運営を図る必要があります。特に、無駄なコストを排除し、収益性の高いプロジェクトにリソースを集中させることが重要です。リスク管理の強化も併せて行い、持続可能な成長を確保するための体制を整えるべきです。企業がこの戦略を実行することで、将来の不確実性に対処しながら、安定した成長を続けることが可能となります。``

kirin-ri commented 3 months ago
{
  income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
  expense: [-26.7, -28.3, -36.0, -30.0, -30.5, -31.0, -31.5, -32.0, -31.7, -32.5, -31.5, -32.0],
  action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなっていることが確認されていますが、7月以降の収益予測はやや改善される見込みです。支出も管理可能な範囲で推移すると見込まれています。この状況では、企業はコスト管理を強化しつつ、収益性の向上を目指して効率的な経営を推進することが重要です。特に、コスト削減の取り組みを進め、無駄な支出を排除することで、財務の安定を図ることが求められます。また、収益性の高いプロジェクトに重点的にリソースを配分し、リスクの高いプロジェクトについては慎重に再評価することが必要です。さらに、外部環境の変化に柔軟に対応するため、企業は常にリスク管理を強化し、迅速に対応できる体制を整えることが重要です。このような戦略を実行することで、企業は持続可能な成長を実現し、長期的な財務健全性を確保することが可能となります。また、将来の市場変化に対応するための戦略的な計画を策定し、企業の競争力を維持することが求められます。`
}
kirin-ri commented 3 months ago
chart.mjs:4009 Uncaught Error: "bar" is not a registered controller.
    at Object._get (chart.mjs:4009:13)
    at Object.getController (chart.mjs:3955:17)
    at hs.buildOrUpdateControllers (chart.mjs:5660:42)
    at hs.update (chart.mjs:5694:33)
    at financingPage.tsx:210:26
    at financingPage.tsx:217:5
    at il (react-dom.production.min.js:243:332)
    at wc (react-dom.production.min.js:285:111)
    at react-dom.production.min.js:282:409
    at _c (react-dom.production.min.js:280:398)
kirin-ri commented 3 months ago
Error: "line" is not a registered controller.
    at Object._get (chart.mjs:4009:13)
    at Object.getController (chart.mjs:3955:17)
    at hs.buildOrUpdateControllers (chart.mjs:5660:42)
    at hs.update (chart.mjs:5694:33)
    at financingPage.tsx:115:26
    at financingPage.tsx:122:5
    at il (react-dom.production.min.js:243:332)
    at wc (react-dom.production.min.js:285:111)
    at react-dom.production.min.js:282:409
    at _c (react-dom.production.min.js:280:398)
kirin-ri commented 3 months ago
const handleStartMonthChange = (startMonth) => {
    if (startMonth >= endMonth) {
        // 終わりの月の前の月までしか選べないように制限する
        setStartMonth(endMonth - 1);
        alert("始まりの月は終わりの月の前の月までしか選べません。");
    } else {
        setStartMonth(startMonth);
    }
};

const handleEndMonthChange = (endMonth) => {
    if (endMonth <= startMonth) {
        // 始まりの月の後の月しか選べないように制限する
        setEndMonth(startMonth + 1);
        alert("終わりの月は始まりの月の後の月以上にしてください。");
    } else {
        setEndMonth(endMonth);
    }
};
kirin-ri commented 3 months ago
ERROR in src/components/pages/inventoryTurnsPage.tsx:24:35
TS7006: Parameter 'startMonth' implicitly has an 'any' type.
    22 |   const displayLabels = ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'];
    23 |
  > 24 |   const handleStartMonthChange = (startMonth) => {
       |                                   ^^^^^^^^^^
    25 |     if (startMonth >= endMonth) {
    26 |         // 終わりの月の前の月までしか選べないように制限する
    27 |         setStartMonth(endMonth - 1);

ERROR in src/components/pages/inventoryTurnsPage.tsx:25:23
TS2304: Cannot find name 'endMonth'.
    23 |
    24 |   const handleStartMonthChange = (startMonth) => {
  > 25 |     if (startMonth >= endMonth) {
       |                       ^^^^^^^^
    26 |         // 終わりの月の前の月までしか選べないように制限する
    27 |         setStartMonth(endMonth - 1);
    28 |         alert("始まりの月は終わりの月の前の月までしか選べません。");

ERROR in src/components/pages/inventoryTurnsPage.tsx:27:9
TS2552: Cannot find name 'setStartMonth'. Did you mean 'startMonth'?
    25 |     if (startMonth >= endMonth) {
    26 |         // 終わりの月の前の月までしか選べないように制限する
  > 27 |         setStartMonth(endMonth - 1);
       |         ^^^^^^^^^^^^^
    28 |         alert("始まりの月は終わりの月の前の月までしか選べません。");
    29 |     } else {
    30 |         setStartMonth(startMonth);

ERROR in src/components/pages/inventoryTurnsPage.tsx:27:23
TS2304: Cannot find name 'endMonth'.
    25 |     if (startMonth >= endMonth) {
    26 |         // 終わりの月の前の月までしか選べないように制限する
  > 27 |         setStartMonth(endMonth - 1);
       |                       ^^^^^^^^
    28 |         alert("始まりの月は終わりの月の前の月までしか選べません。");
    29 |     } else {
    30 |         setStartMonth(startMonth);

ERROR in src/components/pages/inventoryTurnsPage.tsx:30:9
TS2552: Cannot find name 'setStartMonth'. Did you mean 'startMonth'?
    28 |         alert("始まりの月は終わりの月の前の月までしか選べません。");
    29 |     } else {
  > 30 |         setStartMonth(startMonth);
       |         ^^^^^^^^^^^^^
    31 |     }
    32 | };
    33 |

ERROR in src/components/pages/inventoryTurnsPage.tsx:34:31
TS7006: Parameter 'endMonth' implicitly has an 'any' type.
    32 | };
    33 |
  > 34 | const handleEndMonthChange = (endMonth) => {
       |                               ^^^^^^^^
    35 |     if (endMonth <= startMonth) {
    36 |         // 始まりの月の後の月しか選べないように制限する
    37 |         setEndMonth(startMonth + 1);

ERROR in src/components/pages/inventoryTurnsPage.tsx:35:21
TS2304: Cannot find name 'startMonth'.
    33 |
    34 | const handleEndMonthChange = (endMonth) => {
  > 35 |     if (endMonth <= startMonth) {
       |                     ^^^^^^^^^^
    36 |         // 始まりの月の後の月しか選べないように制限する
    37 |         setEndMonth(startMonth + 1);
    38 |         alert("終わりの月は始まりの月の後の月以上にしてください。");

ERROR in src/components/pages/inventoryTurnsPage.tsx:37:9
TS2552: Cannot find name 'setEndMonth'. Did you mean 'endMonth'?
    35 |     if (endMonth <= startMonth) {
    36 |         // 始まりの月の後の月しか選べないように制限する
  > 37 |         setEndMonth(startMonth + 1);
       |         ^^^^^^^^^^^
    38 |         alert("終わりの月は始まりの月の後の月以上にしてください。");
    39 |     } else {
    40 |         setEndMonth(endMonth);

ERROR in src/components/pages/inventoryTurnsPage.tsx:37:21
TS2304: Cannot find name 'startMonth'.
    35 |     if (endMonth <= startMonth) {
    36 |         // 始まりの月の後の月しか選べないように制限する
  > 37 |         setEndMonth(startMonth + 1);
       |                     ^^^^^^^^^^
    38 |         alert("終わりの月は始まりの月の後の月以上にしてください。");
    39 |     } else {
    40 |         setEndMonth(endMonth);

ERROR in src/components/pages/inventoryTurnsPage.tsx:40:9
TS2552: Cannot find name 'setEndMonth'. Did you mean 'endMonth'?
    38 |         alert("終わりの月は始まりの月の後の月以上にしてください。");
    39 |     } else {
  > 40 |         setEndMonth(endMonth);
       |         ^^^^^^^^^^^
    41 |     }
    42 | };
    43 |
kirin-ri commented 3 months ago
import { BarController, BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, LineController, LineElement, PointElement, Title, Tooltip } from 'chart.js';
import { useState } from "react";
import { Chart } from 'react-chartjs-2';

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

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 displayLabels = ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'];

  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: displayLabels.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: displayLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: getColorForProduct(selectedProduct),
          }
        ],
      };
    }

    return {
      labels: displayLabels.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>
    );
  };

  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}
                    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製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に8月と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">12.8回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">86,912万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">6,790万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

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

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

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 displayLabels = ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'];

  const handleStartMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value || '2023-04'; // 默认值为 2023-04
    if (value > selectedEndMonth) {
      alert("始まりの月は終わりの月より前に設定してください。");
    } else {
      setSelectedStartMonth(value);
    }
  };

  const handleEndMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value || '2024-03'; // 默认值为 2024-03
    if (value < selectedStartMonth) {
      alert("終わりの月は始まりの月より後に設定してください。");
    } else {
      setSelectedEndMonth(value);
    }
  };

  const getTimeSeriesData = () => {
    const startIndex = allLabels.indexOf(selectedStartMonth);
    const endIndex = allLabels.indexOf(selectedEndMonth);
    return {
      labels: displayLabels.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: displayLabels.slice(startIndex, endIndex + 1),
        datasets: [
          {
            label: `${selectedProduct}`,
            data: filteredData,
            backgroundColor: getColorForProduct(selectedProduct),
          }
        ],
      };
    }

    return {
      labels: displayLabels.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>
    );
  };

  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}
                    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製品の在庫回転率が最近数ヶ月で大幅に悪化しており、特に8月と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">12.8回</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間売上原価</div>
              <div className="inventory-metrics-value">86,912万円</div>
            </div>
            <div className="inventory-metrics-item">
              <div className="inventory-metrics-label">年間平均在庫金額</div>
              <div className="inventory-metrics-value">6,790万円</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default InventoryTurnsPage;
kirin-ri commented 3 months ago
    plugins: {
      legend: {
        display: true,
        labels: {
          color: 'white',
          boxWidth: 0,
          boxHeight: 0,
        },
      },
kirin-ri commented 3 months ago
const disableLegendClickPlugin = {
  id: 'disableLegendClick',
  beforeInit(chart) {
    chart.legend.options.onClick = () => {}; // 禁用图例点击
  }
};
kirin-ri commented 3 months ago
import { BarController, BarElement, CategoryScale, Chart as ChartJS, ChartTypeRegistry, Legend, LegendItem, LinearScale, LineController, LineElement, PointElement, ScriptableContext, Title, Tooltip } from 'chart.js';
import { useEffect, useRef, useState } from "react";
import { Chart } from 'react-chartjs-2';

ChartJS.register(BarController,LineController,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: 'black' }}>
                <span>{detail.month}</span>
                <span style={{ color: detail.alert ? 'red' : 'black', marginLeft: '5px' }}>
                  {detail.amount}
                </span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

// 定义 AlertBox 组件
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 EmptyPage = () => {
  const pageName = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true);
  const [activeComparison, setActiveComparison] = useState('時系列比較');
  const [incomeLevel, setIncomeLevel] = useState('楽観');
  const [expenseLevel, setExpenseLevel] = useState('楽観');
  const [actionMessage, setActionMessage] = useState('');
  const chartRef = useRef<ChartJS | null>(null);

  const updateChartAndAction = () => {
    const key = `${incomeLevel}-${expenseLevel}` as keyof typeof dataSets;
    const selectedData = dataSets[key];

    if (chartRef.current) {
        // 更新图表数据对象
        chartRef.current.data = {
            labels: ['4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月', '1月', '2月', '3月'],
            datasets: [
                {
                    type: 'bar' as const,
                    label: '収入',
                    data: selectedData.income,
                    backgroundColor: function(context: ScriptableContext<'bar'>) {
                        const index = context.dataIndex;
                        return index < 3 ? 'rgba(153, 102, 255, 0.5)' : 'rgba(153, 102, 255, 0.2)';
                    },
                },
                {
                    type: 'bar' as const,
                    label: '支出',
                    data: selectedData.expense,
                    backgroundColor: function(context: ScriptableContext<'bar'>) {
                        const index = context.dataIndex;
                        return index < 3 ? 'rgba(54, 162, 235, 0.5)' : 'rgba(54, 162, 235, 0.2)';
                    },
                },
                {
                    type: 'line' as const,
                    label: '残高',
                    data: selectedData.income.map((income, i) => income + selectedData.expense[i]),
                    borderColor: 'black',
                    backgroundColor: 'black',
                    fill: false,
                    tension: 0.1,
                    borderWidth: 2,
                    pointStyle: 'rectRot',
                    pointRadius: 6,
                    pointHoverRadius: 8,
                    segment: {
                        borderDash: (ctx: { p0DataIndex: number }) => {
                            return ctx.p0DataIndex < 2 ? [] : [5, 5]; // 4月到6月实线,7月到3月虚线
                        },
                    },
                    pointBackgroundColor: (context: ScriptableContext<'line'>) => {
                        const index = context.dataIndex;
                        const value = context.dataset.data[index] as number;
                        return value < 0 ? 'red' : 'black';
                    }
                }
            ],
        };

        chartRef.current.update(); // 强制触发图表更新
    }

    setActionMessage(selectedData.action);
  };

  useEffect(() => {
    updateChartAndAction();
  }, [incomeLevel, expenseLevel]);

  const options = {
    responsive: true,
    layout: {
      padding: {
        top: 0,
        bottom :0,
        left:0,
        right:0,
      },
    },
    plugins: {
      legend: {
        display: true,
        labels: {
          color: 'white',
          boxWidth: 0,
          boxHeight: 0,
        },
        onclick:null
      },
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: '[百万円]', // 纵轴单位
        },
      },
    },
  };

  const handleIncomeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setIncomeLevel(e.target.value);
  };

  const handleExpenseChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setExpenseLevel(e.target.value);
  };

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

  useEffect(() => {
    ChartJS.register(customLegendPlugin);
    return () => {
      ChartJS.unregister(customLegendPlugin);
    };
  }, []);

  const customLegendPlugin = {
    id: 'customLegend',
    afterDraw: function (chart: ChartJS<keyof ChartTypeRegistry, unknown[], unknown>) {
      const legend = chart?.legend;
      if (!legend || !legend.legendItems) return;

      const ctx = chart.ctx;
      const itemWidth = 100; // 每个图例项的宽度(包括图标和文本)
      const startX = (chart.width - legend.legendItems.length * itemWidth) / 2; // 图例起始X坐标(居中)
      let currentX = startX;

      const y = 10; // 在图例下方添加额外的距离,以确保与图表内容不重叠

      legend.legendItems.forEach((legendItem: LegendItem, i: number) => {
        if (legendItem.text === '残高') {
          ctx.save();
          ctx.strokeStyle = legendItem.strokeStyle as string;
          ctx.lineWidth = 2; // 设置线条的宽度
          ctx.beginPath();
          ctx.moveTo(currentX, y);
          ctx.lineTo(currentX + 40, y); // 假设线条宽度为 40
          ctx.stroke();
          ctx.restore();
        } else {
          ctx.save();
          ctx.fillStyle = legendItem.fillStyle as string;
          ctx.fillRect(currentX, y - 5, 40, 10); // 假设图标宽度为 40, 高度为 10
          ctx.restore();
        }

        ctx.textBaseline = 'middle';
        ctx.fillStyle = 'black';
        ctx.fillText(legendItem.text, currentX + 50, y); // 图标和文本之间的间距为 50

        currentX += itemWidth; // 移动到下一个图例项
      });
    },
  };

  const defaultData = {
    labels:[],
    datasets:[]
  }

  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={chartRef.current?.data || defaultData} options={options} />
          </div>
          <div className="additional-section">
            <div className="data-filter">
              <h2>データ予測</h2>
              <div className="filter-group">
                <div className="filter-btn">収入</div>
                <select className="filter-select" onChange={handleIncomeChange} value={incomeLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
              <div className="filter-group">
                <div className="filter-btn">支出</div>
                <select className="filter-select" onChange={handleExpenseChange} value={expenseLevel}>
                  <option>楽観</option>
                  <option>中立</option>
                  <option>悲観</option>
                </select>
              </div>
            </div>
            <div className="data-comparison">
              <h2>データ比較</h2>
              <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'>
            {actionMessage}
          </div>
          <div className="collapsible-panels">
            <CollapsiblePanel title="営業キャッシュフロー" money="543,163円" details={operatingCashFlow} />
            <CollapsiblePanel title="投資キャッシュフロー" money="-2,761,307円" details={investingCashFlow} />
            <CollapsiblePanel title="財務キャッシュフロー" money="307,902円" details={financingCashFlow} />
          </div>
        </div>
      </div>
    </div>
  );
};

// dami
const dataSets = {
  '楽観-楽観': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -22.0, -22.5, -23.1, -23.7, -24.3, -24.0, -25.0, -24.3, -25.0],
     action: `4月から6月の実績データを見ると、収益は非常に安定しており、特に6月の一時的なマイナスを除けば、健全な財務状況が維持されています。7月以降の予測では、収益がさらに増加し、支出も適切に管理される見込みです。このような状況下では、新たな市場への進出や技術革新への投資が推奨され、企業の長期的な成長が期待されます。全体として、リスクは低く、持続的な成長が見込めます。`
  },
  '楽観-中立': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -26.0, -27.0, -27.8, -28.3, -29.0, -28.7, -29.5, -28.5, -29.0],
    action: `4月から6月の実績データでは、収益は安定しており、支出も適切に管理されていますが、6月に一時的な残高のマイナスが発生しました。7月以降の予測では、収益の増加が見込まれるものの、支出の増加も予測されています。このため、企業はさらなるコスト削減と効率的な運営を図る必要があります。特に、無駄なコストを排除し、収益性の高いプロジェクトにリソースを集中させることが重要です。リスク管理の強化も併せて行い、持続可能な成長を確保するための体制を整えるべきです。企業がこの戦略を実行することで、将来の不確実性に対処しながら、安定した成長を続けることが可能となります。`
  },
  '楽観-悲観': {
    income: [31.2, 32.8, 30.5, 35.0, 38.2, 40.8, 42.1, 44.5, 45.0, 46.2, 47.8, 48.7],
    expense: [-26.7, -28.3, -36.0, -33.0, -34.0, -34.8, -35.5, -36.3, -36.0, -37.0, -36.0, -36.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、財務状況に不安が生じています。7月以降の予測では、収益の増加が続くものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ効果的な対応が必要です。まず、コスト削減を徹底し、無駄な支出を削減することで、キャッシュフローの改善を図ることが求められます。また、低収益プロジェクトの見直しや、必要に応じたリソースの再配分を行い、収益性の高い事業に集中投資することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて資金調達の手段を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。`
  },
  '中立-楽観': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -28.0, -28.5, -29.2, -29.8, -30.5, -30.2, -31.0, -30.0, -30.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなっていますが、収益と支出のバランスはおおむね取れており、安定した財務状況が保たれています。7月以降の予測では、収益がやや増加し、支出も管理可能な範囲で推移する見込みです。このような状況では、企業は収益性の向上を目指し、効率的な経営を推進することが求められます。特に、コスト削減を図りつつ、収益性の高いプロジェクトに重点的にリソースを配分することで、会社全体の利益を最大化する戦略が重要です。また、リスク管理を強化し、予期せぬ事態に迅速に対応できる体制を整えることで、長期的な成長を確保することができます。このような戦略により、競争力を強化し、持続的な成長を実現することが可能となるでしょう。さらに、将来の市場変化に対応するための柔軟な経営戦略を採用し、企業の競争力を維持し続けることが重要です。`
  },
  '中立-中立': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -30.5, -31.0, -31.7, -32.5, -33.0, -32.7, -33.5, -32.5, -33.0],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスになっていることが確認されていますが、収益と支出のバランスは全体的に均衡しています。7月以降の予測では、収益と支出の双方がわずかに増加する見込みです。このような安定した状況を維持するためには、現在の戦略を継続し、効率的な運営を推進することが重要です。また、コスト削減の余地を見直し、無駄を排除することで、財務状況をさらに強化することが求められます。さらに、外部環境の変化に迅速に対応できる柔軟な経営体制を整え、リスク管理を強化することが必要です。これにより、企業は安定した成長を続けることができ、長期的な財務の健全性を確保することが可能となります。また、将来的な投資機会を慎重に評価し、必要に応じて戦略的な投資を行うことで、企業の競争力を維持し、成長を促進することが可能です。`
  },
  '中立-悲観': {
    income: [31.2, 32.8, 30.5, 32.5, 34.2, 36.8, 38.1, 40.5, 41.0, 42.5, 43.2, 43.8],
    expense: [-26.7, -28.3, -36.0, -33.8, -34.3, -35.0, -35.8, -36.5, -36.0, -37.0, -36.0, -36.5],
    action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなり、企業の財務状況に懸念が生じています。7月以降の予測では、収益はわずかに増加するものの、支出がそれ以上に増加する見込みです。この状況下で、企業は財務の安定を確保するために、迅速かつ徹底したコスト管理が求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトに焦点を当てることで、キャッシュフローを改善する必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じてリソースの再配分を検討することが重要です。さらに、短期的な資金繰りの安定を図るため、財務戦略を再構築し、必要に応じて追加の資金調達を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に対応するため、常に柔軟な経営を心がけることが、将来の成功の鍵となります。`
  },
  '悲観-楽観': {
    income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
    expense: [-26.7, -28.3, -36.0, -30.0, -30.5, -31.0, -31.5, -32.0, -31.7, -32.5, -31.5, -32.0],
    action: `4月から6月の実績データでは、6月に残高が大幅にマイナスとなっていることが確認されていますが、7月以降の収益予測はやや改善される見込みです。支出も管理可能な範囲で推移すると見込まれています。この状況では、企業はコスト管理を強化しつつ、収益性の向上を目指して効率的な経営を推進することが重要です。特に、コスト削減の取り組みを進め、無駄な支出を排除することで、財務の安定を図ることが求められます。`
  },
  '悲観-中立':{
    income: [31.2, 32.8, 30.5, 30.0, 30.8, 31.5, 32.0, 32.5, 32.0, 32.7, 31.8, 32.2],
    expense: [-26.7, -28.3, -36.0, -31.2, -31.7, -32.3, -33.0, -33.5, -33.0, -34.0, -33.0, -33.5],
    action: `4月から6月の実績データでは、6月の残高が大幅にマイナスとなり、企業の財務状況に深刻な懸念が生じています。7月以降の予測でも、収益は僅かに増加するものの、支出はそれ以上に増加する見込みです。この状況下で、企業は迅速かつ徹底的なコスト管理を行うことが求められます。まず、無駄な支出を削減し、収益性の高いプロジェクトにリソースを集中させることで、キャッシュフローの改善を図る必要があります。また、低収益のプロジェクトについては再評価を行い、必要に応じて戦略的なリソース配分を検討することが重要です。さらに、短期的な資金繰りを安定させるために、財務戦略を再構築し、追加の資金調達や資金の再配分を検討することも考えられます。このような対応を通じて、企業は予測されるリスクを最小限に抑え、持続可能な成長を実現するための基盤を築くことができるでしょう。また、外部環境の変化に迅速に対応するため、企業は常に柔軟な経営を心がけ、リスク管理を強化することが必要です。`
  },
  '悲観-悲観': {
    income: [31.2, 32.8, 30.5, 26.0, 26.8, 27.5, 28.0, 28.5, 28.0, 28.7, 27.8, 28.2],
    expense: [-26.7, -28.3, -36.0, -35.0, -35.5, -36.2, -36.8, -37.5, -37.0, -38.0, -37.0, -37.5],
    action: `4月から6月の実績データによれば、特に6月の残高が大幅にマイナスになっていることが確認されています。7月以降の予測でも、収益は緩やかに減少し、支出がそれ以上に増加する見込みです。この非常に厳しい状況下で、企業は財務の安定を確保するために、緊急かつ徹底的な対策が必要です。まず、全社的にコスト削減を徹底し、無駄な支出を即座に削減する必要があります。さらに、低収益のプロジェクトや非戦略的な投資については、早急に見直しを行い、必要に応じて停止や縮小を検討することが求められます。また、短期的な資金繰りの安定を図るために、財務戦略を抜本的に見直し、追加の資金調達やコスト削減計画を策定することも重要です。さらに、リスクの高いプロジェクトについては、慎重に再評価を行い、必要に応じて戦略の修正を行うことが求められます。このような対応を迅速に行うことで、企業は予測されるリスクを最小限に抑え、最悪のシナリオを回避するための基盤を築くことができます。また、外部環境の変化に対応するため、常に柔軟な経営を心がけ、危機に備えることが将来の成功の鍵となります。`
  }
  ,
};

const operatingCashFlow = [
  { month: '4月', amount: '1,234,567円' },
  { month: '5月', amount: '▲876,543円', alert: true },
  { month: '6月', amount: '▲567,890円', alert: true },
  { month: '7月', amount: '1,456,789円' },
  { month: '8月', amount: '1,234,890円' },
  { month: '9月', amount: '▲789,123円', alert: true },
  { month: '10月', amount: '1,345,678円' },
  { month: '11月', amount: '▲654,321円', alert: true },
  { month: '12月', amount: '1,567,234円' },
  { month: '1月', amount: '1,789,012円' },
  { month: '2月', amount: '1,678,901円' },
  { month: '3月', amount: '▲901,234円', alert: true }
];
const investingCashFlow = [
  { month: '4月', amount: '▲3,123,456円', alert: true },
  { month: '5月', amount: '▲2,456,789円', alert: true },
  { month: '6月', amount: '▲4,234,567円', alert: true },
  { month: '7月', amount: '▲1,567,890円', alert: true },
  { month: '8月', amount: '▲2,345,678円', alert: true },
  { month: '9月', amount: '▲3,678,901円', alert: true },
  { month: '10月', amount: '▲2,234,567円', alert: true },
  { month: '11月', amount: '▲1,890,123円', alert: true },
  { month: '12月', amount: '▲2,678,901円', alert: true },
  { month: '1月', amount: '▲2,789,012円', alert: true },
  { month: '2月', amount: '▲3,234,567円', alert: true },
  { month: '3月', amount: '▲2,901,234円', alert: true }
];

const financingCashFlow = [
  { month: '4月', amount: '523,456円' },
  { month: '5月', amount: '▲345,678円', alert: true },
  { month: '6月', amount: '278,901円' },
  { month: '7月', amount: '▲198,765円', alert: true },
  { month: '8月', amount: '612,345円' },
  { month: '9月', amount: '▲234,567円', alert: true },
  { month: '10月', amount: '734,567円' },
  { month: '11月', amount: '▲156,789円', alert: true },
  { month: '12月', amount: '812,345円' },
  { month: '1月', amount: '923,456円' },
  { month: '2月', amount: '1,034,567円' },
  { month: '3月', amount: '▲289,012円', alert: true }
];

export default EmptyPage;
kirin-ri commented 3 months ago
useEffect(() => {
  const chartElement = chartRef.current?.canvas?.parentNode;
  if (chartElement) {
    chartElement.querySelector('.chart-legend')?.style.setProperty('pointer-events', 'none');
  }
}, [chartRef.current]);
kirin-ri commented 3 months ago
ERROR in src/components/pages/financingPage.tsx:128:52
TS2339: Property 'style' does not exist on type 'Element'.
    126 |     const chartElement = chartRef.current?.canvas?.parentNode;
    127 |     if (chartElement) {
  > 128 |       chartElement.querySelector('.chart-legend')?.style.setProperty('pointer-events', 'none');
        |                                                    ^^^^^
    129 |     }
    130 |   }, [chartRef.current]);
kirin-ri commented 3 months ago
useEffect(() => {
  const chartElement = chartRef.current?.canvas?.parentNode;
  if (chartElement) {
    // 类型断言为 HTMLElement 确保它有 style 属性
    const legendElement = chartElement.querySelector('.chart-legend') as HTMLElement;
    if (legendElement) {
      legendElement.style.setProperty('pointer-events', 'none');
    }
  }
}, [chartRef.current]);
kirin-ri commented 3 months ago
    tooltip: {
      callbacks: {
        label: function(tooltipItem) {
          let label = tooltipItem.dataset.label || '';
          if (label) {
            label += ': ';
          }
          label += `${tooltipItem.raw} 百万円`; // 在这里添加单位
          return label;
        }
      }
    }
  },