feat: Add Store link to admin header and notification activity log

- Add Store link to admin header (visible when customer SPA is enabled)
- Add storeUrl and customerSpaEnabled to WNW_CONFIG in Assets.php and StandaloneAdmin.php
- Update window.d.ts with new WNW_CONFIG properties
- Create ActivityLog.tsx component with search, filters, and pagination
- Add /notifications/logs API endpoint to NotificationsController
- Update Notifications.tsx to link to activity log page
- Add ActivityLog route to App.tsx
This commit is contained in:
Dwindi Ramadhana
2026-01-04 23:51:54 +07:00
parent 0f542ad452
commit 6c8cbb93e6
8 changed files with 366 additions and 33 deletions

View File

@@ -6,15 +6,15 @@ import { formatRelativeOrDate } from '@/lib/dates';
import { formatMoney } from '@/lib/currency';
import { ArrowLeft, Printer, ExternalLink, Loader2, Ticket, FileText, Pencil, RefreshCw } from 'lucide-react';
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { showErrorToast, showSuccessToast, getPageLoadErrorMessage } from '@/lib/errorHandling';
@@ -67,7 +67,7 @@ export default function OrderShow() {
function printInvoice() {
triggerPrint('invoice');
}
const [showRetryDialog, setShowRetryDialog] = useState(false);
const qrRef = useRef<HTMLCanvasElement | null>(null);
const q = useQuery({
@@ -90,16 +90,16 @@ export default function OrderShow() {
onMutate: async (nextStatus) => {
// Cancel outgoing refetches
await qc.cancelQueries({ queryKey: ['order', id] });
// Snapshot previous value
const previous = qc.getQueryData(['order', id]);
// Optimistically update
qc.setQueryData(['order', id], (old: any) => ({
...old,
status: nextStatus,
}));
return { previous };
},
onSuccess: () => {
@@ -183,8 +183,8 @@ export default function OrderShow() {
useEffect(() => {
if (!isPrintMode || !qrRef.current || !order) return;
(async () => {
try {
const mod = await import( 'qrcode' );
try {
const mod = await import('qrcode');
const QR = (mod as any).default || (mod as any);
const text = `ORDER:${order.number || id}`;
await QR.toCanvas(qrRef.current, text, { width: 128, margin: 1 });
@@ -208,9 +208,6 @@ export default function OrderShow() {
<button className="border rounded-md px-3 py-2 text-sm flex items-center gap-2 no-print" onClick={printLabel} title={__('Print shipping label')}>
<Ticket className="w-4 h-4" /> {__('Label')}
</button>
<Link className="border rounded-md px-3 py-2 text-sm flex items-center gap-2 no-print" to={`/orders`} title={__('Back to orders list')}>
<ExternalLink className="w-4 h-4" /> {__('Orders')}
</Link>
</div>
</div>
@@ -232,8 +229,8 @@ export default function OrderShow() {
<div className="px-4 py-3 border-b flex items-center justify-between">
<div className="font-medium">{__('Summary')}</div>
<div className="w-[180px] flex items-center gap-2">
<Select
value={order.status || ''}
<Select
value={order.status || ''}
onValueChange={(v) => handleStatusChange(v)}
disabled={statusMutation.isPending}
>
@@ -333,9 +330,9 @@ export default function OrderShow() {
<div className="opacity-60">{meta.label}</div>
<div className="font-medium">
{meta.key.includes('url') || meta.key.includes('redirect') ? (
<a
href={meta.value}
target="_blank"
<a
href={meta.value}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline flex items-center gap-1"
>
@@ -482,7 +479,7 @@ export default function OrderShow() {
<div className="flex items-start justify-between mb-6">
<div>
<div className="text-xl font-semibold">Invoice</div>
<div className="opacity-60">Order #{order.number} · {new Date((order.date_ts||0)*1000).toLocaleString()}</div>
<div className="opacity-60">Order #{order.number} · {new Date((order.date_ts || 0) * 1000).toLocaleString()}</div>
</div>
<div className="text-right">
<div className="font-medium">{siteTitle}</div>
@@ -508,7 +505,7 @@ export default function OrderShow() {
</tr>
</thead>
<tbody>
{(order.items || []).map((it:any) => (
{(order.items || []).map((it: any) => (
<tr key={it.id}>
<td className="py-1 pr-2">{it.name}</td>
<td className="py-1 px-2 text-right">×{it.qty}</td>
@@ -542,7 +539,7 @@ export default function OrderShow() {
</div>
<div className="text-xs opacity-60 mb-1">{__('Items')}</div>
<ul className="text-sm list-disc pl-4">
{(order.items||[]).map((it:any)=> (
{(order.items || []).map((it: any) => (
<li key={it.id}>{it.name} ×{it.qty}</li>
))}
</ul>