diff --git a/backend/app/routers/etl.py b/backend/app/routers/etl.py index 2691d0b..e1b1460 100644 --- a/backend/app/routers/etl.py +++ b/backend/app/routers/etl.py @@ -182,9 +182,9 @@ def import_data(request: ImportRequest, db: Session = Depends(get_db)): )) elif file_type == 'sample': sample_id = clean_value(row.get('sample_id'), f'S{idx}') - # ... other fields customer = clean_value(row.get('customer')) pn = clean_value(row.get('pn')) + raw_qty = row.get('qty') if not pn: continue @@ -192,6 +192,13 @@ def import_data(request: ImportRequest, db: Session = Depends(get_db)): continue seen_ids.add(sample_id) + qty_val = 0 + try: + if raw_qty and not pd.isna(raw_qty): + qty_val = int(float(raw_qty)) + except Exception as e: + print(f"[ETL Import] Sample Qty Warning: failed to parse '{raw_qty}' at row {idx}: {e}") + records_to_insert.append(SampleRecord( sample_id=sample_id, order_no=clean_value(row.get('order_no')), @@ -200,7 +207,7 @@ def import_data(request: ImportRequest, db: Session = Depends(get_db)): customer=customer, customer_normalized=normalize_customer_name(customer), pn=sanitize_pn(pn), - qty=int(row.get('qty', 0)) if row.get('qty') and not pd.isna(row.get('qty')) else 0, + qty=qty_val, date=normalize_date(row.get('date')) )) elif file_type == 'order': @@ -209,6 +216,7 @@ def import_data(request: ImportRequest, db: Session = Depends(get_db)): customer = clean_value(row.get('customer')) pn = clean_value(row.get('pn')) + raw_qty = row.get('qty') if not pn: continue @@ -217,6 +225,13 @@ def import_data(request: ImportRequest, db: Session = Depends(get_db)): continue seen_ids.add(unique_key) + qty_val = 0 + try: + if raw_qty and not pd.isna(raw_qty): + qty_val = int(float(raw_qty) * 1000) + except Exception as e: + print(f"[ETL Import] Order Qty Warning: failed to parse '{raw_qty}' at row {idx}: {e}") + records_to_insert.append(OrderRecord( order_id=order_id, order_no=order_no, @@ -224,7 +239,7 @@ def import_data(request: ImportRequest, db: Session = Depends(get_db)): customer=customer, customer_normalized=normalize_customer_name(customer), pn=sanitize_pn(pn), - qty=int(float(row.get('qty', 0)) * 1000) if row.get('qty') and not pd.isna(row.get('qty')) else 0, + qty=qty_val, status=clean_value(row.get('status'), 'Backlog'), amount=float(row.get('amount', 0)) if row.get('amount') and not pd.isna(row.get('amount')) else 0, date=normalize_date(row.get('date')) diff --git a/backend/app/routers/lab.py b/backend/app/routers/lab.py index 8dc7c8f..27ba275 100644 --- a/backend/app/routers/lab.py +++ b/backend/app/routers/lab.py @@ -1,8 +1,8 @@ -from typing import List, Optional +from typing import List, Optional, Tuple, Dict, Set from datetime import datetime, timedelta from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session -from sqlalchemy import func, and_ +from sqlalchemy import func from pydantic import BaseModel from app.models import get_db from app.models.sample import SampleRecord @@ -12,13 +12,15 @@ from app.services.fuzzy_matcher import normalize_pn_for_matching, normalize_cust router = APIRouter(prefix="/lab", tags=["Lab"]) +# --- Pydantic Models --- + class LabKPI(BaseModel): - converted_count: int # 成功收單總數 - avg_velocity: float # 平均轉換時間 (天) - conversion_rate: float # 轉換比例 (%) - orphan_count: int # 孤兒樣品總數 - no_dit_count: int # 未歸因大額樣品數 - high_qty_no_order_count: int # 大額無單樣品數 + converted_count: int + avg_velocity: float + conversion_rate: float + orphan_count: int + no_dit_count: int + high_qty_no_order_count: int class ConversionRecord(BaseModel): customer: str @@ -26,8 +28,8 @@ class ConversionRecord(BaseModel): sample_date: str sample_qty: int order_date: str - order_qty: int # First Order Qty - total_order_qty: int # Total Order Qty (Post-Sample) + order_qty: int + total_order_qty: int days_to_convert: int class ScatterPoint(BaseModel): @@ -61,36 +63,32 @@ class HighQtyNoOrderSample(BaseModel): qty: int days_since_sent: int +# --- Helper Functions --- -def parse_date(date_val) -> Optional[datetime]: +def parse_date_fast(date_val) -> Optional[datetime]: if not date_val: return None if isinstance(date_val, datetime): return date_val if isinstance(date_val, str): - date_str = date_val.strip() - if date_str.endswith(".0"): - date_str = date_str[:-2] + s = date_val.strip().split('.')[0] + if "T" in s: + try: + return datetime.fromisoformat(s.replace("Z", "+00:00")) + except ValueError: + pass try: - if "T" in date_str: - return datetime.fromisoformat(date_str.replace("Z", "+00:00")) - - # Try common formats - for fmt in ["%Y-%m-%d", "%Y/%m/%d", "%Y.%m.%d", "%d-%m-%Y", "%Y%m%d"]: - try: - return datetime.strptime(date_str, fmt) - except ValueError: - continue - - # Fallback: try parsing with pandas if simple strptime fails, - # but for now let's just stick to common formats to avoid heavy dependency inside loop if not needed. - return None + return datetime.strptime(s[:10], "%Y-%m-%d") except ValueError: - return None + pass + for fmt in ["%Y/%m/%d", "%Y.%m.%d", "%d-%m-%Y", "%Y%m%d"]: + try: + return datetime.strptime(s, fmt) + except ValueError: + continue return None def normalize_id(val: any) -> str: - """正規化 ID (去除空白、單引號、轉字串)""" if val is None: return "" s = str(val).strip() @@ -99,55 +97,249 @@ def normalize_id(val: any) -> str: s = s[:-2] return s.upper() -def find_matched_orders(s, order_lookup_by_id, order_lookup_by_name, orders_by_cust_name): - # Use a dictionary to deduplicate matches by a unique key (e.g. order's internal ID or file_id+row which we don't have, so object ID is best if in memory, or full content) - # Since we built lookups with `data` dicts that are created fresh in the loop, we can't rely on object identity of `data`. - # However, `data` might need a unique identifier from the source order. - # Let's add `order_db_id` to `data` in get_conversions first? - # Actually, simpler: just collect all and dedup by `(date, qty, order_no, clean_pn)` tuple? - # Or better, trust the strategy hierarchy but be more permissive? +def fetch_orders_light(db: Session, start_date: str = None) -> List[dict]: + q = db.query( + OrderRecord.customer, + OrderRecord.pn, + OrderRecord.date, + OrderRecord.created_at, + OrderRecord.qty, + OrderRecord.order_no, + OrderRecord.cust_id + ) + if start_date: + q = q.filter(OrderRecord.date >= start_date) - # Strategy change: Try to find ALL valid matches. - # Combine ID and Name matches. + rows = q.all() + memo_cust = {} + memo_pn = {} + memo_id = {} + + processed = [] + for r in rows: + d = parse_date_fast(r.date) or (r.created_at.replace(tzinfo=None) if r.created_at else datetime.max) + + c_raw = r.customer or "" + pn_raw = r.pn or "" + cust_id_raw = r.cust_id + + if c_raw not in memo_cust: memo_cust[c_raw] = normalize_customer_name(c_raw) + if pn_raw not in memo_pn: memo_pn[pn_raw] = normalize_pn_for_matching(pn_raw) + if cust_id_raw not in memo_id: memo_id[cust_id_raw] = normalize_id(cust_id_raw) + + processed.append({ + "customer": c_raw, + "pn": pn_raw, + "date": d, + "qty": r.qty or 0, + "order_no": r.order_no, + "cust_id": cust_id_raw, + "norm_cust_name": memo_cust[c_raw], + "clean_pn": memo_pn[pn_raw], + "clean_cust_id": memo_id[cust_id_raw] + }) + return processed + +def fetch_samples_light(db: Session, start_date: str = None, end_date: str = None) -> List[dict]: + q = db.query( + SampleRecord.id, + SampleRecord.customer, + SampleRecord.pn, + SampleRecord.date, + SampleRecord.qty, + SampleRecord.order_no, + SampleRecord.cust_id + ) + if start_date: + q = q.filter(SampleRecord.date >= start_date) + if end_date: + q = q.filter(SampleRecord.date <= end_date) + + rows = q.all() + memo_cust = {} + memo_pn = {} + + processed = [] + for r in rows: + d = parse_date_fast(r.date) + c_raw = r.customer or "" + pn_raw = r.pn or "" + + if c_raw not in memo_cust: memo_cust[c_raw] = normalize_customer_name(c_raw) + if pn_raw not in memo_pn: memo_pn[pn_raw] = normalize_pn_for_matching(pn_raw) + + processed.append({ + "id": r.id, + "customer": c_raw, + "pn": pn_raw, + "date": d, + "qty": r.qty or 0, + "order_no": r.order_no, + "cust_id": r.cust_id, + "norm_cust_name": memo_cust[c_raw], + "clean_pn": memo_pn[pn_raw], + }) + return processed + +def build_order_lookups(orders: List[dict]): + by_id = {} + by_name = {} + by_cust_name_only = {} + + for o in orders: + if o["clean_cust_id"]: + k = (o["clean_cust_id"], o["clean_pn"]) + if k not in by_id: by_id[k] = [] + by_id[k].append(o) + + k_name = (o["norm_cust_name"], o["clean_pn"]) + if k_name not in by_name: by_name[k_name] = [] + by_name[k_name].append(o) + + cn = o["norm_cust_name"] + if cn not in by_cust_name_only: by_cust_name_only[cn] = [] + by_cust_name_only[cn].append(o) + + return by_id, by_name, by_cust_name_only + +def find_matches_in_memory(sample: dict, order_lookups: Tuple[dict, dict, dict]) -> List[dict]: + (by_id, by_name, by_cust_name) = order_lookups candidates = [] - clean_pn = normalize_pn_for_matching(s.pn) - norm_cust_name = normalize_customer_name(s.customer) - clean_cust_id = normalize_id(s.cust_id) - # 1. Try ID Match - if clean_cust_id: - key_id = (clean_cust_id, clean_pn) - if key_id in order_lookup_by_id: - candidates.extend(order_lookup_by_id[key_id]) + if sample.get("cust_id"): + cid = normalize_id(sample["cust_id"]) + if cid: + k = (cid, sample["clean_pn"]) + if k in by_id: + candidates.extend(by_id[k]) + + k_name = (sample["norm_cust_name"], sample["clean_pn"]) + # 3. Fuzzy Name Match (Fallback) + # Optimization: Iterate unique keys to find substring/superstring matches + # This solves issues where Sample="Corp Inc" but Order="Corp" or vice versa. + if not candidates: + s_name = sample["norm_cust_name"] + + # Key Scan + target_keys = [] + if s_name in by_cust_name: + target_keys.append(s_name) - # 2. Try Name Match (ALWAYS check this too, in case ID is missing on some order rows) - key_name = (norm_cust_name, clean_pn) - if key_name in order_lookup_by_name: - candidates.extend(order_lookup_by_name[key_name]) + # Scan other keys if not exact match or just always scan? + # Always scan allows finding "ABC" when we have "ABC Inc" even if "ABC Inc" has no orders. + for k in by_cust_name.keys(): + if len(k) < 2: continue + if k == s_name: continue - # 3. Try Prefix Match (Only if we have relatively few candidates? Or always?) - # If we already have exact matches, prefix might introduce noise. - # Let's keep prefix as a fallback OR if the existing candidates count is low? - # Actually, let's keep it as fallback for now. Explicit matching is better. - if not candidates and norm_cust_name in orders_by_cust_name: - candidates_prefix = orders_by_cust_name[norm_cust_name] - for o_dat in candidates_prefix: - o_pn = o_dat['clean_pn'] - if o_pn and clean_pn and (clean_pn.startswith(o_pn) or o_pn.startswith(clean_pn)): - candidates.append(o_dat) - - # Deduplicate candidates based on a unique signature - # Signature: (date, qty, order_no) - unique_candidates = [] - seen = set() + # Mutual Containment check + if k in s_name or s_name in k: + target_keys.append(k) + + # Process candidates from identified keys + checked_count = 0 + for k in target_keys: + partial_candidates = by_cust_name[k] + + # Optimization: Safety break if too many candidates + if checked_count > 5000: + break + + spn = sample["clean_pn"] + if spn: + for o in partial_candidates: + opn = o["clean_pn"] + if opn and (spn.startswith(opn) or opn.startswith(spn)): + candidates.append(o) + + checked_count += len(partial_candidates) + + unique_map = {} for c in candidates: - sig = (c["date"], c["qty"], c["order_no"]) - if sig not in seen: - seen.add(sig) - unique_candidates.append(c) + sig = (c["order_no"], c["date"], c["qty"]) + if sig not in unique_map: + unique_map[sig] = c - return unique_candidates + return list(unique_map.values()) + +def fetch_no_dit_samples(db: Session, start_date: str = None, end_date: str = None) -> List[NoDitSample]: + q = db.query(SampleRecord).filter(SampleRecord.qty >= 1000) + if start_date: q = q.filter(SampleRecord.date >= start_date) + if end_date: q = q.filter(SampleRecord.date <= end_date) + + samples = q.all() + if not samples: return [] + + s_ids = [s.id for s in samples] + + matched_ids = db.query(MatchResult.target_id).filter( + MatchResult.target_id.in_(s_ids), + MatchResult.target_type == TargetType.SAMPLE, + MatchResult.status.in_([MatchStatus.accepted, MatchStatus.auto_matched]) + ).all() + matched_set = set(m[0] for m in matched_ids) + + results = [] + for s in samples: + if s.id not in matched_set: + d = parse_date_fast(s.date) + results.append(NoDitSample( + sample_id=str(s.id), + customer=s.customer, + pn=s.pn, + order_no=s.order_no, + date=d.strftime("%Y-%m-%d") if d else "", + qty=s.qty or 0 + )) + + return sorted(results, key=lambda x: x.qty, reverse=True) + +def fetch_high_qty_no_order_samples(db: Session, start_date: str = None, end_date: str = None) -> List[HighQtyNoOrderSample]: + q = db.query( + SampleRecord.id, SampleRecord.customer, SampleRecord.pn, + SampleRecord.date, SampleRecord.qty, SampleRecord.order_no, SampleRecord.cust_id + ).filter(SampleRecord.qty >= 1000) + + if start_date: q = q.filter(SampleRecord.date >= start_date) + if end_date: q = q.filter(SampleRecord.date <= end_date) + + raw_samples = q.all() + samples = [] + for r in raw_samples: + d = parse_date_fast(r.date) + samples.append({ + "id": r.id, "customer": r.customer, "pn": r.pn, "date": d, + "qty": r.qty or 0, "order_no": r.order_no, "cust_id": r.cust_id, + "norm_cust_name": normalize_customer_name(r.customer), + "clean_pn": normalize_pn_for_matching(r.pn), + "clean_cust_id": normalize_id(r.cust_id) + }) + + orders = fetch_orders_light(db, start_date=start_date) + lookups = build_order_lookups(orders) + + results = [] + now = datetime.now() + + for s in samples: + if not s["date"]: continue + matches = find_matches_in_memory(s, lookups) + valid = [o for o in matches if o["date"] >= s["date"]] + + if not valid: + results.append(HighQtyNoOrderSample( + sample_id=str(s["id"]), + customer=s["customer"], + pn=s["pn"], + order_no=s["order_no"], + date=s["date"].strftime("%Y-%m-%d"), + qty=s["qty"], + days_since_sent=(now - s["date"]).days + )) + + return sorted(results, key=lambda x: x.qty, reverse=True) + +# --- Routes --- @router.get("/conversions", response_model=List[ConversionRecord]) def get_conversions( @@ -155,88 +347,40 @@ def get_conversions( end_date: Optional[str] = Query(None), db: Session = Depends(get_db) ): - samples_query = db.query(SampleRecord) - orders_query = db.query(OrderRecord) + if not start_date: + start_date = "2000-01-01" - if start_date: - samples_query = samples_query.filter(SampleRecord.date >= start_date) - orders_query = orders_query.filter(OrderRecord.date >= start_date) - if end_date: - samples_query = samples_query.filter(SampleRecord.date <= end_date) - - samples = samples_query.all() - orders = orders_query.all() - - # Build Lookups - order_lookup_by_id = {} - order_lookup_by_name = {} - orders_by_cust_name = {} # For prefix matching: name -> list of {clean_pn, date, qty, ...} - - for o in orders: - clean_pn = normalize_pn_for_matching(o.pn) - clean_cust_id = o.cust_id.strip().upper() if o.cust_id else "" - norm_cust_name = normalize_customer_name(o.customer) - - o_date = parse_date(o.date) or (o.created_at.replace(tzinfo=None) if o.created_at else datetime.max) - - data = { - "date": o_date, - "qty": o.qty or 0, - "order_no": o.order_no, - "clean_pn": clean_pn # Store for prefix check - } - - if clean_cust_id: - key_id = (clean_cust_id, clean_pn) - if key_id not in order_lookup_by_id: order_lookup_by_id[key_id] = [] - order_lookup_by_id[key_id].append(data) - - key_name = (norm_cust_name, clean_pn) - if key_name not in order_lookup_by_name: order_lookup_by_name[key_name] = [] - order_lookup_by_name[key_name].append(data) - - if norm_cust_name not in orders_by_cust_name: orders_by_cust_name[norm_cust_name] = [] - orders_by_cust_name[norm_cust_name].append(data) - + samples = fetch_samples_light(db, start_date, end_date) + orders = fetch_orders_light(db, start_date=start_date) + lookups = build_order_lookups(orders) conversions = [] for s in samples: - matched_orders = find_matched_orders(s, order_lookup_by_id, order_lookup_by_name, orders_by_cust_name) - s_date = parse_date(s.date) + s_date = s["date"] + if not s_date: continue + + matches = find_matches_in_memory(s, lookups) + valid_orders = [o for o in matches if o["date"] >= s_date] - if matched_orders and s_date: - # STRICT FILTER: Only consider orders AFTER or ON sample date - valid_orders = [o for o in matched_orders if o["date"] >= s_date] + if valid_orders: + valid_orders.sort(key=lambda x: x["date"]) + first_order = valid_orders[0] + first_date = first_order["date"] + first_date_qty = sum(o["qty"] for o in valid_orders if o["date"] == first_date) + total_qty = sum(o["qty"] for o in valid_orders) - if valid_orders: - # Sort orders by date - valid_orders.sort(key=lambda x: x["date"]) - - # Identify First Order Date & Aggregate Qty for that date - first_order = valid_orders[0] - first_date = first_order["date"] - - # Sum qty of ALL orders that match the first order date - first_date_qty = sum(o["qty"] for o in valid_orders if o["date"] == first_date) - - # Total Order Qty (Cumulative for all valid post-sample orders) - total_order_qty = sum(o["qty"] for o in valid_orders) - - days_diff = (first_date - s_date).days - s_date_str = s_date.strftime("%Y-%m-%d") - - conversions.append(ConversionRecord( - customer=s.customer, - pn=s.pn, - sample_date=s_date_str, - sample_qty=s.qty or 0, - order_date=first_date.strftime("%Y-%m-%d"), - order_qty=first_date_qty, # Show First Order Qty ONLY - total_order_qty=total_order_qty, # Show Total Qty - days_to_convert=days_diff - )) + conversions.append(ConversionRecord( + customer=s["customer"], + pn=s["pn"], + sample_date=s_date.strftime("%Y-%m-%d"), + sample_qty=s["qty"], + order_date=first_date.strftime("%Y-%m-%d"), + order_qty=first_date_qty, + total_order_qty=total_qty, + days_to_convert=(first_date - s_date).days + )) - return sorted(conversions, key=lambda x: x.sample_date if x.sample_date else "0000-00-00", reverse=True) + return sorted(conversions, key=lambda x: x.sample_date, reverse=True) @router.get("/kpi", response_model=LabKPI) def get_lab_kpi( @@ -244,143 +388,40 @@ def get_lab_kpi( end_date: Optional[str] = Query(None), db: Session = Depends(get_db) ): - # Fetch Data - samples_query = db.query(SampleRecord) - orders_query = db.query(OrderRecord) + if not start_date: + start_date = "2000-01-01" - if start_date: - samples_query = samples_query.filter(SampleRecord.date >= start_date) - # Optimization: Only fetch orders that could possibly match these samples (Date >= Start Date) - orders_query = orders_query.filter(OrderRecord.date >= start_date) - if end_date: - samples_query = samples_query.filter(SampleRecord.date <= end_date) - # Do NOT filter orders by end_date, to capture conversions that happen after the sample window + samples = fetch_samples_light(db, start_date, end_date) + orders = fetch_orders_light(db, start_date=start_date) + lookups = build_order_lookups(orders) - samples = samples_query.all() - orders = orders_query.all() - - # Build Lookups (Same as conversions) - orders_by_cust_name = {} - order_lookup_by_id = {} - order_lookup_by_name = {} - - for o in orders: - clean_pn = normalize_pn_for_matching(o.pn) - clean_cust_id = o.cust_id.strip().upper() if o.cust_id else "" - norm_cust_name = normalize_customer_name(o.customer) - o_date = parse_date(o.date) or (o.created_at.replace(tzinfo=None) if o.created_at else datetime.max) - - # Standardized Data Object for compatibility with find_matched_orders - data_obj = { - "clean_pn": clean_pn, - "date": o_date, - "qty": o.qty or 0, - "order_no": o.order_no - } - - if clean_cust_id: - key_id = (clean_cust_id, clean_pn) - if key_id not in order_lookup_by_id: order_lookup_by_id[key_id] = [] - order_lookup_by_id[key_id].append(data_obj) - - key_name = (norm_cust_name, clean_pn) - if key_name not in order_lookup_by_name: order_lookup_by_name[key_name] = [] - order_lookup_by_name[key_name].append(data_obj) - - if norm_cust_name not in orders_by_cust_name: orders_by_cust_name[norm_cust_name] = [] - orders_by_cust_name[norm_cust_name].append(data_obj) - - # Group Samples by (CustName, PN) for Project Count - unique_sample_groups = {} + unique_groups = {} + high_qty_samples = [] for s in samples: - clean_pn = normalize_pn_for_matching(s.pn) - norm_cust_name = normalize_customer_name(s.customer) - - key = (norm_cust_name, clean_pn) - if key not in unique_sample_groups: - unique_sample_groups[key] = { + key = (s["norm_cust_name"], s["clean_pn"]) + if key not in unique_groups: + unique_groups[key] = { "dates": [], "cust_ids": set(), - "raw_pns": set() + "raw_pns": set(), + "samples": [] } - s_date = parse_date(s.date) - if s_date: unique_sample_groups[key]["dates"].append(s_date) - if s.cust_id: unique_sample_groups[key]["cust_ids"].add(s.cust_id.strip().upper()) - unique_sample_groups[key]["raw_pns"].add(clean_pn) + if s["date"]: unique_groups[key]["dates"].append(s["date"]) + if s["cust_id"]: unique_groups[key]["cust_ids"].add(normalize_id(s["cust_id"])) + unique_groups[key]["raw_pns"].add(s["clean_pn"]) + unique_groups[key]["samples"].append(s) - # Calculate - total_samples_count = len(unique_sample_groups) + if s["qty"] >= 1000: + high_qty_samples.append(s) + converted_count = 0 orphan_count = 0 velocities = [] now = datetime.now() - - for key, data in unique_sample_groups.items(): - norm_cust_name, group_clean_pn = key - - matched_items = [] - - # 1. Try ID Match - for cid in data["cust_ids"]: - if (cid, group_clean_pn) in order_lookup_by_id: - matched_items.extend(order_lookup_by_id[(cid, group_clean_pn)]) - - # 2. Try Name Match - if not matched_items: - if key in order_lookup_by_name: - matched_items.extend(order_lookup_by_name[key]) - - # 3. Try Prefix Match (Using first available PN in group vs Orders of same customer) - if not matched_items and norm_cust_name in orders_by_cust_name: - candidates = orders_by_cust_name[norm_cust_name] - for o_dat in candidates: - o_pn = o_dat['clean_pn'] - # Check against ANY PN in this sample group - for s_pn in data["raw_pns"]: - if o_pn and (s_pn.startswith(o_pn) or o_pn.startswith(s_pn)): - matched_items.append(o_dat) - - if matched_items: - earliest_sample = min(data["dates"]) if data["dates"] else None - - # STRICT FILTER: Post-Sample Orders Only - # Extract dates from matched items - matched_dates = [item["date"] for item in matched_items] - valid_dates = [] - if earliest_sample: - valid_dates = [d for d in matched_dates if d >= earliest_sample] - - if valid_dates: - converted_count += 1 - first_order = min(valid_dates) - - diff = (first_order - earliest_sample).days - if diff >= 0: - velocities.append(diff) - else: - # No valid post-sample order -> Potential Orphan - if earliest_sample and (now - earliest_sample).days > 90: - orphan_count += 1 - else: - # Orphan Check - earliest_sample = min(data["dates"]) if data["dates"] else None - # If no date, can't determine orphans strictly, but also definitely not converted. - # Only count as orphan if we know it's old enough. - if earliest_sample and (now - earliest_sample).days > 90: - orphan_count += 1 - - avg_velocity = sum(velocities) / len(velocities) if velocities else 0 - conversion_rate = (converted_count / total_samples_count * 100) if total_samples_count > 0 else 0 - - # Calculate No DIT High Qty Samples (Count) - kpi_samples_query = db.query(SampleRecord).filter(SampleRecord.qty >= 1000) - if start_date: kpi_samples_query = kpi_samples_query.filter(SampleRecord.date >= start_date) - if end_date: kpi_samples_query = kpi_samples_query.filter(SampleRecord.date <= end_date) - - high_qty_samples = kpi_samples_query.all() - high_qty_ids = [s.id for s in high_qty_samples] + threshold90 = now - timedelta(days=90) + high_qty_ids = [s["id"] for s in high_qty_samples] no_dit_count = 0 if high_qty_ids: matched_ids = db.query(MatchResult.target_id).filter( @@ -388,148 +429,157 @@ def get_lab_kpi( MatchResult.target_type == TargetType.SAMPLE, MatchResult.status.in_([MatchStatus.accepted, MatchStatus.auto_matched]) ).all() - matched_ids_set = set(m[0] for m in matched_ids) - no_dit_count = len([sid for sid in high_qty_ids if sid not in matched_ids_set]) + matched_set = set(m[0] for m in matched_ids) + no_dit_count = sum(1 for sid in high_qty_ids if sid not in matched_set) - # Calculate High Qty No Order Samples (Count) - # Using existing data structures if possible, or new query - # Criteria: Qty >= 1000 AND No Valid Post-Sample Order - # We can reuse the loop calculation or do it separately. - # Since we already iterated samples to find conversions, let's optimize. - # Actually, the conversion logic above iterates ALL samples. - # Let's add a flag in the main loop? - # Main loop iterates `unique_sample_groups`. - # But High Qty No Order is per SAMPLE, not per group necessarily? - # Actually, business logic wise, if one sample in a group led to order, does it count? - # "Single request quantity > 1000pcs". So it's per sample record. - # If that specific sample has no "attributed" order? - # The current conversion logic is Group-Based (Customer + PN). - # If a group has converted, then likely the samples in it are considered converted. - # BUT, strict definition: "Single sample > 1000". - # Let's iterate high_qty_samples again and check if they belong to a converted group? - # OR check if that sample specifically has a match? - # Our matching logic in `find_matched_orders` is per sample. - - high_qty_no_order_count = 0 - # efficient check: - for s in high_qty_samples: # calculated above - # Check if this sample has valid orders. - # We need to run find_matched_orders for these samples. - # Ensure we have lookups built. They are built in `get_lab_kpi`. - - matched_orders = find_matched_orders(s, order_lookup_by_id, order_lookup_by_name, orders_by_cust_name) - s_date = parse_date(s.date) + for key, data in unique_groups.items(): + earliest_sample = min(data["dates"]) if data["dates"] else None is_converted = False + min_order_date = None - if matched_orders and s_date: - valid_orders = [o for o in matched_orders if o["date"] >= s_date] - if valid_orders: - is_converted = True + candidates = [] + (by_id, by_name, by_cust_name_only) = lookups - if not is_converted: + for cid in data["cust_ids"]: + k = (cid, key[1]) + if k in by_id: candidates.extend(by_id[k]) + + if not candidates and key in by_name: + candidates.extend(by_name[key]) + + if not candidates: + # Fuzzy Name Match (Containment) + found_keys = [] + if key[0] in by_cust_name_only: + found_keys.append(key[0]) + + for k in by_cust_name_only.keys(): + if len(k) < 2: continue + if k == key[0]: continue + if k in key[0] or key[0] in k: + found_keys.append(k) + + checked = 0 + for k in found_keys: + partial = by_cust_name_only[k] + if checked > 5000: break + + spn = key[1] + for o in partial: + opn = o["clean_pn"] + if opn and (spn.startswith(opn) or opn.startswith(spn)): + candidates.append(o) + checked += len(partial) + + if candidates and earliest_sample: + valid_dates = [o["date"] for o in candidates if o["date"] >= earliest_sample] + if valid_dates: + is_converted = True + min_order_date = min(valid_dates) + + if is_converted: + converted_count += 1 + if earliest_sample and min_order_date: + diff = (min_order_date - earliest_sample).days + if diff >= 0: velocities.append(diff) + else: + if earliest_sample and earliest_sample < threshold90: + orphan_count += 1 + + high_qty_no_order_count = 0 + for s in high_qty_samples: + s_date = s["date"] + if not s_date: continue + matches = find_matches_in_memory(s, lookups) + valid = [o for o in matches if o["date"] >= s_date] + if not valid: high_qty_no_order_count += 1 + avg_v = sum(velocities) / len(velocities) if velocities else 0 + c_rate = (converted_count / len(unique_groups) * 100) if unique_groups else 0 + return LabKPI( converted_count=converted_count, - avg_velocity=round(avg_velocity, 1), - conversion_rate=round(conversion_rate, 1), + avg_velocity=round(avg_v, 1), + conversion_rate=round(c_rate, 1), orphan_count=orphan_count, no_dit_count=no_dit_count, high_qty_no_order_count=high_qty_no_order_count ) @router.get("/scatter", response_model=List[ScatterPoint]) -def get_scatter_data( +def get_scatter( start_date: Optional[str] = Query(None), end_date: Optional[str] = Query(None), db: Session = Depends(get_db) ): - samples_query = db.query(SampleRecord) - orders_query = db.query(OrderRecord) - - if start_date: - samples_query = samples_query.filter(SampleRecord.date >= start_date) - orders_query = orders_query.filter(OrderRecord.date >= start_date) - if end_date: - samples_query = samples_query.filter(SampleRecord.date <= end_date) - - samples = samples_query.all() - orders = orders_query.all() - - # Build Lookups (simplified for aggregation) - orders_by_cust_name = {} # name -> list of {clean_pn, qty, date} + samples = fetch_samples_light(db, start_date, end_date) + orders = fetch_orders_light(db, start_date=start_date) + lookups = build_order_lookups(orders) + unique_groups = {} - for o in orders: - norm_cust_name = normalize_customer_name(o.customer) - clean_pn = normalize_pn_for_matching(o.pn) - o_date = parse_date(o.date) or (o.created_at.replace(tzinfo=None) if o.created_at else datetime.max) - - if norm_cust_name not in orders_by_cust_name: - orders_by_cust_name[norm_cust_name] = [] - orders_by_cust_name[norm_cust_name].append({ - "clean_pn": clean_pn, - "qty": o.qty or 0, - "date": o_date - }) - - # Group by (Display Cust, Display PN) - but we need to match broadly - # Strategy: Group by Display Keys first, then try to find match for that group - - unique_groups = {} # (norm_cust, clean_pn) -> {display_cust, display_pn, sample_qty, order_qty, min_sample_date} - for s in samples: - norm_cust_name = normalize_customer_name(s.customer) - clean_pn = normalize_pn_for_matching(s.pn) - s_date = parse_date(s.date) - key = (norm_cust_name, clean_pn) - + key = (s["norm_cust_name"], s["clean_pn"]) if key not in unique_groups: - unique_groups[key] = { - "display_cust": s.customer, - "display_pn": s.pn, - "sample_qty": 0, - "order_qty": 0, - "min_sample_date": s_date - } - unique_groups[key]["sample_qty"] += (s.qty or 0) - - # Update min date - current_min = unique_groups[key]["min_sample_date"] - if s_date: - if not current_min or s_date < current_min: - unique_groups[key]["min_sample_date"] = s_date - - # Fill Order Qty + unique_groups[key] = { + "display_cust": s["customer"], + "display_pn": s["pn"], + "sample_qty": 0, + "order_qty": 0, + "min_date": s["date"] + } + unique_groups[key]["sample_qty"] += s["qty"] + if s["date"] and (not unique_groups[key]["min_date"] or s["date"] < unique_groups[key]["min_date"]): + unique_groups[key]["min_date"] = s["date"] + for key, data in unique_groups.items(): - norm_cust_name, sample_clean_pn = key - min_s_date = data["min_sample_date"] + min_date = data["min_date"] + candidates = [] + (by_id, by_name, by_cust_name_only) = lookups + if key in by_name: + candidates.extend(by_name[key]) + elif not candidates: + # Fuzzy Name Match + found_keys = [] + if key[0] in by_cust_name_only: + found_keys.append(key[0]) + + for k in by_cust_name_only.keys(): + if len(k) < 2: continue + if k == key[0]: continue + if k in key[0] or key[0] in k: + found_keys.append(k) + + checked = 0 + for k in found_keys: + partial = by_cust_name_only[k] + if checked > 5000: break + + spn = key[1] + for o in partial: + opn = o["clean_pn"] + if opn and (spn.startswith(opn) or opn.startswith(spn)): + candidates.append(o) + checked += len(partial) + + seen_orders = set() matched_qty = 0 - - if norm_cust_name in orders_by_cust_name: - candidates = orders_by_cust_name[norm_cust_name] - for o_dat in candidates: - o_pn = o_dat['clean_pn'] - o_date = o_dat['date'] - - # Check Date Causality first - if min_s_date and o_date < min_s_date: - continue - - # Exact or Prefix Match - if o_pn and (sample_clean_pn == o_pn or sample_clean_pn.startswith(o_pn) or o_pn.startswith(sample_clean_pn)): - matched_qty += o_dat['qty'] - + for o in candidates: + sig = (o["order_no"], o["qty"], o["date"]) + if sig in seen_orders: continue + seen_orders.add(sig) + if min_date and o["date"] < min_date: continue + matched_qty += o["qty"] data["order_qty"] = matched_qty - + return [ ScatterPoint( customer=v["display_cust"], pn=v["display_pn"], sample_qty=v["sample_qty"], order_qty=v["order_qty"] - ) - for key, v in unique_groups.items() + ) for v in unique_groups.values() ] @router.get("/orphans", response_model=List[OrphanSample]) @@ -538,137 +588,35 @@ def get_orphans( end_date: Optional[str] = Query(None), db: Session = Depends(get_db) ): - now = datetime.now() - threshold_date = now - timedelta(days=90) - - samples_query = db.query(SampleRecord) - if start_date: - samples_query = samples_query.filter(SampleRecord.date >= start_date) - if end_date: - samples_query = samples_query.filter(SampleRecord.date <= end_date) - - samples = samples_query.all() - # Need to match logic check - # To save time, we can fetch all orders and build lookup - orders_query = db.query(OrderRecord) - if start_date: - orders_query = orders_query.filter(OrderRecord.date >= start_date) - orders = orders_query.all() - - # Build Lookup for Fast Checking - orders_by_cust_name = {} - for o in orders: - norm_cust_name = normalize_customer_name(o.customer) - clean_pn = normalize_pn_for_matching(o.pn) - o_date = parse_date(o.date) or (o.created_at.replace(tzinfo=None) if o.created_at else datetime.max) - - if norm_cust_name not in orders_by_cust_name: orders_by_cust_name[norm_cust_name] = [] - orders_by_cust_name[norm_cust_name].append({ - "clean_pn": clean_pn, - "date": o_date - }) - - # Aggregation Dictionary - # Key: (normalized_customer, normalized_pn, order_no, date_str) - # Value: { "raw_customer": str, "raw_pn": str, "qty": int, "date_obj": datetime } + samples = fetch_samples_light(db, start_date, end_date) + orders = fetch_orders_light(db, start_date=start_date) + lookups = build_order_lookups(orders) + threshold_date = datetime.now() - timedelta(days=90) orphan_groups = {} - + for s in samples: - norm_cust_name = normalize_customer_name(s.customer) - clean_pn = normalize_pn_for_matching(s.pn) - s_date = parse_date(s.date) - s_date_str = s_date.strftime("%Y-%m-%d") if s_date else "Unknown" - s_order_no = s.order_no.strip() if s.order_no else "" - - # Check if matched (Logic same as before, check against all orders) - matched = False - - if s_date and norm_cust_name in orders_by_cust_name: - candidates = orders_by_cust_name[norm_cust_name] - for o_dat in candidates: - o_pn = o_dat['clean_pn'] - o_date = o_dat['date'] - - # Check Date Causality first - if o_date < s_date: - continue - - # Check PN Match (Exact or Prefix) - if o_pn and (clean_pn == o_pn or clean_pn.startswith(o_pn) or o_pn.startswith(clean_pn)): - matched = True - break - - if not matched: - # Only consider old enough samples - if s_date and s_date < threshold_date: - # Add to group - # We use the FIRST raw customer/pn encountered for display, or could be smarter. - # Group Key: (norm_cust, clean_pn, order_no, date) - key = (norm_cust_name, clean_pn, s_order_no, s_date_str) - - if key not in orphan_groups: - orphan_groups[key] = { - "customer": s.customer, - "pn": s.pn, - "order_no": s.order_no, - "date": s_date_str, - "qty": 0, - "days": (now - s_date).days - } - - orphan_groups[key]["qty"] += (s.qty or 0) - - # Convert groups to list - orphans = [] - for data in orphan_groups.values(): - orphans.append(OrphanSample( - customer=data["customer"], - pn=data["pn"], - days_since_sent=data["days"], - order_no=data["order_no"], - date=data["date"], - sample_qty=data["qty"] - )) - - return sorted(orphans, key=lambda x: x.days_since_sent, reverse=True) - -def fetch_no_dit_samples(db: Session, start_date: Optional[str] = None, end_date: Optional[str] = None) -> List[NoDitSample]: - # Filter High Qty Samples - query = db.query(SampleRecord).filter(SampleRecord.qty >= 1000) - if start_date: - query = query.filter(SampleRecord.date >= start_date) - if end_date: - query = query.filter(SampleRecord.date <= end_date) - - high_qty_samples = query.all() - - results = [] - # Batch query matches for efficiency - sample_ids = [s.id for s in high_qty_samples] - if not sample_ids: - return [] - - matched_ids = db.query(MatchResult.target_id).filter( - MatchResult.target_id.in_(sample_ids), - MatchResult.target_type == TargetType.SAMPLE, - MatchResult.status.in_([MatchStatus.accepted, MatchStatus.auto_matched]) - ).all() - - matched_ids_set = set(m[0] for m in matched_ids) - - for s in high_qty_samples: - if s.id not in matched_ids_set: - s_date = parse_date(s.date) - results.append(NoDitSample( - sample_id=str(s.id), - customer=s.customer, - pn=s.pn, - order_no=s.order_no, - date=s_date.strftime("%Y-%m-%d") if s_date else (s.date or ""), - qty=s.qty - )) - - return sorted(results, key=lambda x: x.qty, reverse=True) + s_date = s["date"] + if not s_date or s_date >= threshold_date: continue + matches = find_matches_in_memory(s, lookups) + valid = [o for o in matches if o["date"] >= s_date] + if not valid: + key = (s["norm_cust_name"], s["clean_pn"], s["order_no"] or "", s_date) + if key not in orphan_groups: + orphan_groups[key] = { + "customer": s["customer"], + "pn": s["pn"], + "order_no": s["order_no"], + "date": s_date.strftime("%Y-%m-%d"), + "sample_qty": 0, + "days_since_sent": (datetime.now() - s_date).days + } + orphan_groups[key]["sample_qty"] += s["qty"] + + return sorted( + [OrphanSample(**v) for v in orphan_groups.values()], + key=lambda x: x.days_since_sent, + reverse=True + ) @router.get("/no_dit_samples", response_model=List[NoDitSample]) def get_no_dit_samples( @@ -678,83 +626,8 @@ def get_no_dit_samples( ): return fetch_no_dit_samples(db, start_date, end_date) -def fetch_high_qty_no_order_samples(db: Session, start_date: Optional[str] = None, end_date: Optional[str] = None) -> List[HighQtyNoOrderSample]: - # 1. Get High Qty Samples - query = db.query(SampleRecord).filter(SampleRecord.qty >= 1000) - if start_date: - query = query.filter(SampleRecord.date >= start_date) - if end_date: - query = query.filter(SampleRecord.date <= end_date) - - high_qty_samples = query.all() - if not high_qty_samples: - return [] - - # 2. Get All Orders for Matching - orders_query = db.query(OrderRecord) - if start_date: - orders_query = orders_query.filter(OrderRecord.date >= start_date) - orders = orders_query.all() - - # 3. Build Lookup (Same logic as kpi/conversions) - order_lookup_by_id = {} - order_lookup_by_name = {} - orders_by_cust_name = {} - - for o in orders: - clean_pn = normalize_pn_for_matching(o.pn) - clean_cust_id = o.cust_id.strip().upper() if o.cust_id else "" - norm_cust_name = normalize_customer_name(o.customer) - o_date = parse_date(o.date) or (o.created_at.replace(tzinfo=None) if o.created_at else datetime.max) - - data_obj = { - "clean_pn": clean_pn, - "date": o_date, - "qty": o.qty or 0, - "order_no": o.order_no - } - - if clean_cust_id: - key_id = (clean_cust_id, clean_pn) - if key_id not in order_lookup_by_id: order_lookup_by_id[key_id] = [] - order_lookup_by_id[key_id].append(data_obj) - - key_name = (norm_cust_name, clean_pn) - if key_name not in order_lookup_by_name: order_lookup_by_name[key_name] = [] - order_lookup_by_name[key_name].append(data_obj) - - if norm_cust_name not in orders_by_cust_name: orders_by_cust_name[norm_cust_name] = [] - orders_by_cust_name[norm_cust_name].append(data_obj) - - results = [] - now = datetime.now() - - for s in high_qty_samples: - matched_orders = find_matched_orders(s, order_lookup_by_id, order_lookup_by_name, orders_by_cust_name) - s_date = parse_date(s.date) - is_converted = False - - if matched_orders and s_date: - valid_orders = [o for o in matched_orders if o["date"] >= s_date] - if valid_orders: - is_converted = True - - if not is_converted: - days_since = (now - s_date).days if s_date else 0 - results.append(HighQtyNoOrderSample( - sample_id=str(s.id), - customer=s.customer, - pn=s.pn, - order_no=s.order_no, - date=s_date.strftime("%Y-%m-%d") if s_date else (s.date or ""), - qty=s.qty, - days_since_sent=days_since - )) - - return sorted(results, key=lambda x: x.qty, reverse=True) - @router.get("/high_qty_no_order_samples", response_model=List[HighQtyNoOrderSample]) -def get_high_qty_no_order_samples( +def get_high_qty_no_order( start_date: Optional[str] = Query(None), end_date: Optional[str] = Query(None), db: Session = Depends(get_db) diff --git a/backend/app/services/excel_parser.py b/backend/app/services/excel_parser.py index 3e189a1..8a4421d 100644 --- a/backend/app/services/excel_parser.py +++ b/backend/app/services/excel_parser.py @@ -48,19 +48,19 @@ COLUMN_MAPPING = { 'oppy_no': ['oppy no', 'oppy_no', '案號', '案件編號', 'opportunity no'], 'cust_id': ['cust id', 'cust_id', '客戶編號', '客戶代碼', '客戶代號'], 'customer': ['客戶名稱', '客戶簡稱', '客戶', 'customer', 'customer name'], - 'pn': ['item', '料號', 'part number', 'pn', 'part no', '產品料號', '索樣數量', 'type'], - 'qty': ['索樣數量pcs', '索樣數量 k', '數量', 'qty', 'quantity', '申請數量', '索樣數量'], + 'pn': ['強茂料號', 'item', '料號', 'part number', 'pn', 'part no', '產品料號', 'type'], + 'qty': ['索樣數量pcs', '索樣數量 pcs', '索樣數量 k', '數量', 'qty', 'quantity', '申請數量', '索樣數量'], 'date': ['出貨日', '需求日', '日期', 'date', '申請日期'] }, 'order': { - 'order_id': ['項次', '訂單編號', 'order_id', 'order id'], - 'order_no': ['訂單單號', '訂單號', 'order_no', 'order no', '銷貨單號'], - 'cust_id': ['客戶編號', '客戶代碼', '客戶代號', 'cust_id', 'cust id', 'erp code', 'erp_code', 'erpcode', 'erp'], + 'order_id': ['項次', '訂單項次', '訂單編號', 'order_id', 'order id'], + 'order_no': ['訂單單號', '訂單號碼', '訂單號', 'order_no', 'order no', '銷貨單號'], + 'cust_id': ['客戶編號', '客戶代碼', '客戶代號', '客戶', 'cust_id', 'cust id', 'erp code', 'erp_code', 'erpcode', 'erp'], 'customer': ['客戶', '客戶名稱', 'customer', 'customer name'], - 'pn': ['內部料號', '料號', 'part number', 'pn', 'part no', '產品料號', 'type'], - 'qty': ['訂單量', '數量', 'qty', 'quantity', '訂購數量', '出貨數量'], - 'status': ['狀態', 'status', '訂單狀態'], - 'amount': ['原幣金額(含稅)', '台幣金額(未稅)', '金額', 'amount', 'total', '訂單金額'], + 'pn': ['強茂料號', '內部料號', '料號', 'part number', 'pn', 'part no', '產品料號', 'type'], + 'qty': ['訂單需求量', '訂單量', '數量', 'qty', 'quantity', '訂購數量', '出貨數量'], + 'status': ['明細行狀態', '狀態', 'status', '訂單狀態'], + 'amount': ['台幣金額', '原幣金額(含稅)', '台幣金額(未稅)', '金額', 'amount', 'total', '訂單金額'], 'date': ['訂單日期', '日期', 'date', 'order date', 'order_date'] } } @@ -99,13 +99,37 @@ class ExcelParser: df_columns = [str(c).lower().strip() for c in df.columns] + # 第一階段:嘗試精確比對 (Case-insensitive) for standard_name, variants in column_map.items(): + variants_lower = [v.lower().strip() for v in variants] + for idx, col in enumerate(df_columns): + if col in variants_lower: + mapping[df.columns[idx]] = standard_name + print(f"[DEBUG] Exact Mapped '{df.columns[idx]}' to '{standard_name}'") + break + + # 第二階段:嘗試子字串包含比對 (僅針對未對應到的欄位) + mapped_indices = set() + for col_name in mapping.keys(): + for idx, col in enumerate(df.columns): + if col == col_name: + mapped_indices.add(idx) + + for standard_name, variants in column_map.items(): + if standard_name in mapping.values(): + continue + for variant in variants: - variant_lower = variant.lower() + variant_lower = variant.lower().strip() + if len(variant_lower) < 2: continue # 避免過短的關鍵字誤判 + for idx, col in enumerate(df_columns): + if idx in mapped_indices: continue + if variant_lower in col or col in variant_lower: mapping[df.columns[idx]] = standard_name - print(f"[DEBUG] Mapped '{df.columns[idx]}' to '{standard_name}' (matched '{variant}')") + mapped_indices.add(idx) + print(f"[DEBUG] Substring Mapped '{df.columns[idx]}' to '{standard_name}' (matched '{variant}')") break if standard_name in mapping.values(): break diff --git a/backend/app/services/fuzzy_matcher.py b/backend/app/services/fuzzy_matcher.py index 994d666..58d26ee 100644 --- a/backend/app/services/fuzzy_matcher.py +++ b/backend/app/services/fuzzy_matcher.py @@ -197,7 +197,10 @@ class FuzzyMatcher: if dit.op_id: potential_samples.extend(samples_by_oppy.get(dit.op_id, [])) if dit_norm_pn: - potential_samples.extend(samples_by_pn.get(dit_norm_pn, [])) + # 允許前綴匹配 (Prefix Matching) + for spn in samples_by_pn.keys(): + if dit_norm_pn.startswith(spn) or spn.startswith(dit_norm_pn): + potential_samples.extend(samples_by_pn[spn]) # 去重 seen_sample_ids = set() @@ -227,8 +230,10 @@ class FuzzyMatcher: score = 100.0 reason = "Golden Key Match" - # Priority 2 & 3 則限制在相同 PN (Ignored symbols) - elif dit_norm_pn == normalize_pn_for_matching(sample.pn): + # Priority 2 & 3 則限制在 PN 匹配 (支援前綴) + elif dit_norm_pn and normalize_pn_for_matching(sample.pn) and \ + (dit_norm_pn.startswith(normalize_pn_for_matching(sample.pn)) or + normalize_pn_for_matching(sample.pn).startswith(dit_norm_pn)): # Priority 2: 客戶代碼比對 (Silver Key) dit_erp = normalize_id(dit.erp_account) sample_cust = normalize_id(sample.cust_id) @@ -268,7 +273,13 @@ class FuzzyMatcher: # --- 比對訂單 (DIT -> Order) --- # 訂單比對通常基於 PN if dit_norm_pn: - for order in orders_by_pn.get(dit_norm_pn, []): + # 收集所有 PN 匹配(支援前綴)的訂單 + matched_orders = [] + for opn in orders_by_pn.keys(): + if dit_norm_pn.startswith(opn) or opn.startswith(dit_norm_pn): + matched_orders.extend(orders_by_pn[opn]) + + for order in matched_orders: match_priority = 0 match_source = "" score = 0.0 diff --git a/backend/check_info.py b/backend/check_info.py new file mode 100644 index 0000000..b6f4c92 --- /dev/null +++ b/backend/check_info.py @@ -0,0 +1,5 @@ +import requests +import json + +resp = requests.get("http://localhost:8000/api/lab/kpi") +print(json.dumps(resp.json(), indent=2)) diff --git a/backend/check_kpi.py b/backend/check_kpi.py new file mode 100644 index 0000000..506f4db --- /dev/null +++ b/backend/check_kpi.py @@ -0,0 +1,15 @@ +import requests +import time + +try: + print("Testing /api/lab/kpi...") + start = time.time() + resp = requests.get("http://localhost:8000/api/lab/kpi", timeout=10) + print(f"Status: {resp.status_code}") + print(f"Time: {time.time() - start:.2f}s") + if resp.status_code == 200: + print("Success") + else: + print("Fail") +except Exception as e: + print(f"Error: {e}") diff --git a/backend/check_range.py b/backend/check_range.py new file mode 100644 index 0000000..dba1013 --- /dev/null +++ b/backend/check_range.py @@ -0,0 +1,17 @@ +from app.models import get_db +from app.models.sample import SampleRecord +from app.models.order import OrderRecord +from sqlalchemy import func + +db = next(get_db()) + +min_sample = db.query(func.min(SampleRecord.date)).scalar() +max_sample = db.query(func.max(SampleRecord.date)).scalar() +count_sample = db.query(SampleRecord).count() + +min_order = db.query(func.min(OrderRecord.date)).scalar() +max_order = db.query(func.max(OrderRecord.date)).scalar() +count_order = db.query(OrderRecord).count() + +print(f"Samples: {count_sample}, Min: {min_sample}, Max: {max_sample}") +print(f"Orders: {count_order}, Min: {min_order}, Max: {max_order}") diff --git a/backend/check_records.py b/backend/check_records.py new file mode 100644 index 0000000..db95845 --- /dev/null +++ b/backend/check_records.py @@ -0,0 +1,30 @@ +from app.models import SessionLocal, DitRecord, OrderRecord +from app.services.fuzzy_matcher import normalize_pn_for_matching, normalize_id + +db = SessionLocal() +try: + print("--- DIT Check ---") + dit = db.query(DitRecord).filter(DitRecord.erp_account == '184100').first() + if dit: + print(f"DIT Found: ID={dit.id}, Op={dit.op_id}, PN={dit.pn}, ERP={dit.erp_account}") + norm_pn = normalize_pn_for_matching(dit.pn) + print(f" Normalized PN: {norm_pn}") + + print("\n--- Order Check ---") + orders = db.query(OrderRecord).filter(OrderRecord.cust_id == '184100').all() + print(f"Orders found for ERP 184100: {len(orders)}") + for o in orders: + o_norm_pn = normalize_pn_for_matching(o.pn) + print(f" Order ID={o.id}, No={o.order_no}, PN={o.pn}, NormPN={o_norm_pn}") + if o_norm_pn == norm_pn: + print(" !!! PN MATCH FOUND !!!") + else: + print("DIT with ERP 184100 not found") + # Search by name + dit = db.query(DitRecord).filter(DitRecord.customer.like('%瀚碩%')).first() + if dit: + print(f"DIT Found by name: ID={dit.id}, ERP={dit.erp_account}") +except Exception as e: + print(f"Error: {e}") +finally: + db.close() diff --git a/backend/debug_api.py b/backend/debug_api.py new file mode 100644 index 0000000..eae41cc --- /dev/null +++ b/backend/debug_api.py @@ -0,0 +1,22 @@ +from app.routers.lab import get_lab_kpi, get_conversions +from app.models import get_db +from fastapi import APIRouter, Depends + +# Mock DB dependency +db = next(get_db()) + +print("--- Testing KPI API Logic ---") +try: + kpi = get_lab_kpi(start_date=None, end_date=None, db=db) + print("KPI Result:", kpi) +except Exception as e: + print("KPI Error:", e) + +print("\n--- Testing Conversions API Logic ---") +try: + conversions = get_conversions(start_date=None, end_date=None, db=db) + print(f"Conversions Found: {len(conversions)}") + for c in conversions: + print(c) +except Exception as e: + print("Conversions Error:", e) diff --git a/backend/debug_dates.py b/backend/debug_dates.py new file mode 100644 index 0000000..73f4e3c --- /dev/null +++ b/backend/debug_dates.py @@ -0,0 +1,46 @@ +from app.models import SessionLocal, DitRecord, OrderRecord +from app.services.fuzzy_matcher import normalize_pn_for_matching, normalize_id +import pandas as pd + +db = SessionLocal() +try: + print("=== DIT Record Info ===") + # DIT Project: PJQ5514S6C-AU R2 002A1 + target_pn = "PJQ5514S6C-AU R2 002A1" + target_norm_pn = normalize_pn_for_matching(target_pn) + + dits = db.query(DitRecord).filter(DitRecord.pn.like('%PJQ5514S6C%')).all() + if not dits: + print("No DIT found for PJQ5514S6C") + for d in dits: + print(f"DIT ID: {d.id}") + print(f" Op ID: {d.op_id}") + print(f" Customer: {d.customer}") + print(f" PN: {d.pn} (Norm: {normalize_pn_for_matching(d.pn)})") + print(f" ERP: {d.erp_account}") + print(f" Date: {d.date}") + + print("\n=== Order Records Info ===") + orders = db.query(OrderRecord).filter(OrderRecord.pn.like('%PJQ5514S6C%')).all() + print(f"Total orders found: {len(orders)}") + + # Group by date to see range + if orders: + df = pd.DataFrame([{ + 'id': o.id, + 'order_no': o.order_no, + 'cust_id': o.cust_id, + 'pn': o.pn, + 'date': o.date + } for o in orders]) + print("\nOrder Date Summary:") + print(df['date'].value_counts().sort_index()) + + print("\nDetail of first 5 orders:") + for o in orders[:5]: + print(f" ID: {o.id}, No: {o.order_no}, CustID: {o.cust_id}, PN: {o.pn}, Date: {o.date}") + +except Exception as e: + print(f"Error: {e}") +finally: + db.close() diff --git a/backend/debug_dates_out.txt b/backend/debug_dates_out.txt new file mode 100644 index 0000000..9f8c30b Binary files /dev/null and b/backend/debug_dates_out.txt differ diff --git a/backend/debug_logic.py b/backend/debug_logic.py new file mode 100644 index 0000000..567dea1 --- /dev/null +++ b/backend/debug_logic.py @@ -0,0 +1,44 @@ +from app.routers.lab import parse_date_fast, fetch_samples_light, fetch_orders_light, build_order_lookups, find_matches_in_memory +from app.models import get_db +from datetime import datetime, timedelta + +# Test Date Parsing +d_str = "20250702.0" +parsed = parse_date_fast(d_str) +print(f"Parsing '{d_str}': {parsed}") + +if not parsed: + print("CRITICAL: Date parsing failed!") + +# Test Data Fetching +db = next(get_db()) +start_date = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d") +print(f"Testing with Start Date: {start_date}") + +print("Fetching samples...") +samples = fetch_samples_light(db, start_date=start_date) +print(f"Fetched {len(samples)} samples.") +if samples: + print("Sample 0:", samples[0]) + +print("Fetching orders...") +orders = fetch_orders_light(db, start_date=start_date) +print(f"Fetched {len(orders)} orders.") + +# Test Matching +lookups = build_order_lookups(orders) +matches_found = 0 + +for s in samples: + if not s['date']: continue + matches = find_matches_in_memory(s, lookups) + if matches: + print(f"Match found for: {s['customer']} {s['pn']}") + for m in matches: + print(f" -> Order Date: {m['date']}, Sample Date: {s['date']}") + if m['date'] >= s['date']: + matches_found += 1 + else: + print(" -> INVALID (Order before Sample)") + +print(f"Total Matches Found: {matches_found}") diff --git a/backend/debug_match_output.txt b/backend/debug_match_output.txt new file mode 100644 index 0000000..e52b3f1 --- /dev/null +++ b/backend/debug_match_output.txt @@ -0,0 +1,868 @@ +Target Norm PN: PJQ5514S6CAUR2002A1 +Target ERP: 184100 + +Found 2 DIT records matching PN or ERP: + ID: 78136, Op: OP0000022509, Cust: 瀚碩科技股份有限公司, ERP: SF0000001343, PN: PJQ5514S6C-AU_R2_002A1, Date: 11/28/2025 + Norm PN: PJQ5514S6CAUR2002A1, Norm ERP: SF0000001343 + ID: 78235, Op: OP0000022496, Cust: WPG SOUTH ASIA PTE. LTD., ERP: SF0000010267, PN: PJQ5514S6C-AU_R2_002A1, Date: 11/27/2025 + Norm PN: PJQ5514S6CAUR2002A1, Norm ERP: SF0000010267 + +Found 426 Order records matching PN or ERP: + ID: 352143, OrderNo: 1125023655, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR20200FCT, Qty: 2000, Date: 2025-08-04 + Norm PN: MBR20200FCT, Norm CustID: 184100 + ID: 352144, OrderNo: 1125023656, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54BL, Qty: 30000, Date: 2025-08-04 + Norm PN: SK54BL, Norm CustID: 184100 + ID: 352145, OrderNo: 1125023656, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVT12100V, Qty: 10000, Date: 2025-08-04 + Norm PN: SVT12100V, Norm CustID: 184100 + ID: 352150, OrderNo: 1125023659, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PE1605C2A, Qty: 18000, Date: 2025-08-04 + Norm PN: PE1605C2A, Norm CustID: 184100 + ID: 353488, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 2N7002K, Qty: 90000, Date: 2025-08-11 + Norm PN: 2N7002K, Norm CustID: 184100 + ID: 353489, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR10H150PC-AU, Qty: 10000, Date: 2025-08-11 + Norm PN: MBR10H150PCAU, Norm CustID: 184100 + ID: 353490, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3432, Qty: 300000, Date: 2025-08-11 + Norm PN: PJA3432, Norm CustID: 184100 + ID: 353491, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 45000, Date: 2025-08-11 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 353492, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 135000, Date: 2025-08-11 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 353493, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 50400, Date: 2025-08-11 + Norm PN: S100, Norm CustID: 184100 + ID: 353494, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB1200AH, Qty: 30000, Date: 2025-08-11 + Norm PN: SB1200AH, Norm CustID: 184100 + ID: 353495, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB1200AH, Qty: 970000, Date: 2025-08-11 + Norm PN: SB1200AH, Norm CustID: 184100 + ID: 353496, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54BL, Qty: 30000, Date: 2025-08-11 + Norm PN: SK54BL, Norm CustID: 184100 + ID: 353497, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS0540, Qty: 120000, Date: 2025-08-11 + Norm PN: SS0540, Norm CustID: 184100 + ID: 353498, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 306000, Date: 2025-08-11 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 353499, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 294000, Date: 2025-08-11 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 353500, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 225000, Date: 2025-08-11 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 353501, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 75000, Date: 2025-08-11 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 353502, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM860VB, Qty: 10000, Date: 2025-08-11 + Norm PN: SVM860VB, Norm CustID: 184100 + ID: 353503, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40WS, Qty: 45000, Date: 2025-08-11 + Norm PN: BAS40WS, Norm CustID: 184100 + ID: 353504, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40WS, Qty: 5000, Date: 2025-08-11 + Norm PN: BAS40WS, Norm CustID: 184100 + ID: 353505, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS2100S, Qty: 4000, Date: 2025-08-11 + Norm PN: TS2100S, Norm CustID: 184100 + ID: 353506, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS2100S, Qty: 6000, Date: 2025-08-11 + Norm PN: TS2100S, Norm CustID: 184100 + ID: 353507, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54A, Qty: 150000, Date: 2025-08-11 + Norm PN: BAT54A, Norm CustID: 184100 + ID: 353508, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54A, Qty: 90000, Date: 2025-08-11 + Norm PN: BAT54A, Norm CustID: 184100 + ID: 353509, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54C, Qty: 150000, Date: 2025-08-11 + Norm PN: BAT54C, Norm CustID: 184100 + ID: 353510, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54TS, Qty: 5000000, Date: 2025-08-11 + Norm PN: BAT54TS, Norm CustID: 184100 + ID: 353511, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54WS, Qty: 50000, Date: 2025-08-11 + Norm PN: BAT54WS, Norm CustID: 184100 + ID: 353512, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER1JAFC, Qty: 21000, Date: 2025-08-11 + Norm PN: ER1JAFC, Norm CustID: 184100 + ID: 353513, OrderNo: 1125025101, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR10H150PC-AU, Qty: 10000, Date: 2025-08-11 + Norm PN: MBR10H150PCAU, Norm CustID: 184100 + ID: 353697, OrderNo: 1125025215, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PE1605C4C6, Qty: 3000, Date: 2025-08-12 + Norm PN: PE1605C4C6, Norm CustID: 184100 + ID: 354009, OrderNo: 1125025311, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SBT12100VPC-AU, Qty: 10000, Date: 2025-08-13 + Norm PN: SBT12100VPCAU, Norm CustID: 184100 + ID: 354010, OrderNo: 1125025311, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SBT12100VPC-AU, Qty: 10000, Date: 2025-08-13 + Norm PN: SBT12100VPCAU, Norm CustID: 184100 + ID: 354821, OrderNo: 1125025592, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER1JAFC, Qty: 21000, Date: 2025-08-15 + Norm PN: ER1JAFC, Norm CustID: 184100 + ID: 355187, OrderNo: 1125025806, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BX310, Qty: 30600, Date: 2025-08-18 + Norm PN: BX310, Norm CustID: 184100 + ID: 355188, OrderNo: 1125025806, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER1JAFC, Qty: 12000, Date: 2025-08-18 + Norm PN: ER1JAFC, Norm CustID: 184100 + ID: 355377, OrderNo: 1125025934, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 30000, Date: 2025-08-19 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 355957, OrderNo: 1125026249, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ4576AP-AU, Qty: 10000, Date: 2025-08-21 + Norm PN: PJQ4576APAU, Norm CustID: 184100 + ID: 356112, OrderNo: 1125026365, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 30000, Date: 2025-08-22 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 356503, OrderNo: 1125026487, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5241B, Qty: 3000, Date: 2025-08-25 + Norm PN: MMSZ5241B, Norm CustID: 184100 + ID: 356504, OrderNo: 1125026488, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER204, Qty: 30000, Date: 2025-08-25 + Norm PN: ER204, Norm CustID: 184100 + ID: 356505, OrderNo: 1125026488, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER204, Qty: 30000, Date: 2025-08-25 + Norm PN: ER204, Norm CustID: 184100 + ID: 356506, OrderNo: 1125026488, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2025-08-25 + Norm PN: ER206, Norm CustID: 184100 + ID: 356507, OrderNo: 1125026488, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2025-08-25 + Norm PN: ER206, Norm CustID: 184100 + ID: 356508, OrderNo: 1125026488, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER304, Qty: 7500, Date: 2025-08-25 + Norm PN: ER304, Norm CustID: 184100 + ID: 356509, OrderNo: 1125026488, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER506, Qty: 5000, Date: 2025-08-25 + Norm PN: ER506, Norm CustID: 184100 + ID: 356510, OrderNo: 1125026488, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GS1M, Qty: 7200, Date: 2025-08-25 + Norm PN: GS1M, Norm CustID: 184100 + ID: 356511, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 15KE120CA, Qty: 2500, Date: 2025-08-25 + Norm PN: 15KE120CA, Norm CustID: 184100 + ID: 356512, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5236B, Qty: 60000, Date: 2025-08-25 + Norm PN: MMSZ5236B, Norm CustID: 184100 + ID: 356513, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5245B, Qty: 210000, Date: 2025-08-25 + Norm PN: MMSZ5245B, Norm CustID: 184100 + ID: 356514, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5246B, Qty: 60000, Date: 2025-08-25 + Norm PN: MMSZ5246B, Norm CustID: 184100 + ID: 356515, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 18000, Date: 2025-08-25 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 356516, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 351000, Date: 2025-08-25 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 356517, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 27000, Date: 2025-08-25 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 356518, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54, Qty: 6000, Date: 2025-08-25 + Norm PN: SK54, Norm CustID: 184100 + ID: 356519, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS14, Qty: 61200, Date: 2025-08-25 + Norm PN: SS14, Norm CustID: 184100 + ID: 356520, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS1100S, Qty: 4000, Date: 2025-08-25 + Norm PN: TS1100S, Norm CustID: 184100 + ID: 356521, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 1N4148WS, Qty: 50000, Date: 2025-08-25 + Norm PN: 1N4148WS, Norm CustID: 184100 + ID: 356522, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40WS, Qty: 100000, Date: 2025-08-25 + Norm PN: BAS40WS, Norm CustID: 184100 + ID: 356523, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5221B, Qty: 33000, Date: 2025-08-25 + Norm PN: MMSZ5221B, Norm CustID: 184100 + ID: 356524, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5221B, Qty: 27000, Date: 2025-08-25 + Norm PN: MMSZ5221B, Norm CustID: 184100 + ID: 356525, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 60000, Date: 2025-08-25 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 356526, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 30000, Date: 2025-08-25 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 356527, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 30000, Date: 2025-08-25 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 356528, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5228B, Qty: 150000, Date: 2025-08-25 + Norm PN: MMSZ5228B, Norm CustID: 184100 + ID: 356529, OrderNo: 1125026489, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5236B, Qty: 60000, Date: 2025-08-25 + Norm PN: MMSZ5236B, Norm CustID: 184100 + ID: 356551, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 93000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356552, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 321000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356553, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 48000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356554, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 3000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356555, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 420000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356556, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 168000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356557, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 18000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356558, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 3000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356559, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 93000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356560, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 351000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356561, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 3000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356562, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 480000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356563, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 159000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356564, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 282000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356565, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 459000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356566, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 216000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356567, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 207000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356568, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 159000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356569, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 480000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356570, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 264000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356571, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 480000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356572, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 273000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 356573, OrderNo: 1125026504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 21000, Date: 2025-08-25 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 357986, OrderNo: 1125027373, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 750000, Date: 2025-09-02 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 357987, OrderNo: 1125027373, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 252000, Date: 2025-09-02 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 357988, OrderNo: 1125027373, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 750000, Date: 2025-09-02 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 357989, OrderNo: 1125027373, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5514S6C-AU, Qty: 252000, Date: 2025-09-02 + Norm PN: PJQ5514S6CAU, Norm CustID: 184100 + ID: 358387, OrderNo: 1125027490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PE1605C4C6, Qty: 3000, Date: 2025-09-03 + Norm PN: PE1605C4C6, Norm CustID: 184100 + ID: 358388, OrderNo: 1125027490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PE1605C4C6, Qty: 12000, Date: 2025-09-03 + Norm PN: PE1605C4C6, Norm CustID: 184100 + ID: 358389, OrderNo: 1125027494, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS1030FL, Qty: 30000, Date: 2025-09-03 + Norm PN: SS1030FL, Norm CustID: 184100 + ID: 358501, OrderNo: 1125027526, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5228B, Qty: 150000, Date: 2025-09-03 + Norm PN: MMSZ5228B, Norm CustID: 184100 + ID: 359082, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 2N7002K, Qty: 300000, Date: 2025-09-08 + Norm PN: 2N7002K, Norm CustID: 184100 + ID: 359083, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BR210, Qty: 120600, Date: 2025-09-08 + Norm PN: BR210, Norm CustID: 184100 + ID: 359084, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BR210, Qty: 90000, Date: 2025-09-08 + Norm PN: BR210, Norm CustID: 184100 + ID: 359085, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BZT52-B3V3, Qty: 30000, Date: 2025-09-08 + Norm PN: BZT52B3V3, Norm CustID: 184100 + ID: 359086, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ES1D, Qty: 25200, Date: 2025-09-08 + Norm PN: ES1D, Norm CustID: 184100 + ID: 359087, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT2222A, Qty: 45000, Date: 2025-09-08 + Norm PN: MMBT2222A, Norm CustID: 184100 + ID: 359088, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 738000, Date: 2025-09-08 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 359089, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ58A, Qty: 18000, Date: 2025-09-08 + Norm PN: P6SMBJ58A, Norm CustID: 184100 + ID: 359090, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3432, Qty: 300000, Date: 2025-09-08 + Norm PN: PJA3432, Norm CustID: 184100 + ID: 359091, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3432, Qty: 300000, Date: 2025-09-08 + Norm PN: PJA3432, Norm CustID: 184100 + ID: 359092, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 600000, Date: 2025-09-08 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 359093, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40C, Qty: 30000, Date: 2025-09-08 + Norm PN: BAS40C, Norm CustID: 184100 + ID: 359094, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 510000, Date: 2025-09-08 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 359095, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: RB720M-30, Qty: 56000, Date: 2025-09-08 + Norm PN: RB720M30, Norm CustID: 184100 + ID: 359096, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: RB720M-30, Qty: 24000, Date: 2025-09-08 + Norm PN: RB720M30, Norm CustID: 184100 + ID: 359097, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 81000, Date: 2025-09-08 + Norm PN: S100, Norm CustID: 184100 + ID: 359098, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB1200AH, Qty: 750000, Date: 2025-09-08 + Norm PN: SB1200AH, Norm CustID: 184100 + ID: 359099, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54BL, Qty: 30000, Date: 2025-09-08 + Norm PN: SK54BL, Norm CustID: 184100 + ID: 359100, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SR24, Qty: 201600, Date: 2025-09-08 + Norm PN: SR24, Norm CustID: 184100 + ID: 359101, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SR24, Qty: 201600, Date: 2025-09-08 + Norm PN: SR24, Norm CustID: 184100 + ID: 359102, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS0540, Qty: 120000, Date: 2025-09-08 + Norm PN: SS0540, Norm CustID: 184100 + ID: 359103, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 150000, Date: 2025-09-08 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 359104, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS14, Qty: 30600, Date: 2025-09-08 + Norm PN: SS14, Norm CustID: 184100 + ID: 359105, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40WS, Qty: 50000, Date: 2025-09-08 + Norm PN: BAS40WS, Norm CustID: 184100 + ID: 359106, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS1100S, Qty: 5000, Date: 2025-09-08 + Norm PN: TS1100S, Norm CustID: 184100 + ID: 359107, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS2100S, Qty: 15000, Date: 2025-09-08 + Norm PN: TS2100S, Norm CustID: 184100 + ID: 359108, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40WS, Qty: 50000, Date: 2025-09-08 + Norm PN: BAS40WS, Norm CustID: 184100 + ID: 359109, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54C, Qty: 150000, Date: 2025-09-08 + Norm PN: BAT54C, Norm CustID: 184100 + ID: 359110, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54C, Qty: 150000, Date: 2025-09-08 + Norm PN: BAT54C, Norm CustID: 184100 + ID: 359111, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54WS, Qty: 120000, Date: 2025-09-08 + Norm PN: BAT54WS, Norm CustID: 184100 + ID: 359112, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT750, Qty: 30000, Date: 2025-09-08 + Norm PN: BAT750, Norm CustID: 184100 + ID: 359113, OrderNo: 1125027996, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAV21WS, Qty: 60000, Date: 2025-09-08 + Norm PN: BAV21WS, Norm CustID: 184100 + ID: 359145, OrderNo: 1125028008, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ES1D, Qty: 25200, Date: 2025-09-08 + Norm PN: ES1D, Norm CustID: 184100 + ID: 359146, OrderNo: 1125028008, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: UF1010G, Qty: 40000, Date: 2025-09-08 + Norm PN: UF1010G, Norm CustID: 184100 + ID: 359195, OrderNo: 1125028039, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 108000, Date: 2025-09-09 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 359196, OrderNo: 1125028039, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 12000, Date: 2025-09-09 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 359619, OrderNo: 1125028285, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54WS, Qty: 75000, Date: 2025-09-10 + Norm PN: BAT54WS, Norm CustID: 184100 + ID: 360513, OrderNo: 1125028904, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5576A-AU, Qty: 2494, Date: 2025-09-15 + Norm PN: PJQ5576AAU, Norm CustID: 184100 + ID: 360514, OrderNo: 1125028904, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5576A-AU, Qty: 2499, Date: 2025-09-15 + Norm PN: PJQ5576AAU, Norm CustID: 184100 + ID: 360515, OrderNo: 1125028904, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5576A-AU, Qty: 2976, Date: 2025-09-15 + Norm PN: PJQ5576AAU, Norm CustID: 184100 + ID: 360516, OrderNo: 1125028904, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5576A-AU, Qty: 2252, Date: 2025-09-15 + Norm PN: PJQ5576AAU, Norm CustID: 184100 + ID: 360525, OrderNo: 1125028910, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 1N4148WS, Qty: 50000, Date: 2025-09-15 + Norm PN: 1N4148WS, Norm CustID: 184100 + ID: 360526, OrderNo: 1125028910, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BX310, Qty: 30600, Date: 2025-09-15 + Norm PN: BX310, Norm CustID: 184100 + ID: 360527, OrderNo: 1125028910, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BZT52-C16, Qty: 75000, Date: 2025-09-15 + Norm PN: BZT52C16, Norm CustID: 184100 + ID: 360528, OrderNo: 1125028910, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT2907A, Qty: 30000, Date: 2025-09-15 + Norm PN: MMBT2907A, Norm CustID: 184100 + ID: 360529, OrderNo: 1125028910, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 30000, Date: 2025-09-15 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 360530, OrderNo: 1125028910, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK36, Qty: 12000, Date: 2025-09-15 + Norm PN: SK36, Norm CustID: 184100 + ID: 360531, OrderNo: 1125028910, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM860VB, Qty: 15000, Date: 2025-09-15 + Norm PN: SVM860VB, Norm CustID: 184100 + ID: 360532, OrderNo: 1125028910, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS2100S, Qty: 11000, Date: 2025-09-15 + Norm PN: TS2100S, Norm CustID: 184100 + ID: 361767, OrderNo: 1125029652, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5576A-AU, Qty: 9000, Date: 2025-09-22 + Norm PN: PJQ5576AAU, Norm CustID: 184100 + ID: 361895, OrderNo: 1125029824, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ12A, Qty: 9000, Date: 2025-09-23 + Norm PN: P4SMAJ12A, Norm CustID: 184100 + ID: 361896, OrderNo: 1125029824, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ12A, Qty: 41400, Date: 2025-09-23 + Norm PN: P4SMAJ12A, Norm CustID: 184100 + ID: 361897, OrderNo: 1125029824, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ12A, Qty: 50400, Date: 2025-09-23 + Norm PN: P4SMAJ12A, Norm CustID: 184100 + ID: 362612, OrderNo: 1125030087, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SC4200VPC, Qty: 10000, Date: 2025-09-25 + Norm PN: SC4200VPC, Norm CustID: 184100 + ID: 362613, OrderNo: 1125030087, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SC4200VPC, Qty: 10000, Date: 2025-09-25 + Norm PN: SC4200VPC, Norm CustID: 184100 + ID: 362873, OrderNo: 1125030276, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB1200AH, Qty: 750000, Date: 2025-09-26 + Norm PN: SB1200AH, Norm CustID: 184100 + ID: 362876, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40WS, Qty: 50000, Date: 2025-09-26 + Norm PN: BAS40WS, Norm CustID: 184100 + ID: 362877, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MS120, Qty: 7200, Date: 2025-09-26 + Norm PN: MS120, Norm CustID: 184100 + ID: 362878, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ60CA, Qty: 12000, Date: 2025-09-26 + Norm PN: P6SMBJ60CA, Norm CustID: 184100 + ID: 362879, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 180000, Date: 2025-09-26 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 362880, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: RB720M-30, Qty: 80000, Date: 2025-09-26 + Norm PN: RB720M30, Norm CustID: 184100 + ID: 362881, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB1200AH, Qty: 1000000, Date: 2025-09-26 + Norm PN: SB1200AH, Norm CustID: 184100 + ID: 362882, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS14, Qty: 45000, Date: 2025-09-26 + Norm PN: SS14, Norm CustID: 184100 + ID: 362883, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS2060FL, Qty: 30000, Date: 2025-09-26 + Norm PN: SS2060FL, Norm CustID: 184100 + ID: 362884, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 15000, Date: 2025-09-26 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 362885, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 15000, Date: 2025-09-26 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 362886, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS1100S, Qty: 5000, Date: 2025-09-26 + Norm PN: TS1100S, Norm CustID: 184100 + ID: 362887, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54WS, Qty: 80000, Date: 2025-09-26 + Norm PN: BAT54WS, Norm CustID: 184100 + ID: 362888, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS2100S, Qty: 15000, Date: 2025-09-26 + Norm PN: TS2100S, Norm CustID: 184100 + ID: 362889, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT750, Qty: 30000, Date: 2025-09-26 + Norm PN: BAT750, Norm CustID: 184100 + ID: 362890, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAV21WS, Qty: 50000, Date: 2025-09-26 + Norm PN: BAV21WS, Norm CustID: 184100 + ID: 362891, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ES1D, Qty: 18000, Date: 2025-09-26 + Norm PN: ES1D, Norm CustID: 184100 + ID: 362892, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBD4148, Qty: 30000, Date: 2025-09-26 + Norm PN: MMBD4148, Norm CustID: 184100 + ID: 362893, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT2222A, Qty: 90000, Date: 2025-09-26 + Norm PN: MMBT2222A, Norm CustID: 184100 + ID: 362894, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 45000, Date: 2025-09-26 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 362895, OrderNo: 1125030278, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5228B, Qty: 30000, Date: 2025-09-26 + Norm PN: MMSZ5228B, Norm CustID: 184100 + ID: 362905, OrderNo: 1125030281, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER204, Qty: 30000, Date: 2025-09-26 + Norm PN: ER204, Norm CustID: 184100 + ID: 362906, OrderNo: 1125030281, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2025-09-26 + Norm PN: ER206, Norm CustID: 184100 + ID: 362907, OrderNo: 1125030281, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ES1D, Qty: 25200, Date: 2025-09-26 + Norm PN: ES1D, Norm CustID: 184100 + ID: 362908, OrderNo: 1125030281, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: UF1010G, Qty: 40000, Date: 2025-09-26 + Norm PN: UF1010G, Norm CustID: 184100 + ID: 363176, OrderNo: 1125030446, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54WS, Qty: 50000, Date: 2025-09-30 + Norm PN: BAT54WS, Norm CustID: 184100 + ID: 364385, OrderNo: 1125031058, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5221B, Qty: 27000, Date: 2025-10-07 + Norm PN: MMSZ5221B, Norm CustID: 184100 + ID: 364386, OrderNo: 1125031058, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5221B, Qty: 3000, Date: 2025-10-07 + Norm PN: MMSZ5221B, Norm CustID: 184100 + ID: 364387, OrderNo: 1125031058, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5228B, Qty: 6000, Date: 2025-10-07 + Norm PN: MMSZ5228B, Norm CustID: 184100 + ID: 364388, OrderNo: 1125031058, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5228B, Qty: 81000, Date: 2025-10-07 + Norm PN: MMSZ5228B, Norm CustID: 184100 + ID: 364389, OrderNo: 1125031058, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5228B, Qty: 3000, Date: 2025-10-07 + Norm PN: MMSZ5228B, Norm CustID: 184100 + ID: 364390, OrderNo: 1125031058, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5236B, Qty: 30000, Date: 2025-10-07 + Norm PN: MMSZ5236B, Norm CustID: 184100 + ID: 364391, OrderNo: 1125031058, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5245B, Qty: 120000, Date: 2025-10-07 + Norm PN: MMSZ5245B, Norm CustID: 184100 + ID: 364392, OrderNo: 1125031058, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5246B, Qty: 60000, Date: 2025-10-07 + Norm PN: MMSZ5246B, Norm CustID: 184100 + ID: 364393, OrderNo: 1125031059, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BR220, Qty: 18000, Date: 2025-10-07 + Norm PN: BR220, Norm CustID: 184100 + ID: 364394, OrderNo: 1125031059, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 14400, Date: 2025-10-07 + Norm PN: SS16, Norm CustID: 184100 + ID: 364395, OrderNo: 1125031059, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 14400, Date: 2025-10-07 + Norm PN: SS16, Norm CustID: 184100 + ID: 364417, OrderNo: 1125031061, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2025-10-07 + Norm PN: ER206, Norm CustID: 184100 + ID: 365416, OrderNo: 1125031564, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54BL, Qty: 30000, Date: 2025-10-13 + Norm PN: SK54BL, Norm CustID: 184100 + ID: 365417, OrderNo: 1125031564, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS1030FL, Qty: 30000, Date: 2025-10-13 + Norm PN: SS1030FL, Norm CustID: 184100 + ID: 365584, OrderNo: 1125031662, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5237B, Qty: 9000, Date: 2025-10-13 + Norm PN: MMSZ5237B, Norm CustID: 184100 + ID: 365585, OrderNo: 1125031662, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5252B, Qty: 15000, Date: 2025-10-13 + Norm PN: MMSZ5252B, Norm CustID: 184100 + ID: 365586, OrderNo: 1125031662, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3405, Qty: 18000, Date: 2025-10-13 + Norm PN: PJA3405, Norm CustID: 184100 + ID: 367127, OrderNo: 1125032442, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SC4200VPC, Qty: 10000, Date: 2025-10-20 + Norm PN: SC4200VPC, Norm CustID: 184100 + ID: 367128, OrderNo: 1125032442, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM860VB, Qty: 10000, Date: 2025-10-20 + Norm PN: SVM860VB, Norm CustID: 184100 + ID: 368935, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 2N7002K, Qty: 30000, Date: 2025-10-27 + Norm PN: 2N7002K, Norm CustID: 184100 + ID: 368936, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 360000, Date: 2025-10-27 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 368937, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ58A, Qty: 15000, Date: 2025-10-27 + Norm PN: P6SMBJ58A, Norm CustID: 184100 + ID: 368938, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ60CA, Qty: 9000, Date: 2025-10-27 + Norm PN: P6SMBJ60CA, Norm CustID: 184100 + ID: 368939, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3412, Qty: 501000, Date: 2025-10-27 + Norm PN: PJA3412, Norm CustID: 184100 + ID: 368940, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 540000, Date: 2025-10-27 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 368941, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 18000, Date: 2025-10-27 + Norm PN: S100, Norm CustID: 184100 + ID: 368942, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK36, Qty: 6000, Date: 2025-10-27 + Norm PN: SK36, Norm CustID: 184100 + ID: 368943, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54, Qty: 6000, Date: 2025-10-27 + Norm PN: SK54, Norm CustID: 184100 + ID: 368944, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS0540, Qty: 60000, Date: 2025-10-27 + Norm PN: SS0540, Norm CustID: 184100 + ID: 368945, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 450000, Date: 2025-10-27 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 368946, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54A, Qty: 60000, Date: 2025-10-27 + Norm PN: BAT54A, Norm CustID: 184100 + ID: 368947, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS2040FL, Qty: 180000, Date: 2025-10-27 + Norm PN: SS2040FL, Norm CustID: 184100 + ID: 368948, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 5000, Date: 2025-10-27 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 368949, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 5000, Date: 2025-10-27 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 368950, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS1100S, Qty: 5000, Date: 2025-10-27 + Norm PN: TS1100S, Norm CustID: 184100 + ID: 368951, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS2100S, Qty: 5000, Date: 2025-10-27 + Norm PN: TS2100S, Norm CustID: 184100 + ID: 368952, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54C, Qty: 150000, Date: 2025-10-27 + Norm PN: BAT54C, Norm CustID: 184100 + ID: 368953, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54WS, Qty: 50000, Date: 2025-10-27 + Norm PN: BAT54WS, Norm CustID: 184100 + ID: 368954, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAV21WS, Qty: 50000, Date: 2025-10-27 + Norm PN: BAV21WS, Norm CustID: 184100 + ID: 368955, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BX310, Qty: 7200, Date: 2025-10-27 + Norm PN: BX310, Norm CustID: 184100 + ID: 368956, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ES1D, Qty: 18000, Date: 2025-10-27 + Norm PN: ES1D, Norm CustID: 184100 + ID: 368957, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR20100DC, Qty: 6400, Date: 2025-10-27 + Norm PN: MBR20100DC, Norm CustID: 184100 + ID: 368958, OrderNo: 1125033490, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT2222A, Qty: 60000, Date: 2025-10-27 + Norm PN: MMBT2222A, Norm CustID: 184100 + ID: 369046, OrderNo: 1125033600, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3405, Qty: 18000, Date: 2025-10-28 + Norm PN: PJA3405, Norm CustID: 184100 + ID: 369976, OrderNo: 1125033862, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: LL4148, Qty: 25000, Date: 2025-10-29 + Norm PN: LL4148, Norm CustID: 184100 + ID: 370918, OrderNo: 1125034495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 1N4148W, Qty: 30000, Date: 2025-11-03 + Norm PN: 1N4148W, Norm CustID: 184100 + ID: 370919, OrderNo: 1125034495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54AW, Qty: 30000, Date: 2025-11-03 + Norm PN: BAT54AW, Norm CustID: 184100 + ID: 370920, OrderNo: 1125034495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS1030FL, Qty: 18000, Date: 2025-11-03 + Norm PN: SS1030FL, Norm CustID: 184100 + ID: 370921, OrderNo: 1125034495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS1030FL, Qty: 9000, Date: 2025-11-03 + Norm PN: SS1030FL, Norm CustID: 184100 + ID: 370922, OrderNo: 1125034495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS1030FL, Qty: 3000, Date: 2025-11-03 + Norm PN: SS1030FL, Norm CustID: 184100 + ID: 373254, OrderNo: 1125035718, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER204, Qty: 30000, Date: 2025-11-11 + Norm PN: ER204, Norm CustID: 184100 + ID: 373255, OrderNo: 1125035718, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2025-11-11 + Norm PN: ER206, Norm CustID: 184100 + ID: 373256, OrderNo: 1125035718, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ES1D, Qty: 30600, Date: 2025-11-11 + Norm PN: ES1D, Norm CustID: 184100 + ID: 373257, OrderNo: 1125035718, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ES1D, Qty: 30600, Date: 2025-11-11 + Norm PN: ES1D, Norm CustID: 184100 + ID: 373258, OrderNo: 1125035718, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: UF1010G, Qty: 40000, Date: 2025-11-11 + Norm PN: UF1010G, Norm CustID: 184100 + ID: 373259, OrderNo: 1125035718, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: UF1010G, Qty: 40000, Date: 2025-11-11 + Norm PN: UF1010G, Norm CustID: 184100 + ID: 373260, OrderNo: 1125035720, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 15SMC36CA, Qty: 100800, Date: 2025-11-11 + Norm PN: 15SMC36CA, Norm CustID: 184100 + ID: 373261, OrderNo: 1125035720, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR2100, Qty: 20000, Date: 2025-11-11 + Norm PN: MBR2100, Norm CustID: 184100 + ID: 373262, OrderNo: 1125035720, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SR24, Qty: 201600, Date: 2025-11-11 + Norm PN: SR24, Norm CustID: 184100 + ID: 373263, OrderNo: 1125035720, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SR24, Qty: 201600, Date: 2025-11-11 + Norm PN: SR24, Norm CustID: 184100 + ID: 373749, OrderNo: 1125035793, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5476AL-AU, Qty: 6000, Date: 2025-11-13 + Norm PN: PJQ5476ALAU, Norm CustID: 184100 + ID: 374895, OrderNo: 1125036400, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GS1M, Qty: 7200, Date: 2025-11-17 + Norm PN: GS1M, Norm CustID: 184100 + ID: 374896, OrderNo: 1125036400, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 27000, Date: 2025-11-17 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 374897, OrderNo: 1125036400, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 273600, Date: 2025-11-17 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 374898, OrderNo: 1125036400, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SBT12100VPC-AU, Qty: 10000, Date: 2025-11-17 + Norm PN: SBT12100VPCAU, Norm CustID: 184100 + ID: 374899, OrderNo: 1125036400, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK36, Qty: 18000, Date: 2025-11-17 + Norm PN: SK36, Norm CustID: 184100 + ID: 374900, OrderNo: 1125036400, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 25000, Date: 2025-11-17 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 374901, OrderNo: 1125036400, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3411, Qty: 30000, Date: 2025-11-17 + Norm PN: PJA3411, Norm CustID: 184100 + ID: 376698, OrderNo: 1125037494, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5576A-AU, Qty: 24000, Date: 2025-11-24 + Norm PN: PJQ5576AAU, Norm CustID: 184100 + ID: 376699, OrderNo: 1125037494, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 27000, Date: 2025-11-24 + Norm PN: S100, Norm CustID: 184100 + ID: 376700, OrderNo: 1125037494, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 66600, Date: 2025-11-24 + Norm PN: S100, Norm CustID: 184100 + ID: 376701, OrderNo: 1125037494, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 32400, Date: 2025-11-24 + Norm PN: S100, Norm CustID: 184100 + ID: 376702, OrderNo: 1125037494, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS1030FL, Qty: 30000, Date: 2025-11-24 + Norm PN: SS1030FL, Norm CustID: 184100 + ID: 376703, OrderNo: 1125037494, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SX34, Qty: 18000, Date: 2025-11-24 + Norm PN: SX34, Norm CustID: 184100 + ID: 376704, OrderNo: 1125037494, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 54000, Date: 2025-11-24 + Norm PN: S100, Norm CustID: 184100 + ID: 376705, OrderNo: 1125037495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR2045CT, Qty: 4000, Date: 2025-11-24 + Norm PN: MBR2045CT, Norm CustID: 184100 + ID: 376706, OrderNo: 1125037495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ24A, Qty: 3200, Date: 2025-11-24 + Norm PN: P6SMBJ24A, Norm CustID: 184100 + ID: 376707, OrderNo: 1125037495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJD30N04S-AU, Qty: 6000, Date: 2025-11-24 + Norm PN: PJD30N04SAU, Norm CustID: 184100 + ID: 376708, OrderNo: 1125037495, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SBM2045VCT, Qty: 8000, Date: 2025-11-24 + Norm PN: SBM2045VCT, Norm CustID: 184100 + ID: 376807, OrderNo: 1125037547, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER506, Qty: 10000, Date: 2025-11-24 + Norm PN: ER506, Norm CustID: 184100 + ID: 378928, OrderNo: 1125038683, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJD30N04S-AU, Qty: 2303, Date: 2025-12-02 + Norm PN: PJD30N04SAU, Norm CustID: 184100 + ID: 378929, OrderNo: 1125038683, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJD30N04S-AU, Qty: 1138, Date: 2025-12-02 + Norm PN: PJD30N04SAU, Norm CustID: 184100 + ID: 378932, OrderNo: 1125038699, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 35000, Date: 2025-12-02 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 380413, OrderNo: 1125039596, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB54AFC, Qty: 27000, Date: 2025-12-08 + Norm PN: SB54AFC, Norm CustID: 184100 + ID: 380414, OrderNo: 1125039601, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40WS, Qty: 120000, Date: 2025-12-08 + Norm PN: BAS40WS, Norm CustID: 184100 + ID: 380415, OrderNo: 1125039601, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54S, Qty: 600000, Date: 2025-12-08 + Norm PN: BAT54S, Norm CustID: 184100 + ID: 380416, OrderNo: 1125039601, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ60CA, Qty: 6000, Date: 2025-12-08 + Norm PN: P6SMBJ60CA, Norm CustID: 184100 + ID: 380417, OrderNo: 1125039601, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ60CA, Qty: 18000, Date: 2025-12-08 + Norm PN: P6SMBJ60CA, Norm CustID: 184100 + ID: 380913, OrderNo: 1125039848, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54TS, Qty: 600000, Date: 2025-12-09 + Norm PN: BAT54TS, Norm CustID: 184100 + ID: 382210, OrderNo: 1125040797, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 1N4148W, Qty: 30000, Date: 2025-12-15 + Norm PN: 1N4148W, Norm CustID: 184100 + ID: 382211, OrderNo: 1125040797, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54AW, Qty: 30000, Date: 2025-12-15 + Norm PN: BAT54AW, Norm CustID: 184100 + ID: 382212, OrderNo: 1125040797, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 300600, Date: 2025-12-15 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 382213, OrderNo: 1125040797, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 450000, Date: 2025-12-15 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 382214, OrderNo: 1125040797, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS1030FL, Qty: 30000, Date: 2025-12-15 + Norm PN: SS1030FL, Norm CustID: 184100 + ID: 382215, OrderNo: 1125040797, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 40000, Date: 2025-12-15 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 382218, OrderNo: 1125040799, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2025-12-15 + Norm PN: ER206, Norm CustID: 184100 + ID: 382219, OrderNo: 1125040799, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER304, Qty: 7500, Date: 2025-12-15 + Norm PN: ER304, Norm CustID: 184100 + ID: 382220, OrderNo: 1125040799, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER506, Qty: 10000, Date: 2025-12-15 + Norm PN: ER506, Norm CustID: 184100 + ID: 382221, OrderNo: 1125040799, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GS1M, Qty: 7200, Date: 2025-12-15 + Norm PN: GS1M, Norm CustID: 184100 + ID: 382222, OrderNo: 1125040799, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS14, Qty: 7200, Date: 2025-12-15 + Norm PN: SS14, Norm CustID: 184100 + ID: 382223, OrderNo: 1125040799, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 18000, Date: 2025-12-15 + Norm PN: SS16, Norm CustID: 184100 + ID: 382224, OrderNo: 1125040799, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 18000, Date: 2025-12-15 + Norm PN: SS16, Norm CustID: 184100 + ID: 382225, OrderNo: 1125040799, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 1N4148W, Qty: 99000, Date: 2025-12-15 + Norm PN: 1N4148W, Norm CustID: 184100 + ID: 382271, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GS1M, Qty: 7200, Date: 2025-12-16 + Norm PN: GS1M, Norm CustID: 184100 + ID: 382272, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS1100S, Qty: 4000, Date: 2025-12-16 + Norm PN: TS1100S, Norm CustID: 184100 + ID: 382273, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MB310, Qty: 30000, Date: 2025-12-16 + Norm PN: MB310, Norm CustID: 184100 + ID: 382274, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR20100DC, Qty: 4800, Date: 2025-12-16 + Norm PN: MBR20100DC, Norm CustID: 184100 + ID: 382275, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT3904, Qty: 300000, Date: 2025-12-16 + Norm PN: MMBT3904, Norm CustID: 184100 + ID: 382276, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 45000, Date: 2025-12-16 + Norm PN: S100, Norm CustID: 184100 + ID: 382277, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS14, Qty: 30600, Date: 2025-12-16 + Norm PN: SS14, Norm CustID: 184100 + ID: 382278, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 20000, Date: 2025-12-16 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 382279, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM860VB, Qty: 10000, Date: 2025-12-16 + Norm PN: SVM860VB, Norm CustID: 184100 + ID: 382280, OrderNo: 1125040871, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SX34, Qty: 18000, Date: 2025-12-16 + Norm PN: SX34, Norm CustID: 184100 + ID: 382360, OrderNo: 1125040955, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5231B, Qty: 30000, Date: 2025-12-16 + Norm PN: MMSZ5231B, Norm CustID: 184100 + ID: 382361, OrderNo: 1125040955, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5242B, Qty: 30000, Date: 2025-12-16 + Norm PN: MMSZ5242B, Norm CustID: 184100 + ID: 383927, OrderNo: 1125041640, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 15SMC33A, Qty: 1600, Date: 2025-12-22 + Norm PN: 15SMC33A, Norm CustID: 184100 + ID: 384000, OrderNo: 1125041692, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 15SMC33A, Qty: 800, Date: 2025-12-22 + Norm PN: 15SMC33A, Norm CustID: 184100 + ID: 384001, OrderNo: 1125041692, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 15SMC33A, Qty: 1600, Date: 2025-12-22 + Norm PN: 15SMC33A, Norm CustID: 184100 + ID: 384215, OrderNo: 1125041925, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2025-12-23 + Norm PN: ER206, Norm CustID: 184100 + ID: 384216, OrderNo: 1125041925, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER304, Qty: 7500, Date: 2025-12-23 + Norm PN: ER304, Norm CustID: 184100 + ID: 384217, OrderNo: 1125041925, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER506, Qty: 10000, Date: 2025-12-23 + Norm PN: ER506, Norm CustID: 184100 + ID: 384218, OrderNo: 1125041925, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GS1M, Qty: 7200, Date: 2025-12-23 + Norm PN: GS1M, Norm CustID: 184100 + ID: 384219, OrderNo: 1125041925, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS14, Qty: 7200, Date: 2025-12-23 + Norm PN: SS14, Norm CustID: 184100 + ID: 384220, OrderNo: 1125041925, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 18000, Date: 2025-12-23 + Norm PN: SS16, Norm CustID: 184100 + ID: 384221, OrderNo: 1125041925, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 18000, Date: 2025-12-23 + Norm PN: SS16, Norm CustID: 184100 + ID: 384248, OrderNo: 1125041971, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54A, Qty: 501000, Date: 2025-12-23 + Norm PN: BAT54A, Norm CustID: 184100 + ID: 384249, OrderNo: 1125041971, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54TS, Qty: 5000000, Date: 2025-12-23 + Norm PN: BAT54TS, Norm CustID: 184100 + ID: 384250, OrderNo: 1125041971, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54TS, Qty: 8000000, Date: 2025-12-23 + Norm PN: BAT54TS, Norm CustID: 184100 + ID: 384251, OrderNo: 1125041971, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54TS, Qty: 10000000, Date: 2025-12-23 + Norm PN: BAT54TS, Norm CustID: 184100 + ID: 384399, OrderNo: 1125042041, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBZ5226B, Qty: 30000, Date: 2025-12-24 + Norm PN: MMBZ5226B, Norm CustID: 184100 + ID: 385289, OrderNo: 1125042503, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB54AFC, Qty: 30000, Date: 2025-12-29 + Norm PN: SB54AFC, Norm CustID: 184100 + ID: 385290, OrderNo: 1125042504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ58A, Qty: 30000, Date: 2025-12-29 + Norm PN: P6SMBJ58A, Norm CustID: 184100 + ID: 385291, OrderNo: 1125042504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PDZ56B, Qty: 50000, Date: 2025-12-29 + Norm PN: PDZ56B, Norm CustID: 184100 + ID: 385292, OrderNo: 1125042504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK36, Qty: 9000, Date: 2025-12-29 + Norm PN: SK36, Norm CustID: 184100 + ID: 385293, OrderNo: 1125042504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54BL, Qty: 30000, Date: 2025-12-29 + Norm PN: SK54BL, Norm CustID: 184100 + ID: 385294, OrderNo: 1125042504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 150000, Date: 2025-12-29 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 385295, OrderNo: 1125042504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS2040FL, Qty: 300000, Date: 2025-12-29 + Norm PN: SS2040FL, Norm CustID: 184100 + ID: 385296, OrderNo: 1125042504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 20000, Date: 2025-12-29 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 385297, OrderNo: 1125042504, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1545LB, Qty: 10000, Date: 2025-12-29 + Norm PN: SVM1545LB, Norm CustID: 184100 + ID: 387025, OrderNo: 1126000612, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR10H150PC-AU, Qty: 10000, Date: 2026-01-07 + Norm PN: MBR10H150PCAU, Norm CustID: 184100 + ID: 387037, OrderNo: 1126000622, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 2SD1781A, Qty: 3000, Date: 2026-01-07 + Norm PN: 2SD1781A, Norm CustID: 184100 + ID: 387038, OrderNo: 1126000622, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5245B, Qty: 3000, Date: 2026-01-07 + Norm PN: MMSZ5245B, Norm CustID: 184100 + ID: 387039, OrderNo: 1126000622, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5248B, Qty: 3000, Date: 2026-01-07 + Norm PN: MMSZ5248B, Norm CustID: 184100 + ID: 387040, OrderNo: 1126000622, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PG2010, Qty: 3000, Date: 2026-01-07 + Norm PN: PG2010, Norm CustID: 184100 + ID: 387041, OrderNo: 1126000622, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PSDF3060L1, Qty: 2000, Date: 2026-01-07 + Norm PN: PSDF3060L1, Norm CustID: 184100 + ID: 387157, OrderNo: 1126000738, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 100800, Date: 2026-01-08 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 387597, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 15SMC33A, Qty: 49600, Date: 2026-01-12 + Norm PN: 15SMC33A, Norm CustID: 184100 + ID: 387598, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GBU2510, Qty: 10000, Date: 2026-01-12 + Norm PN: GBU2510, Norm CustID: 184100 + ID: 387599, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GS1M, Qty: 10800, Date: 2026-01-12 + Norm PN: GS1M, Norm CustID: 184100 + ID: 387600, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MB310, Qty: 30000, Date: 2026-01-12 + Norm PN: MB310, Norm CustID: 184100 + ID: 387601, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR20100DC, Qty: 16000, Date: 2026-01-12 + Norm PN: MBR20100DC, Norm CustID: 184100 + ID: 387602, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR20100DC, Qty: 6400, Date: 2026-01-12 + Norm PN: MBR20100DC, Norm CustID: 184100 + ID: 387603, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT2907A, Qty: 30000, Date: 2026-01-12 + Norm PN: MMBT2907A, Norm CustID: 184100 + ID: 387604, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT3904, Qty: 1200000, Date: 2026-01-12 + Norm PN: MMBT3904, Norm CustID: 184100 + ID: 387605, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT3904, Qty: 1200000, Date: 2026-01-12 + Norm PN: MMBT3904, Norm CustID: 184100 + ID: 387606, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ58A, Qty: 75000, Date: 2026-01-12 + Norm PN: P6SMBJ58A, Norm CustID: 184100 + ID: 387607, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ60CA, Qty: 15000, Date: 2026-01-12 + Norm PN: P6SMBJ60CA, Norm CustID: 184100 + ID: 387608, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 1N4148W, Qty: 150000, Date: 2026-01-12 + Norm PN: 1N4148W, Norm CustID: 184100 + ID: 387609, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3412, Qty: 540000, Date: 2026-01-12 + Norm PN: PJA3412, Norm CustID: 184100 + ID: 387610, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3412, Qty: 651000, Date: 2026-01-12 + Norm PN: PJA3412, Norm CustID: 184100 + ID: 387611, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3432, Qty: 501000, Date: 2026-01-12 + Norm PN: PJA3432, Norm CustID: 184100 + ID: 387612, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3432, Qty: 450000, Date: 2026-01-12 + Norm PN: PJA3432, Norm CustID: 184100 + ID: 387613, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3432, Qty: 450000, Date: 2026-01-12 + Norm PN: PJA3432, Norm CustID: 184100 + ID: 387614, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 600000, Date: 2026-01-12 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 387615, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 360000, Date: 2026-01-12 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 387616, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 81000, Date: 2026-01-12 + Norm PN: S100, Norm CustID: 184100 + ID: 387617, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: S100, Qty: 108000, Date: 2026-01-12 + Norm PN: S100, Norm CustID: 184100 + ID: 387618, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB1200AH, Qty: 1500000, Date: 2026-01-12 + Norm PN: SB1200AH, Norm CustID: 184100 + ID: 387619, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 2N7002K, Qty: 120000, Date: 2026-01-12 + Norm PN: 2N7002K, Norm CustID: 184100 + ID: 387620, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SB1200AH, Qty: 1000000, Date: 2026-01-12 + Norm PN: SB1200AH, Norm CustID: 184100 + ID: 387621, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK36, Qty: 15000, Date: 2026-01-12 + Norm PN: SK36, Norm CustID: 184100 + ID: 387622, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS0540, Qty: 150000, Date: 2026-01-12 + Norm PN: SS0540, Norm CustID: 184100 + ID: 387623, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS0540, Qty: 150000, Date: 2026-01-12 + Norm PN: SS0540, Norm CustID: 184100 + ID: 387624, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 600000, Date: 2026-01-12 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 387625, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 450000, Date: 2026-01-12 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 387626, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS14, Qty: 30600, Date: 2026-01-12 + Norm PN: SS14, Norm CustID: 184100 + ID: 387627, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS2040FL, Qty: 360000, Date: 2026-01-12 + Norm PN: SS2040FL, Norm CustID: 184100 + ID: 387628, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS2040FL, Qty: 300000, Date: 2026-01-12 + Norm PN: SS2040FL, Norm CustID: 184100 + ID: 387629, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 60000, Date: 2026-01-12 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 387630, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 2N7002K, Qty: 120000, Date: 2026-01-12 + Norm PN: 2N7002K, Norm CustID: 184100 + ID: 387631, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SVM1045VB, Qty: 30000, Date: 2026-01-12 + Norm PN: SVM1045VB, Norm CustID: 184100 + ID: 387632, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SX34, Qty: 43200, Date: 2026-01-12 + Norm PN: SX34, Norm CustID: 184100 + ID: 387633, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS2100S, Qty: 6000, Date: 2026-01-12 + Norm PN: TS2100S, Norm CustID: 184100 + ID: 387634, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54A, Qty: 501000, Date: 2026-01-12 + Norm PN: BAT54A, Norm CustID: 184100 + ID: 387635, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54C, Qty: 300000, Date: 2026-01-12 + Norm PN: BAT54C, Norm CustID: 184100 + ID: 387636, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54C, Qty: 201000, Date: 2026-01-12 + Norm PN: BAT54C, Norm CustID: 184100 + ID: 387637, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54WS, Qty: 120000, Date: 2026-01-12 + Norm PN: BAT54WS, Norm CustID: 184100 + ID: 387638, OrderNo: 1126001107, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BR210, Qty: 120600, Date: 2026-01-12 + Norm PN: BR210, Norm CustID: 184100 + ID: 388166, OrderNo: 1126001411, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR10H150PC-AU, Qty: 20000, Date: 2026-01-13 + Norm PN: MBR10H150PCAU, Norm CustID: 184100 + ID: 388167, OrderNo: 1126001411, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MBR10H150PC-AU, Qty: 10000, Date: 2026-01-13 + Norm PN: MBR10H150PCAU, Norm CustID: 184100 + ID: 388168, OrderNo: 1126001411, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJQ5576A-AU, Qty: 30000, Date: 2026-01-13 + Norm PN: PJQ5576AAU, Norm CustID: 184100 + ID: 388169, OrderNo: 1126001411, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SBT12100VPC-AU, Qty: 10000, Date: 2026-01-13 + Norm PN: SBT12100VPCAU, Norm CustID: 184100 + ID: 388170, OrderNo: 1126001411, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SBT12100VPC-AU, Qty: 10000, Date: 2026-01-13 + Norm PN: SBT12100VPCAU, Norm CustID: 184100 + ID: 388171, OrderNo: 1126001411, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SC4200VPC, Qty: 10000, Date: 2026-01-13 + Norm PN: SC4200VPC, Norm CustID: 184100 + ID: 388172, OrderNo: 1126001411, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SC4200VPC, Qty: 10000, Date: 2026-01-13 + Norm PN: SC4200VPC, Norm CustID: 184100 + ID: 389183, OrderNo: 1126002021, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SBA120CS, Qty: 50000, Date: 2026-01-19 + Norm PN: SBA120CS, Norm CustID: 184100 + ID: 389540, OrderNo: 1126002403, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PG2010, Qty: 3000, Date: 2026-01-20 + Norm PN: PG2010, Norm CustID: 184100 + ID: 389541, OrderNo: 1126002404, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BX320, Qty: 9000, Date: 2026-01-20 + Norm PN: BX320, Norm CustID: 184100 + ID: 389542, OrderNo: 1126002404, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3474S, Qty: 30000, Date: 2026-01-20 + Norm PN: PJA3474S, Norm CustID: 184100 + ID: 389543, OrderNo: 1126002404, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK36, Qty: 18000, Date: 2026-01-20 + Norm PN: SK36, Norm CustID: 184100 + ID: 389544, OrderNo: 1126002404, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 300000, Date: 2026-01-20 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 389545, OrderNo: 1126002404, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54, Qty: 9000, Date: 2026-01-20 + Norm PN: SK54, Norm CustID: 184100 + ID: 390835, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2026-01-26 + Norm PN: ER206, Norm CustID: 184100 + ID: 390836, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2026-01-26 + Norm PN: ER206, Norm CustID: 184100 + ID: 390837, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER206, Qty: 120000, Date: 2026-01-26 + Norm PN: ER206, Norm CustID: 184100 + ID: 390838, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ER304, Qty: 7500, Date: 2026-01-26 + Norm PN: ER304, Norm CustID: 184100 + ID: 390839, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GS1M, Qty: 7200, Date: 2026-01-26 + Norm PN: GS1M, Norm CustID: 184100 + ID: 390840, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: GS1M, Qty: 7200, Date: 2026-01-26 + Norm PN: GS1M, Norm CustID: 184100 + ID: 390841, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 18000, Date: 2026-01-26 + Norm PN: SS16, Norm CustID: 184100 + ID: 390842, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 18000, Date: 2026-01-26 + Norm PN: SS16, Norm CustID: 184100 + ID: 390843, OrderNo: 1126003512, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS16, Qty: 18000, Date: 2026-01-26 + Norm PN: SS16, Norm CustID: 184100 + ID: 390845, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 15SMCJ12A, Qty: 120000, Date: 2026-01-26 + Norm PN: 15SMCJ12A, Norm CustID: 184100 + ID: 390846, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54TS, Qty: 1200000, Date: 2026-01-26 + Norm PN: BAT54TS, Norm CustID: 184100 + ID: 390847, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAV21WS, Qty: 50000, Date: 2026-01-26 + Norm PN: BAV21WS, Norm CustID: 184100 + ID: 390848, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BR210, Qty: 90000, Date: 2026-01-26 + Norm PN: BR210, Norm CustID: 184100 + ID: 390849, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: ES1D, Qty: 18000, Date: 2026-01-26 + Norm PN: ES1D, Norm CustID: 184100 + ID: 390850, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT2222A, Qty: 60000, Date: 2026-01-26 + Norm PN: MMBT2222A, Norm CustID: 184100 + ID: 390851, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMBT3904, Qty: 90000, Date: 2026-01-26 + Norm PN: MMBT3904, Norm CustID: 184100 + ID: 390852, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 30000, Date: 2026-01-26 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 390853, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5226B, Qty: 30000, Date: 2026-01-26 + Norm PN: MMSZ5226B, Norm CustID: 184100 + ID: 390854, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5231B, Qty: 30000, Date: 2026-01-26 + Norm PN: MMSZ5231B, Norm CustID: 184100 + ID: 390855, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5235B, Qty: 30000, Date: 2026-01-26 + Norm PN: MMSZ5235B, Norm CustID: 184100 + ID: 390856, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 1N4148W, Qty: 51000, Date: 2026-01-26 + Norm PN: 1N4148W, Norm CustID: 184100 + ID: 390857, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5236B, Qty: 60000, Date: 2026-01-26 + Norm PN: MMSZ5236B, Norm CustID: 184100 + ID: 390858, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: MMSZ5245B, Qty: 150000, Date: 2026-01-26 + Norm PN: MMSZ5245B, Norm CustID: 184100 + ID: 390859, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P4SMAJ58A, Qty: 360000, Date: 2026-01-26 + Norm PN: P4SMAJ58A, Norm CustID: 184100 + ID: 390860, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: P6SMBJ60CA, Qty: 12000, Date: 2026-01-26 + Norm PN: P6SMBJ60CA, Norm CustID: 184100 + ID: 390861, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3412, Qty: 501000, Date: 2026-01-26 + Norm PN: PJA3412, Norm CustID: 184100 + ID: 390862, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJA3412, Qty: 300000, Date: 2026-01-26 + Norm PN: PJA3412, Norm CustID: 184100 + ID: 390863, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJF12N65M, Qty: 2000, Date: 2026-01-26 + Norm PN: PJF12N65M, Norm CustID: 184100 + ID: 390864, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJMBZ6V2, Qty: 660000, Date: 2026-01-26 + Norm PN: PJMBZ6V2, Norm CustID: 184100 + ID: 390865, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54, Qty: 9000, Date: 2026-01-26 + Norm PN: SK54, Norm CustID: 184100 + ID: 390866, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SK54BL, Qty: 15000, Date: 2026-01-26 + Norm PN: SK54BL, Norm CustID: 184100 + ID: 390867, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 1N4148W, Qty: 30000, Date: 2026-01-26 + Norm PN: 1N4148W, Norm CustID: 184100 + ID: 390868, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS0540, Qty: 120000, Date: 2026-01-26 + Norm PN: SS0540, Norm CustID: 184100 + ID: 390869, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 300000, Date: 2026-01-26 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 390870, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 600000, Date: 2026-01-26 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 390871, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS10100FL, Qty: 750000, Date: 2026-01-26 + Norm PN: SS10100FL, Norm CustID: 184100 + ID: 390872, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: SS14, Qty: 30600, Date: 2026-01-26 + Norm PN: SS14, Norm CustID: 184100 + ID: 390873, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS1100S, Qty: 7000, Date: 2026-01-26 + Norm PN: TS1100S, Norm CustID: 184100 + ID: 390874, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: TS2100S, Qty: 10000, Date: 2026-01-26 + Norm PN: TS2100S, Norm CustID: 184100 + ID: 390875, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: 2N7002K, Qty: 120000, Date: 2026-01-26 + Norm PN: 2N7002K, Norm CustID: 184100 + ID: 390876, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAS40WS, Qty: 50000, Date: 2026-01-26 + Norm PN: BAS40WS, Norm CustID: 184100 + ID: 390877, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54A, Qty: 501000, Date: 2026-01-26 + Norm PN: BAT54A, Norm CustID: 184100 + ID: 390878, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54C, Qty: 120000, Date: 2026-01-26 + Norm PN: BAT54C, Norm CustID: 184100 + ID: 390879, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54TS, Qty: 1200000, Date: 2026-01-26 + Norm PN: BAT54TS, Norm CustID: 184100 + ID: 390880, OrderNo: 1126003519, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: BAT54TS, Qty: 1200000, Date: 2026-01-26 + Norm PN: BAT54TS, Norm CustID: 184100 + ID: 391006, OrderNo: 1126003645, Cust: 瀚碩科技股份有限公司, CustID: 184100, PN: PJL9580, Qty: 30000, Date: 2026-01-27 + Norm PN: PJL9580, Norm CustID: 184100 + +Simulating matching for DIT 78136 (OP0000022509)... + +Simulating matching for DIT 78235 (OP0000022496)... + +Found 0 existing MatchResults for these DITs: diff --git a/backend/debug_matching.py b/backend/debug_matching.py new file mode 100644 index 0000000..ff624fd --- /dev/null +++ b/backend/debug_matching.py @@ -0,0 +1,49 @@ +from app.routers.lab import fetch_orders_light, normalize_id, normalize_customer_name, normalize_pn_for_matching +from app.models import get_db + +db = next(get_db()) + +target_cust_name = "緯創資通" +target_id = "177100" +target_pn = "PEC3205M1QR100201" + +print(f"Targeting: Name={target_cust_name}, ID={target_id}, PN={target_pn}") + +print("Fetching orders...") +orders = fetch_orders_light(db, start_date="2025-01-01") + +found_cust = 0 +found_id = 0 +found_pn = 0 + +matching_orders = [] + +for o in orders: + n_name = o['norm_cust_name'] + c_id = o['clean_cust_id'] + c_pn = o['clean_pn'] + + if target_cust_name in n_name: + found_cust += 1 + if target_id == c_id: + found_id += 1 + if target_pn == c_pn: + found_pn += 1 + print("!!! PN MATCH FOUND !!!") + print(f"Order Cust: '{o['customer']}'") + print(f"Order Norm Cust: '{o['norm_cust_name']}'") + print(f"Order ID: '{o['cust_id']}'") + print("Full Order:", o) + + if target_id == c_id and target_pn == c_pn: + matching_orders.append(o) + elif target_cust_name == n_name and target_pn == c_pn: + matching_orders.append(o) + +print(f"Orders with similar Name: {found_cust}") +print(f"Orders with Exact ID: {found_id}") +print(f"Orders with Exact PN: {found_pn}") +print(f"Exact Matches Found in List: {len(matching_orders)}") + +if matching_orders: + print("Example Match:", matching_orders[0]) diff --git a/backend/debug_matching_issue.py b/backend/debug_matching_issue.py new file mode 100644 index 0000000..957d4bd --- /dev/null +++ b/backend/debug_matching_issue.py @@ -0,0 +1,80 @@ +from app.models import SessionLocal, OrderRecord, DitRecord, MatchResult +from app.services.fuzzy_matcher import FuzzyMatcher, normalize_pn_for_matching, normalize_customer_name, normalize_id +import pandas as pd +import sys + +def debug_specific_match(): + db = SessionLocal() + with open("debug_match_output.txt", "w", encoding="utf-8") as f: + try: + target_pn = "PJQ5514S6C-AU R2 002A1" + target_norm_pn = normalize_pn_for_matching(target_pn) + target_erp = "184100" + + f.write(f"Target Norm PN: {target_norm_pn}\n") + f.write(f"Target ERP: {target_erp}\n") + + # 1. Check DIT Records + dits = db.query(DitRecord).all() + matching_dits = [] + for d in dits: + d_norm_pn = normalize_pn_for_matching(d.pn) + d_erp = normalize_id(d.erp_account) + if d_norm_pn == target_norm_pn or d_erp == target_erp: + matching_dits.append(d) + + f.write(f"\nFound {len(matching_dits)} DIT records matching PN or ERP:\n") + for d in matching_dits: + f.write(f" ID: {d.id}, Op: {d.op_id}, Cust: {d.customer}, ERP: {d.erp_account}, PN: {d.pn}, Date: {d.date}\n") + f.write(f" Norm PN: {normalize_pn_for_matching(d.pn)}, Norm ERP: {normalize_id(d.erp_account)}\n") + + # 2. Check Order Records + orders = db.query(OrderRecord).all() + matching_orders = [] + for o in orders: + o_norm_pn = normalize_pn_for_matching(o.pn) + o_cust_id = normalize_id(o.cust_id) + if o_norm_pn == target_norm_pn or o_cust_id == target_erp: + matching_orders.append(o) + + f.write(f"\nFound {len(matching_orders)} Order records matching PN or ERP:\n") + for o in matching_orders: + f.write(f" ID: {o.id}, OrderNo: {o.order_no}, Cust: {o.customer}, CustID: {o.cust_id}, PN: {o.pn}, Qty: {o.qty}, Date: {o.date}\n") + f.write(f" Norm PN: {normalize_pn_for_matching(o.pn)}, Norm CustID: {normalize_id(o.cust_id)}\n") + + # 3. Simulate Matching + if matching_dits: + for dit in matching_dits: + f.write(f"\nSimulating matching for DIT {dit.id} ({dit.op_id})...\n") + + dit_norm_pn = normalize_pn_for_matching(dit.pn) + dit_erp = normalize_id(dit.erp_account) + + for o in matching_orders: + o_norm_pn = normalize_pn_for_matching(o.pn) + o_cust_id = normalize_id(o.cust_id) + + if dit_norm_pn == o_norm_pn: + f.write(f" PN Match with Order {o.id} ({o.order_no})\n") + if dit_erp and o_cust_id and dit_erp == o_cust_id: + f.write(f" SUCCESS: Silver Key Match found!\n") + else: + from app.services.fuzzy_matcher import calculate_similarity + score, reason = calculate_similarity(dit.customer, o.customer) + f.write(f" Name Similarity with {o.customer}: {score} ({reason})\n") + # else: + # f.write(f" PN Mismatch: DIT({dit_norm_pn}) != Order({o_norm_pn})\n") + + # 4. Check existing MatchResults + if matching_dits: + dit_ids = [d.id for d in matching_dits] + matches = db.query(MatchResult).filter(MatchResult.dit_id.in_(dit_ids)).all() + f.write(f"\nFound {len(matches)} existing MatchResults for these DITs:\n") + for m in matches: + f.write(f" DIT: {m.dit_id}, Target: {m.target_type} {m.target_id}, Score: {m.score}, Status: {m.status}, Priority: {m.match_priority}\n") + + finally: + db.close() + +if __name__ == "__main__": + debug_specific_match() diff --git a/backend/mapping_out.txt b/backend/mapping_out.txt new file mode 100644 index 0000000..e0ef48d Binary files /dev/null and b/backend/mapping_out.txt differ diff --git a/backend/search_op.py b/backend/search_op.py new file mode 100644 index 0000000..20dc796 --- /dev/null +++ b/backend/search_op.py @@ -0,0 +1,22 @@ +from app.models import SessionLocal, DitRecord, OrderRecord +from app.services.fuzzy_matcher import normalize_pn_for_matching, normalize_id + +db = SessionLocal() +try: + print("--- Searching DITs for Op: OP0000022509 ---") + dit = db.query(DitRecord).filter(DitRecord.op_id == 'OP0000022509').first() + if dit: + print(f"DIT Found: ID={dit.id}, Op={dit.op_id}, ERP={dit.erp_account}, PN={dit.pn}, Cust={dit.customer}") + print(f" Normalized ERP: {normalize_id(dit.erp_account)}") + else: + print("DIT OP0000022509 not found!") + # List first 10 DITs + print("\nFirst 10 DITs in DB:") + dits = db.query(DitRecord).limit(10).all() + for d in dits: + print(f" Op={d.op_id}, ERP={d.erp_account}, PN={d.pn}") + +except Exception as e: + print(f"Error: {e}") +finally: + db.close() diff --git a/backend/search_records.py b/backend/search_records.py new file mode 100644 index 0000000..526e15f --- /dev/null +++ b/backend/search_records.py @@ -0,0 +1,21 @@ +from app.models import SessionLocal, DitRecord, OrderRecord +from app.services.fuzzy_matcher import normalize_pn_for_matching, normalize_id + +db = SessionLocal() +try: + print("--- Searching DITs for 瀚碩 ---") + dits = db.query(DitRecord).filter(DitRecord.customer.like('%瀚碩%')).all() + print(f"Total DITs for 瀚碩: {len(dits)}") + for d in dits: + print(f" ID: {d.id}, Op: {d.op_id}, ERP: {d.erp_account}, PN: {d.pn}, Cust: {d.customer}") + + print("\n--- Searching Orders for 184100 or 瀚碩 ---") + orders = db.query(OrderRecord).filter((OrderRecord.cust_id == '184100') | (OrderRecord.customer.like('%瀚碩%'))).all() + print(f"Total Orders for 184100 or 瀚碩: {len(orders)}") + for o in orders: + print(f" ID: {o.id}, OrderNo: {o.order_no}, CustID: {o.cust_id}, PN: {o.pn}, Cust: {o.customer}") + +except Exception as e: + print(f"Error: {e}") +finally: + db.close() diff --git a/backend/test_fuzzy_lookup.py b/backend/test_fuzzy_lookup.py new file mode 100644 index 0000000..388294d --- /dev/null +++ b/backend/test_fuzzy_lookup.py @@ -0,0 +1,61 @@ +from app.routers.lab import fetch_orders_light, fetch_samples_light +from app.models import get_db +import time + +db = next(get_db()) + +print("Fetching data...") +orders = fetch_orders_light(db, start_date="2025-01-01") +samples = fetch_samples_light(db, start_date="2025-01-01") + +unique_order_custs = {} +for o in orders: + n = o['norm_cust_name'] + if n not in unique_order_custs: + unique_order_custs[n] = [] + unique_order_custs[n].append(o) + +print(f"Total Orders: {len(orders)}") +print(f"Unique Order Customers: {len(unique_order_custs)}") +print(f"Total Samples: {len(samples)}") + +start_time = time.time() +matches_found = 0 + +for s in samples: + s_name = s['norm_cust_name'] + s_pn = s['clean_pn'] + + # Fuzzy Lookup + candidates = [] + + # Optimization: Only check if s_name is NOT in dict (Exact match handled elsewhere) + if s_name in unique_order_custs: + candidates.extend(unique_order_custs[s_name]) + else: + # Fallback: Scan all keys + for o_name in unique_order_custs.keys(): + if len(o_name) < 2: continue # Skip too short + + # Check containment + if o_name in s_name or s_name in o_name: + candidates.extend(unique_order_custs[o_name]) + + # Filter by PN and Date + final_matches = [] + + s_date = s['date'] if s['date'] else None + + for c in candidates: + if c['clean_pn'] == s_pn or (s_pn and c['clean_pn'] and s_pn.startswith(c['clean_pn'])): + if s_date and c['date'] and c['date'] >= s_date: + final_matches.append(c) + + if final_matches: + matches_found += 1 + print(f"Match: {s['customer']} -> {final_matches[0]['customer']}") + print(f"Dates: Sample {s_date} <= Order {final_matches[0]['date']}") + +end_time = time.time() +print(f"Loop Match Time: {end_time - start_time:.4f}s") +print(f"Matches Found: {matches_found}") diff --git a/backend/test_mapping.py b/backend/test_mapping.py new file mode 100644 index 0000000..d0a736f --- /dev/null +++ b/backend/test_mapping.py @@ -0,0 +1,24 @@ +import sys +import os +import pandas as pd +from pathlib import Path + +# Add the current directory and its parent to the path +current_dir = Path.cwd() +sys.path.append(str(current_dir)) + +from app.services.excel_parser import excel_parser + +def test_mapping(): + # Case 1: Sample mapping + df_sample = pd.DataFrame(columns=['Item', 'Type', '索樣數量PCS', '日期']) + mapping_sample = excel_parser.map_columns(df_sample, 'sample') + print(f"Sample Mapping: {mapping_sample}") + + # Case 2: Order mapping + df_order = pd.DataFrame(columns=['客戶', '客戶代號', '訂單號碼', '內部料號', '訂單日期', '訂單需求量', '台幣金額', '明細行狀態']) + mapping_order = excel_parser.map_columns(df_order, 'order') + print(f"Order Mapping: {mapping_order}") + +if __name__ == "__main__": + test_mapping() diff --git a/backend/verify_all_api.py b/backend/verify_all_api.py new file mode 100644 index 0000000..213d0f4 --- /dev/null +++ b/backend/verify_all_api.py @@ -0,0 +1,24 @@ +import requests + +endpoints = [ + "/api/lab/kpi", + "/api/lab/scatter", + "/api/lab/orphans", + "/api/lab/no_dit_samples", + "/api/lab/high_qty_no_order_samples", + "/api/lab/conversions" +] + +for ep in endpoints: + try: + url = f"http://localhost:8001{ep}" + print(f"Requesting {url}...") + resp = requests.get(url, timeout=10) + print(f" Status: {resp.status_code}") + if resp.status_code == 200: + data = resp.json() + print(f" Count/Result: {len(data) if isinstance(data, list) else 'Object'}") + else: + print(f" Error Body: {resp.text}") + except Exception as e: + print(f" Exception: {e}") diff --git a/backend/verify_api_http.py b/backend/verify_api_http.py new file mode 100644 index 0000000..41fdf3e --- /dev/null +++ b/backend/verify_api_http.py @@ -0,0 +1,10 @@ +import requests + +try: + url = "http://localhost:8001/api/lab/kpi" + print(f"Requesting {url}...") + resp = requests.get(url, timeout=5) + print("Status Code:", resp.status_code) + print("Response JSON:", resp.json()) +except Exception as e: + print("Error:", e) diff --git a/backend/verify_perf.py b/backend/verify_perf.py new file mode 100644 index 0000000..c733f67 --- /dev/null +++ b/backend/verify_perf.py @@ -0,0 +1,24 @@ +import requests +import time + +def test_endpoint(url): + print(f"Testing {url}...") + start = time.time() + try: + resp = requests.get(url) + print(f"Status: {resp.status_code}") + if resp.status_code == 200: + print("Success!") + # print(resp.json()) # Verbose + else: + print(f"Error: {resp.text}") + except Exception as e: + print(f"Exception: {e}") + end = time.time() + print(f"Time taken: {end - start:.4f} seconds\n") + +if __name__ == "__main__": + base = "http://localhost:8000/api/lab" + test_endpoint(f"{base}/kpi") + test_endpoint(f"{base}/conversions") + test_endpoint(f"{base}/scatter") diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 628397d..294f45a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,25 +8,49 @@ "name": "sales-pipeline-frontend", "version": "1.0.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@headlessui/react": "^2.2.9", + "@heroicons/react": "^2.2.0", + "@mui/icons-material": "^7.3.7", + "@mui/material": "^7.3.7", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toast": "^1.2.15", "@tanstack/react-query": "^5.8.0", + "@tanstack/react-table": "^8.21.3", "axios": "^1.6.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "framer-motion": "^12.29.2", "i18next": "^23.7.6", "i18next-browser-languagedetector": "^7.2.0", "lucide-react": "^0.294.0", + "motion": "^12.29.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^13.5.0", "react-router-dom": "^6.20.0", - "recharts": "^2.10.3" + "recharts": "^2.15.4", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { + "@iconify/react": "^6.0.2", "@types/node": "^20.10.0", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@vitejs/plugin-react": "^4.2.0", - "autoprefixer": "^10.4.16", - "postcss": "^8.4.31", - "tailwindcss": "^3.3.5", + "autoprefixer": "^10.4.23", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.19", "typescript": "^5.2.2", "vite": "^7.3.0" } @@ -35,7 +59,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -48,7 +71,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -104,7 +126,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", @@ -138,7 +159,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -148,7 +168,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -190,7 +209,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -200,7 +218,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -234,7 +251,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -291,7 +307,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -306,7 +321,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -325,7 +339,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -335,6 +348,158 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -777,11 +942,115 @@ "node": ">=18" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@headlessui/react": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz", + "integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, + "node_modules/@iconify/react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-6.0.2.tgz", + "integrity": "sha512-SMmC2sactfpJD427WJEDN6PMyznTFMhByK9yLW0gOTtnjzzbsi/Ke/XqsumsavFPwNiXs8jSiYeZTmLCLwO+Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -803,7 +1072,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -813,25 +1081,267 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.7.tgz", + "integrity": "sha512-8jWwS6FweMkpyRkrJooamUGe1CQfO1yJ+lM43IyUJbrhHW/ObES+6ry4vfGi8EKaldHL3t3BG1bcLcERuJPcjg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.7.tgz", + "integrity": "sha512-3Q+ulAqG+A1+R4ebgoIs7AccaJhIGy+Xi/9OnvX376jQ6wcy+rz4geDGrxQxCGzdjOQr4Z3NgyFSZCz4T999lA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.7", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.7.tgz", + "integrity": "sha512-6bdIxqzeOtBAj2wAsfhWCYyMKPLkRO9u/2o5yexcL0C3APqyy91iGSWgT3H7hg+zR2XgE61+WAu12wXPON8b6A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/core-downloads-tracker": "^7.3.7", + "@mui/system": "^7.3.7", + "@mui/types": "^7.4.10", + "@mui/utils": "^7.3.7", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.3", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.7", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.7.tgz", + "integrity": "sha512-w7r1+CYhG0syCAQUWAuV5zSaU2/67WA9JXUderdb7DzCIJdp/5RmJv6L85wRjgKCMsxFF0Kfn0kPgPbPgw/jdw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.7", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.7.tgz", + "integrity": "sha512-y/QkNXv6cF6dZ5APztd/dFWfQ6LHKPx3skyYO38YhQD4+Cxd6sFAL3Z38WMSSC8LQz145Mpp3CcLrSCLKPwYAg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.7.tgz", + "integrity": "sha512-DovL3k+FBRKnhmatzUMyO5bKkhMLlQ9L7Qw5qHrre3m8zCZmE+31NDVBFfqrbrA7sq681qaEIHdkWD5nmiAjyQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/private-theming": "^7.3.7", + "@mui/styled-engine": "^7.3.7", + "@mui/types": "^7.4.10", + "@mui/utils": "^7.3.7", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.10.tgz", + "integrity": "sha512-0+4mSjknSu218GW3isRqoxKRTOrTLd/vHi/7UC4+wZcUrOAqD9kRk7UQRL1mcrzqRoe7s3UT6rsRpbLkW5mHpQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.7.tgz", + "integrity": "sha512-+YjnjMRnyeTkWnspzoxRdiSOgkrcpTikhNPoxOZW0APXx+urHtUoXJ9lbtCZRCA5a4dg5gSbd19alL1DvRs5fg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/types": "^7.4.10", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -845,7 +1355,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -855,7 +1364,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -865,6 +1373,1054 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@react-aria/focus": { + "version": "3.21.3", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.3.tgz", + "integrity": "sha512-FsquWvjSCwC2/sBk4b+OqJyONETUIXQ2vM0YdPAuC+QFQh2DT6TIBo6dOZVSezlhudDla69xFBd6JvCFq1AbUw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.26.0", + "@react-aria/utils": "^3.32.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.26.0.tgz", + "integrity": "sha512-AAEcHiltjfbmP1i9iaVw34Mb7kbkiHpYdqieWufldh4aplWgsF11YQZOfaCJW4QoR2ML4Zzoa9nfFwLXA52R7Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.32.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.32.0.tgz", + "integrity": "sha512-/7Rud06+HVBIlTwmwmJa2W8xVtgxgzm0+kLbuFooZRzKDON6hhozS1dOMR/YLMxyJOaYOTpImcP4vRR9gL1hEg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.11.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", + "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.11.0.tgz", + "integrity": "sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@remix-run/router": { "version": "1.23.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz", @@ -1189,6 +2745,15 @@ "win32" ] }, + "node_modules/@swc/helpers": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@tanstack/query-core": { "version": "5.90.12", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.12.tgz", @@ -1215,6 +2780,66 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.18.tgz", + "integrity": "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.18.tgz", + "integrity": "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1340,18 +2965,22 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.27", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1362,12 +2991,21 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1393,14 +3031,12 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -1414,9 +3050,20 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, "license": "MIT" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1471,6 +3118,21 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.9.9", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.9.tgz", @@ -1485,7 +3147,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1498,7 +3159,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -1554,11 +3214,19 @@ "node": ">= 0.4" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -1589,7 +3257,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -1614,7 +3281,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -1623,6 +3289,18 @@ "node": ">= 6" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -1648,7 +3326,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -1661,11 +3338,35 @@ "dev": true, "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -1801,11 +3502,20 @@ "node": ">=12" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1834,18 +3544,22 @@ "node": ">=0.4.0" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, "license": "Apache-2.0" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, "license": "MIT" }, "node_modules/dom-helpers": { @@ -1879,6 +3593,15 @@ "dev": true, "license": "ISC" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1976,6 +3699,18 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -1995,7 +3730,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2012,7 +3746,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -2025,7 +3758,6 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -2035,7 +3767,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -2044,6 +3775,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -2094,11 +3831,37 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.29.2.tgz", + "integrity": "sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.29.2", + "motion-utils": "^12.29.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2152,6 +3915,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -2169,7 +3941,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -2229,6 +4000,21 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -2270,6 +4056,22 @@ "@babel/runtime": "^7.23.2" } }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -2279,11 +4081,16 @@ "node": ">=12" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -2296,7 +4103,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -2312,7 +4118,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2322,7 +4127,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -2335,7 +4139,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -2345,7 +4148,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -2361,7 +4163,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2370,6 +4171,12 @@ "node": ">=6" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2387,7 +4194,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -2400,7 +4206,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, "license": "MIT" }, "node_modules/lodash": { @@ -2453,7 +4258,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -2463,7 +4267,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -2494,18 +4297,57 @@ "node": ">= 0.6" } }, + "node_modules/motion": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.29.2.tgz", + "integrity": "sha512-jMpHdAzEDF1QQ055cB+1lOBLdJ6ialVWl6QQzpJI2OvmHequ7zFVHM2mx0HNAy+Tu4omUlApfC+4vnkX0geEOg==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.29.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.29.2.tgz", + "integrity": "sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.29.2" + } + }, + "node_modules/motion-utils": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -2517,7 +4359,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -2543,7 +4384,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2562,31 +4402,66 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -2599,7 +4474,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2609,7 +4483,6 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -2619,7 +4492,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2648,7 +4520,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -2666,7 +4537,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2692,7 +4562,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2735,7 +4604,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2761,7 +4629,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -2775,7 +4642,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, "license": "MIT" }, "node_modules/prop-types": { @@ -2805,7 +4671,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -2885,6 +4750,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.30.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz", @@ -2932,6 +4844,28 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -2952,7 +4886,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -2962,7 +4895,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -3007,7 +4939,6 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -3024,11 +4955,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -3081,7 +5020,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -3120,21 +5058,34 @@ "semver": "bin/semver.js" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -3157,7 +5108,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3166,11 +5116,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -3204,11 +5169,19 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -3218,7 +5191,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -3237,7 +5209,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -3254,7 +5225,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -3272,7 +5242,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3285,7 +5254,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -3298,9 +5266,14 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -3353,11 +5326,62 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/victory-vendor": { @@ -3503,6 +5527,23 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } } } } diff --git a/frontend/package.json b/frontend/package.json index f7e35b9..3df0b42 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,25 +9,49 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@headlessui/react": "^2.2.9", + "@heroicons/react": "^2.2.0", + "@mui/icons-material": "^7.3.7", + "@mui/material": "^7.3.7", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toast": "^1.2.15", "@tanstack/react-query": "^5.8.0", + "@tanstack/react-table": "^8.21.3", "axios": "^1.6.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "framer-motion": "^12.29.2", "i18next": "^23.7.6", "i18next-browser-languagedetector": "^7.2.0", "lucide-react": "^0.294.0", + "motion": "^12.29.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^13.5.0", "react-router-dom": "^6.20.0", - "recharts": "^2.10.3" + "recharts": "^2.15.4", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { + "@iconify/react": "^6.0.2", "@types/node": "^20.10.0", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", "@vitejs/plugin-react": "^4.2.0", - "autoprefixer": "^10.4.16", - "postcss": "^8.4.31", - "tailwindcss": "^3.3.5", + "autoprefixer": "^10.4.23", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.19", "typescript": "^5.2.2", "vite": "^7.3.0" }