Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"private": true,
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build": "vite build",
"preview": "vite preview",
"test": "vitest run",
"test:ui": "vitest --ui",
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/components/BulkPaymentStatusTracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,29 @@ function FragmentRow({
<p className="text-sm text-muted">Loading recipient statuses...</p>
) : (
<div className="space-y-3">
{run.status === 'Pending' || run.status === 'Processing' ? (
<div className="mb-4">
<div className="flex justify-between text-xs text-muted mb-1">
<span>Processing on-chain...</span>
<span>
{Math.round(
((onChainState?.successCount ?? 0) / summary.items.length) * 100
)}
%
</span>
</div>
<div className="w-full h-2 bg-surface-hi rounded-full overflow-hidden">
<div
className="h-full bg-accent transition-all duration-500 ease-out"
style={{
width: `${Math.round(
((onChainState?.successCount ?? 0) / summary.items.length) * 100
)}%`,
}}
/>
</div>
</div>
) : null}
<div className="flex flex-wrap items-center gap-4 rounded-md border border-hi/30 px-3 py-2 text-xs text-muted">
<span>Recipients: {summary.items.length}</span>
<span>Confirmed on-chain: {onChainState?.successCount ?? 0}</span>
Expand Down
40 changes: 28 additions & 12 deletions frontend/src/components/EmployeeList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { useDebounce } from '../hooks/useDebounce';
import { Avatar } from './Avatar';
import { AvatarUpload } from './AvatarUpload';
import { CSVUploader } from './CSVUploader';
import type { CSVRow } from './CSVUploader';
import { Pencil, Trash2 } from 'lucide-react';
import { Pencil, Trash2, Search, X } from 'lucide-react';
import { EmployeeRemovalConfirmModal } from './EmployeeRemovalConfirmModal';

interface Employee {
Expand Down Expand Up @@ -53,8 +53,13 @@ export const EmployeeList: React.FC<EmployeeListProps> = ({
const [sortKey, setSortKey] = useState<keyof Employee>('name');
const [sortAsc, setSortAsc] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const searchInputRef = useRef<HTMLInputElement>(null);
const debouncedSearch = useDebounce(searchQuery, 300);

useEffect(() => {
searchInputRef.current?.focus();
}, []);

const handleDataParsed = (data: CSVRow[]) => {
const newEmployees = data.map((row) => ({
id: String(Date.now() + Math.random()),
Expand Down Expand Up @@ -91,7 +96,8 @@ export const EmployeeList: React.FC<EmployeeListProps> = ({
return (
emp.name.toLowerCase().includes(q) ||
emp.email.toLowerCase().includes(q) ||
emp.position.toLowerCase().includes(q)
emp.position.toLowerCase().includes(q) ||
emp.wallet?.toLowerCase().includes(q)
);
})
: employees;
Expand Down Expand Up @@ -165,15 +171,25 @@ export const EmployeeList: React.FC<EmployeeListProps> = ({
<div className="w-full card glass noise overflow-hidden p-0">
<div className="flex flex-wrap justify-between items-center gap-3 p-6">
<span className="font-bold text-lg">Employees</span>
<input
type="search"
id="employee-search"
aria-label="Search employees"
placeholder="Search by name, email, or role…"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="rounded border border-gray-300 bg-transparent px-3 py-1.5 text-sm placeholder:text-muted focus:outline-none focus:ring-2 focus:ring-accent"
/>
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted" />
<input
ref={searchInputRef}
type="text"
placeholder="Search employees..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 pr-8 py-2 text-sm bg-surface border border-hi rounded-lg outline-none focus:border-accent w-64"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted hover:text-text"
>
<X className="w-4 h-4" />
</button>
)}
</div>
</div>
<table className="w-full table-fixed text-left border-collapse">
<thead>
Expand Down
58 changes: 58 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,61 @@ select {
.Select__icon {
pointer-events: none;
}

/* ── Dropdown Menu Standardization ── */
select,
.SDS-select,
[data-select] {
background-color: var(--surface) !important;
border: 1px solid var(--border-hi) !important;
border-radius: 8px !important;
padding: 0.5rem 2.5rem 0.5rem 0.75rem !important;
font-size: 0.875rem !important;
color: var(--text) !important;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease !important;
}

select:hover,
.SDS-select:hover,
[data-select]:hover {
border-color: var(--accent) !important;
}

select:focus,
.SDS-select:focus,
[data-select]:focus {
outline: none !important;
border-color: var(--accent) !important;
box-shadow: 0 0 0 2px rgba(74, 240, 184, 0.2) !important;
}

select option,
.SDS-select option,
[data-select] option {
background-color: var(--surface) !important;
color: var(--text) !important;
padding: 0.5rem !important;
}

select option:hover,
select option:checked,
.SDS-select option:hover,
[data-select] option:checked {
background-color: var(--accent) !important;
color: var(--bg) !important;
}

/* Dropdown arrow indicator */
select,
.SDS-select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238b949e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E") !important;
background-repeat: no-repeat !important;
background-position: right 0.5rem center !important;
}

[data-theme='light'] select,
[data-theme='light'] .SDS-select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%231f2328' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E") !important;
}
10 changes: 8 additions & 2 deletions frontend/src/pages/TransactionHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,15 @@ export default function TransactionHistory() {

{item.txHash ? (
<div className="mt-4 pt-4 border-t border-hi/50 flex items-center justify-between">
<span className="text-[10px] font-mono text-muted truncate max-w-[70%]">
<a
href={`https://stellar.expert/explorer/public/tx/${item.txHash}`}
target="_blank"
rel="noreferrer"
className="text-[10px] font-mono text-accent hover:underline truncate max-w-[70%]"
title={item.txHash}
>
{item.txHash}
</span>
</a>
<a
href={`https://stellar.expert/explorer/public/tx/${item.txHash}`}
target="_blank"
Expand Down
3 changes: 2 additions & 1 deletion frontend/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
"include": ["src"],
"exclude": ["src/**/__tests__/**", "src/**/*.test.ts", "src/**/*.test.tsx"]
}
Loading