openxiangda 1.0.39 → 1.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +2 -2
  2. package/lib/cli.js +9 -1
  3. package/openxiangda-skills/SKILL.md +6 -3
  4. package/openxiangda-skills/references/best-practices.md +29 -5
  5. package/openxiangda-skills/references/component-guide.md +79 -45
  6. package/openxiangda-skills/references/forms/component-registry.md +33 -1
  7. package/openxiangda-skills/references/openxiangda-api.md +7 -0
  8. package/openxiangda-skills/references/permissions-settings.md +27 -4
  9. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +2 -2
  10. package/openxiangda-skills/skills/openxiangda-form/SKILL.md +9 -2
  11. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +5 -3
  12. package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +5 -0
  13. package/package.json +1 -1
  14. package/packages/sdk/dist/runtime/index.cjs +546 -0
  15. package/packages/sdk/dist/runtime/index.cjs.map +1 -1
  16. package/packages/sdk/dist/runtime/index.d.mts +13 -1
  17. package/packages/sdk/dist/runtime/index.d.ts +13 -1
  18. package/packages/sdk/dist/runtime/index.mjs +546 -0
  19. package/packages/sdk/dist/runtime/index.mjs.map +1 -1
  20. package/packages/sdk/src/build-source/scripts/build-forms.mjs +5 -1
  21. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-form.mdc +4 -0
  22. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-page.mdc +2 -0
  23. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +3 -0
  24. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-form.md +4 -0
  25. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-page.md +2 -0
  26. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +3 -0
  27. package/templates/sy-lowcode-app-workspace/AGENTS.md +5 -0
  28. package/templates/sy-lowcode-app-workspace/examples/best-practices/README.md +8 -0
  29. package/templates/sy-lowcode-app-workspace/examples/best-practices/catalog.json +32 -0
  30. package/templates/sy-lowcode-app-workspace/examples/best-practices/decision-guide.md +20 -0
  31. package/templates/sy-lowcode-app-workspace/examples/best-practices/design-style.md +48 -0
  32. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/glass-home-dashboard/App.tsx +8 -0
  33. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/glass-home-dashboard/GlassHomeDashboard.tsx +232 -0
  34. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/glass-home-dashboard/index.tsx +10 -0
  35. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/glass-home-dashboard/page.config.ts +14 -0
  36. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/glass-home-dashboard/styles.css +196 -0
  37. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mint-analytics-dashboard/App.tsx +8 -0
  38. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mint-analytics-dashboard/MintAnalyticsDashboard.tsx +279 -0
  39. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mint-analytics-dashboard/index.tsx +10 -0
  40. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mint-analytics-dashboard/page.config.ts +14 -0
  41. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mint-analytics-dashboard/styles.css +163 -0
  42. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/ops-monitor-dashboard/App.tsx +8 -0
  43. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/ops-monitor-dashboard/OpsMonitorDashboard.tsx +306 -0
  44. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/ops-monitor-dashboard/index.tsx +10 -0
  45. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/ops-monitor-dashboard/page.config.ts +14 -0
  46. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/ops-monitor-dashboard/styles.css +248 -0
  47. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/work-order-list-drawer/App.tsx +8 -0
  48. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/work-order-list-drawer/WorkOrderListDrawerPage.tsx +371 -0
  49. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/work-order-list-drawer/index.tsx +10 -0
  50. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/work-order-list-drawer/page.config.ts +14 -0
  51. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/work-order-list-drawer/styles.css +182 -0
  52. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/admin-ui-templates/DashboardPrimitives.tsx +832 -0
  53. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/admin-ui-templates/chartOptions.ts +140 -0
  54. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/admin-ui-templates/sampleData.ts +466 -0
  55. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/admin-ui-templates/styles.css +874 -0
  56. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/admin-ui-templates/types.ts +150 -0
  57. package/templates/sy-lowcode-app-workspace/src/forms/README.md +4 -0
  58. package/templates/sy-lowcode-app-workspace/src/main.tsx +4 -0
@@ -0,0 +1,371 @@
1
+ import {
2
+ DownloadOutlined,
3
+ PlusOutlined,
4
+ ReloadOutlined,
5
+ } from "@ant-design/icons";
6
+ import {
7
+ App as AntdApp,
8
+ Avatar,
9
+ Button,
10
+ DatePicker,
11
+ Input,
12
+ Select,
13
+ Space,
14
+ Table,
15
+ Tag,
16
+ } from "antd";
17
+ import type { TableProps } from "antd";
18
+ import type { Key } from "react";
19
+ import { useMemo, useState } from "react";
20
+
21
+ import { QueryState } from "@/shared/components/QueryState";
22
+ import {
23
+ AdminShell,
24
+ AdminTopbar,
25
+ IconGlyph,
26
+ Panel,
27
+ PriorityTag,
28
+ WorkOrderDrawer,
29
+ WorkOrderStatusTag,
30
+ } from "@/shared/components/admin-ui-templates/DashboardPrimitives";
31
+ import {
32
+ workOrderNavItems,
33
+ workOrders,
34
+ } from "@/shared/components/admin-ui-templates/sampleData";
35
+ import type {
36
+ DrawerMode,
37
+ WorkOrderPriority,
38
+ WorkOrderRecord,
39
+ WorkOrderStatus,
40
+ } from "@/shared/components/admin-ui-templates/types";
41
+
42
+ type StatusFilter = "all" | WorkOrderStatus;
43
+ type ViewState = "ready" | "loading" | "empty" | "error";
44
+
45
+ const statusOptions: Array<{ value: StatusFilter; label: string }> = [
46
+ { value: "all", label: "全部" },
47
+ { value: "pending", label: "待处理" },
48
+ { value: "processing", label: "处理中" },
49
+ { value: "done", label: "已完成" },
50
+ { value: "cancelled", label: "已取消" },
51
+ ];
52
+
53
+ const priorityOptions: Array<{ value: "all" | WorkOrderPriority; label: string }> = [
54
+ { value: "all", label: "全部优先级" },
55
+ { value: "high", label: "高" },
56
+ { value: "medium", label: "中" },
57
+ { value: "low", label: "低" },
58
+ ];
59
+
60
+ export function WorkOrderListDrawerPage() {
61
+ const { message } = AntdApp.useApp();
62
+ const [activeNav, setActiveNav] = useState("ticket-list");
63
+ const [keyword, setKeyword] = useState("");
64
+ const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
65
+ const [priorityFilter, setPriorityFilter] = useState<"all" | WorkOrderPriority>("all");
66
+ const [viewState, setViewState] = useState<ViewState>("ready");
67
+ const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
68
+ const [drawerMode, setDrawerMode] = useState<DrawerMode | null>(null);
69
+ const [selectedRecord, setSelectedRecord] = useState<WorkOrderRecord | null>(null);
70
+
71
+ const filteredRows = useMemo(() => {
72
+ const nextRows = workOrders.filter((item) => {
73
+ const keywordMatched =
74
+ !keyword ||
75
+ item.title.includes(keyword) ||
76
+ item.id.includes(keyword) ||
77
+ item.description.includes(keyword);
78
+ const statusMatched =
79
+ statusFilter === "all" ? true : item.status === statusFilter;
80
+ const priorityMatched =
81
+ priorityFilter === "all" ? true : item.priority === priorityFilter;
82
+ return keywordMatched && statusMatched && priorityMatched;
83
+ });
84
+ return viewState === "empty" ? [] : nextRows;
85
+ }, [keyword, priorityFilter, statusFilter, viewState]);
86
+
87
+ const statusStats = useMemo(
88
+ () => [
89
+ {
90
+ key: "all" as StatusFilter,
91
+ label: "全部工单",
92
+ value: workOrders.length,
93
+ icon: "ticket" as const,
94
+ tone: "blue" as const,
95
+ },
96
+ {
97
+ key: "pending" as StatusFilter,
98
+ label: "待处理",
99
+ value: workOrders.filter((item) => item.status === "pending").length,
100
+ icon: "task" as const,
101
+ tone: "orange" as const,
102
+ },
103
+ {
104
+ key: "processing" as StatusFilter,
105
+ label: "处理中",
106
+ value: workOrders.filter((item) => item.status === "processing").length,
107
+ icon: "monitor" as const,
108
+ tone: "blue" as const,
109
+ },
110
+ {
111
+ key: "done" as StatusFilter,
112
+ label: "已完成",
113
+ value: workOrders.filter((item) => item.status === "done").length,
114
+ icon: "approval" as const,
115
+ tone: "green" as const,
116
+ },
117
+ {
118
+ key: "cancelled" as StatusFilter,
119
+ label: "已取消",
120
+ value: workOrders.filter((item) => item.status === "cancelled").length,
121
+ icon: "setting" as const,
122
+ tone: "slate" as const,
123
+ },
124
+ ],
125
+ [],
126
+ );
127
+
128
+ const columns: TableProps<WorkOrderRecord>["columns"] = [
129
+ {
130
+ title: "工单编号",
131
+ dataIndex: "id",
132
+ width: 150,
133
+ fixed: "left",
134
+ },
135
+ {
136
+ title: "标题",
137
+ dataIndex: "title",
138
+ width: 220,
139
+ ellipsis: true,
140
+ },
141
+ {
142
+ title: "状态",
143
+ dataIndex: "status",
144
+ width: 100,
145
+ render: (status: WorkOrderStatus) => <WorkOrderStatusTag status={status} />,
146
+ },
147
+ {
148
+ title: "优先级",
149
+ dataIndex: "priority",
150
+ width: 90,
151
+ render: (priority: WorkOrderPriority) => <PriorityTag priority={priority} />,
152
+ },
153
+ {
154
+ title: "负责人",
155
+ dataIndex: "owner",
156
+ width: 110,
157
+ render: (_value: string, record) => (
158
+ <Space size={6}>
159
+ <Avatar size="small">{record.avatar}</Avatar>
160
+ {record.owner}
161
+ </Space>
162
+ ),
163
+ },
164
+ {
165
+ title: "所属部门",
166
+ dataIndex: "department",
167
+ width: 110,
168
+ },
169
+ {
170
+ title: "创建时间",
171
+ dataIndex: "createdAt",
172
+ width: 150,
173
+ },
174
+ {
175
+ title: "更新时间",
176
+ dataIndex: "updatedAt",
177
+ width: 150,
178
+ },
179
+ {
180
+ title: "操作",
181
+ key: "actions",
182
+ width: 150,
183
+ fixed: "right",
184
+ render: (_value: unknown, record) => (
185
+ <Space size={6}>
186
+ <Button type="link" size="small" onClick={() => openDrawer("detail", record)}>
187
+ 查看
188
+ </Button>
189
+ <Button type="link" size="small" onClick={() => openDrawer("edit", record)}>
190
+ 编辑
191
+ </Button>
192
+ <Button type="link" size="small" onClick={() => openDrawer("process", record)}>
193
+ 处理
194
+ </Button>
195
+ </Space>
196
+ ),
197
+ },
198
+ ];
199
+
200
+ function openDrawer(mode: DrawerMode, record: WorkOrderRecord | null) {
201
+ setDrawerMode(mode);
202
+ setSelectedRecord(record);
203
+ }
204
+
205
+ function refresh() {
206
+ setViewState("loading");
207
+ window.setTimeout(() => {
208
+ setViewState("ready");
209
+ message.success("工单列表已刷新");
210
+ }, 500);
211
+ }
212
+
213
+ function submitDrawer() {
214
+ setDrawerMode(null);
215
+ message.success("操作已提交");
216
+ }
217
+
218
+ return (
219
+ <AdminShell
220
+ variant="work"
221
+ activeKey={activeNav}
222
+ navItems={workOrderNavItems}
223
+ brandTitle="OpenXiangda"
224
+ brandSubtitle="运营管理后台"
225
+ topbar={
226
+ <AdminTopbar
227
+ title="工作台 / 工单管理 / 工单列表"
228
+ searchPlaceholder="搜索功能、工单、人员"
229
+ userName="管理员"
230
+ extra={
231
+ <Select<ViewState>
232
+ size="small"
233
+ value={viewState}
234
+ onChange={setViewState}
235
+ options={[
236
+ { value: "ready", label: "正常" },
237
+ { value: "loading", label: "加载" },
238
+ { value: "empty", label: "空态" },
239
+ { value: "error", label: "错误" },
240
+ ]}
241
+ />
242
+ }
243
+ />
244
+ }
245
+ onNavChange={setActiveNav}
246
+ >
247
+ <Panel className="bp-work-filter-panel">
248
+ <div className="bp-work-filter-grid">
249
+ <label>
250
+ <span>关键词</span>
251
+ <Input.Search
252
+ allowClear
253
+ value={keyword}
254
+ placeholder="请输入标题/编号/内容"
255
+ onChange={(event) => setKeyword(event.target.value)}
256
+ onSearch={setKeyword}
257
+ />
258
+ </label>
259
+ <label>
260
+ <span>状态</span>
261
+ <Select<StatusFilter>
262
+ value={statusFilter}
263
+ onChange={setStatusFilter}
264
+ options={statusOptions}
265
+ />
266
+ </label>
267
+ <label>
268
+ <span>优先级</span>
269
+ <Select<"all" | WorkOrderPriority>
270
+ value={priorityFilter}
271
+ onChange={setPriorityFilter}
272
+ options={priorityOptions}
273
+ />
274
+ </label>
275
+ <label>
276
+ <span>创建时间</span>
277
+ <DatePicker.RangePicker />
278
+ </label>
279
+ <div className="bp-work-filter-actions">
280
+ <Button onClick={() => setViewState("ready")}>重置</Button>
281
+ <Button type="primary" onClick={() => setViewState("ready")}>
282
+ 筛选
283
+ </Button>
284
+ </div>
285
+ </div>
286
+ </Panel>
287
+
288
+ <div className="bp-work-toolbar">
289
+ <Space wrap>
290
+ <Button
291
+ type="primary"
292
+ icon={<PlusOutlined />}
293
+ onClick={() => openDrawer("create", null)}
294
+ >
295
+ 新建记录
296
+ </Button>
297
+ <Button>批量操作</Button>
298
+ <Button icon={<DownloadOutlined />}>导出</Button>
299
+ <Button icon={<ReloadOutlined />} loading={viewState === "loading"} onClick={refresh}>
300
+ 刷新
301
+ </Button>
302
+ </Space>
303
+ <span>已选择 {selectedRowKeys.length} 项</span>
304
+ </div>
305
+
306
+ <section className="bp-work-stats">
307
+ {statusStats.map((item) => (
308
+ <button
309
+ key={item.key}
310
+ type="button"
311
+ className={statusFilter === item.key ? "is-active" : ""}
312
+ onClick={() => {
313
+ setStatusFilter(item.key);
314
+ setViewState("ready");
315
+ }}
316
+ >
317
+ <div>
318
+ <span>{item.label}</span>
319
+ <strong>{item.value}</strong>
320
+ <small>较昨日 +5.2%</small>
321
+ </div>
322
+ <IconGlyph name={item.icon} tone={item.tone} />
323
+ </button>
324
+ ))}
325
+ </section>
326
+
327
+ {viewState === "error" ? (
328
+ <Panel className="bp-work-state-panel">
329
+ <QueryState
330
+ error="模拟列表接口异常,请点击重试恢复。"
331
+ onRetry={() => setViewState("ready")}
332
+ />
333
+ </Panel>
334
+ ) : (
335
+ <Panel className="bp-work-table-panel">
336
+ {viewState === "empty" ? (
337
+ <QueryState empty />
338
+ ) : null}
339
+ <Table<WorkOrderRecord>
340
+ rowKey="id"
341
+ size="small"
342
+ columns={columns}
343
+ dataSource={filteredRows}
344
+ loading={viewState === "loading"}
345
+ rowSelection={{
346
+ selectedRowKeys,
347
+ onChange: (keys) => setSelectedRowKeys(keys),
348
+ }}
349
+ scroll={{ x: 1200 }}
350
+ pagination={{
351
+ pageSize: 8,
352
+ showSizeChanger: true,
353
+ showTotal: (total) => `共 ${total} 条`,
354
+ }}
355
+ onRow={(record) => ({
356
+ onDoubleClick: () => openDrawer("detail", record),
357
+ })}
358
+ />
359
+ </Panel>
360
+ )}
361
+
362
+ <WorkOrderDrawer
363
+ mode={drawerMode}
364
+ record={selectedRecord}
365
+ onClose={() => setDrawerMode(null)}
366
+ onSubmit={submitDrawer}
367
+ onSwitchMode={setDrawerMode}
368
+ />
369
+ </AdminShell>
370
+ );
371
+ }
@@ -0,0 +1,10 @@
1
+ import { createReactPage } from "openxiangda/runtime";
2
+
3
+ import App from "./App";
4
+
5
+ const page = createReactPage(App);
6
+
7
+ export const mount = page.mount;
8
+ export const update = page.update;
9
+ export const unmount = page.unmount;
10
+ export default page;
@@ -0,0 +1,14 @@
1
+ import { definePageConfig } from "@/types/app-workspace.types";
2
+
3
+ export default definePageConfig({
4
+ code: "work_order_list_drawer",
5
+ name: "工单列表抽屉模板",
6
+ description: "后台管理 CRUD 列表页与右侧覆盖式抽屉模板",
7
+ route: { pathKey: "work_order_list_drawer" },
8
+ entry: {
9
+ mode: "app-shell",
10
+ hidePlatformNav: true,
11
+ defaultRoute: "ticket-list",
12
+ },
13
+ menu: { name: "工单列表抽屉模板" },
14
+ });
@@ -0,0 +1,182 @@
1
+ .bp-work-filter-panel {
2
+ padding: 16px;
3
+ }
4
+
5
+ .bp-work-filter-grid {
6
+ display: grid;
7
+ grid-template-columns: minmax(220px, 1.2fr) 160px 160px minmax(280px, 1.1fr) auto;
8
+ gap: 14px;
9
+ align-items: end;
10
+ }
11
+
12
+ .bp-work-filter-grid label {
13
+ display: grid;
14
+ gap: 7px;
15
+ min-width: 0;
16
+ }
17
+
18
+ .bp-work-filter-grid label > span {
19
+ color: #334155;
20
+ font-size: 13px;
21
+ font-weight: 600;
22
+ }
23
+
24
+ .bp-work-filter-actions {
25
+ display: flex;
26
+ gap: 8px;
27
+ justify-content: flex-end;
28
+ }
29
+
30
+ .bp-work-toolbar {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: space-between;
34
+ gap: 16px;
35
+ }
36
+
37
+ .bp-work-toolbar > span {
38
+ color: #64748b;
39
+ font-size: 13px;
40
+ }
41
+
42
+ .bp-work-stats {
43
+ display: grid;
44
+ grid-template-columns: repeat(5, minmax(0, 1fr));
45
+ gap: 14px;
46
+ }
47
+
48
+ .bp-work-stats button {
49
+ display: grid;
50
+ grid-template-columns: minmax(0, 1fr) 52px;
51
+ gap: 12px;
52
+ align-items: center;
53
+ min-height: 104px;
54
+ padding: 16px;
55
+ border: 1px solid #e5eaf2;
56
+ border-radius: 6px;
57
+ background: #ffffff;
58
+ color: #334155;
59
+ text-align: left;
60
+ cursor: pointer;
61
+ transition:
62
+ border 160ms ease,
63
+ box-shadow 160ms ease,
64
+ transform 160ms ease;
65
+ }
66
+
67
+ .bp-work-stats button:hover,
68
+ .bp-work-stats button.is-active {
69
+ border-color: rgba(37, 99, 235, 0.42);
70
+ box-shadow: 0 10px 26px rgba(37, 99, 235, 0.1);
71
+ transform: translateY(-1px);
72
+ }
73
+
74
+ .bp-work-stats span,
75
+ .bp-work-stats small {
76
+ display: block;
77
+ overflow: hidden;
78
+ text-overflow: ellipsis;
79
+ white-space: nowrap;
80
+ }
81
+
82
+ .bp-work-stats span {
83
+ color: #64748b;
84
+ font-size: 13px;
85
+ }
86
+
87
+ .bp-work-stats strong {
88
+ display: block;
89
+ margin: 8px 0;
90
+ color: #0f172a;
91
+ font-size: 25px;
92
+ line-height: 1;
93
+ }
94
+
95
+ .bp-work-stats small {
96
+ color: #10b981;
97
+ font-size: 12px;
98
+ }
99
+
100
+ .bp-work-stats .bp-admin-icon {
101
+ width: 52px;
102
+ height: 52px;
103
+ border-radius: 50%;
104
+ font-size: 22px;
105
+ }
106
+
107
+ .bp-work-stats .bp-admin-tone-blue {
108
+ color: #2563eb;
109
+ background: #dbeafe;
110
+ }
111
+
112
+ .bp-work-stats .bp-admin-tone-orange {
113
+ color: #f97316;
114
+ background: #ffedd5;
115
+ }
116
+
117
+ .bp-work-stats .bp-admin-tone-green,
118
+ .bp-work-stats .bp-admin-tone-mint {
119
+ color: #16a34a;
120
+ background: #dcfce7;
121
+ }
122
+
123
+ .bp-work-stats .bp-admin-tone-purple {
124
+ color: #7c3aed;
125
+ background: #ede9fe;
126
+ }
127
+
128
+ .bp-work-stats .bp-admin-tone-slate {
129
+ color: #64748b;
130
+ background: #f1f5f9;
131
+ }
132
+
133
+ .bp-work-table-panel {
134
+ padding: 0;
135
+ overflow: hidden;
136
+ }
137
+
138
+ .bp-work-table-panel .ant-table-wrapper {
139
+ padding: 0;
140
+ }
141
+
142
+ .bp-work-table-panel .ant-table-thead > tr > th {
143
+ color: #475569;
144
+ font-size: 13px;
145
+ font-weight: 700;
146
+ background: #f8fafc;
147
+ }
148
+
149
+ .bp-work-table-panel .ant-table-tbody > tr > td {
150
+ color: #334155;
151
+ font-size: 13px;
152
+ }
153
+
154
+ .bp-work-table-panel .ant-pagination {
155
+ padding: 0 16px 14px;
156
+ }
157
+
158
+ .bp-work-state-panel {
159
+ min-height: 320px;
160
+ }
161
+
162
+ @media (max-width: 1280px) {
163
+ .bp-work-filter-grid {
164
+ grid-template-columns: 1fr 1fr;
165
+ }
166
+
167
+ .bp-work-stats {
168
+ grid-template-columns: repeat(3, minmax(0, 1fr));
169
+ }
170
+ }
171
+
172
+ @media (max-width: 760px) {
173
+ .bp-work-filter-grid,
174
+ .bp-work-stats {
175
+ grid-template-columns: 1fr;
176
+ }
177
+
178
+ .bp-work-toolbar {
179
+ align-items: stretch;
180
+ flex-direction: column;
181
+ }
182
+ }