Tham chiếu

Component giao diện

Danh mục element pos-* dùng trong extension Worker UI: thuộc tính, sự kiện, quy tắc.

Xem Markdown

Áp dụng cho api_version: "2026-01".

Extension Worker UI (block, action modal/fullscreen, trang render: "worker") chỉ dựng giao diện bằng các element pos-* dưới đây. POS vẽ mỗi element bằng component thật của POS, nên giao diện luôn đồng bộ với POS.

Trang iframe không dùng danh sách này; dùng HTML/React tự do, có thể kèm @pancake-pos/ui để giao diện giống POS.

Quy tắc chung

  • Chỉ dùng element trong danh sách. Thẻ HTML thường (div, span, img, table, script…) và element lạ không được vẽ; extension báo lỗi trong nhật ký của app.
  • Text chỉ hiển thị khi nằm trong pos-text, pos-heading, pos-badge, pos-link, pos-button, pos-banner.
  • Không có style, className, HTML thô. Màu sắc, khoảng cách, font theo POS.
  • URL (href, src) chỉ chấp nhận https://.
  • Sự kiện truyền giá trị qua event.detail. Trong JSX dùng thuộc tính onClick, onChange… như component thông thường. Với JS thuần, tên sự kiện là phần sau on viết thường: onPageChange → addEventListener('pagechange', …).
  • Giới hạn 2.000 element mỗi extension; pos-table tối đa 500 dòng mỗi lần render (dùng phân trang).
  • Thuộc tính dạng mảng/đối tượng (options, columns, rows, pagination) truyền trực tiếp bằng JSX, không cần JSON.stringify.
TSX
<pos-stack gap="small">
  <pos-text-field label="Mã phiếu" value={code} onChange={(e) => setCode(e.detail)} />
  <pos-button variant="primary" loading={saving} onClick={save}>Lưu</pos-button>
</pos-stack>

Không dùng framework thì dựng bằng DOM API của runtime (xem Môi trường worker):

JavaScript
let code = '';

const field = document.createElement('pos-text-field');
field.label = 'Mã phiếu';
field.addEventListener('change', (e) => { code = e.detail; });

const button = document.createElement('pos-button');
button.variant = 'primary';
button.textContent = 'Lưu';
button.addEventListener('click', () => save(code));

const stack = document.createElement('pos-stack');
stack.gap = 'small';
stack.append(field, button);
document.body.append(stack);

Màn hình chuẩn

POS đặt element theo layout chung của các trang POS; app chỉ chọn đúng element và thứ tự, không chỉnh màu hay khoảng cách.

  • Trang app.page: pos-page là khung trang của POS. heading và description nằm ở dòng tiêu đề trên cùng, slot actions ở bên phải cạnh nút chính (pos.page.setPrimaryAction); nội dung xếp dọc trên nền xám.
  • Khối: pos-section đặt thẳng trong pos-page là thẻ trắng; pos-section lồng trong pos-section là nhóm phẳng; trong block và modal, pos-section là thẻ trắng viền mảnh. pos-table, pos-chart đặt thẳng trong pos-page tự nằm trong thẻ trắng.
  • Số liệu: pos-stat trong pos-grid 3–5 cột; không ghép pos-section với pos-heading để làm ô số liệu.
  • Lọc: gom ô lọc vào một pos-filter-bar ở đầu trang hoặc đầu khối; khoảng ngày dùng pos-date-range. Lọc theo trạng thái dùng pos-tabs có badge: chip có số đếm như thanh trạng thái của các trang danh sách POS.
  • Nút: nút công cụ (tải lại, lọc, cài đặt) là nút chỉ có icon kèm tooltip; nhiều thao tác phụ thì gom vào pos-menu-button. Trạng thái trong bảng dùng cột format: 'badge'; chi tiết một dòng mở trong pos-drawer hoặc pos-modal với pos-description-list, pos-timeline.
  • Không lặp tiêu đề trang trong nội dung, không dùng emoji hay ký tự trang trí trong nhãn (dùng tone của pos-stat, pos-badge), không lồng pos-section quá hai cấp, không bọc từng element trong một pos-section riêng.
  • Bỏ khỏi biểu đồ và bảng các dòng toàn số 0 hoặc không có dữ liệu; hiện tên thay vì mã (tra tên qua Open API).

Trang báo cáo

TSX
<pos-page description="Đối chiếu thời gian giao thực tế với SLA cam kết của ĐVVC" width="full">
  <pos-button slot="actions" icon="download" onClick={exportCsv}>Xuất</pos-button>
  <pos-filter-bar>
    <pos-date-range label="Ngày tạo đơn" value={range} onChange={(e) => e.detail && setRange(e.detail)} />
    <pos-button icon="refresh" tooltip="Tải lại" onClick={load} />
  </pos-filter-bar>
  <pos-grid columns={4}>
    <pos-stat label="Tỷ lệ đúng hạn" value={58.3} format="percent" help="12 đơn đã giao" />
    <pos-stat label="Đúng hạn" value={7} tone="success" selected={state === 'on_time'} onClick={() => setState('on_time')} />
    <pos-stat label="Giao trễ" value={5} tone="critical" selected={state === 'late'} onClick={() => setState('late')} />
    <pos-stat label="Phí ship" value={2450000} format="money" />
  </pos-grid>
  <pos-section heading="Theo ĐVVC">
    <pos-chart type="bar" xKey="carrier" data={byCarrier} series={[{ key: 'rate', label: 'Tỷ lệ đúng hạn (%)' }]} onSelect={(e) => setCarrier(e.detail.x)} />
  </pos-section>
</pos-page>

Trang danh sách

TSX
<pos-page width="full">
  <pos-button slot="actions" icon="upload" onClick={importFile}>Nhập</pos-button>
  <pos-menu-button slot="actions" items={[{ id: 'csv', label: 'Xuất CSV' }]} onSelect={exportAs} />
  <pos-section>
    <pos-filter-bar>
      <pos-text-field label="Tìm mã đơn" value={search} onChange={(e) => setSearch(e.detail)} />
      <pos-select label="Kho" options={warehouses} value={warehouse} onChange={(e) => setWarehouse(e.detail)} />
    </pos-filter-bar>
    <pos-tabs tabs={[{ id: 'all', label: 'Tất cả', badge: 169 }, { id: 'late', label: 'Giao trễ', badge: 12 }]} selected={status} onChange={(e) => setStatus(e.detail)}>
      <pos-table
        columns={[
          { key: 'code', title: 'Mã đơn', format: 'link' },
          { key: 'customer', title: 'Khách hàng', subtitle: 'phone' },
          { key: 'state', title: 'SLA', format: 'badge', tones: { 'Trễ': 'critical', 'Đúng hạn': 'success' } },
          { key: 'fee', title: 'Phí ship', format: 'money', align: 'right', sortable: true },
        ]}
        rows={rows}
        sort={sort}
        pagination={{ page, pageSize: 30, total }}
        onSortChange={(e) => setSort(e.detail)}
        onPageChange={(e) => setPage(e.detail.page)}
        onCellClick={(e) => setDetail(e.detail.row)}
      />
    </pos-tabs>
  </pos-section>
</pos-page>
TSX
<pos-drawer open={!!detail} heading={detail?.code} onClose={() => setDetail(null)}>
  <pos-description-list items={[{ label: 'ĐVVC', value: detail?.carrier }, { label: 'Phí ship', value: detail?.fee, format: 'money' }]} />
  <pos-timeline items={detail?.events ?? []} />
</pos-drawer>

Nút chính của trang (ví dụ "Đồng bộ") đặt bằng pos.page.setPrimaryAction, luôn nằm cuối dòng tiêu đề. Xuất file (CSV, Excel) bằng pos.download.

Bố cục

pos-stack

Xếp các phần tử con theo hàng dọc hoặc ngang.

Thuộc tính Kiểu Mặc định Mô tả
direction 'vertical' | 'horizontal' 'vertical' Hướng xếp
gap 'none' | 'small' | 'medium' | 'large' 'medium' Khoảng cách giữa phần tử
align 'start' | 'center' | 'end' | 'stretch' 'stretch' Căn theo trục phụ
justify 'start' | 'center' | 'end' | 'space-between' 'start' Căn theo trục chính
wrap boolean false Cho xuống dòng (khi horizontal)

pos-grid

Lưới cột đều nhau; ô hẹp hơn 180 px thì tự xuống hàng (màn hình hẹp còn một cột).

Thuộc tính Kiểu Mặc định Mô tả
columns 1–6 2 Số cột
gap 'none' | 'small' | 'medium' | 'large' 'medium' Khoảng cách

pos-section

Khối có tiêu đề. Đặt thẳng trong pos-page là thẻ trắng; lồng trong pos-section khác là nhóm phẳng; trong block và modal là thẻ trắng viền mảnh (xem Màn hình chuẩn).

Thuộc tính Kiểu Mô tả
heading string Tiêu đề
description string Mô tả ngắn dưới tiêu đề

Slot actions: pos-button, pos-link, pos-menu-button hiển thị ở góc phải tiêu đề.

TSX
<pos-section heading="Công nợ">
  <pos-button slot="actions" variant="tertiary" onClick={reload}>Tải lại</pos-button>
  <pos-text>...</pos-text>
</pos-section>

pos-divider

Đường kẻ ngang. Không có thuộc tính.

Hiển thị

pos-text

Thuộc tính Kiểu Mặc định
tone 'default' | 'subdued' | 'success' | 'warning' | 'critical' 'default'
size 'small' | 'medium' | 'large' 'medium'
weight 'regular' | 'semibold' | 'bold' 'regular'

pos-heading

Thuộc tính Kiểu Mặc định
level 1–4 3

Cỡ chữ: 1 20 px (bằng tiêu đề trang), 2 18 px, 3 16 px (bằng tiêu đề khối), 4 14 px.

pos-badge

Nhãn trạng thái ngắn.

Thuộc tính Kiểu Mặc định
tone 'default' | 'info' | 'success' | 'warning' | 'critical' 'default'

pos-money

Hiển thị số tiền theo định dạng và đơn vị tiền tệ của shop.

Thuộc tính Kiểu Mô tả
value number Số tiền
currency string Mã tiền tệ ISO 4217; mặc định là tiền tệ của shop
tone như pos-text Màu chữ
Thuộc tính Kiểu Mô tả
href string URL https://, luôn mở tab mới

Sự kiện onClick: dùng khi link thực hiện thao tác trong app thay vì mở URL (không đặt href).

pos-image

Thuộc tính Kiểu Mặc định Mô tả
src string URL https:// thuộc domain trong network.allowed_origins, hoặc public/<tên file> trỏ tới file trong thư mục public/ của dự án
alt string Văn bản thay thế
width, height number Kích thước (px)
fit 'cover' | 'contain' 'cover' Cách co ảnh

POS tải ảnh qua máy chủ trung gian của sandbox: chỉ nhận định dạng ảnh, tối đa 5 MB. Ảnh từ domain chưa khai không hiển thị (chế độ phát triển có cảnh báo).

pos-description-list

Cặp nhãn – giá trị như khối thông tin đơn của POS (nhãn xám bên trái, giá trị bên phải); dùng trong modal, panel, khối chi tiết.

Thuộc tính Kiểu Mặc định Mô tả
items Item[] [] Tối đa 50 dòng
columns 1–4 1 Số cột; ô hẹp hơn 220 px thì xuống hàng
TypeScript
type Item = {
  label: string;
  value: string | number | null;     // null hoặc rỗng hiện "—"
  format?: 'text' | 'number' | 'money' | 'percent' | 'date' | 'datetime' | 'badge';
  tone?: 'default' | 'info' | 'success' | 'warning' | 'critical';  // màu khi format là badge
};
TSX
<pos-description-list columns={2} items={[
  { label: 'ĐVVC', value: 'GHN' },
  { label: 'Phí ship', value: 25000, format: 'money' },
  { label: 'SLA', value: 'Trễ 2 ngày', format: 'badge', tone: 'critical' },
]} />

pos-timeline

Dòng thời gian: hành trình đơn, lịch sử thay đổi.

Thuộc tính Kiểu Mô tả
items { title: string; time?: string; description?: string; tone?: Tone }[] Tối đa 100 mốc; time dạng ISO 8601 được định dạng theo POS, chữ khác giữ nguyên; tone là màu chấm

Nhập liệu

Thuộc tính chung của mọi ô nhập:

Thuộc tính Kiểu Mô tả
label string Nhãn
value tuỳ component Giá trị hiện tại (component có kiểm soát)
disabled boolean Khoá ô
required boolean Hiện dấu bắt buộc (không tự kiểm tra)
error string Thông báo lỗi dưới ô
help string Gợi ý dưới ô

Sự kiện chung: onChange (event.detail là giá trị mới), onBlur.

POS hiện ngay chữ người dùng gõ rồi mới gửi onChange cho app, nên ô không giật dù app đang bận. Nếu app đặt value khác giá trị người dùng vừa nhập (ví dụ chuẩn hoá chữ hoa hoặc xóa ô), POS hiển thị giá trị của app. Nên đặt value ngay trong onChange; đổi value sau một lệnh await có thể ghi đè chữ người dùng đang gõ.

pos-text-field

Thuộc tính Kiểu Mô tả
value string
placeholder string
multiline boolean | number Ô nhiều dòng; số là số dòng hiển thị
maxLength number Giới hạn ký tự

pos-number-field

Thuộc tính Kiểu Mô tả
value number | null
min, max, step number
suffix string Đơn vị hiển thị sau số, ví dụ "%"

pos-select

Thuộc tính Kiểu Mô tả
options { label: string; value: string; disabled?: boolean }[] Danh sách lựa chọn
value string | string[] Mảng khi multiple
multiple boolean Chọn nhiều
placeholder string
searchable boolean Cho gõ tìm trong danh sách

pos-checkbox

Thuộc tính Kiểu Mô tả
checked boolean
label string

onChange: event.detail là boolean.

pos-date-picker

Thuộc tính Kiểu Mô tả
value string | null Ngày dạng YYYY-MM-DD
min, max string Giới hạn ngày chọn, dạng YYYY-MM-DD

pos-date-range

Khoảng ngày như bộ chọn thời gian của trang Thống kê, có sẵn lựa chọn nhanh: Hôm nay, Hôm qua, 7 ngày qua, 30 ngày qua, Đầu tuần đến nay, Đầu tháng đến nay, Tháng trước.

Thuộc tính Kiểu Mặc định Mô tả
value { from: string; to: string } | null Ngày dạng YYYY-MM-DD
min, max string Giới hạn ngày chọn được
presets boolean true Hiện lựa chọn nhanh

Cùng các thuộc tính chung của ô nhập (label, disabled, required, error, help). Sự kiện onChange: event.detail là { from, to } hoặc null khi xoá.

TSX
<pos-date-range label="Ngày tạo đơn" value={range} onChange={(e) => e.detail && setRange(e.detail)} />

Phản hồi

pos-banner

Thông báo nổi bật trong extension.

Thuộc tính Kiểu Mặc định Mô tả
tone 'info' | 'success' | 'warning' | 'critical' 'info'
heading string Tiêu đề
dismissible boolean false Hiện nút đóng

Sự kiện onDismiss.

pos-spinner

Thuộc tính Kiểu Mặc định
size 'small' | 'medium' | 'large' 'medium'

pos-progress

Thanh tiến độ cho tỷ lệ (đúng hạn, hoàn thành chỉ tiêu).

Thuộc tính Kiểu Mặc định Mô tả
value number 0 Phần trăm 0–100
label string Nhãn bên trái, phần trăm bên phải
tone 'default' | 'info' | 'success' | 'warning' | 'critical' 'default' Màu thanh
showValue boolean true Hiện số phần trăm

pos-empty

Trạng thái trống như các trang POS: hình minh hoạ nhỏ, tiêu đề, mô tả và nút hành động.

Thuộc tính Kiểu Mô tả
heading string Tiêu đề
description string Mô tả

Slot actions cho pos-button, pos-link.

TSX
<pos-empty heading="Chưa có đơn trễ" description="Đổi khoảng ngày để xem thêm">
  <pos-button slot="actions" onClick={reset}>Đặt lại bộ lọc</pos-button>
</pos-empty>

Dữ liệu và thao tác

pos-button

Thuộc tính Kiểu Mặc định Mô tả
variant 'primary' | 'secondary' | 'tertiary' | 'danger' 'secondary' Kiểu nút
size 'small' | 'medium' 'medium'
loading boolean false Hiện trạng thái đang xử lý, chặn bấm
disabled boolean false
icon xem dưới Icon trước chữ; nút không có chữ là nút chỉ có icon
tooltip string Chú thích khi rê chuột; bắt buộc với nút chỉ có icon (cũng là tên cho trình đọc màn hình)

Sự kiện onClick.

Icon (bộ icon của POS): plus, edit, delete, download, upload, refresh, filter, search, settings, print, copy, link, external, view, send, check, close, more, calendar, info, warning.

TSX
<pos-button icon="download" onClick={exportCsv}>Xuất</pos-button>
<pos-button icon="refresh" tooltip="Tải lại" onClick={reload} />

pos-menu-button

Nút mở danh sách thao tác. Không có label thì là nút "⋯".

Thuộc tính Kiểu Mặc định Mô tả
items { id: string; label: string; danger?: boolean; disabled?: boolean }[] [] Tối đa 30 mục
label string Chữ trên nút
icon như pos-button more khi không có label
variant 'primary' | 'secondary' | 'tertiary' 'secondary'
size 'small' | 'medium' 'medium'
disabled boolean false
tooltip string Chú thích khi rê chuột

Sự kiện onSelect: event.detail là id của mục được bấm.

TSX
<pos-menu-button slot="actions" items={[{ id: 'csv', label: 'Xuất CSV' }, { id: 'delete', label: 'Xoá', danger: true }]} onSelect={(e) => run(e.detail)} />

pos-table

Bảng dữ liệu như bảng danh sách của POS. Ô chỉ chứa giá trị (chuỗi, số); cách hiển thị (định dạng, badge, link, dòng phụ) khai ở cột.

Thuộc tính Kiểu Mặc định Mô tả
columns Column[] Định nghĩa cột
rows object[] [] Dữ liệu, tối đa 500 dòng
rowKey string 'id' Trường dùng làm khoá dòng
loading boolean false
pagination { page: number; pageSize: number; total: number } | null null Phân trang phía app
emptyText string Nội dung khi không có dòng
sort { key: string; order: 'asc' | 'desc' } | null null Cột đang sắp xếp (app tự sắp dữ liệu hoặc gọi lại API)
summary object | null null Dòng tổng cuối bảng, giá trị theo key của cột, định dạng theo cột
selectable boolean false Ô chọn đầu mỗi dòng
selected (string | number)[] [] Khoá các dòng đang chọn
TypeScript
type Column = {
  key: string;                       // tên trường trong row
  title: string;
  align?: 'left' | 'center' | 'right';
  width?: number;                    // px
  format?: 'text' | 'number' | 'money' | 'percent' | 'date' | 'datetime' | 'badge' | 'link';
  tones?: Record<string, 'default' | 'info' | 'success' | 'warning' | 'critical'>;  // màu badge theo giá trị ô
  subtitle?: string;                 // trường hiện thành dòng phụ màu xám (ví dụ mã SKU dưới tên)
  sortable?: boolean;
  fixed?: 'left' | 'right';          // cố định cột khi cuộn ngang
  ellipsis?: boolean;                // cắt chữ dài trên một dòng
};
  • percent: số là phần trăm (12.5 → 12,5%). badge: giá trị ô thành nhãn màu theo tones. link: chữ dạng link, bấm phát onCellClick.

Sự kiện:

  • onPageChange: event.detail là { page, pageSize }.
  • onRowClick: event.detail là row được bấm.
  • onSortChange: event.detail là { key, order }, hoặc null khi bỏ sắp xếp.
  • onSelectionChange: event.detail là mảng khoá các dòng đang chọn (giữ cả dòng ở trang khác).
  • onCellClick: event.detail là { key, row } khi bấm ô format: 'link'.
TSX
<pos-table
  columns={[
    { key: 'system_id', title: 'Mã đơn' },
    { key: 'total_price', title: 'Tổng tiền', format: 'money', align: 'right' },
  ]}
  rows={orders}
  rowKey="system_id"
  pagination={{ page, pageSize: 20, total }}
  onPageChange={(e) => setPage(e.detail.page)}
/>
TSX
<pos-table
  columns={[
    { key: 'name', title: 'Sản phẩm', subtitle: 'sku', sortable: true, fixed: 'left' },
    { key: 'state', title: 'SLA', format: 'badge', tones: { 'Trễ': 'critical', 'Đúng hạn': 'success' } },
    { key: 'code', title: 'Mã đơn', format: 'link' },
    { key: 'rate', title: 'Tỷ lệ', format: 'percent', align: 'right' },
  ]}
  rows={rows}
  sort={sort}
  summary={{ name: 'Tổng', rate: 62.5 }}
  selectable
  selected={selected}
  onSortChange={(e) => setSort(e.detail)}
  onSelectionChange={(e) => setSelected(e.detail)}
  onCellClick={(e) => openOrder(e.detail.row)}
/>

pos-modal

Hộp thoại mở từ bên trong extension (ví dụ từ một nút trong block).

Thuộc tính Kiểu Mô tả
open boolean Trạng thái mở
heading string Tiêu đề

Slot footer cho các nút cuối hộp thoại. Sự kiện onClose khi người dùng bấm đóng hoặc ra ngoài.

TSX
<pos-modal open={open} heading="Xác nhận" onClose={() => setOpen(false)}>
  <pos-text>Đẩy đơn sang ERP?</pos-text>
  <pos-button slot="footer" onClick={() => setOpen(false)}>Huỷ</pos-button>
  <pos-button slot="footer" variant="primary" onClick={push}>Đẩy</pos-button>
</pos-modal>

Action presentation: "modal" đã có sẵn modal của POS; không cần bọc thêm pos-modal.

pos-drawer

Panel trượt từ cạnh phải, như "Cài đặt" của trang Thống kê: xem chi tiết một dòng mà không rời danh sách.

Thuộc tính Kiểu Mặc định Mô tả
open boolean Trạng thái mở
heading string Tiêu đề
width 'small' | 'medium' | 'large' 'medium' 400, 560 hoặc 800 px

Slot footer cho các nút cuối panel. Sự kiện onClose khi người dùng bấm đóng hoặc ra ngoài.

Trang Worker UI

Các element dưới đây dùng để dựng trang render: "worker" và màn báo cáo (xem Extension point › app.page); dùng được cả trong block và modal.

pos-page

Khung trang. Trên trang app.page, heading và description hiện ở dòng tiêu đề của POS (không có heading thì dùng tiêu đề đặt bằng pos.page.setTitle hoặc title của extension), slot actions nằm bên phải cạnh nút chính, nội dung xếp dọc trên nền xám. Trong block và modal, pos-page là khối có tiêu đề.

Thuộc tính Kiểu Mặc định Mô tả
heading string Tiêu đề trang
description string Mô tả dưới tiêu đề
width 'narrow' | 'default' | 'full' 'default' Độ rộng nội dung: 720 px, tối đa 1.800 px hoặc toàn màn hình

Slot actions cho pos-button, pos-link, pos-menu-button; trên trang app.page nằm trước nút chính.

pos-tabs

Thanh chọn dạng chip có số đếm (badge), như thanh trạng thái của các trang danh sách POS. App giữ tab đang chọn và vẽ nội dung của tab đó làm element con.

Thuộc tính Kiểu Mô tả
tabs { id: string; label: string; badge?: number; disabled?: boolean }[] Tối đa 20 tab; badge là số hiện cạnh nhãn
selected string id của tab đang chọn

Sự kiện onChange: event.detail là id của tab được chọn. POS đổi tab ngay khi bấm, không chờ app.

pos-filter-bar

Hàng bộ lọc phía trên bảng hoặc biểu đồ. Element con: pos-text-field, pos-number-field, pos-select, pos-checkbox, pos-date-picker, pos-date-range, pos-button, pos-menu-button. Ô nhập vẽ gọn một hàng, label thành chữ gợi ý khi không có placeholder; hết chỗ thì xuống dòng.

TSX
<pos-filter-bar>
  <pos-text-field label="Tìm mã đơn" value={search} onChange={(e) => setSearch(e.detail)} />
  <pos-select label="Nguồn đơn" options={sources} value={source} onChange={(e) => setSource(e.detail)} />
  <pos-date-picker label="Từ ngày" value={from} onChange={(e) => setFrom(e.detail)} />
  <pos-button onClick={reset}>Đặt lại</pos-button>
</pos-filter-bar>

pos-chart

Biểu đồ do POS vẽ; app chỉ truyền dữ liệu. Màu theo bảng màu của POS.

Thuộc tính Kiểu Mặc định Mô tả
type 'line' | 'bar' | 'area' | 'pie' 'line' Loại biểu đồ
data object[] [] Mỗi điểm một object, giá trị chuỗi hoặc số; tối đa 500 điểm, 21 trường
xKey string Trường làm trục ngang (tên lát với pie)
series { key: string; label: string; format?: 'number' | 'money' | 'percent'; type?: 'bar' | 'line' }[] [] Tối đa 10 chuỗi số liệu; pie dùng chuỗi đầu tiên. type từng chuỗi (biểu đồ bar, line) để vẽ cột kết hợp đường
selected string | number | null Giá trị trục ngang đang chọn: cột, lát khác mờ đi; biểu đồ đường, vùng có vạch đánh dấu
stacked boolean false Chồng cột/vùng
horizontal boolean false Cột nằm ngang (chỉ bar): hợp khi nhãn dài hoặc nhiều nhóm; chiều cao tự tăng theo số nhóm
height number 300 120–600 px
loading boolean false
emptyText string Nội dung khi không có dữ liệu
  • Sự kiện onSelect: có nghe thì bấm được vào cột, lát hoặc điểm; event.detail là { x, series }: x là giá trị xKey được bấm, series là key của cột được bấm (null với line, area, pie).
  • format: 'percent': giá trị là phần trăm; mọi chuỗi đều là phần trăm thì trục số ghi thêm "%". Các chuỗi dùng chung một trục số, nên chỉ kết hợp những chuỗi cùng cỡ giá trị (số đơn với số đơn trễ, không phải số đơn với doanh thu).
  • Nhãn trục ngang dài được cắt cho vừa chỗ, rê chuột để xem đủ; nhiều nhóm đến mức mỗi nhóm quá hẹp thì nhãn xoay nghiêng. Nên bỏ các nhóm toàn số 0 và gom nhóm nhỏ vào "Khác" trước khi truyền data.
TSX
<pos-chart
  type="bar"
  xKey="day"
  data={[{ day: '01/10', revenue: 12500000, cost: 8000000 }, { day: '02/10', revenue: 9800000, cost: 7100000 }]}
  series={[{ key: 'revenue', label: 'Doanh thu', format: 'money' }, { key: 'cost', label: 'Giá vốn', format: 'money' }]}
/>

pos-stat

Ô số liệu cho màn báo cáo: nhãn, giá trị lớn, dòng phụ và mức thay đổi so với kỳ trước. Đặt nhiều ô trong pos-grid. Trên nền trang là thẻ trắng; trong khối trắng (pos-section, modal, block) là ô nền xám nhạt.

Thuộc tính Kiểu Mặc định Mô tả
label string Tên chỉ số
value number | string | null Số được định dạng theo format; chuỗi hiện nguyên văn; null hiện —
format 'number' | 'money' | 'percent' 'number' percent: value là phần trăm (58.3 → 58,3%)
currency string tiền tệ của shop Mã ISO 4217, dùng với money
tone 'default' | 'info' | 'success' | 'warning' | 'critical' 'default' Màu của giá trị
help string Dòng phụ dưới giá trị, ví dụ "12 đơn đã giao"
change number Mức thay đổi theo %, hiện kèm mũi tên; tăng là tốt (xanh), giảm là xấu (đỏ)
changeLabel string Ghi chú cạnh mức thay đổi, ví dụ "so với tuần trước"
inverse boolean false Đảo màu của change cho chỉ số càng thấp càng tốt (số đơn trễ, chi phí)
selected boolean false Viền nổi, dùng khi thẻ đang làm bộ lọc
loading boolean false Hiện khung chờ thay cho giá trị

Sự kiện onClick: có nghe thì thẻ bấm được (cả bằng phím Enter), ví dụ bấm "Giao trễ" để lọc bảng bên dưới.

TSX
<pos-grid columns={4}>
  <pos-stat label="Tỷ lệ đúng hạn" value={58.3} format="percent" help="12 đơn đã giao" change={4.2} changeLabel="so với tuần trước" />
  <pos-stat label="Giao trễ" value={5} tone="critical" change={12.5} inverse selected={filter === 'late'} onClick={() => setFilter('late')} />
  <pos-stat label="Phí hoàn" value={2450000} format="money" />
</pos-grid>