creat action and ci cd
CI/CD Pipeline / build-and-deploy (push) Has been cancelled
Details
CI/CD Pipeline / build-and-deploy (push) Has been cancelled
Details
This commit is contained in:
parent
16cd65c4da
commit
3e227089fa
|
|
@ -0,0 +1,51 @@
|
|||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main # اگر اسم برنچ اصلیت master هست، این رو بکن master
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
didvan/basa_houshan_panel:latest
|
||||
didvan/basa_houshan_panel:${{ github.sha }}
|
||||
|
||||
- name: Deploy to Server
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.SERVER_IP_BASA_PANEL }}
|
||||
username: ${{ secrets.SERVER_USERNAME }}
|
||||
# اگر از کلید SSH استفاده میکنی این خط بمونه:
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
# اگر از پسورد استفاده میکنی، خط بالا رو پاک کن و خط پایین رو از کامنت دربیار:
|
||||
# password: ${{ secrets.SERVER_PASSWORD }}
|
||||
script: |
|
||||
# !!! بسیار مهم: مسیر دقیق پوشه پروژه روی سرورت رو اینجا جایگزین کن !!!
|
||||
cd /home/ubuntu/app/panel-houshan
|
||||
|
||||
# لاگین به داکر هاب روی سرور (برای دانلود ایمیجهای Private)
|
||||
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
|
||||
# دانلود ایمیج جدید و ریاستارت فقط همون کانتینر
|
||||
docker compose pull app
|
||||
docker compose up -d app
|
||||
|
||||
# پاکسازی ایمیجهای قدیمی
|
||||
docker image prune -f
|
||||
|
|
@ -9,91 +9,110 @@ import { Receipt, SearchNormal1 } from "iconsax-react";
|
|||
|
||||
import { API_ROUTES, request } from "@/services/api";
|
||||
import { Container } from "@/components/container";
|
||||
import type { Transaction } from "@/types/transaction";
|
||||
import type { Transaction, TransactionListResponse } from "@/types/transaction";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
export default function TransactionsPage() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
// filters
|
||||
const [search, setSearch] = useState("");
|
||||
const [typeFilter, setTypeFilter] = useState<string | undefined>(undefined);
|
||||
const [statusFilter, setStatusFilter] = useState<string | undefined>(undefined);
|
||||
|
||||
const fetchData = async () => {
|
||||
const fetchData = async (): Promise<TransactionListResponse> => {
|
||||
const res = await request.get(API_ROUTES.TRANSACTION.ALL, {
|
||||
page,
|
||||
limit: 10,
|
||||
q: search,
|
||||
type: typeFilter,
|
||||
size: 10,
|
||||
...(search ? { q: search } : {}),
|
||||
...(typeFilter ? { type: typeFilter } : {}),
|
||||
...(statusFilter ? { status: statusFilter } : {}),
|
||||
});
|
||||
|
||||
setTotal(res.data.total_count || 0);
|
||||
return res.data.transactions as Transaction[];
|
||||
return res.data as TransactionListResponse;
|
||||
};
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["transactions", page, search, typeFilter],
|
||||
queryKey: ["transactions", page, search, typeFilter, statusFilter],
|
||||
queryFn: fetchData,
|
||||
});
|
||||
|
||||
const columns: ColumnsType<Transaction> = [
|
||||
{
|
||||
title: "#",
|
||||
dataIndex: "id",
|
||||
key: "id",
|
||||
width: 60,
|
||||
render: (id: number) => <span className="text-gray-400 text-xs">{id}</span>,
|
||||
},
|
||||
{
|
||||
title: "کاربر",
|
||||
dataIndex: "user",
|
||||
key: "user",
|
||||
render: (user) =>
|
||||
user ? (
|
||||
render: (user: Transaction["user"]) => (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<Link
|
||||
href={`/user/${user.username}`}
|
||||
className="text-blue-600 font-medium hover:underline"
|
||||
href={`/user/${user.id}`}
|
||||
className="text-blue-600 font-medium hover:underline text-sm"
|
||||
>
|
||||
{user.username}
|
||||
{user.name}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-gray-400">سیستمی / حذف شده</span>
|
||||
),
|
||||
<span className="text-gray-400 text-xs" dir="ltr">{user.mobile_number}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "مقدار",
|
||||
title: "اعتبار",
|
||||
dataIndex: "credit",
|
||||
key: "credit",
|
||||
render: (credit: number) => (
|
||||
<span className="font-semibold text-indigo-600">{credit.toLocaleString()}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "مبلغ (ریال)",
|
||||
dataIndex: "amount",
|
||||
key: "amount",
|
||||
render: (amount: number) => (
|
||||
<span className="font-bold">{amount.toLocaleString()}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "شماره پیگیری",
|
||||
dataIndex: "ref_id",
|
||||
key: "ref_id",
|
||||
render: (ref_id: string) => (
|
||||
<span dir="ltr" className="text-xs font-mono text-gray-600">{ref_id || "—"}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "نوع",
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: string) => {
|
||||
let color: "default" | "green" | "blue" | "gold" = "default";
|
||||
let text = type;
|
||||
|
||||
switch (type) {
|
||||
case "gift":
|
||||
color = "green";
|
||||
text = "هدیه";
|
||||
break;
|
||||
case "credit":
|
||||
color = "blue";
|
||||
text = "اعتبار";
|
||||
break;
|
||||
case "free":
|
||||
color = "gold";
|
||||
text = "رایگان";
|
||||
break;
|
||||
}
|
||||
|
||||
return <Tag color={color}>{text}</Tag>;
|
||||
const map: Record<string, { color: string; label: string }> = {
|
||||
basa: { color: "purple", label: "باسا" },
|
||||
gift: { color: "green", label: "هدیه" },
|
||||
credit: { color: "blue", label: "اعتبار" },
|
||||
free: { color: "gold", label: "رایگان" },
|
||||
};
|
||||
const info = map[type] ?? { color: "default", label: type };
|
||||
return <Tag color={info.color}>{info.label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "توضیحات",
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
ellipsis: true,
|
||||
title: "وضعیت",
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
render: (status: string) => {
|
||||
const map: Record<string, { color: string; label: string }> = {
|
||||
success: { color: "success", label: "موفق" },
|
||||
failed: { color: "error", label: "ناموفق" },
|
||||
pending: { color: "warning", label: "در انتظار" },
|
||||
};
|
||||
const info = map[status] ?? { color: "default", label: status };
|
||||
return <Tag color={info.color}>{info.label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "تاریخ",
|
||||
|
|
@ -127,7 +146,7 @@ export default function TransactionsPage() {
|
|||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<Input
|
||||
prefix={<SearchNormal1 size={18} className="text-gray-400" />}
|
||||
placeholder="جستجو (نام کاربری، توضیحات...)"
|
||||
placeholder="جستجو (نام، شماره موبایل، شماره پیگیری...)"
|
||||
className="md:w-1/3"
|
||||
allowClear
|
||||
value={search}
|
||||
|
|
@ -137,22 +156,36 @@ export default function TransactionsPage() {
|
|||
<Select
|
||||
placeholder="نوع تراکنش"
|
||||
allowClear
|
||||
className="md:w-48"
|
||||
className="md:w-40"
|
||||
value={typeFilter}
|
||||
onChange={(value) => setTypeFilter(value)}
|
||||
onChange={(value) => { setTypeFilter(value); setPage(1); }}
|
||||
>
|
||||
<Option value="gift">هدیه (Gift)</Option>
|
||||
<Option value="credit">اعتبار (Credit)</Option>
|
||||
<Option value="free">رایگان (Free)</Option>
|
||||
<Option value="basa">باسا</Option>
|
||||
<Option value="gift">هدیه</Option>
|
||||
<Option value="credit">اعتبار</Option>
|
||||
<Option value="free">رایگان</Option>
|
||||
</Select>
|
||||
|
||||
{(search || typeFilter) && (
|
||||
<Select
|
||||
placeholder="وضعیت"
|
||||
allowClear
|
||||
className="md:w-36"
|
||||
value={statusFilter}
|
||||
onChange={(value) => { setStatusFilter(value); setPage(1); }}
|
||||
>
|
||||
<Option value="success">موفق</Option>
|
||||
<Option value="failed">ناموفق</Option>
|
||||
<Option value="pending">در انتظار</Option>
|
||||
</Select>
|
||||
|
||||
{(search || typeFilter || statusFilter) && (
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
onClick={() => {
|
||||
setSearch("");
|
||||
setTypeFilter(undefined);
|
||||
setStatusFilter(undefined);
|
||||
setPage(1);
|
||||
}}
|
||||
>
|
||||
|
|
@ -165,16 +198,17 @@ export default function TransactionsPage() {
|
|||
{/* Table */}
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
dataSource={data?.items}
|
||||
rowKey="id"
|
||||
loading={isLoading}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize: 10,
|
||||
total,
|
||||
pageSize: data?.size ?? 10,
|
||||
total: data?.total ?? 0,
|
||||
onChange: setPage,
|
||||
showSizeChanger: false,
|
||||
position: ["bottomCenter"],
|
||||
showTotal: (total) => `مجموع ${total} تراکنش`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const API_ROUTES = {
|
|||
ONE: (id: string) => `/admin/ticket/${id}`,
|
||||
},
|
||||
TRANSACTION: {
|
||||
ALL: "/admin/transaction/", // اندپوینت دریافت همه تراکنشها
|
||||
ALL: "/admin/payment/", // اندپوینت دریافت همه تراکنشها
|
||||
},
|
||||
USER: {
|
||||
ALL: "/admin/user/",
|
||||
|
|
|
|||
|
|
@ -1,17 +1,25 @@
|
|||
// panel-houshan/src/types/transaction.ts
|
||||
|
||||
export type TransactionType = "gift" | "credit" | "free";
|
||||
export type TransactionStatus = "success" | "failed" | "pending" | string;
|
||||
|
||||
export type Transaction = {
|
||||
id: string;
|
||||
id: number;
|
||||
credit: number;
|
||||
amount: number;
|
||||
type: TransactionType;
|
||||
description: string;
|
||||
ref_id: string;
|
||||
type: string;
|
||||
status: TransactionStatus;
|
||||
created_at: string;
|
||||
// این فیلد برای نمایش نام کاربر در لیست کلی اضافه میشود
|
||||
user?: {
|
||||
user: {
|
||||
id: string;
|
||||
name: string;
|
||||
mobile_number: string;
|
||||
username: string;
|
||||
name?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TransactionListResponse = {
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
total_pages: number;
|
||||
items: Transaction[];
|
||||
};
|
||||
Loading…
Reference in New Issue