feat: 全局概览/产品管理/人员看板跳转统一改为新标签页打开
This commit is contained in:
@ -4,7 +4,6 @@ import {
|
||||
RefreshCw, Loader2, AlertCircle, ArrowRight, ArrowUp, ArrowDown, ArrowUpDown,
|
||||
Clock, MessageCircle, CheckCircle2, Users,
|
||||
} from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Radio, DatePicker, Drawer, Input } from "antd";
|
||||
import dayjs, { type Dayjs } from "dayjs";
|
||||
import {
|
||||
@ -79,10 +78,9 @@ function durationLabel(h: number) {
|
||||
}
|
||||
|
||||
function WipRow({ t }: { t: WipTask }) {
|
||||
const nav = useNavigate();
|
||||
const handleClick = () => {
|
||||
if (t.product_sn) {
|
||||
nav(`/admin/tasks?sn=${t.product_sn}`);
|
||||
window.open(`/admin/tasks?sn=${t.product_sn}`, "_blank");
|
||||
}
|
||||
};
|
||||
return (
|
||||
@ -162,11 +160,10 @@ function MsgRow({ m }: { m: ProductMessageItem }) {
|
||||
|
||||
// ─── 已完成明细项(卡片式) ─────────────────────────────
|
||||
function CompletedRow({ t }: { t: CompletedTask }) {
|
||||
const nav = useNavigate();
|
||||
const completedTime = t.completed_at ? dayjs(t.completed_at).format("YYYY-MM-DD HH:mm") : "";
|
||||
return (
|
||||
<div
|
||||
onClick={() => t.product_sn && nav(`/admin/tasks?sn=${t.product_sn}`)}
|
||||
onClick={() => t.product_sn && window.open(`/admin/tasks?sn=${t.product_sn}`, "_blank")}
|
||||
className="cursor-pointer rounded-lg border border-gray-100 bg-white px-4 py-3 transition-shadow hover:border-emerald-200 hover:shadow-md"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@ -191,12 +188,11 @@ function CompletedRow({ t }: { t: CompletedTask }) {
|
||||
|
||||
// ─── 被驳回明细项(卡片式) ──────────────────────────────
|
||||
function RejectedRow({ t }: { t: RejectedTask }) {
|
||||
const nav = useNavigate();
|
||||
const timeStr = t.rejected_at ? dayjs(t.rejected_at).format("YYYY-MM-DD HH:mm") : "";
|
||||
const isRework = t.kind === "rework";
|
||||
return (
|
||||
<div
|
||||
onClick={() => t.product_sn && nav(`/admin/tasks?sn=${t.product_sn}`)}
|
||||
onClick={() => t.product_sn && window.open(`/admin/tasks?sn=${t.product_sn}`, "_blank")}
|
||||
className="cursor-pointer rounded-lg border border-gray-100 bg-white px-4 py-3 transition-shadow hover:border-red-200 hover:shadow-md"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@ -286,8 +282,6 @@ export default function AdminDashboard() {
|
||||
const [opDetailLoading, setOpDetailLoading] = useState(false);
|
||||
const [opDetailTitle, setOpDetailTitle] = useState("");
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// ── 加载主数据 ──
|
||||
const loadData = useCallback((key: DateRangeKey, range: [Dayjs, Dayjs] | null) => {
|
||||
setLoading(true);
|
||||
@ -536,7 +530,7 @@ export default function AdminDashboard() {
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center justify-between border-t border-gray-100 pt-3">
|
||||
<button onClick={() => navigate("/notifications")}
|
||||
<button onClick={() => window.open("/notifications", "_blank")}
|
||||
className="flex items-center gap-1 text-xs text-gray-500 hover:text-blue-600">
|
||||
<Bell className="h-3.5 w-3.5" />
|
||||
系统通知 {stats.unread_notifications > 0 ? `(${stats.unread_notifications})` : ""} ↗
|
||||
@ -596,15 +590,15 @@ export default function AdminDashboard() {
|
||||
<div className="rounded-xl bg-white p-5 shadow-sm">
|
||||
<h3 className="mb-4 text-sm font-semibold text-gray-700">⚡ 快捷入口</h3>
|
||||
<div className="space-y-2">
|
||||
<button onClick={() => navigate("/admin/products")}
|
||||
<button onClick={() => window.open("/admin/products", "_blank")}
|
||||
className="flex w-full items-center justify-between rounded-lg bg-blue-50 px-4 py-3 text-left text-sm font-medium text-blue-700 hover:bg-blue-100 transition-colors">
|
||||
<span>📦 产品管理</span><ArrowRight className="h-4 w-4" />
|
||||
</button>
|
||||
<button onClick={() => navigate("/admin/tasks")}
|
||||
<button onClick={() => window.open("/admin/tasks", "_blank")}
|
||||
className="flex w-full items-center justify-between rounded-lg bg-purple-50 px-4 py-3 text-left text-sm font-medium text-purple-700 hover:bg-purple-100 transition-colors">
|
||||
<span>📋 任务管理</span><ArrowRight className="h-4 w-4" />
|
||||
</button>
|
||||
<button onClick={() => navigate("/notifications")}
|
||||
<button onClick={() => window.open("/notifications", "_blank")}
|
||||
className="flex w-full items-center justify-between rounded-lg bg-blue-50 px-4 py-3 text-left text-sm font-medium text-blue-700 hover:bg-blue-100 transition-colors">
|
||||
<span>🔔 通知中心</span><ArrowRight className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
/** 人员看板 — 实时在制品 + 历史工时台账 */
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Users, Package, ChevronDown, ChevronRight, Loader2, AlertCircle, RefreshCw, Clock, Download,
|
||||
Users, Package, ChevronDown, ChevronRight, Loader2, AlertCircle, Clock, Download,
|
||||
} from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Radio, DatePicker, Table, Button, AutoComplete, Modal, Timeline, Image, Select } from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import dayjs, { type Dayjs } from "dayjs";
|
||||
@ -83,8 +82,6 @@ export default function AdminPeoplePage() {
|
||||
}
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const load = () => {
|
||||
setLoading(true);
|
||||
fetchPeopleWorkload()
|
||||
@ -338,7 +335,7 @@ export default function AdminPeoplePage() {
|
||||
{w.devices.map((d) => (
|
||||
<div
|
||||
key={d.product_id}
|
||||
onClick={() => navigate(`/admin/tasks?sn=${d.serial_number}`)}
|
||||
onClick={() => window.open(`/admin/tasks?sn=${d.serial_number}`, "_blank")}
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg border border-gray-100 bg-white px-4 py-2.5 transition-shadow hover:border-blue-200 hover:shadow-md"
|
||||
>
|
||||
<span className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-bold ${d.task_status === "WIP" ? "bg-blue-100 text-blue-700" : "bg-amber-100 text-amber-700"}`}>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Printer, RefreshCw, Loader2, QrCode, Plus, Settings, X,
|
||||
Package, Hash, Tag, MapPin, Clock, Timer, CalendarDays,
|
||||
@ -29,7 +28,6 @@ interface ProductGroup { groupKey: string; products: ProductResponse[]; allInWar
|
||||
|
||||
export default function AdminProductsPage() {
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [products, setProducts] = useState<ProductResponse[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@ -229,7 +227,7 @@ export default function AdminProductsPage() {
|
||||
<div className="border-t border-gray-100 px-5 py-4">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{group.products.map(p => (
|
||||
<div key={p.id} onClick={() => navigate(`/admin/tasks?sn=${p.serial_number}`)} className="group relative flex cursor-pointer flex-col rounded-xl bg-white shadow-sm ring-1 ring-gray-100 transition-shadow hover:shadow-md">
|
||||
<div key={p.id} onClick={() => window.open(`/admin/tasks?sn=${p.serial_number}`, "_blank")} className="group relative flex cursor-pointer flex-col rounded-xl bg-white shadow-sm ring-1 ring-gray-100 transition-shadow hover:shadow-md">
|
||||
<div className="absolute top-2 right-2 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100 z-10">
|
||||
<button onClick={(e) => { e.stopPropagation(); openEdit(p); }} className="rounded-lg bg-white p-1.5 text-gray-400 shadow-sm hover:bg-blue-50 hover:text-blue-600"><Pencil className="h-3.5 w-3.5" /></button>
|
||||
<button onClick={(e) => { e.stopPropagation(); confirmDelete(p); }} className="rounded-lg bg-white p-1.5 text-gray-400 shadow-sm hover:bg-red-50 hover:text-red-500"><Trash2 className="h-3.5 w-3.5" /></button>
|
||||
|
||||
Reference in New Issue
Block a user