kirin-ri / memo

0 stars 0 forks source link

azure #27

Open kirin-ri opened 1 month ago

kirin-ri commented 1 month ago

let cpu_rate_per_core_hour = 0.048; // 1 vCPUあたりの料金(USD/時間) let mem_rate_per_gb_hour = 0.012; // 1 GiB RAMあたりの料金(USD/時間)

// 各名前空間のCPU使用量の料金計算 let cpu_cost = KubePodInventory | summarize AvgCPU=avg(TotalCpuUsageNanoCores) by Namespace | extend CPUHours = AvgCPU (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / 1e9 | extend CPUCost = CPUHours cpu_rate_per_core_hour;

// 各名前空間のメモリ使用量の料金計算 let mem_cost = KubePodInventory | summarize AvgMemory=avg(TotalMemoryUsageBytes) by Namespace | extend MemoryHours = AvgMemory (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / (1024 1024 1024) | extend MemoryCost = MemoryHours mem_rate_per_gb_hour;

// CPUとメモリの料金を統合 cpu_cost | join kind=inner (mem_cost) on Namespace | project Namespace, CPUCost, MemoryCost, TotalCost = CPUCost + MemoryCost

kirin-ri commented 1 month ago

Error

The name 'cpu_cost' does not refer to any known table, tabular variable or function.

kirin-ri commented 1 month ago
let cpu_rate_per_core_hour = 0.048;  // 1 vCPUあたりの料金(USD/時間)
let mem_rate_per_gb_hour = 0.012;    // 1 GiB RAMあたりの料金(USD/時間)

// 各名前空間のCPU使用量の料金計算
let cpu_cost = KubePodInventory
| summarize AvgCPU=avg(TotalCpuUsageNanoCores) by Namespace
| extend CPUHours = AvgCPU * (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / 1e9
| extend CPUCost = CPUHours * cpu_rate_per_core_hour;

// 各名前空間のメモリ使用量の料金計算
let mem_cost = KubePodInventory
| summarize AvgMemory=avg(TotalMemoryUsageBytes) by Namespace
| extend MemoryHours = AvgMemory * (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / (1024 * 1024 * 1024)
| extend MemoryCost = MemoryHours * mem_rate_per_gb_hour;

// CPUとメモリの料金を統合
cpu_cost
| join kind=inner (mem_cost) on Namespace
| project Namespace, CPUCost, MemoryCost, TotalCost = CPUCost + MemoryCost
kirin-ri commented 1 month ago

let cpu_rate_per_core_hour = 0.048; // 1 vCPUあたりの料金(USD/時間) let mem_rate_per_gb_hour = 0.012; // 1 GiB RAMあたりの料金(USD/時間)

// 各名前空間のCPU使用量の料金計算 let cpu_cost = KubePodInventory | summarize AvgCPU=avg(TotalCpuUsageNanoCores) by Namespace | extend CPUHours = AvgCPU (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / 1e9 | extend CPUCost = CPUHours cpu_rate_per_core_hour;

// 各名前空間のメモリ使用量の料金計算 let mem_cost = KubePodInventory | summarize AvgMemory=avg(TotalMemoryUsageBytes) by Namespace | extend MemoryHours = AvgMemory (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / (1024 1024 1024) | extend MemoryCost = MemoryHours mem_rate_per_gb_hour;

// CPUとメモリの料金を統合 cpu_cost | join kind=inner (mem_cost) on Namespace | project Namespace, CPUCost, MemoryCost, TotalCost = CPUCost + MemoryCost

kirin-ri commented 1 month ago

let cpu_rate_per_core_hour = 0.048; // 1 vCPUあたりの料金(USD/時間) let mem_rate_per_gb_hour = 0.012; // 1 GiB RAMあたりの料金(USD/時間)

// 各名前空間のCPU使用量の料金計算 let cpu_cost = KubePodInventory | where TimeGenerated >= ago(30d) | summarize AvgCPU=avg(CpuUsageNanoCores) by Namespace | extend CPUHours = AvgCPU (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / 1e9 | extend CPUCost = CPUHours cpu_rate_per_core_hour;

// 各名前空間のメモリ使用量の料金計算 let mem_cost = KubePodInventory | where TimeGenerated >= ago(30d) | summarize AvgMemory=avg(MemoryUsageBytes) by Namespace | extend MemoryHours = AvgMemory (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / (1024 1024 1024) | extend MemoryCost = MemoryHours mem_rate_per_gb_hour;

// CPUとメモリの料金を統合 cpu_cost | join kind=inner (mem_cost) on Namespace | project Namespace, CPUCost, MemoryCost, TotalCost = CPUCost + MemoryCost

kirin-ri commented 1 month ago

TenantId c88bf5c1-8f71-4cea-ac74-f7c80746b79e SourceSystem Containers TimeGenerated [UTC] 2024-07-24T01:19:58Z Computer aks-nodepool1-20355066-vmss000003 ClusterId /subscriptions/234a74a7-bd43-4159-906b-bee68f8574be/resourceGroups/rg-i-dna-aks/providers/Microsoft.ContainerService/managedClusters/I-DNA-Cluster ContainerCreationTimeStamp [UTC] 2024-03-22T07:37:02Z PodUid f5da154d-a68a-4d2d-a9c9-03a18afa0cc6 PodCreationTimeStamp [UTC] 2024-03-22T07:36:54Z ContainerRestartCount 0 PodRestartCount 0 PodStartTime [UTC] 2024-03-22T07:36:54Z ControllerKind DaemonSet ControllerName csi-blob-node ContainerStatus running ContainerID cce984b8b3981ebc47dd752747a2458870c3654ef088a7323ce6751e4b930a3e ContainerName f5da154d-a68a-4d2d-a9c9-03a18afa0cc6/blob Name csi-blob-node-9lfwz PodLabel [{"app":"csi-blob-node","controller-revision-hash":"6fb8776f9d","kubernetes.azure.com\/managedby":"aks","pod-template-generation":"1"}] Namespace kube-system PodStatus Running ClusterName I-DNA-Cluster PodIp 10.224.0.4 ContainerLastStatus {} Type KubePodInventory _ResourceId /subscriptions/234a74a7-bd43-4159-90

kirin-ri commented 1 month ago

KubePodInventory | distinct ContainerID, Namespace | join kind=innerunique ( ContainerLog | where _IsBillable == true | summarize BillableDataBytes = sum(_BilledSize) by ContainerID ) on ContainerID | union ( KubePodInventory | distinct ContainerID, Namespace | join kind=innerunique ( ContainerLogV2 | project-rename ContainerID = ContainerId | where _IsBillable == true | summarize BillableDataBytes = sum(_BilledSize) by ContainerID ) on ContainerID ) | summarize Total=sum(BillableDataBytes) by Namespace | render piechart

kirin-ri commented 1 month ago
import subprocess
import pandas as pd

# 名前空間を指定
namespace = "<your-namespace>"

# kubectlコマンドを実行してリソース使用量を取得
kubectl_command = f"kubectl top pod --namespace {namespace}"
result = subprocess.run(kubectl_command.split(), capture_output=True, text=True)

# コマンド出力を処理
data = []
lines = result.stdout.split("\n")
for line in lines[1:]:
    if line:
        parts = line.split()
        pod_name = parts[0]
        cpu_usage = parts[1]
        memory_usage = parts[2]
        data.append({"pod_name": pod_name, "cpu_usage": cpu_usage, "memory_usage": memory_usage})

# データフレームに変換
df = pd.DataFrame(data)

# 単位変換 (mCPU to CPU, Mi to GiB)
df['cpu_usage'] = df['cpu_usage'].apply(lambda x: float(x.replace('m', '')) / 1000)
df['memory_usage'] = df['memory_usage'].apply(lambda x: float(x.replace('Mi', '')) / 1024)

# 料金計算用のレート (仮定の値を使用)
cpu_rate_per_core_hour = 0.048  # 1 vCPUあたりの料金(USD/時間)
mem_rate_per_gb_hour = 0.012    # 1 GiB RAMあたりの料金(USD/時間)

# 使用時間を仮定 (1時間の使用量)
usage_hours = 1

# CPUとメモリのコストを計算
df['cpu_cost'] = df['cpu_usage'] * cpu_rate_per_core_hour * usage_hours
df['memory_cost'] = df['memory_usage'] * mem_rate_per_gb_hour * usage_hours

# 合計コストを計算
df['total_cost'] = df['cpu_cost'] + df['memory_cost']

# 名前空間ごとのコストを集計
namespace_costs = df.sum()

print(namespace_costs)
kirin-ri commented 1 month ago
(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis
unrecognized arguments: --enable-cost-analysis

Examples from AI knowledge base:
az aks update --resource-group MyResourceGroup --name MyManagedCluster --load-balancer-managed-outbound-ip-count 2
Update a kubernetes cluster with standard SKU load balancer to use two AKS created IPs for the load balancer outbound connection usage.

az aks update --resource-group MyResourceGroup --name MyManagedCluster --api-server-authorized-ip-ranges 0.0.0.0/32
Restrict apiserver traffic in a kubernetes cluster to agentpool nodes.

az version
Show the versions of Azure CLI modules and extensions in JSON format by default or format configured by --output (autogenerated)

https://docs.microsoft.com/en-US/cli/azure/aks#az_aks_update
Read more about the command in reference docs
kirin-ri commented 1 month ago

表示するコスト データがありません クラスターでコスト分析アドオンが有効になっていることを確認してください。アドオンが有効になっている場合は、正しいフィルターが選択されていることを確認してください。

kirin-ri commented 1 month ago

az aks enable-addons --resource-group --name --addons cost-management

kirin-ri commented 1 month ago

(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks enable-addons --resource-group rg-i-dna-aks --name I-DNA-Cluster --addons cost-management Invalid addon name: cost-management. (base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis unrecognized arguments: --enable-cost-analysis

kirin-ri commented 1 month ago

az monitor log-analytics workspace list --resource-group --query "[].{Name:name, ID:id}" -o table

kirin-ri commented 1 month ago

(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az monitor log-analytics workspace list --resource-group --query "[].{Name:name, ID:id}" -o table argument --resource-group/-g: expected one argument

kirin-ri commented 1 month ago

az monitor log-analytics workspace list --resource-group rg-i-dna-aks --query "[].{Name:name, ID:id}" -o table

kirin-ri commented 1 month ago

az monitor log-analytics workspace create --resource-group rg-i-dna-aks --workspace-name myLogAnalyticsWorkspace

kirin-ri commented 1 month ago

(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis unrecognized arguments: --enable-cost-analysis

Examples from AI knowledge base: az aks update --resource-group MyResourceGroup --name MyManagedCluster --load-balancer-managed-outbound-ip-count 2 Update a kubernetes cluster with standard SKU load balancer to use two AKS created IPs for the load balancer outbound connection usage.

az aks update --resource-group MyResourceGroup --name MyManagedCluster --api-server-authorized-ip-ranges 0.0.0.0/32 Restrict apiserver traffic in a kubernetes cluster to agentpool nodes.

az version Show the versions of Azure CLI modules and extensions in JSON format by default or format configured by --output (autogenerated)

https://docs.microsoft.com/en-US/cli/azure/aks#az_aks_update Read more about the command in reference docs

kirin-ri commented 1 month ago
(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis
The command failed with an unexpected error. Here is the traceback:
module 'azure.core.pipeline.policies' has no attribute 'SensitiveHeaderCleanupPolicy'
Traceback (most recent call last):
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/knack/cli.py", line 233, in invoke
    cmd_result = self.invocation.execute(args)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 664, in execute
    raise ex
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 731, in _run_jobs_serially
    results.append(self._run_job(expanded_arg, cmd_copy))
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 701, in _run_job
    result = cmd_copy(params)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 334, in __call__
    return self.handler(*args, **kwargs)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/command_operation.py", line 112, in handler
    client = self.client_factory(self.cli_ctx, command_args) if self.client_factory else None
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/command_modules/acs/_client_factory.py", line 27, in cf_managed_clusters
    return get_container_service_client(cli_ctx).managed_clusters
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/command_modules/acs/_client_factory.py", line 19, in get_container_service_client
    return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_CONTAINERSERVICE, subscription_id=subscription_id)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/client_factory.py", line 83, in get_mgmt_service_client
    client, _ = _get_mgmt_service_client(cli_ctx, client_type, subscription_id=subscription_id,
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/client_factory.py", line 254, in _get_mgmt_service_client
    client = client_type(credential, subscription_id, **client_kwargs)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/mgmt/containerservice/_container_service_client.py", line 101, in __init__
    policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None,
AttributeError: module 'azure.core.pipeline.policies' has no attribute 'SensitiveHeaderCleanupPolicy'
To check existing issues, please visit: https://github.com/Azure/azure-cli/issues
kirin-ri commented 1 month ago
az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis
(OperationNotAllowed) The 'costAnalysis' feature cannot be used unless the cluster SKU.Tier is at least 'Standard'
Code: OperationNotAllowed
Message: The 'costAnalysis' feature cannot be used unless the cluster SKU.Tier is at least 'Standard'``
kirin-ri commented 1 month ago

az aks show --resource-group rg-i-dna-aks --name I-DNA-Cluster --query "sku.tier"

kirin-ri commented 1 month ago
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function AppMenu() {
  metricsList = []
kirin-ri commented 1 month ago
const metricsList: MetricsList[] = [
  {
    subCategory: 'Performance',
    metrics: [
      { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
      { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
      { name: 'Disk I/O', provided: true, subCategory: 'Performance' }
    ]
  },
  {
    subCategory: 'Availability',
    metrics: [
      { name: 'Uptime', provided: true, subCategory: 'Availability' },
      { name: 'Downtime', provided: false, subCategory: 'Availability' }
    ]
  },
  {
    subCategory: 'Security',
    metrics: [
      { name: 'Vulnerability Scans', provided: true, subCategory: 'Security' },
      { name: 'Intrusion Detection', provided: false, subCategory: 'Security' }
    ]
  }
];
kirin-ri commented 1 month ago
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function AppMenu() {
  const metricsList: MetricsList[] = [
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' }
      ]
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' }
      ]
    },
    {
      subCategory: 'Security',
      metrics: [
        { name: 'Vulnerability Scans', provided: true, subCategory: 'Security' },
        { name: 'Intrusion Detection', provided: false, subCategory: 'Security' }
      ]
    }
  ];

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/new-metrics-list" className="nav-link">
                  指標一覧
                </Link>
              </li>
              {metricsList?.map((c, idx) => (
                <CategoryItem category={c} idx={idx} key={idx} />
              ))}
            </ul>
          </nav>
          {/* /.sidebar-menu */}
          <div className="copyright">Copyright ©NTT DATA Corporation</div>
        </div>
        {/* /.sidebar */}
      </aside>
    </>
  );
}
export default AppMenu;
kirin-ri commented 1 month ago
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function AppMenu() {
  const metricsList: MetricsList[] = [
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' }
      ]
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' }
      ]
    }
  ];

  const handleSubmenu = (e: MouseEvent<HTMLAnchorElement>) => {
    const $menuItem = $(e.currentTarget as HTMLElement);
    const id = $menuItem.data('target');
    const $submenu = $(`#${id}`);

    $('.dropdown-menu').removeClass('show');
    $(document).off('click.openSubmenu');
    $submenu.addClass('show');
    setTimeout(() => {
      $(document).on('click.openSubmenu', (e) => {
        if ($(e.target).closest($('.dropdown-menu')).length === 0) {
          $('.dropdown-menu').removeClass('show');
          $(document).off('click.openSubmenu');
        }
      });
    });
    poppers[id].update();
  };

  function CategoryItem({
    category: group,
    idx,
  }: {
    category: MetricsList;
    idx: number;
  }) {
    const defaultCat = defaultCategories.find(
      (e) => e.name === group.subCategory,
    );
    return (
      <li className="nav-item">
        <a
          className="nav-link dropdown"
          data-target={`sidebar-cat-${idx + 1}`}
          onClick={handleSubmenu}
        >
          <div className="item-icon">
            <i className={`fa ${defaultCat?.icon ?? 'fa-calculator'}`} />
          </div>
          <div className="item-label">{group.subCategory}</div>
        </a>
      </li>
    );
  }

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/new-metrics-list" className="nav-link">
                  指標一覧
                </Link>
              </li>
              {metricsList?.map((c, idx) => (
                <CategoryItem category={c} idx={idx} key={idx} />
              ))}
            </ul>
          </nav>
          {/* /.sidebar-menu */}
          <div className="copyright">Copyright ©NTT DATA Corporation</div>
        </div>
        {/* /.sidebar */}
      </aside>
    </>
  );
}
export default AppMenu;
kirin-ri commented 1 month ago
import React from 'react';
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

const defaultCategories = [
  { name: 'Performance', icon: 'fa-tachometer-alt' },
  { name: 'Availability', icon: 'fa-clock' }
];

const poppers: { [key: string]: any } = {};

function AppMenu() {
  const metricsList: MetricsList[] = [
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' }
      ]
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' }
      ]
    }
  ];

  const handleSubmenu = (e: React.MouseEvent<HTMLAnchorElement>) => {
    const $menuItem = $(e.currentTarget as HTMLElement);
    const id = $menuItem.data('target');
    const $submenu = $(`#${id}`);

    $('.dropdown-menu').removeClass('show');
    $(document).off('click.openSubmenu');
    $submenu.addClass('show');
    setTimeout(() => {
      $(document).on('click.openSubmenu', (e) => {
        if ($(e.target).closest($('.dropdown-menu')).length === 0) {
          $('.dropdown-menu').removeClass('show');
          $(document).off('click.openSubmenu');
        }
      });
    });
    poppers[id].update();
  };

  function CategoryItem({
    category: group,
    idx,
  }: {
    category: MetricsList;
    idx: number;
  }) {
    const defaultCat = defaultCategories.find(
      (e) => e.name === group.subCategory,
    );
    return (
      <li className="nav-item">
        <a
          className="nav-link dropdown"
          data-target={`sidebar-cat-${idx + 1}`}
          onClick={handleSubmenu}
        >
          <div className="item-icon">
            <i className={`fa ${defaultCat?.icon ?? 'fa-calculator'}`} />
          </div>
          <div className="item-label">{group.subCategory}</div>
        </a>
      </li>
    );
  }

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/new-metrics-list" className="nav-link">
                  指標一覧
                </Link>
              </li>
              {metricsList?.map((c, idx) => (
                <CategoryItem category={c} idx={idx} key={idx} />
              ))}
            </ul>
          </nav>
          {/* /.sidebar-menu */}
          <div className="copyright">Copyright ©NTT DATA Corporation</div>
        </div>
        {/* /.sidebar */}
      </aside>
    </>
  );
}
export default AppMenu;
kirin-ri commented 1 month ago
function TopPage() {

  const props = {
    metricsList,
    setMetricsList,
    category,
    setCategory,
    isCategoryTxtDisplayed,
    setCategoryTxtDisplayed,
    // graph,
    // setGraph,
    text,
    setText,
    isAnalyzeBtnDisabled,
    setAnalyzeBtnDisabled,
    enableCreatingMetrics,
  };

  return (
    <div className="content-wrapper top-page">
      <section className="page-cover page-cover-big">
        <div className="page-cover-title-frame">
          <h1>指標一覧</h1>
        </div>
      </section>
      <section className="content-header">
        <div className="content-header-left">
          <h1>指標一覧</h1>
          <div className='sub'>見たい指標を選択してください</div>
        </div>
      </section>
    </div>
        <div className="content-wrapper metrics-list">
        <section className="page-cover">
          <h1>{title}</h1>
        </section>
        {/* Content Header (Page header) */}
        <section className="content-header">
          <div className="content-header-left">

            <h1>{title}</h1>
          </div>
        </section>
      {/* Metrics List (Grid) */}
      {Spacer({ size: 50 })}
      {view(props as any)}
    </div>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <>
        <section className="content">
          <div className="list-wrapper">
            {GridMetricsList(props)}
          </div>
        </section>
      </>
    );
  }
};

// API List
function GridMetricsList(props: MetricsListProps) {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
}

// Metrics Card
function MetricsCard(props: MetricsListProps, metrics: MetricsList) {
  return (
    <>
      <div>
        <div className="card card-tertiary card-collapse-metrics">
          <div className="card-header">
            <div
              className="name-font"
              style={{
                fontSize: `clamp(0.65rem, ${
                  30 / metrics.subCategory.length
                }vw, 1rem)`,
              }}
            >
              {metrics.subCategory}
            </div>
          </div>
          <div className="card-body" id={metrics.subCategory}>
            {
              <>
                {metrics.metrics.map((val) => {
                  return (
                    <a
                      href={`#/metrics-details/${val.name}`}
                      className="link"
                    >
                      <p>
                        <i
                          className={`fa fa-circle ${
                            val.provided
                              ? 'metrics-provide-icon'
                              : 'metrics-no-provide-icon'
                          }`}
                          aria-hidden="true"
                        />
                        &nbsp;
                        {val.name}
                      </p>
                    </a>
                  );
                })}
              </>
            }
          </div>
        </div>
      </div>
    </>
  );
}
export default TopPage;
kirin-ri commented 1 month ago

ERROR in src/app2/components/pages/topPage.tsx:98:39 TS7006: Parameter 'val' implicitly has an 'any' type.

kirin-ri commented 1 month ago
import React from 'react';

type MetricsListProps = {
  metricsList: MetricsList[];
  setMetricsList: React.Dispatch<React.SetStateAction<MetricsList[]>>;
  category: string;
  setCategory: React.Dispatch<React.SetStateAction<string>>;
  isCategoryTxtDisplayed: boolean;
  setCategoryTxtDisplayed: React.Dispatch<React.SetStateAction<boolean>>;
  // graph: any; // Uncomment if needed
  // setGraph: any; // Uncomment if needed
  text: string;
  setText: React.Dispatch<React.SetStateAction<string>>;
  isAnalyzeBtnDisabled: boolean;
  setAnalyzeBtnDisabled: React.Dispatch<React.SetStateAction<boolean>>;
  enableCreatingMetrics: boolean;
};

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};

type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function TopPage() {
  const metricsList: MetricsList[] = []; // Initialize with actual data
  const setMetricsList = () => {}; // Replace with actual setter function
  const category = ''; // Replace with actual state
  const setCategory = () => {}; // Replace with actual setter function
  const isCategoryTxtDisplayed = false; // Replace with actual state
  const setCategoryTxtDisplayed = () => {}; // Replace with actual setter function
  const text = ''; // Replace with actual state
  const setText = () => {}; // Replace with actual setter function
  const isAnalyzeBtnDisabled = false; // Replace with actual state
  const setAnalyzeBtnDisabled = () => {}; // Replace with actual setter function
  const enableCreatingMetrics = false; // Replace with actual state

  const props: MetricsListProps = {
    metricsList,
    setMetricsList,
    category,
    setCategory,
    isCategoryTxtDisplayed,
    setCategoryTxtDisplayed,
    // graph,
    // setGraph,
    text,
    setText,
    isAnalyzeBtnDisabled,
    setAnalyzeBtnDisabled,
    enableCreatingMetrics,
  };

  return (
    <>
      <div className="content-wrapper top-page">
        <section className="page-cover page-cover-big">
          <div className="page-cover-title-frame">
            <h1>指標一覧</h1>
          </div>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>指標一覧</h1>
            <div className="sub">見たい指標を選択してください</div>
          </div>
        </section>
      </div>
      <div className="content-wrapper metrics-list">
        <section className="page-cover">
          <h1>{props.category}</h1>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>{props.category}</h1>
          </div>
        </section>
        {Spacer({ size: 50 })}
        {view(props)}
      </div>
    </>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <section className="content">
        <div className="list-wrapper">
          {GridMetricsList(props)}
        </div>
      </section>
    );
  }
  return null;
};

const GridMetricsList = (props: MetricsListProps) => {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
};

const MetricsCard = (props: MetricsListProps, metrics: MetricsList) => {
  return (
    <div>
      <div className="card card-tertiary card-collapse-metrics">
        <div className="card-header">
          <div
            className="name-font"
            style={{
              fontSize: `clamp(0.65rem, ${
                30 / metrics.subCategory.length
              }vw, 1rem)`,
            }}
          >
            {metrics.subCategory}
          </div>
        </div>
        <div className="card-body" id={metrics.subCategory}>
          <>
            {metrics.metrics.map((val: Metrics) => {
              return (
                <a href={`#/metrics-details/${val.name}`} className="link" key={val.name}>
                  <p>
                    <i
                      className={`fa fa-circle ${
                        val.provided
                          ? 'metrics-provide-icon'
                          : 'metrics-no-provide-icon'
                      }`}
                      aria-hidden="true"
                    />
                    &nbsp;
                    {val.name}
                  </p>
                </a>
              );
            })}
          </>
        </div>
      </div>
    </div>
  );
};

const Spacer = ({ size }: { size: number }) => (
  <div style={{ height: size }} />
);

export default TopPage;
kirin-ri commented 1 month ago
import React, { useState } from 'react';
import { Link } from 'react-router-dom';

type MetricsListProps = {
  metricsList: MetricsList[];
  setMetricsList: React.Dispatch<React.SetStateAction<MetricsList[]>>;
  category: string;
  setCategory: React.Dispatch<React.SetStateAction<string>>;
  isCategoryTxtDisplayed: boolean;
  setCategoryTxtDisplayed: React.Dispatch<React.SetStateAction<boolean>>;
  // graph: any; // Uncomment if needed
  // setGraph: any; // Uncomment if needed
  text: string;
  setText: React.Dispatch<React.SetStateAction<string>>;
  isAnalyzeBtnDisabled: boolean;
  setAnalyzeBtnDisabled: React.Dispatch<React.SetStateAction<boolean>>;
  enableCreatingMetrics: boolean;
};

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};

type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function TopPage() {
  const [metricsList, setMetricsList] = useState<MetricsList[]>([
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' },
      ],
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' },
      ],
    },
  ]);

  const [category, setCategory] = useState('Performance');
  const [isCategoryTxtDisplayed, setCategoryTxtDisplayed] = useState(true);
  const [text, setText] = useState('Sample Text');
  const [isAnalyzeBtnDisabled, setAnalyzeBtnDisabled] = useState(false);
  const [enableCreatingMetrics, setEnableCreatingMetrics] = useState(true);

  const props: MetricsListProps = {
    metricsList,
    setMetricsList,
    category,
    setCategory,
    isCategoryTxtDisplayed,
    setCategoryTxtDisplayed,
    // graph,
    // setGraph,
    text,
    setText,
    isAnalyzeBtnDisabled,
    setAnalyzeBtnDisabled,
    enableCreatingMetrics,
  };

  return (
    <>
      <div className="content-wrapper top-page">
        <section className="page-cover page-cover-big">
          <div className="page-cover-title-frame">
            <h1>指標一覧</h1>
          </div>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>指標一覧</h1>
            <div className="sub">見たい指標を選択してください</div>
          </div>
        </section>
      </div>
      <div className="content-wrapper metrics-list">
        <section className="page-cover">
          <h1>{category}</h1>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>{category}</h1>
          </div>
        </section>
        {Spacer({ size: 50 })}
        {view(props)}
      </div>
    </>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <section className="content">
        <div className="list-wrapper">
          {GridMetricsList(props)}
        </div>
      </section>
    );
  }
  return null;
};

const GridMetricsList = (props: MetricsListProps) => {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
};

const MetricsCard = (props: MetricsListProps, metrics: MetricsList) => {
  return (
    <div>
      <div className="card card-tertiary card-collapse-metrics">
        <div className="card-header">
          <div
            className="name-font"
            style={{
              fontSize: `clamp(0.65rem, ${
                30 / metrics.subCategory.length
              }vw, 1rem)`,
            }}
          >
            {metrics.subCategory}
          </div>
        </div>
        <div className="card-body" id={metrics.subCategory}>
          <>
            {metrics.metrics.map((val: Metrics) => {
              return (
                <a href={`#/metrics-details/${val.name}`} className="link" key={val.name}>
                  <p>
                    <i
                      className={`fa fa-circle ${
                        val.provided
                          ? 'metrics-provide-icon'
                          : 'metrics-no-provide-icon'
                      }`}
                      aria-hidden="true"
                    />
                    &nbsp;
                    {val.name}
                  </p>
                </a>
              );
            })}
          </>
        </div>
      </div>
    </div>
  );
};

const Spacer = ({ size }: { size: number }) => (
  <div style={{ height: size }} />
);

export default TopPage;
kirin-ri commented 1 month ago
import React, { useState } from 'react';
import { Link } from 'react-router-dom';

type MetricsListProps = {
  metricsList: MetricsList[];
  setMetricsList: React.Dispatch<React.SetStateAction<MetricsList[]>>;
  category: string;
  setCategory: React.Dispatch<React.SetStateAction<string>>;
  isCategoryTxtDisplayed: boolean;
  setCategoryTxtDisplayed: React.Dispatch<React.SetStateAction<boolean>>;
  // graph: any; // Uncomment if needed
  // setGraph: any; // Uncomment if needed
  text: string;
  setText: React.Dispatch<React.SetStateAction<string>>;
  isAnalyzeBtnDisabled: boolean;
  setAnalyzeBtnDisabled: React.Dispatch<React.SetStateAction<boolean>>;
  enableCreatingMetrics: boolean;
};

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};

type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function TopPage() {
  const [metricsList, setMetricsList] = useState<MetricsList[]>([
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' },
      ],
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' },
      ],
    },
  ]);

  const [category, setCategory] = useState('Performance');
  const [isCategoryTxtDisplayed, setCategoryTxtDisplayed] = useState(true);
  const [text, setText] = useState('Sample Text');
  const [isAnalyzeBtnDisabled, setAnalyzeBtnDisabled] = useState(false);
  const [enableCreatingMetrics, setEnableCreatingMetrics] = useState(true);

  const props: MetricsListProps = {
    metricsList,
    setMetricsList,
    category,
    setCategory,
    isCategoryTxtDisplayed,
    setCategoryTxtDisplayed,
    // graph,
    // setGraph,
    text,
    setText,
    isAnalyzeBtnDisabled,
    setAnalyzeBtnDisabled,
    enableCreatingMetrics,
  };

  return (
    <>
      <div className="content-wrapper top-page">
        <section className="page-cover page-cover-big">
          <div className="page-cover-title-frame">
            <h1>指標一覧</h1>
          </div>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>指標一覧</h1>
            <div className='sub'>見たい指標を選択してください</div>
          </div>
        </section>
      </div>
      <div className="content-wrapper metrics-list">
        <section className="page-cover">
          <h1>{category}</h1>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>{category}</h1>
          </div>
        </section>
        {Spacer({ size: 50 })}
        {view(props)}
        <div className="columns">
          <div className="column">
            <div className="column-title">Column 1</div>
            <div className="column-item">Item 1</div>
            <div className="column-item">Item 2</div>
            <div className="column-item">Item 3</div>
          </div>
          <div className="column">
            <div className="column-title">Column 2</div>
            <div className="column-item">Item 1</div>
            <div className="column-item">Item 2</div>
            <div className="column-item">Item 3</div>
          </div>
        </div>
      </div>
    </>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <section className="content">
        <div className="list-wrapper">
          {GridMetricsList(props)}
        </div>
      </section>
    );
  }
  return null;
};

const GridMetricsList = (props: MetricsListProps) => {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
};

const MetricsCard = (props: MetricsListProps, metrics: MetricsList) => {
  return (
    <div>
      <div className="card card-tertiary card-collapse-metrics">
        <div className="card-header">
          <div
            className="name-font"
            style={{
              fontSize: `clamp(0.65rem, ${
                30 / metrics.subCategory.length
              }vw, 1rem)`,
            }}
          >
            {metrics.subCategory}
          </div>
        </div>
        <div className="card-body" id={metrics.subCategory}>
          <>
            {metrics.metrics.map((val: Metrics) => {
              return (
                <a href={`#/metrics-details/${val.name}`} className="link" key={val.name}>
                  <p>
                    <i
                      className={`fa fa-circle ${
                        val.provided
                          ? 'metrics-provide-icon'
                          : 'metrics-no-provide-icon'
                      }`}
                      aria-hidden="true"
                    />
                    &nbsp;
                    {val.name}
                  </p>
                </a>
              );
            })}
          </>
        </div>
      </div>
    </div>
  );
};

const Spacer = ({ size }: { size: number }) => (
  <div style={{ height: size }} />
);

export default TopPage;
kirin-ri commented 1 month ago
.content-wrapper {
  padding: 20px;
}

.page-cover-title-frame h1 {
  font-size: 2rem;
  margin: 0;
  padding: 20px;
  background-color: black;
  color: white;
  text-align: center;
}

.content-header-left h1 {
  font-size: 1.5rem;
  margin-bottom: 10px;
}

.content-header-left .sub {
  font-size: 1rem;
  color: gray;
}

.columns {
  display: flex;
  justify-content: space-around;
  margin-top: 30px;
}

.column {
  width: 45%;
}

.column-title {
  background-color: black;
  color: white;
  padding: 10px;
  font-size: 1.2rem;
  text-align: center;
}

.column-item {
  background-color: white;
  color: black;
  padding: 10px;
  margin: 5px 0;
  border: 1px solid black;
  text-align: center;
}
kirin-ri commented 1 month ago
.metrics-list .list-wrapper {
  /* display: flex; */
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: start;
  gap: 15vw;
  padding-left: 5vw;
  padding-right: 5vw;
}
kirin-ri commented 1 month ago
/* COLLAPSE */
.metrics-list .card-collapse-metrics .card-header {
  display: grid;
  /* grid-template-columns: 1fr auto; */
  cursor: pointer;
  position: relative;
  /* white-space: nowrap; */
}

.metrics-list .card-collapse-metrics .card-header:after {
  content: ' ';
  display: block;
  transition: transform linear 0.3s;
  text-align: center;
  font-size: 1rem;
}

.metrics-list .card-collapse-metrics .card-header.collapsed:after {
  content: '+';
  display: block;
  transition: transform linear 0.3s;
  font-size: 1rem;
}

.metrics-list .card-header .name-font {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  padding-left: 5%;
  white-space: nowrap;
  text-align: center;
}

.metrics-list .centering-btn {
  text-align: center;
}

.metrics-list .btn-datainsert {
  background-color: rgb(235, 235, 235);
}

/* BASE */
.metrics-list .list-wrapper {
  /* display: flex; */
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: start;
  gap: 5vw;
  padding-left: 5vw;
  padding-right: 5vw;
}

/* COLLAPSE */
.metrics-list .list {
  width: 100%;
}

.metrics-list .list .card {
  transition: top, left linear 0.3s;
  /* border-radius: 0px 0px 10px 10px; */
}

.metrics-list .list .update ul {
  list-style: none;
  padding-inline-start: 0;
}

.metrics-list .list .metrics img {
  width: 100%;
}

.metrics-list .list .favorities .fav-list {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.metrics-list .list .favorities .card-collapse .card-header {
  border-bottom: none;
}

.metrics-list .list .favorities .card-collapse {
  border-bottom: 1px solid var(--border-color);
  margin-bottom: 0;
}

.metrics-list .list .favorities .fav-list .btn {
  padding-left: 10px;
  padding-right: 10px;
  font-weight: normal;
}

/* List Contents */
/* .metrics-list .list-title {
  padding-left: 1vw;
  font-weight: bold;
}

.metrics-list .list-contents-grid {
  display: grid;
  grid-template-columns: 3fr 0.5fr;
  align-items: start;
  gap: 3vw;
  padding-left: 1.5vw;
  padding-right: 1.5vw;
}

.metrics-list .col-delete-btn {
  text-align: right;
}

.metrics-list .col-delete-btn .fa-times-circle {
  color: red;
} */

/* Metrics Detail Modal */
.metrics-list .metrics-dtl-modal-disp-area {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

.metrics-list .metrics-dtl-modal-disp-area .metrics-name {
  display: flex;
  padding-top: 0.5vw;
}

.metrics-list .metrics-dtl-modal-disp-area .sample-data-btn {
  text-align: right;
}

.metrics-list .dtl-modal-font-color {
  color: #ce638e;
}

.metrics-list .metrics-text {
  display: inline-block;
  _display: inline;
}

.metrics-list .modal-body .dividing-line {
  border: 1px solid #ced4da;
}

.metrics-list .metrics-provide-icon {
  color: rgb(0, 255, 135);
}

.metrics-list .metrics-no-provide-icon {
  color: red;
}

.metrics-list .link {
  color: var(--font-color-main);
  text-decoration: underline;
}
kirin-ri commented 1 month ago
.metrics-list .metrics-provide-icon {
  color: rgb(0, 255, 135);
}
kirin-ri commented 1 month ago
                {val.name}
                {!val.provided && (
                  <i className="fa fa-exclamation metrics-no-provide-icon" aria-hidden="true" />
                )}``
kirin-ri commented 1 month ago
.card-tertiary .card-body{
  background-color: var(--content-bg-color);
}
kirin-ri commented 1 month ago
<div className="card-body" id={metrics.subCategory}>
          <>
            {metrics.metrics.map((val: Metrics) => {
              return (
                <a href={`#/metrics-details/${val.name}`} className="link" key={val.name}>
                  <p>
                    {!val.alert && (
                      <i
                        className="fa fa-exclamation metrics-no-provide-icon"
                        aria-hidden="true"
                        style={{marginLeft:'10px'}}/>)}
                    {val.name}
                  </p>
                </a>
              );
            })}
          </>
kirin-ri commented 1 month ago
.card-tertiary .card-body {
  background-color: white; /* 设置背景颜色为白色 */
  color: black; /* 设置文字颜色为黑色 */
  border-radius: 10px; /* 设置圆角 */
  padding: 20px; /* 设置内边距 */
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 设置阴影效果 */
}
kirin-ri commented 1 month ago
.metrics-item {
  background-color: white;
  color: black;
  padding: 10px;
  margin: 5px 0;
  border: 1px solid black;
  border-radius: 10px; /* 设置圆角 */
  text-align: center;
  display: flex;
  justify-content: space-between; /* 在两端之间保持间距 */
  align-items: center; /* 垂直方向居中对齐 */
}
kirin-ri commented 1 month ago
.metrics-item-container {
  display: flex;
  align-items: center; /* 垂直方向居中对齐 */
  margin: 5px 0; /* 调整项目之间的间距 */
}
kirin-ri commented 1 month ago
.metrics-item::after {
  content: ''; /* 添加一个空白占位符 */
  display: inline-block;
  width: 2rem; /* 设置与感叹号相同的宽度 */
  height: 2rem; /* 设置与感叹号相同的高度 */
  visibility: hidden; /* 确保占位符不可见 */
}
kirin-ri commented 1 month ago
import * as Popper from '@popperjs/core';
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

const defaultCategories = [
  { name: 'Performance', icon: 'fa-tachometer-alt' },
  { name: 'Availability', icon: 'fa-clock' }
];

const poppers: { [key: string]: Popper.Instance } = {};

function AppMenu() {
  const metricsList: MetricsList[] = [
    {
      subCategory: '経営指標',
      metrics: [
        { name: '売上', provided: true, subCategory: '経営指標' },
        { name: '売上高総利益', provided: true, subCategory: '経営指標' },
        { name: '営業利益', provided: true, subCategory: '経営指標' },
        { name: '売上高総利益率', provided: true, subCategory: '経営指標' },
        { name: '売上高営業利益率', provided: true, subCategory: '経営指標' },
        { name: 'EBITDA', provided: true, subCategory: '経営指標' },
        { name: '当座比率', provided: true, subCategory: '経営指標' },
        { name: '運転資本', provided: true, subCategory: '経営指標' },
        { name: '損益分岐点比率', provided: true, subCategory: '経営指標' },
        { name: '在庫回転率', provided: true, subCategory: '経営指標' },
        { name: '労働生産性①従業員あたり', provided: true, subCategory: '経営指標' },
        { name: '労働生産性②労働時間あたり', provided: true, subCategory: '経営指標' },
        { name: '資金繰り表', provided: true, subCategory: '経営指標' }
      ]
    },
    {
      subCategory: '管理指標',
      metrics: [
        { name: '総合設備効率(OEE)', provided: true, subCategory: '管理指標' },
        { name: '設備稼働率', provided: false, subCategory: '管理指標' },
        { name: '工程別生産性', provided: false, subCategory: '管理指標' },
        { name: '歩留まり率', provided: false, subCategory: '管理指標' },
        { name: '良品率', provided: false, subCategory: '管理指標' },
        { name: '直行率生', provided: false, subCategory: '管理指標' },
        { name: '生産実績', provided: false, subCategory: '管理指標' },
        { name: '在庫数', provided: false, subCategory: '管理指標' },
        { name: '工程管理', provided: false, subCategory: '管理指標' },
        { name: 'マーケット情報', provided: false, subCategory: '管理指標' }
      ]
    }
  ];
  useEffect(() => {
    $('.dropdown').each((idx, menuItem) => {
      const $menuItem = $(menuItem);
      const id = $menuItem.data('target');
      const $submenu = $(`#${id}`);
      const popper = Popper.createPopper(menuItem, $submenu[0], {
        placement: 'right-start',
        strategy: 'fixed',
        modifiers: [
          {
            name: 'offset',
            options: {
              offset: [-1, 0],
            },
          },
        ],
      });
      poppers[id] = popper;
    });

    $('.dropdown-menu .dropdown-item').on('click', () => {
      $('.dropdown-menu').removeClass('show');
      $(document).off('click.openSubmenu');
    });
  }, [metricsList]);

  const handleSubmenu = (e: React.MouseEvent<HTMLAnchorElement>) => {
    const $menuItem = $(e.currentTarget as HTMLElement);
    const id = $menuItem.data('target');
    const $submenu = $(`#${id}`);

    $('.dropdown-menu').removeClass('show');
    $(document).off('click.openSubmenu');
    $submenu.addClass('show');
    setTimeout(() => {
      $(document).on('click.openSubmenu', (e) => {
        if ($(e.target).closest($('.dropdown-menu')).length === 0) {
          $('.dropdown-menu').removeClass('show');
          $(document).off('click.openSubmenu');
        }
      });
    });
    poppers[id].update();
  };

  function CategoryItem({
    category: group,
    idx,
  }: {
    category: MetricsList;
    idx: number;
  }) {
    const defaultCat = defaultCategories.find(
      (e) => e.name === group.subCategory,
    );
    return (
      <li className="nav-item">
        <a
          className="nav-link dropdown"
          data-target={`sidebar-cat-${idx + 1}`}
          onClick={handleSubmenu}
        >
          <div className="item-icon">
            <i className={`fa ${defaultCat?.icon ?? 'fa-calculator'}`} />
          </div>
          <div className="item-label">{group.subCategory}</div>
        </a>
      </li>
    );
  }

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/" className="nav-link">
                  指標一覧
                </Link>
              </li>
              {metricsList?.map((c, idx) => (
                <CategoryItem category={c} idx={idx} key={idx} />
              ))}
            </ul>
          </nav>
          {/* /.sidebar-menu */}
          <div className="copyright">Copyright ©NTT DATA Corporation</div>
        </div>
        {/* /.sidebar */}
      </aside>
      <div className="sidebar-dropdowns">
        {metricsList?.map((g, idx) => (
          <ul className="dropdown-menu" id={`sidebar-cat-${idx + 1}`} key={idx}>
            {g.metrics.map((m) => (
              <li className="dropdown-item" key={m.name}>
                <Link to={`/metrics-details/${m.name}`} className="nav-link">
                  {m.name}
                </Link>
              </li>
            ))}
          </ul>
        ))}
      </div>
    </>
  );
}
export default AppMenu;
kirin-ri commented 1 month ago

<i className={fa ${val.icon} metrics-icon} aria-hidden="true" />

kirin-ri commented 1 month ago

<i className={fa ${val.icon} metrics-icon} aria-hidden="true" />

kirin-ri commented 1 month ago

<i className={fa ${val.icon} metrics-icon} aria-hidden="true" />

kirin-ri commented 1 month ago

<i className={fa ${val.icon} metrics-icon} aria-hidden="true" />

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

.alert-box span {
  flex-grow: 1;
}

.close-btn {
  background-color: transparent;
  border: none;
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px;
  margin-left: 10px;
}
kirin-ri commented 1 month ago
import React, { useState } from 'react';
import AlertBox from './AlertBox'; // 引入警告弹窗组件

function AppMenu() {
  const [showAlert, setShowAlert] = useState(true); // 控制警告弹窗的显示

  return (
    <>
      {/* 其他代码 */}
      {showAlert && (
        <AlertBox
          message="これは警告メッセージです"
          onClose={() => setShowAlert(false)}
        />
      )}
    </>
  );
}

export default AppMenu;
kirin-ri commented 1 month ago
import React from 'react';

const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
  return (
    <div className="alert-box">
      <span>{message}</span>
      <button className="close-btn" onClick={onClose}>非表示</button>
    </div>
  );
};

export default AlertBox;
kirin-ri commented 1 month ago
import { useState } from "react";

function EmptyPage() {
  // function get() {
  const test = '資金繰り表';
  //   return arr.map((str) => {
  //     SampleDataModal(str);
  //   });
  // }
  const [showAlert, setShowAlert] = useState(true); // 控制警告弹窗的显示
  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <span>{message}</span>
        <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>{test}</h1>
        </div>
      </section>
      {/* Content Header (Page header) */}
      <section className="content-header">
        <div className="content-header-left">
          <div>期中に資金がマイナスとなる期間があります</div>
        </div>
      </section>
    </div>
  );
}

export default EmptyPage;
kirin-ri commented 1 month ago
import { useState } from "react";

function EmptyPage() {
  const test = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true); // 控制警告弹窗的显示

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <span>{message}</span>
        <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>{test}</h1>
        </div>
      </section>
      {/* Content Header (Page header) */}
      <section className="content-header">
        <div className="content-header-left">
          <div>期中に資金がマイナスとなる期間があります</div>
        </div>
      </section>
      {/* 警告弹窗 */}
      {showAlert && (
        <AlertBox
          message="これは警告メッセージです"
          onClose={() => setShowAlert(false)}
        />
      )}
    </div>
  );
}

export default EmptyPage;