full refactor

This commit is contained in:
Mohamadzadeh 2026-06-16 10:25:04 +03:30
parent 67cd686868
commit c0a058e183
24 changed files with 1648 additions and 1104 deletions

619
README.md
View File

@ -1,13 +1,10 @@
# کتابخانه uikit
این README رو جایگزین قبلی کن. importها با ساختار جدید هماهنگه: `utils`, `pagination`, `layout`, `core`, `table`. چون ظاهراً README قبلی هم مثل خیلی از READMEها بیشتر آرزو بود تا واقعیت.
یک کتابخانه‌ی React برای استفاده‌ی مشترک بین پروژه‌ها.
# uikit
این پکیج شامل این موارد است:
کتابخانه‌ی مشترک React برای استفاده بین پروژه‌های BI.
* base api config
* keycloak config
* permissions config
* BiProvider
این پکیج به چند entry جدا تقسیم شده تا پروژه‌ها فقط همان بخشی را import کنند که لازم دارند و مجبور نباشند برای یک helper ساده، dependencyهای سنگین مثل Chakra، Keycloak یا React Query نصب کنند.
---
@ -17,10 +14,10 @@
yarn add git+https://git.d.aiengines.ir/bi/uikit.git
```
اگر این پکیج روی پروژه نصب شد ولی خطای dependency گرفتی، این‌ها را هم نصب کن:
یا اگر branch/tag خاصی لازم داری:
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion keycloak-js axios @tanstack/react-query react-icons
yarn add git+https://git.d.aiengines.ir/bi/uikit.git#main
```
---
@ -31,19 +28,153 @@ yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion keycloak-
yarn upgrade uikit
```
اگر از git dependency استفاده می‌کنی و نسخه جدید نگرفتی، lockfile را هم بررسی کن:
```bash
yarn remove uikit
yarn add git+https://git.d.aiengines.ir/bi/uikit.git
```
---
## استفاده اصلی
# ساختار importها
| مسیر | کاربرد | dependency اضافی |
| ------------------ | ------------------------------------- | --------------------------------------- |
| `uikit` | خروجی سبک، فعلاً فقط utils | ندارد |
| `uikit/utils` | helperهای ساده مثل تبدیل عدد | ندارد |
| `uikit/pagination` | کامپوننت‌های pagination | Chakra + React + react-icons |
| `uikit/layout` | کامپوننت‌های layout مثل Header | Chakra + React |
| `uikit/core` | API، BiProvider، Keycloak، Permission | Chakra + React Query + Axios + Keycloak |
| `uikit/table` | DataTable | Chakra + TanStack Table + Pagination |
---
# استفاده از utils
برای helperهای ساده نیازی به نصب هیچ dependency اضافه‌ای نیست.
```js
import { toFaDigits, toEnDigits, toFaNumber } from "uikit/utils";
toFaDigits(123456); // ۱۲۳۴۵۶
toEnDigits("۱۲۳۴۵۶"); // 123456
toFaNumber(123456); // عدد فارسی‌شده
```
همچنین چون root package فقط utils را export می‌کند، این هم قابل استفاده است:
```js
import { toFaDigits } from "uikit";
```
---
# استفاده از pagination
اگر از pagination استفاده می‌کنی، این dependencyها باید در پروژه نصب باشند:
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons
```
استفاده:
```jsx
import { BiProvider } from "uikit";
import { Pagination, LightPagination, SimplePagination } from "uikit/pagination";
function Page() {
return (
<Pagination
currentPage={1}
totalCount={100}
totalPages={5}
onPageChange={(page) => console.log(page)}
/>
);
}
```
---
# استفاده از Header
`Header` داخل مسیر `uikit/layout` قرار دارد.
dependencyهای لازم:
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
```
استفاده:
```jsx
import { Header } from "uikit/layout";
function AppHeader() {
return (
<Header>
<Header.BrandSection>
<Header.Logo src="/logo.png" />
<Header.Brand>
<Header.Title>عنوان سامانه</Header.Title>
<Header.Description>توضیح کوتاه سامانه</Header.Description>
</Header.Brand>
</Header.BrandSection>
<Header.Bi />
</Header>
);
}
```
اجزای موجود:
```jsx
<Header />
<Header.BrandSection />
<Header.Logo />
<Header.Brand />
<Header.Title />
<Header.Description />
<Header.Bi />
```
---
# استفاده از core
بخش core شامل این موارد است:
* API config
* `createApi`
* `BiProvider`
* Keycloak
* Permission
* React Query provider
* Chakra provider
برای استفاده از core این dependencyها باید در پروژه نصب باشند:
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion @tanstack/react-query axios keycloak-js
```
---
## استفاده از BiProvider
```jsx
import { BiProvider } from "uikit/core";
export default function App() {
return (
<BiProvider
apiBaseUrl="https://api.example.com"
keycloakClientId="client_id"
permissionClientId="client_id"
keycloakClientId="front-client"
permissionClientId="permission-client"
>
<YourApp />
</BiProvider>
@ -51,107 +182,451 @@ export default function App() {
}
```
---
`BiProvider` به صورت داخلی این کارها را انجام می‌دهد:
## پراپس های `BiProvider`
| Prop | توضیح |
| -------------------- | ----------------------------------------------- |
| `apiBaseUrl` | آدرس اصلی API |
| `keycloakClientId` | client id مربوط به Keycloak |
| `permissionClientId` | client id مربوط به Permission |
| `keycloakUrl` | آدرس Keycloak |
| `keycloakRealm` | realm مربوط به Keycloak |
| `permissionUrl` | آدرس API مربوط به permission |
| `loading` | کامپوننت loading |
| `updateChecker` | فعال یا غیرفعال کردن بررسی آپدیت |
| `updateCheckerProps` | props مربوط به UpdateChecker |
* تنظیم base url برای API
* راه‌اندازی Keycloak در صورت فعال بودن
* گرفتن permissionها در صورت فعال بودن
* اضافه کردن `ChakraProvider`
* اضافه کردن `QueryClientProvider`
* اضافه کردن فونت و theme داخلی
* فعال کردن اختیاری `UpdateChecker`
---
## مقدارهای پیش‌فرض
## props مربوط به BiProvider
| Prop | توضیح |
| -------------------- | ------------------------------------------------------ |
| `apiBaseUrl` | آدرس اصلی API |
| `keycloakClientId` | client id مربوط به Keycloak |
| `permissionClientId` | client id مربوط به Permission |
| `loading` | کامپوننت loading هنگام init شدن Keycloak یا Permission |
| `updateChecker` | فعال یا غیرفعال کردن بررسی آپدیت |
| `updateCheckerProps` | props مربوط به UpdateChecker |
| `keycloakUrl` | آدرس Keycloak |
| `keycloakRealm` | realm مربوط به Keycloak |
| `permissionUrl` | آدرس API مربوط به permission |
| `keycloakEnabled` | فعال یا غیرفعال کردن Keycloak به صورت دستی |
| `permissionEnabled` | فعال یا غیرفعال کردن Permission به صورت دستی |
---
## مقدارهای پیش‌فرض BiProvider
```js
keycloakUrl = "https://auth.ibagher.ir"
keycloakRealm = "bi"
permissionUrl = "https://api.d.aiengines.ir/user_api/v1/permissions/list"
updateChecker = true
keycloakUrl = "https://auth.ibagher.ir";
keycloakRealm = "bi";
permissionUrl = "https://api.d.aiengines.ir/user_api/v1/permissions/list";
updateChecker = false;
```
نکته: اگر `keycloakClientId` داده شود، Keycloak فعال می‌شود. اگر `permissionClientId` داده شود، Permission فعال می‌شود.
برای کنترل دستی:
```jsx
<BiProvider
apiBaseUrl="https://api.example.com"
keycloakEnabled={false}
permissionEnabled={false}
>
<YourApp />
</BiProvider>
```
---
## غیرفعال کردن `UpdateChecker`
# UpdateChecker
اگر پروژه فایل نیاز به چک کردن مداوم نسخه ندارد، بهتر است `updateChecker` را خاموش کنی:
به صورت پیش‌فرض `updateChecker` خاموش است.
برای فعال کردن:
```jsx
<BiProvider
apiBaseUrl="https://api.example.com"
keycloakClientId="front-client"
permissionClientId="permission-client"
updateChecker={false}
updateChecker
>
<YourApp />
</BiProvider>
```
و اگر `true` باشد نیاز هست تا فایل `write-version.js` در روت پروژه گذاشته شود و اسکریپت `prebuild` نیز به `package.json` اضافه شود.
اگر `updateChecker` را فعال می‌کنی، پروژه باید فایل version تولید کند. معمولاً باید یک اسکریپت مثل `write-version.js` در root پروژه داشته باشی و در `package.json` پروژه target، قبل از build اجرا شود.
---
نمونه:
## استفاده از `Header`
```jsx
<Header>
<Header.BrandSection>
<Header.Logo src="/logo.png" />
<Header.Brand>
<Header.Title>Title</Header.Title>
<Header.Description>Description</Header.Description>
</Header.Brand>
</Header.BrandSection>
<Header.Bi />
</Header>
```json
{
"scripts": {
"prebuild": "node write-version.js",
"build": "next build"
}
}
```
---
## موارد دیگر `js`
# استفاده از API پایه
```js
toFaDigits();
toEnDigits();
toFaNumber();
import { api, fetcher, configureApi, getApi } from "uikit/core";
```
---
اگر از `BiProvider` استفاده می‌کنی، معمولاً نیازی نیست دستی `configureApi` را صدا بزنی، چون `BiProvider` این کار را انجام می‌دهد.
## موارد دیگر `jsx`
```jsx
<Pagination />
<LightPagination />
<SimplePagination />
```
---
## موارد دیگر `keycloak`
نمونه استفاده مستقیم:
```js
import { api } from "uikit/core";
const response = await api.get("/users");
```
یا:
```js
import { fetcher } from "uikit/core";
const data = await fetcher("/users");
```
---
# استفاده از createApi
`createApi` برای ساختن یک API ساده بر پایه React Query استفاده می‌شود.
```js
import { createApi } from "uikit/core";
export const profileApi = createApi({
key: "profile",
url: "/content/rokn/profiles",
actions: {
update: {
method: "patch",
url: (id) => `/content/rokn/profiles/${id}`,
},
delete: {
method: "delete",
},
generate: {
method: "post",
url: "/content/rokn/generate/profile",
},
updateImages: {
method: "post",
url: (id) => `/content/rokn/profile-update/${id}/images`,
body: ({ formData }) => formData,
},
},
});
```
---
## گرفتن لیست
```jsx
const { data, isLoading, error } = profileApi.useGet({
page: 1,
size: 20,
is_confirmed: true,
});
```
این request ساخته می‌شود:
```txt
GET /content/rokn/profiles?page=1&size=20&is_confirmed=true
```
---
## اجرای action
```jsx
const updateProfile = profileApi.useAction("update");
await updateProfile.mutateAsync({
id: 10,
username: "new_username",
});
```
در این حالت:
```txt
PATCH /content/rokn/profiles/10
```
body:
```js
{
username: "new_username"
}
```
`id` به صورت پیش‌فرض از body حذف می‌شود، چون برای ساختن URL استفاده شده است.
---
## action با همان URL اصلی
اگر action همان URL اصلی را لازم دارد، نیازی نیست `url` را دوباره بنویسی.
```js
export const identityGenerateApi = createApi({
key: "identity-generate",
url: "/content/rokn/identity-generate/logs",
actions: {
delete: {
method: "delete",
},
},
});
```
یعنی:
```txt
DELETE /content/rokn/identity-generate/logs
```
---
## action با FormData
```js
const updateImages = profileApi.useAction("updateImages");
const formData = new FormData();
formData.append("image", file);
await updateImages.mutateAsync({
id: 10,
formData,
});
```
در تعریف action:
```js
updateImages: {
method: "post",
url: (id) => `/content/rokn/profile-update/${id}/images`,
body: ({ formData }) => formData,
}
```
`body` مشخص می‌کند چه چیزی واقعاً به عنوان body ارسال شود.
---
# استفاده از Keycloak
```js
import {
getKeycloakToken,
loginKeycloak,
logoutKeycloak,
updateKeycloakToken,
} from "uikit/core";
const token = getKeycloakToken();
logoutKeycloak();
await updateKeycloakToken(30);
loginKeycloak();
logoutKeycloak({
redirectUri: window.location.origin,
});
```
---
## موارد دیگر `permission`
# استفاده از Permission
```js
<Can permission="users.list">
<L />
```jsx
import { Can, usePermission } from "uikit/core";
function Page() {
const { can, cannot, permissions } = usePermission();
return (
<>
{can("users:list") && <div>لیست کاربران</div>}
<Can perm="users:create" fallback={null}>
<button>ایجاد کاربر</button>
</Can>
</>
);
}
```
نکته: prop کامپوننت `Can` برابر `perm` است.
```jsx
<Can perm="users:list">
<UsersList />
</Can>
```
---
# استفاده از DataTable
`DataTable` داخل مسیر `uikit/table` قرار دارد.
dependencyهای لازم:
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion @tanstack/react-table react-icons
```
استفاده:
```jsx
import { DataTable } from "uikit/table";
function UsersTable({ data, columns }) {
return (
<DataTable
data={data}
columns={columns}
page={1}
size={20}
pagination={{
totalCount: 100,
totalPages: 5,
}}
onPageChange={(page) => console.log(page)}
getRowId={(row) => String(row.id)}
enableSelection
showIndex
>
<DataTable.Topbar>
<DataTable.Title>لیست کاربران</DataTable.Title>
<DataTable.Spacer />
<DataTable.Actions>
<DataTable.SelectAll />
</DataTable.Actions>
</DataTable.Topbar>
<DataTable.Content>
<DataTable.Table />
<DataTable.Pagination />
</DataTable.Content>
</DataTable>
);
}
```
---
# dependencyهای پیشنهادی بر اساس نیاز
## فقط utils
نیازی به نصب dependency اضافه نیست.
```bash
yarn add git+https://git.d.aiengines.ir/bi/uikit.git
```
---
## pagination یا layout
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons
```
برای `layout` معمولاً `react-icons` لازم نیست، ولی اگر پروژه از pagination هم استفاده می‌کند، نصبش کن.
---
## core
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion @tanstack/react-query axios keycloak-js
```
---
## table
```bash
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion @tanstack/react-table react-icons
```
---
# نکات مهم توسعه
بعد از تغییر فایل‌ها، build بگیر:
```bash
yarn build
```
فایل‌های خروجی مورد انتظار:
```txt
dist/index.js
dist/utils.js
dist/pagination.js
dist/layout.js
dist/core.js
dist/table.js
```
اگر entry جدید اضافه شد، باید هم در `vite.config.js` و هم در `package.json > exports` ثبت شود.
---
# قانون کلی
هر چیزی که dependency سنگین ندارد:
```txt
uikit/utils
```
هر چیزی که UI سبک است و Chakra می‌خواهد:
```txt
uikit/pagination
uikit/layout
```
هر چیزی که مربوط به API، auth، permission و provider است:
```txt
uikit/core
```
هر چیزی که مربوط به table است:
```txt
uikit/table
```
Root package یعنی:
```txt
uikit
```
باید سبک بماند و فقط چیزهایی مثل utils را export کند.

147
dist/chunks/Pagination-6xafzB0w.js vendored Normal file
View File

@ -0,0 +1,147 @@
import { jsxs as x, jsx as l } from "react/jsx-runtime";
import { useState as B, useRef as A, useEffect as v } from "react";
import { HStack as w, Text as u, IconButton as y, Button as S, Input as E } from "@chakra-ui/react";
import { IoChevronForwardOutline as M, IoChevronBackOutline as N } from "react-icons/io5";
import { toFaDigits as e, toEnDigits as C } from "../utils.js";
const V = ({
currentPage: i,
totalPages: r,
totalCount: I,
onPageChange: c
}) => {
const [p, o] = B(e(i)), [m, d] = B(!1), [h, f] = B(null), a = A(null), D = Array.from(
/* @__PURE__ */ new Set([1, i, i + 1, r])
).filter((n) => n >= 1 && n <= r).sort((n, t) => n - t);
v(() => {
if (h !== null) {
i === h && (f(null), d(!1), o(e(i)));
return;
}
m || o(e(i));
}, [i, m, h]), v(() => {
if (!m || !p || h !== null) return;
const n = parseInt(C(p), 10);
if (isNaN(n)) return;
const t = setTimeout(() => {
var b;
const s = Math.min(Math.max(n, 1), r);
s !== i ? (f(s), o(e(s)), c(s)) : (d(!1), o(e(i))), (b = a.current) == null || b.blur();
}, 1e3);
return () => clearTimeout(t);
}, [
p,
m,
h,
r,
i,
c
]);
const k = () => {
var s, b;
const n = parseInt(C(p), 10);
if (isNaN(n)) {
o(e(i)), d(!1), f(null), (s = a.current) == null || s.blur();
return;
}
const t = Math.min(Math.max(n, 1), r);
t !== i ? (f(t), o(e(t)), c(t)) : (o(e(i)), d(!1), f(null)), (b = a.current) == null || b.blur();
};
return r === 0 || !I ? null : /* @__PURE__ */ x(
w,
{
justifyContent: "space-around",
backgroundColor: "white",
py: "10px",
px: "16px",
rounded: "lg",
border: "1px",
borderColor: "blue.200",
w: "100%",
children: [
/* @__PURE__ */ x(w, { spacing: 2, whiteSpace: "nowrap", children: [
/* @__PURE__ */ l(u, { fontWeight: "bold", children: "صفحه" }),
/* @__PURE__ */ l(u, { children: e(i) }),
/* @__PURE__ */ l(u, { fontWeight: "bold", children: "از" }),
/* @__PURE__ */ l(u, { children: e(r) })
] }),
/* @__PURE__ */ x(w, { spacing: 2, alignItems: "center", justifyContent: "center", children: [
/* @__PURE__ */ l(
y,
{
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => c(i - 1),
isDisabled: i === 1,
icon: /* @__PURE__ */ l(M, {}),
"aria-label": "صفحه قبلی"
}
),
D.map((n) => /* @__PURE__ */ l(
S,
{
size: "md",
bg: i === n ? "#4B49AC" : "#e6e6f1",
color: i === n ? "#e6e6f1" : "#4B49AC",
_hover: { bg: i === n ? "#4B49AC" : "#cbcbe8" },
onClick: () => c(n),
variant: "soft",
children: e(n)
},
n
)),
/* @__PURE__ */ l(
y,
{
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => c(i + 1),
isDisabled: i === r,
icon: /* @__PURE__ */ l(N, {}),
"aria-label": "صفحه بعدی"
}
),
/* @__PURE__ */ l(
E,
{
ref: a,
type: "text",
inputMode: "numeric",
placeholder: "صفحه",
w: "100px",
value: p,
onFocus: () => {
d(!0), f(null), o("");
},
onChange: (n) => {
const t = n.target.value, s = C(t).replace(/\D/g, "");
o(e(s));
},
onBlur: () => {
p || (o(e(i)), d(!1), f(null));
},
onKeyDown: (n) => {
n.key === "Enter" && k();
},
textAlign: "center",
color: "#1D2939",
px: 1,
minW: 0,
dir: "ltr"
}
)
] }),
/* @__PURE__ */ x(w, { spacing: 2, whiteSpace: "nowrap", children: [
/* @__PURE__ */ l(u, { fontWeight: "bold", children: "تعداد کل:" }),
/* @__PURE__ */ l(u, { children: e(I) })
] })
]
}
);
};
export {
V as P
};

View File

@ -1,149 +0,0 @@
import { jsxs as w, jsx as t } from "react/jsx-runtime";
import { useState as S, useRef as D, useEffect as C } from "react";
import { HStack as I, Text as p, IconButton as N, Button as k, Input as A } from "@chakra-ui/react";
import { IoChevronForwardOutline as E, IoChevronBackOutline as F } from "react-icons/io5";
const O = (i) => new Intl.NumberFormat("fa-IR").format(Number(i)), e = (i) => String(i).replace(/\d/g, (o) => "۰۱۲۳۴۵۶۷۸۹"[Number(o)]), B = (i) => String(i).replace(/[۰-۹]/g, (m) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(m))), R = ({
currentPage: i,
totalPages: o,
totalCount: m,
onPageChange: f
}) => {
const [u, l] = S(e(i)), [h, d] = S(!1), [a, c] = S(null), x = D(null), v = Array.from(
/* @__PURE__ */ new Set([1, i, i + 1, o])
).filter((n) => n >= 1 && n <= o).sort((n, r) => n - r);
C(() => {
if (a !== null) {
i === a && (c(null), d(!1), l(e(i)));
return;
}
h || l(e(i));
}, [i, h, a]), C(() => {
if (!h || !u || a !== null) return;
const n = parseInt(B(u), 10);
if (isNaN(n)) return;
const r = setTimeout(() => {
var b;
const s = Math.min(Math.max(n, 1), o);
s !== i ? (c(s), l(e(s)), f(s)) : (d(!1), l(e(i))), (b = x.current) == null || b.blur();
}, 1e3);
return () => clearTimeout(r);
}, [
u,
h,
a,
o,
i,
f
]);
const y = () => {
var s, b;
const n = parseInt(B(u), 10);
if (isNaN(n)) {
l(e(i)), d(!1), c(null), (s = x.current) == null || s.blur();
return;
}
const r = Math.min(Math.max(n, 1), o);
r !== i ? (c(r), l(e(r)), f(r)) : (l(e(i)), d(!1), c(null)), (b = x.current) == null || b.blur();
};
return o === 0 || !m ? null : /* @__PURE__ */ w(
I,
{
justifyContent: "space-around",
backgroundColor: "white",
py: "10px",
px: "16px",
rounded: "lg",
border: "1px",
borderColor: "blue.200",
w: "100%",
children: [
/* @__PURE__ */ w(I, { spacing: 2, whiteSpace: "nowrap", children: [
/* @__PURE__ */ t(p, { fontWeight: "bold", children: "صفحه" }),
/* @__PURE__ */ t(p, { children: e(i) }),
/* @__PURE__ */ t(p, { fontWeight: "bold", children: "از" }),
/* @__PURE__ */ t(p, { children: e(o) })
] }),
/* @__PURE__ */ w(I, { spacing: 2, alignItems: "center", justifyContent: "center", children: [
/* @__PURE__ */ t(
N,
{
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => f(i - 1),
isDisabled: i === 1,
icon: /* @__PURE__ */ t(E, {}),
"aria-label": "صفحه قبلی"
}
),
v.map((n) => /* @__PURE__ */ t(
k,
{
size: "md",
bg: i === n ? "#4B49AC" : "#e6e6f1",
color: i === n ? "#e6e6f1" : "#4B49AC",
_hover: { bg: i === n ? "#4B49AC" : "#cbcbe8" },
onClick: () => f(n),
variant: "soft",
children: e(n)
},
n
)),
/* @__PURE__ */ t(
N,
{
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => f(i + 1),
isDisabled: i === o,
icon: /* @__PURE__ */ t(F, {}),
"aria-label": "صفحه بعدی"
}
),
/* @__PURE__ */ t(
A,
{
ref: x,
type: "text",
inputMode: "numeric",
placeholder: "صفحه",
w: "100px",
value: u,
onFocus: () => {
d(!0), c(null), l("");
},
onChange: (n) => {
const r = n.target.value, s = B(r).replace(/\D/g, "");
l(e(s));
},
onBlur: () => {
u || (l(e(i)), d(!1), c(null));
},
onKeyDown: (n) => {
n.key === "Enter" && y();
},
textAlign: "center",
color: "#1D2939",
px: 1,
minW: 0,
dir: "ltr"
}
)
] }),
/* @__PURE__ */ w(I, { spacing: 2, whiteSpace: "nowrap", children: [
/* @__PURE__ */ t(p, { fontWeight: "bold", children: "تعداد کل:" }),
/* @__PURE__ */ t(p, { children: e(m) })
] })
]
}
);
};
export {
R as P,
B as a,
O as b,
e as t
};

470
dist/core.js vendored Normal file

File diff suppressed because one or more lines are too long

807
dist/index.js vendored

File diff suppressed because one or more lines are too long

74
dist/layout.js vendored Normal file

File diff suppressed because one or more lines are too long

268
dist/pagination.js vendored Normal file
View File

@ -0,0 +1,268 @@
import { jsxs as I, jsx as s } from "react/jsx-runtime";
import { useState as C, useRef as N, useEffect as v, useCallback as E } from "react";
import { HStack as y, IconButton as A, Button as g, Input as j, Text as M } from "@chakra-ui/react";
import { IoChevronForwardOutline as z, IoChevronBackOutline as S } from "react-icons/io5";
import { toFaDigits as f, toEnDigits as k } from "./utils.js";
import { P as G } from "./chunks/Pagination-6xafzB0w.js";
const L = ({
currentPage: e,
totalPages: l,
totalCount: D,
onPageChange: a
}) => {
const [r, n] = C(f(e)), [d, c] = C(!1), [b, o] = C(null), x = N(null), h = [.../* @__PURE__ */ new Set([1, e, l])].filter(
(i) => i >= 1 && i <= l
);
v(() => {
if (b !== null) {
e === b && (o(null), c(!1), n(f(e)));
return;
}
d || n(f(e));
}, [e, d, b]), v(() => {
if (!d || !r || b !== null) return;
const i = parseInt(k(r), 10);
if (isNaN(i)) return;
const m = setTimeout(() => {
var B;
const u = Math.min(Math.max(i, 1), l);
u !== e ? (o(u), n(f(u)), a(u)) : (c(!1), n(f(e))), (B = x.current) == null || B.blur();
}, 1e3);
return () => clearTimeout(m);
}, [
r,
d,
b,
l,
e,
a
]);
const w = () => {
var u, B;
const i = parseInt(k(r), 10);
if (isNaN(i)) {
n(f(e)), c(!1), o(null), (u = x.current) == null || u.blur();
return;
}
const m = Math.min(Math.max(i, 1), l);
m !== e ? (o(m), n(f(m)), a(m)) : (n(f(e)), c(!1), o(null)), (B = x.current) == null || B.blur();
};
return l === 0 || !D ? null : /* @__PURE__ */ I(
y,
{
justifyContent: "space-around",
backgroundColor: "white",
py: "10px",
px: "16px",
rounded: "lg",
border: "1px",
borderColor: "blue.200",
w: "100%",
children: [
/* @__PURE__ */ I(y, { spacing: 2, alignItems: "center", justifyContent: "center", children: [
/* @__PURE__ */ s(
A,
{
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => a(e - 1),
isDisabled: e === 1,
icon: /* @__PURE__ */ s(z, {}),
"aria-label": "صفحه قبلی"
}
),
h.map((i) => /* @__PURE__ */ s(
g,
{
size: "md",
px: 2,
bg: e === i ? "#4B49AC" : "#e6e6f1",
color: e === i ? "#e6e6f1" : "#4B49AC",
_hover: { bg: e === i ? "#4B49AC" : "#cbcbe8" },
onClick: () => a(i),
variant: "soft",
children: f(i)
},
i
)),
/* @__PURE__ */ s(
A,
{
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => a(e + 1),
isDisabled: e === l,
icon: /* @__PURE__ */ s(S, {}),
"aria-label": "صفحه بعدی"
}
),
/* @__PURE__ */ s(
j,
{
ref: x,
type: "text",
inputMode: "numeric",
placeholder: "صفحه",
w: "56px",
value: r,
onFocus: () => {
c(!0), o(null), n("");
},
onChange: (i) => {
const m = i.target.value, u = k(m).replace(/\D/g, "");
n(f(u));
},
onBlur: () => {
r || (n(f(e)), c(!1), o(null));
},
onKeyDown: (i) => {
i.key === "Enter" && w();
},
textAlign: "center",
color: "#1D2939",
px: 1,
minW: 0,
dir: "ltr"
}
)
] }),
/* @__PURE__ */ I(y, { spacing: 2, children: [
/* @__PURE__ */ s(M, { w: "auto", fontWeight: "bold", whiteSpace: "nowrap", children: "تعداد کل:" }),
/* @__PURE__ */ s(M, { children: f(D) })
] })
]
}
);
}, O = ({
currentPage: e,
totalPages: l,
totalCount: D,
onPageChange: a
}) => {
const [r, n] = C(""), [d, c] = C(!1), [b, o] = C(null), x = N(null), h = b ?? e, w = E(
(t) => {
const p = Math.min(Math.max(t, 1), l);
c(!1), n(""), o(null), p !== e && a(p);
},
[e, l, a]
), i = E(() => {
if (!r) {
c(!1), n(""), o(null);
return;
}
const t = parseInt(k(r), 10);
if (isNaN(t)) {
c(!1), n(""), o(null);
return;
}
const p = Math.min(Math.max(t, 1), l);
c(!1), n(""), p !== e ? (o(p), a(p)) : o(null);
}, [r, e, l, a]);
v(() => {
var t;
d && ((t = x.current) == null || t.focus());
}, [d]), v(() => {
b !== null && e === b && o(null);
}, [e, b]), v(() => {
if (!d || !r) return;
const t = setTimeout(() => {
i();
}, 1e3);
return () => clearTimeout(t);
}, [r, d, i]);
const m = () => {
o(null), n(""), c(!0);
}, u = (t, p = !1) => /* @__PURE__ */ s(
g,
{
size: "md",
bg: p ? "#4B49AC" : "#e6e6f1",
color: p ? "#e6e6f1" : "#4B49AC",
_hover: { bg: p ? "#4B49AC" : "#cbcbe8" },
onClick: p ? m : () => w(t),
variant: "soft",
children: f(t)
},
t
);
if (l === 0 || !D) return null;
const B = l > 1 && h !== 1, T = l > 1 && h !== l;
return /* @__PURE__ */ s(
y,
{
justifyContent: "center",
backgroundColor: "white",
py: "10px",
px: "16px",
rounded: "lg",
border: "1px",
borderColor: "blue.200",
w: "100%",
children: /* @__PURE__ */ I(y, { spacing: 2, alignItems: "center", justifyContent: "center", children: [
/* @__PURE__ */ s(
A,
{
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => w(h - 1),
isDisabled: h === 1,
icon: /* @__PURE__ */ s(z, {}),
"aria-label": "صفحه قبلی"
}
),
B && u(1),
d ? /* @__PURE__ */ s(
j,
{
ref: x,
type: "text",
inputMode: "numeric",
placeholder: "صفحه",
w: "48px",
value: r,
onChange: (t) => {
const p = t.target.value, V = k(p).replace(/\D/g, "");
n(f(V));
},
onBlur: () => {
r ? i() : (n(""), c(!1), o(null));
},
onKeyDown: (t) => {
t.key === "Enter" && i(), t.key === "Escape" && (n(""), c(!1), o(null));
},
textAlign: "center",
color: "#1D2939",
px: 1,
minW: 0,
dir: "ltr"
}
) : u(h, !0),
T && u(l),
/* @__PURE__ */ s(
A,
{
size: "md",
bg: "#e6e6f1",
color: "#4B49AC",
_hover: { bg: "#cbcbe8" },
onClick: () => w(h + 1),
isDisabled: h === l,
icon: /* @__PURE__ */ s(S, {}),
"aria-label": "صفحه بعدی"
}
)
] })
}
);
};
export {
L as LightPagination,
G as Pagination,
O as SimplePagination
};

55
dist/table.js vendored
View File

@ -1,7 +1,8 @@
import { jsx as r, jsxs as X, Fragment as ne } from "react/jsx-runtime";
import { VStack as oe, Box as _, Tbody as re, Tr as H, Td as F, Text as R, Thead as le, Th as ce, Table as se, Checkbox as W, HStack as Y, Spacer as ie } from "@chakra-ui/react";
import { P as ae, t as q } from "./chunks/Pagination-LRiUo8_9.js";
import { useReactTable as ue, getCoreRowModel as de, flexRender as J } from "@tanstack/react-table";
import { P as ue } from "./chunks/Pagination-6xafzB0w.js";
import { toFaDigits as q } from "./utils.js";
import { useReactTable as ae, getCoreRowModel as de, flexRender as J } from "@tanstack/react-table";
import { createContext as me, useMemo as P, useCallback as p, useState as fe, useRef as ge, useEffect as he, useContext as be } from "react";
const K = me(null);
function C() {
@ -17,7 +18,7 @@ function xe({
page: s = 1,
size: c = 10,
pagination: l,
onPageChange: u,
onPageChange: a,
getRowId: g = (d) => String(d == null ? void 0 : d.id),
enableSelection: b = !1,
showIndex: S = !0,
@ -30,15 +31,15 @@ function xe({
pageSize: h
}), [d, h]), Q = p(
(n) => {
const a = (typeof n == "function" ? n(z) : n).pageIndex + 1;
a !== d && (u == null || u(a));
const u = (typeof n == "function" ? n(z) : n).pageIndex + 1;
u !== d && (a == null || a(u));
},
[z, d, u]
[z, d, a]
), [A, U] = fe(L), m = p(
(n) => {
U((i) => {
const a = typeof n == "function" ? n(i) : n;
return k == null || k(a), a;
const u = typeof n == "function" ? n(i) : n;
return k == null || k(u), u;
});
},
[k]
@ -59,7 +60,7 @@ function xe({
m([]);
}, [m]), B = p(
(n) => {
m((i) => f.includes(n) ? i.includes(n) ? i.filter((a) => a !== n) : [...i, n] : i);
m((i) => f.includes(n) ? i.includes(n) ? i.filter((u) => u !== n) : [...i, n] : i);
},
[f, m]
), Z = B, I = p(() => {
@ -80,20 +81,20 @@ function xe({
return b && n.push({
id: "__selection",
header: ({ table: i }) => {
const a = i.options.meta;
const u = i.options.meta;
return /* @__PURE__ */ r(
W,
{
isChecked: a.isAllSelected,
isIndeterminate: a.isIndeterminate,
onChange: (x) => a.toggleSelectAll(x.target.checked),
isChecked: u.isAllSelected,
isIndeterminate: u.isIndeterminate,
onChange: (x) => u.toggleSelectAll(x.target.checked),
colorScheme: "purple",
size: "md"
}
);
},
cell: ({ row: i, table: a }) => {
const x = a.options.meta, D = x.getRowId(i.original);
cell: ({ row: i, table: u }) => {
const x = u.options.meta, D = x.getRowId(i.original);
return /* @__PURE__ */ r(
W,
{
@ -108,14 +109,14 @@ function xe({
}), S && n.push({
id: "__index",
header: "ردیف",
cell: ({ row: i, table: a }) => {
const { pageIndex: x, pageSize: D } = a.getState().pagination, te = x * D + i.index + 1;
cell: ({ row: i, table: u }) => {
const { pageIndex: x, pageSize: D } = u.getState().pagination, te = x * D + i.index + 1;
return /* @__PURE__ */ r(R, { fontWeight: "bold", sx: { fontVariantNumeric: "normal" }, children: q(te) });
},
meta: { isCentered: !0 }
}), [...n, ...o];
}, [o, b, S]), ee = {
table: ue({
table: ae({
data: t,
columns: E,
getCoreRowModel: de(),
@ -148,7 +149,7 @@ function xe({
page: d,
size: h,
pagination: l,
onPageChange: u,
onPageChange: a,
paginationState: z,
totalCount: y,
totalPages: v,
@ -192,14 +193,14 @@ function ke({ children: e = "انتخاب همه", ...t }) {
enableSelection: s,
isAllSelected: c,
isIndeterminate: l,
toggleSelectAll: u
toggleSelectAll: a
} = C();
return !s || o.length === 0 ? null : /* @__PURE__ */ r(
W,
{
isChecked: c,
isIndeterminate: l,
onChange: (g) => u(g.target.checked),
onChange: (g) => a(g.target.checked),
colorScheme: "purple",
size: "lg",
...t,
@ -321,7 +322,7 @@ function De({ emptyText: e = "موردی وجود ندارد" }) {
_hover: { bg: "blue.50" },
transition: "0.2s",
children: c.getVisibleCells().map((l) => {
var u;
var a;
return /* @__PURE__ */ r(
F,
{
@ -330,7 +331,7 @@ function De({ emptyText: e = "موردی وجود ندارد" }) {
color: "gray.700",
borderRightWidth: "1px",
borderRightColor: "gray.200",
textAlign: (u = l.column.columnDef.meta) != null && u.isCentered ? "center" : "start",
textAlign: (a = l.column.columnDef.meta) != null && a.isCentered ? "center" : "start",
verticalAlign: "middle",
children: l.column.columnDef.cell ? J(l.column.columnDef.cell, l.getContext()) : l.getValue()
},
@ -345,20 +346,20 @@ function Pe({
pagination: e,
onPageChange: t
}) {
const o = C(), { table: s } = o, c = e ?? o.pagination, l = (c == null ? void 0 : c.total) ?? (c == null ? void 0 : c.totalCount) ?? o.totalCount ?? 0, u = (c == null ? void 0 : c.total_pages) ?? (c == null ? void 0 : c.totalPages) ?? o.totalPages ?? s.getPageCount() ?? 0, g = s.getState().pagination.pageIndex + 1, b = (S) => {
const o = C(), { table: s } = o, c = e ?? o.pagination, l = (c == null ? void 0 : c.total) ?? (c == null ? void 0 : c.totalCount) ?? o.totalCount ?? 0, a = (c == null ? void 0 : c.total_pages) ?? (c == null ? void 0 : c.totalPages) ?? o.totalPages ?? s.getPageCount() ?? 0, g = s.getState().pagination.pageIndex + 1, b = (S) => {
if (t) {
t(S);
return;
}
s.setPageIndex(S - 1);
};
return !o.onPageChange && !t || u <= 0 ? null : /* @__PURE__ */ r(_, { mt: 1, flexShrink: 0, children: /* @__PURE__ */ r(
ae,
return !o.onPageChange && !t || a <= 0 ? null : /* @__PURE__ */ r(_, { mt: 1, flexShrink: 0, children: /* @__PURE__ */ r(
ue,
{
currentPage: g,
onPageChange: b,
totalCount: l,
totalPages: u
totalPages: a
}
) });
}

6
dist/utils.js vendored Normal file
View File

@ -0,0 +1,6 @@
const e = (r) => new Intl.NumberFormat("fa-IR").format(Number(r)), i = (r) => String(r).replace(/\d/g, (t) => "۰۱۲۳۴۵۶۷۸۹"[Number(t)]), o = (r) => String(r).replace(/[۰-۹]/g, (n) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(n)));
export {
o as toEnDigits,
i as toFaDigits,
e as toFaNumber
};

View File

@ -6,6 +6,10 @@
"module": "./dist/index.js",
"exports": {
".": "./dist/index.js",
"./utils": "./dist/utils.js",
"./pagination": "./dist/pagination.js",
"./layout": "./dist/layout.js",
"./core": "./dist/core.js",
"./table": "./dist/table.js"
},
"files": [
@ -17,24 +21,60 @@
"peerDependencies": {
"@chakra-ui/react": "^2.0.0",
"@emotion/react": "^11.0.0",
"@emotion/styled": "^11.0.0",
"@tanstack/react-query": "^5.0.0",
"@tanstack/react-table": "^8.21.3",
"axios": "^1.0.0",
"framer-motion": "^10.0.0 || ^11.0.0 || ^12.0.0",
"keycloak-js": "^26.0.0",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"react-icons": "^5.0.0"
},
"peerDependenciesMeta": {
"@chakra-ui/react": {
"optional": true
},
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@tanstack/react-query": {
"optional": true
},
"@tanstack/react-table": {
"optional": true
},
"axios": {
"optional": true
},
"framer-motion": {
"optional": true
},
"keycloak-js": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-icons": {
"optional": true
}
},
"devDependencies": {
"@chakra-ui/react": "^2.0.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@tanstack/react-query": "^5.0.0",
"@tanstack/react-table": "^8.21.3",
"@vitejs/plugin-react": "4.3.4",
"axios": "^1.17.0",
"framer-motion": "^12.0.0",
"keycloak-js": "^26.2.4",
"react": "^18.0.0",
"react-dom": "^18.0.0",
@ -42,4 +82,4 @@
"vite": "5.4.19"
},
"dependencies": {}
}
}

View File

@ -1,12 +1,12 @@
import { useEffect, useMemo, useState } from "react";
import { ChakraProvider, Spinner } from "@chakra-ui/react";
import { QueryClientProvider } from "@tanstack/react-query";
import { KeycloakProvider } from "../core/keycloak.jsx";
import { PermissionProvider } from "../core/permission.jsx";
import { UpdateChecker } from "../components/UpdateChecker.jsx";
import { configureApi } from "../core/api.js";
import { queryClient } from "../core/query.js";
import { Fonts, theme } from "../core/style.jsx";
import { KeycloakProvider } from "./keycloak.jsx";
import { PermissionProvider } from "./permission.jsx";
import { UpdateChecker } from "./UpdateChecker.jsx";
import { configureApi } from "./api.js";
import { queryClient } from "./query.js";
import { Fonts, theme } from "./style.jsx";
export function BiProvider({
children,

View File

@ -73,7 +73,7 @@ export function createApi({
mutationFn: async (data = {}) => {
const method = action.method || "post";
const requestUrl = resolveUrl(action.url, data);
const requestUrl = resolveUrl(action.url || url, data);
const payload = action.body
? action.body(data)

31
src/core/index.js Normal file
View File

@ -0,0 +1,31 @@
export { configureApi, getApi, api, fetcher } from "./api.js";
export { createApi } from "./createApi.js";
export {
KeycloakProvider,
initKeycloak,
isKeycloakInitialized,
isKeycloakAuthenticated,
getKeycloakToken,
updateKeycloakToken,
logoutKeycloak,
loginKeycloak
} from "./keycloak.jsx";
export {
PermissionProvider,
usePermission,
usePermissionsQuery,
Can,
AdminGuard,
PanelAccessGuard,
getCachedPermissions,
setCachedPermissions,
clearCachedPermissions,
createPermissionSet,
checkPermission,
permissionKeys,
getPermissions
} from "./permission.jsx";
export { BiProvider } from "./BiProvider.jsx";

View File

@ -1,40 +1 @@
export { toFaNumber, toFaDigits, toEnDigits } from "./utils/numbers.js";
export { LightPagination } from "./components/pagination/LightPagination.jsx";
export { Pagination } from "./components/pagination/Pagination.jsx";
export { SimplePagination } from "./components/pagination/SimplePagination.jsx";
export {
KeycloakProvider,
initKeycloak,
isKeycloakInitialized,
isKeycloakAuthenticated,
getKeycloakToken,
updateKeycloakToken,
logoutKeycloak,
loginKeycloak,
} from "./core/keycloak.jsx";
export {
PermissionProvider,
usePermission,
usePermissionsQuery,
Can,
AdminGuard,
PanelAccessGuard,
getCachedPermissions,
setCachedPermissions,
clearCachedPermissions,
createPermissionSet,
checkPermission,
permissionKeys,
getPermissions,
} from "./core/permission.jsx";
export { configureApi, getApi, api, fetcher } from "./core/api.js";
export { createApi } from "./core/createApi";
export { BiProvider } from "./provider/BiProvider.jsx";
export { Header } from "./components/Header.jsx";
export * from "./utils/index.js";

1
src/layout/index.js Normal file
View File

@ -0,0 +1 @@
export { Header } from "./Header.jsx";

View File

@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from "react";
import { Button, HStack, IconButton, Input, Text } from "@chakra-ui/react";
import { IoChevronBackOutline, IoChevronForwardOutline } from "react-icons/io5";
import { toEnDigits, toFaDigits } from "../../utils/numbers.js";
import { toEnDigits, toFaDigits } from "../utils/numbers.js";
export const LightPagination = ({
currentPage,

View File

@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from "react";
import { Button, HStack, IconButton, Input, Text } from "@chakra-ui/react";
import { IoChevronBackOutline, IoChevronForwardOutline } from "react-icons/io5";
import { toEnDigits, toFaDigits } from "../../utils/numbers.js";
import { toEnDigits, toFaDigits } from "../utils/numbers.js";
export const Pagination = ({
currentPage,

View File

@ -1,7 +1,7 @@
import { Button, HStack, IconButton, Input } from "@chakra-ui/react";
import { useCallback, useEffect, useRef, useState } from "react";
import { IoChevronBackOutline, IoChevronForwardOutline } from "react-icons/io5";
import { toEnDigits, toFaDigits } from "../../utils/numbers.js";
import { toEnDigits, toFaDigits } from "../utils/numbers.js";
export const SimplePagination = ({
currentPage,

3
src/pagination/index.js Normal file
View File

@ -0,0 +1,3 @@
export { LightPagination } from "./LightPagination.jsx";
export { Pagination } from "./Pagination.jsx";
export { SimplePagination } from "./SimplePagination.jsx";

View File

@ -14,7 +14,7 @@ import {
Tr,
VStack,
} from "@chakra-ui/react";
import { Pagination as UIKitPagination } from "../components/pagination/Pagination.jsx";
import { Pagination as UIKitPagination } from "../pagination/Pagination.jsx";
import { toFaDigits } from "../utils/numbers.js";
import {
flexRender,

1
src/utils/index.js Normal file
View File

@ -0,0 +1 @@
export { toFaNumber, toFaDigits, toEnDigits } from "./numbers.js";

View File

@ -9,6 +9,8 @@ const externalPackages = [
"@chakra-ui/react",
"@emotion/react",
"@emotion/styled",
"framer-motion",
"keycloak-js",
"axios",
@ -21,21 +23,33 @@ const externalPackages = [
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: {
index: path.resolve(process.cwd(), "src/index.js"),
utils: path.resolve(process.cwd(), "src/utils/index.js"),
pagination: path.resolve(process.cwd(), "src/pagination/index.js"),
layout: path.resolve(process.cwd(), "src/layout/index.js"),
core: path.resolve(process.cwd(), "src/core/index.js"),
table: path.resolve(process.cwd(), "src/table/index.js"),
},
formats: ["es"],
},
rollupOptions: {
external: (id) => {
return externalPackages.includes(id) || id.startsWith("react-icons/");
return (
externalPackages.includes(id) ||
id.startsWith("react-icons/") ||
id.startsWith("@emotion/")
);
},
output: {
entryFileNames: "[name].js",
chunkFileNames: "chunks/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash][extname]",
},
},
},