iT邦幫忙

2023 iThome 鐵人賽

DAY 26
0
Modern Web

TypeScript 啟動!系列 第 26

[Day 26] TypeScript 零售業者模擬演練 IV

  • 分享至 

  • xImage
  •  

今天應該會完成大致上的呈現~

Step1 修正 App.tsx

// App.tsx

import React, { useState } from 'react';
import LineChart from './Component/LineChart';
import PricingControl from './Component/PricingControl';
import { SaleRecord } from './Component/types';
import { simulateSales } from './Component/salesLogic';
import SalesTable from './Component/SalesTable';
import SummaryTable from './Component/SummaryTable';

const INITIAL_STOCK = 2000;

const App: React.FC = () => {
    const [salesData, setSalesData] = useState<SaleRecord[]>([
        { week: 1, price: 60, initialStock: INITIAL_STOCK, sales: 0, remainingStock: INITIAL_STOCK, revenue: 0, accumulatedRevenue: 0 },
    ]);
    const handlePricingSelect = (price: number) => {
        const currentWeek = salesData.length;
        const previousRecord = salesData[currentWeek - 1];
        const sales = simulateSales(previousRecord.remainingStock);
        const newRemainingStock = previousRecord.remainingStock - sales;
        const revenue = sales * price;
        const accumulatedRevenue = previousRecord.accumulatedRevenue + revenue;

        const record: SaleRecord = {
            week: currentWeek + 1,
            price,
            initialStock: INITIAL_STOCK,
            sales,
            remainingStock: newRemainingStock,
            revenue,
            accumulatedRevenue,
        };

        setSalesData([...salesData, record]);
    };

    return (
        <div className="App" style={{ textAlign: 'center' }}>
            <h1 style={{ backgroundColor: 'green', color: 'white', padding: '20px', borderRadius: '5px' }}>
                Retail Simulation
            </h1>
            <div style={{ width: "60%", margin: "auto" }}>
                <LineChart data={salesData} />
            </div>
            <PricingControl onSelect={handlePricingSelect} isGameEnded={salesData.length >= 15} />
            <SalesTable data={salesData} />
            <SummaryTable salesData={salesData} />
        </div>
    );
};

export default App;

LineChart 的部分寬度稍微調小一點,避免完全看不到販售的資料。

並新增 SummaryTable 來處理回合結束後的統計資料。

Step2 修改 LineChart.tsx

// components/LineChart.tsx

import React from 'react';
import { Line } from 'react-chartjs-2';
import { SaleRecord } from './types';
import { Chart, registerables } from 'chart.js';

type Props = {
    data: SaleRecord[];
};

const LineChart: React.FC<Props> = ({ data }) => {
    Chart.register(...registerables);
    const options = {
        scales: {
            y: {
                min: 0,
                max: 2000
            },
            x: {
                ticks: {
                    autoSkip: false,
                    maxRotation: 0,
                    minRotation: 0
                }
            }
        }
    };
    const chartData = {
        labels: data.map(record => `Week ${record.week}`),
        datasets: [
            {
                label: 'Remaining Stock',
                data: data.map(record => record.remainingStock),
                fill: false,
                backgroundColor: 'rgb(75, 192, 192)',
                borderColor: 'rgba(75, 192, 192, 0.2)',
            },
        ],
    };

    return <Line data={chartData} options={options} />;
};

export default LineChart;

透過 options :來調整Y軸最高最低值,以及一些小參數。

Step3. 新增型態

// types.tsx

export type SaleRecord = {
    week: number;
    price: number;       // 價錢
    initialStock: number; // 存貨,初始的存貨量
    sales: number;      // 銷售,賣出的數量
    remainingStock: number; // 剩餘存貨 = 初始存貨 - 賣出的數量
    revenue: number;    // 營收 = 價錢 x 賣出的數量
    accumulatedRevenue: number; // 累積營收
  };

  export type Summary = {
    residualValue: number; // 殘值
    totalRevenue: number; // 總營收
    maxPossibleRevenue: number; // 最大可能營收
    decisionQuality: number; // 決策品質(總營收/最大可能營收)
};

Step4. 修改運算邏輯(salesLogic.tsx)

// Component/salesLogic.tsx
import { SaleRecord, Summary } from './types';

export const simulateSales = (remainingStock: number): number => {
    const sales = Math.floor(Math.random() * (120 - 70 + 1) + 70);
    return Math.min(sales, remainingStock);
}

export const calculateSummary = (salesData: SaleRecord[]): Summary => {
    const finalWeekData = salesData[salesData.length - 1];
    const MIN_PRICE = 36;

    const residualValue = finalWeekData.remainingStock * MIN_PRICE;
    const totalRevenue = salesData.reduce((sum, record) => sum + record.price * (record.initialStock - record.remainingStock), 0);
    const maxPossibleRevenue = totalRevenue + residualValue;
    const decisionQuality = totalRevenue / maxPossibleRevenue;

    return {
        residualValue,
        totalRevenue,
        maxPossibleRevenue,
        decisionQuality
    };
};
  • const residualValue :計算剩餘存貨並用最低價格來計算。
  • const totalRevenue :總營收,就統計一下每週營收。
  • const maxPossibleRevenue :最大可能營收,就是加上殘值,假設能賣完的話。
  • const decisionQuality :本次決策品質,(總營收/最大可能營收)的百分比。

Step5. 新增 SummaryTable.tsx 來處理結果

import React, { useState, useEffect } from 'react';
import { SaleRecord, Summary } from './types';
import { calculateSummary } from './salesLogic';

type Props = {
    salesData: SaleRecord[];
};

const SummaryTable: React.FC<Props> = ({ salesData }) => {
    const [summary, setSummary] = useState<Summary | null>(null);

    useEffect(() => {
        if (salesData.length === 15) {
            const gameSummary = calculateSummary(salesData);
            setSummary(gameSummary);
        }
    }, [salesData]);

    if (!summary) return null;

    return (
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%'  }}>
            <div>
                <table style={{ width: '100%', textAlign: 'center'}}>
                    <thead>
                        <tr>
                            <th style={{ padding: '0 100px' }}>殘值</th>
                            <th style={{ padding: '0 100px' }}>總營收</th>
                            <th style={{ padding: '0 100px' }}>最大可能營收</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>${summary.residualValue.toLocaleString()}</td>
                            <td>${summary.totalRevenue.toLocaleString()}</td>
                            <td>${summary.maxPossibleRevenue.toLocaleString()}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <h2 style={{ fontWeight: 'bold', color: 'black', fontSize: '1.2em' }}>本次決策品質(總營收/最大可能營收)</h2>
            <h1 style={{ fontWeight: 'bold', color: 'green', fontSize: '1.6em' }}>{(summary.decisionQuality * 100).toFixed(2)}%</h1>
        </div>
    );
};

export default SummaryTable;
  • 來負責最後統計資料的呈現,當然其中就是把陣列超過15的時候就可以是結果圖了。終於完成了大概有87%相似度吧哈哈~

https://ithelp.ithome.com.tw/upload/images/20231009/201631070OuzNK5LN1.png

一連串新知識,react 和前端知識(雖然以前有寫,但是都忘得差不多了)不過也終於完成這一個小專案。從一開始的概念發想,到逐步逐步的完成每一功能,每一步都充滿了記憶的回顧與探索新知識。對我而言,最大的收穫不僅僅是寫了一小專案,更是在過程中對於 React 和 TypeScript 的了解。接下來,將會繼續深入探索 TypeScript,希望能夠更加熟練地運用它。


上一篇
[Day 25] TypeScript 零售業者模擬演練 III
下一篇
[Day 27] TypeScript 模組系統
系列文
TypeScript 啟動!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言