Search bugs are fixed. Added Global searching parameter
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { apiFetch } from '../api';
|
||||
import { apiFetch } from '../api';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
Search,
|
||||
@@ -104,10 +104,10 @@ const Orders: React.FC = () => {
|
||||
|
||||
const fetchProducts = async () => {
|
||||
try {
|
||||
const response = await apiFetch(`http://${window.location.hostname}:8080/api/products`);
|
||||
const response = await apiFetch(`http://${window.location.hostname}:8080/api/products?size=1000`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setAllProducts(data);
|
||||
setAllProducts(Array.isArray(data) ? data : (data.content || []));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching products:', error);
|
||||
@@ -580,13 +580,15 @@ const Orders: React.FC = () => {
|
||||
|
||||
{/* Interactive Controls Overlay for active orders */}
|
||||
<div className="absolute right-6 top-1/2 -translate-y-1/2 flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleEditOrder}
|
||||
className="p-2 bg-white rounded-lg border border-slate-200 text-indigo-500 shadow-sm hover:border-indigo-400 transition-all active:scale-95"
|
||||
title="Edit Order"
|
||||
>
|
||||
<Edit2 size={16} />
|
||||
</button>
|
||||
{selectedOrder.status.toUpperCase() !== 'COMPLETED' && selectedOrder.status.toUpperCase() !== 'DELIVERED' && (
|
||||
<button
|
||||
onClick={handleEditOrder}
|
||||
className="p-2 bg-white rounded-lg border border-slate-200 text-indigo-500 shadow-sm hover:border-indigo-400 transition-all active:scale-95 cursor-pointer"
|
||||
title="Edit Order"
|
||||
>
|
||||
<Edit2 size={16} />
|
||||
</button>
|
||||
)}
|
||||
<div className="relative action-menu-container">
|
||||
<button
|
||||
onClick={() => setShowActionMenu(!showActionMenu)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { apiFetch } from '../api';
|
||||
import { apiFetch } from '../api';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
User,
|
||||
@@ -34,7 +34,7 @@ const Settings = () => {
|
||||
|
||||
// Team State
|
||||
const [admins, setAdmins] = useState<any[]>([]);
|
||||
const [newAdmin, setNewAdmin] = useState({ name: '', email: '', password: '' });
|
||||
const [newAdmin, setNewAdmin] = useState({ name: '', email: '', password: '', role: 'MASTER' });
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
|
||||
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
||||
@@ -48,11 +48,19 @@ const Settings = () => {
|
||||
|
||||
const fetchAdmins = async () => {
|
||||
try {
|
||||
const response = await apiFetch('/api/system/admins');
|
||||
const data = await response.json();
|
||||
setAdmins(data);
|
||||
const [adminsRes, managersRes] = await Promise.all([
|
||||
apiFetch('/api/system/admins'),
|
||||
apiFetch('/api/system/managers')
|
||||
]);
|
||||
const adminsData = await adminsRes.json();
|
||||
const managersData = await managersRes.json();
|
||||
const merged = [
|
||||
...adminsData.map((a: any) => ({ ...a, role: 'MASTER' })),
|
||||
...managersData.map((m: any) => ({ ...m, role: 'MANAGER' }))
|
||||
];
|
||||
setAdmins(merged);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch admins');
|
||||
console.error('Failed to fetch team members:', err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -103,22 +111,36 @@ const Settings = () => {
|
||||
e.preventDefault();
|
||||
setStatus('loading');
|
||||
try {
|
||||
const response = await apiFetch('/api/system/admins', {
|
||||
const endpoint = newAdmin.role === 'MANAGER' ? '/api/system/managers' : '/api/system/admins';
|
||||
const payload = newAdmin.role === 'MANAGER'
|
||||
? {
|
||||
name: newAdmin.name,
|
||||
email: newAdmin.email,
|
||||
password: newAdmin.password,
|
||||
viewOnly: false,
|
||||
permissions: ["dashboard", "sale", "customers", "purchases", "inventory", "expense", "reports", "stores", "table", "wallet", "promotions", "feedback"]
|
||||
}
|
||||
: {
|
||||
name: newAdmin.name,
|
||||
email: newAdmin.email,
|
||||
password: newAdmin.password
|
||||
};
|
||||
const response = await apiFetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(newAdmin)
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setStatus('success');
|
||||
setMessage('New administrator added');
|
||||
setNewAdmin({ name: '', email: '', password: '' });
|
||||
setMessage(newAdmin.role === 'MANAGER' ? 'New manager added' : 'New administrator added');
|
||||
setNewAdmin({ name: '', email: '', password: '', role: 'MASTER' });
|
||||
setShowAddModal(false);
|
||||
fetchAdmins();
|
||||
setTimeout(() => setStatus('idle'), 3000);
|
||||
} else {
|
||||
setStatus('error');
|
||||
setMessage('Failed to add administrator');
|
||||
setMessage('Failed to add user');
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus('error');
|
||||
@@ -286,15 +308,15 @@ const Settings = () => {
|
||||
>
|
||||
<div className="flex justify-between items-center bg-white p-6 rounded-3xl border border-[#e2e8f0] shadow-sm">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-[#001828]">Administrator Team</h3>
|
||||
<p className="text-sm text-[#64748b]">Found {admins.length} active system administrators</p>
|
||||
<h3 className="text-xl font-bold text-[#001828]">Team Directory</h3>
|
||||
<p className="text-sm text-[#64748b]">Found {admins.length} active system members</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAddModal(true)}
|
||||
className="bg-gradient-to-r from-[#001828] to-[#6366f1] text-white px-6 py-3 rounded-xl font-bold flex items-center gap-2 shadow-lg shadow-indigo-100 hover:scale-[1.02] transition-all"
|
||||
>
|
||||
<UserPlus size={18} />
|
||||
Add New Admin
|
||||
Add Team Member
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -317,7 +339,7 @@ const Settings = () => {
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-[#64748b]">
|
||||
<ShieldCheck size={14} className="text-green-500" />
|
||||
<span>{admin.role} ACCESS</span>
|
||||
<span>{admin.role || 'MASTER'} ACCESS</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -337,10 +359,21 @@ const Settings = () => {
|
||||
<div className="w-16 h-16 bg-white/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<UserPlus size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold">New Administrator</h3>
|
||||
<p className="text-sm text-indigo-200">Grant full system access to a team member</p>
|
||||
<h3 className="text-xl font-bold">New Team Member</h3>
|
||||
<p className="text-sm text-indigo-200">Grant system access to a team member</p>
|
||||
</div>
|
||||
<form onSubmit={handleAddAdmin} className="p-8 space-y-5">
|
||||
<div>
|
||||
<label className="block text-xs font-black text-[#64748b] mb-2 uppercase tracking-widest">Role</label>
|
||||
<select
|
||||
value={newAdmin.role}
|
||||
onChange={(e) => setNewAdmin({...newAdmin, role: e.target.value})}
|
||||
className="w-full bg-gray-50 border border-gray-100 rounded-xl py-3 px-4 outline-none focus:border-indigo-500 transition-all font-semibold text-sm text-[#1e293b] appearance-none"
|
||||
>
|
||||
<option value="MASTER">Administrator (Full Access)</option>
|
||||
<option value="MANAGER">Manager (Counter & Management Access)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-black text-[#64748b] mb-2 uppercase tracking-widest">Full Name</label>
|
||||
<input
|
||||
@@ -375,12 +408,21 @@ const Settings = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-100 p-4 rounded-2xl flex gap-3 text-amber-700">
|
||||
<ShieldAlert size={20} className="shrink-0" />
|
||||
<p className="text-[10px] leading-relaxed font-bold">
|
||||
CRITICAL: This user will have full MASTER privileges. They can manage sales, tokens, and other administrators.
|
||||
</p>
|
||||
</div>
|
||||
{newAdmin.role === 'MASTER' ? (
|
||||
<div className="bg-amber-50 border border-amber-100 p-4 rounded-2xl flex gap-3 text-amber-700">
|
||||
<ShieldAlert size={20} className="shrink-0" />
|
||||
<p className="text-[10px] leading-relaxed font-bold">
|
||||
CRITICAL: This user will have full MASTER privileges. They can manage sales, tokens, and other administrators.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-indigo-50 border border-indigo-100 p-4 rounded-2xl flex gap-3 text-indigo-700">
|
||||
<ShieldCheck size={20} className="shrink-0" />
|
||||
<p className="text-[10px] leading-relaxed font-bold">
|
||||
INFO: This user will have MANAGER privileges. They will have access to the Counter Dashboard and other management tasks.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 pt-2">
|
||||
<button type="button" onClick={() => setShowAddModal(false)} className="flex-1 py-3 rounded-xl font-bold text-gray-500 hover:bg-gray-50 transition-all">Cancel</button>
|
||||
|
||||
@@ -2,11 +2,11 @@ import { apiFetch } from '../api';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
Monitor,
|
||||
Plus,
|
||||
MapPin,
|
||||
ShieldCheck,
|
||||
Trash2,
|
||||
Monitor,
|
||||
Plus,
|
||||
MapPin,
|
||||
ShieldCheck,
|
||||
Trash2,
|
||||
Pencil,
|
||||
Search,
|
||||
ExternalLink,
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
WifiOff,
|
||||
LinkIcon,
|
||||
Unlink,
|
||||
Smartphone
|
||||
Smartphone,
|
||||
Ban
|
||||
} from 'lucide-react';
|
||||
import AddTerminalModal from '../components/AddTerminalModal.tsx';
|
||||
import PinVerificationModal from '../components/PinVerificationModal.tsx';
|
||||
@@ -29,6 +30,7 @@ interface Terminal {
|
||||
location: string;
|
||||
apiKey: string;
|
||||
paired: boolean;
|
||||
blocked: boolean;
|
||||
deviceId: string | null;
|
||||
pairedAt: string | null;
|
||||
}
|
||||
@@ -96,6 +98,24 @@ const Terminals = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlock = async (id: number, currentStatus: boolean) => {
|
||||
const action = currentStatus ? 'unblock' : 'block';
|
||||
if (window.confirm(`Are you sure you want to ${action} this terminal?`)) {
|
||||
try {
|
||||
const response = await apiFetch(`/api/terminals/${id}/block`, { method: 'PUT' });
|
||||
if (response.ok) {
|
||||
fetchTerminals();
|
||||
} else {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
alert(data.message || `Failed to ${action} terminal (HTTP ${response.status})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to ${action} terminal:`, error);
|
||||
alert(`Network error: Could not ${action} terminal`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const pairedCount = terminals.filter(t => t.paired).length;
|
||||
|
||||
const filteredTerminals = terminals.filter(t => {
|
||||
@@ -204,6 +224,20 @@ const Terminals = () => {
|
||||
</div>
|
||||
{/* Action buttons — completely separate from card click */}
|
||||
<div className="flex items-center gap-1 relative z-20">
|
||||
{terminal.paired && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.preventDefault(); e.stopPropagation(); handleBlock(terminal.id, terminal.blocked); }}
|
||||
title={terminal.blocked ? "Unblock terminal" : "Block terminal"}
|
||||
className={`p-2 rounded-xl transition-all ${
|
||||
terminal.blocked
|
||||
? 'text-red-600 bg-red-50 hover:bg-red-100'
|
||||
: 'text-gray-400 hover:text-red-500 hover:bg-red-50'
|
||||
}`}
|
||||
>
|
||||
<Ban size={16} />
|
||||
</button>
|
||||
)}
|
||||
{terminal.paired && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -263,8 +297,10 @@ const Terminals = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
{terminal.paired ? (
|
||||
<>
|
||||
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
|
||||
<span className="text-[12px] font-bold text-green-600 uppercase tracking-wider">Paired</span>
|
||||
<div className={`w-2 h-2 rounded-full animate-pulse ${terminal.blocked ? 'bg-red-500' : 'bg-green-500'}`} />
|
||||
<span className={`text-[12px] font-bold uppercase tracking-wider ${terminal.blocked ? 'text-red-600' : 'text-green-600'}`}>
|
||||
{terminal.blocked ? 'Out of Order' : 'Paired'}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user