Update a.jsx
Browse files
a.jsx
CHANGED
|
@@ -1565,7 +1565,7 @@ function CountdownBadge({ remaining, urgent, warning }) {
|
|
| 1565 |
}
|
| 1566 |
|
| 1567 |
// ββ TestingProfilesPortal βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1568 |
-
function TestingProfilesPortal({ currentUser, onEdit, onBack, onLogout }) {
|
| 1569 |
const [profiles, setProfiles] = useState([]);
|
| 1570 |
const [loading, setLoading] = useState(true);
|
| 1571 |
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
@@ -1687,7 +1687,7 @@ function TestingProfilesPortal({ currentUser, onEdit, onBack, onLogout }) {
|
|
| 1687 |
}
|
| 1688 |
|
| 1689 |
// ββ ProfileEditorPortal βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1690 |
-
function ProfileEditorPortal({ profile: initialProfile, currentUser, onBack, onLogout }) {
|
| 1691 |
const [profile, setProfile] = useState(initialProfile);
|
| 1692 |
const [qs, setQs] = useState(() => initialProfile.questions ?? []);
|
| 1693 |
const [dirty, setDirty] = useState(false);
|
|
@@ -1971,7 +1971,7 @@ function ProfilePickerPortal({ mode, session, onPick, onCancel, onLogout }) {
|
|
| 1971 |
}
|
| 1972 |
|
| 1973 |
// ββ ProductsPortal ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1974 |
-
function ProductsPortal({ currentUser, onBack, onLogout }) {
|
| 1975 |
const [products, setProducts] = useState([]);
|
| 1976 |
const [loading, setLoading] = useState(true);
|
| 1977 |
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
@@ -2116,7 +2116,7 @@ const ROLE_DESC = {
|
|
| 2116 |
tester: "Tester portal only β upload screenshots per session",
|
| 2117 |
};
|
| 2118 |
|
| 2119 |
-
function AccountsPortal({ currentUser, onBack, onLogout }) {
|
| 2120 |
const [users, setUsers] = useState([]);
|
| 2121 |
const [allProducts, setAllProducts] = useState([]);
|
| 2122 |
const [loading, setLoading] = useState(true);
|
|
@@ -2519,19 +2519,21 @@ function KanbanPortal({ currentUser, currentRole, onBack, onLogout, onNavigate }
|
|
| 2519 |
|
| 2520 |
const openEdit = (task) => setModal({ mode: "edit", task: { ...task } });
|
| 2521 |
|
| 2522 |
-
const handleSave =
|
| 2523 |
const orig = tasks.find(t => t.id === task.id);
|
| 2524 |
task.updatedAt = Date.now();
|
| 2525 |
if (task.label === "daily" && task.status === "backlog") task.status = "in-progress";
|
| 2526 |
-
await dbSaveTask(task);
|
| 2527 |
|
| 2528 |
-
//
|
| 2529 |
setTasks(prev => {
|
| 2530 |
const idx = prev.findIndex(t => t.id === task.id);
|
| 2531 |
return idx >= 0 ? prev.map(t => t.id === task.id ? task : t) : [...prev, task];
|
| 2532 |
});
|
| 2533 |
setModal(null);
|
| 2534 |
|
|
|
|
|
|
|
|
|
|
| 2535 |
// Activity log + notifications fire in background
|
| 2536 |
const isNew = !orig;
|
| 2537 |
const changes = [];
|
|
@@ -2636,7 +2638,7 @@ function KanbanPortal({ currentUser, currentRole, onBack, onLogout, onNavigate }
|
|
| 2636 |
</div>
|
| 2637 |
<div className="header-right">
|
| 2638 |
{canCreate && <button className="btn-view-report" onClick={openCreate}>+ New Task</button>}
|
| 2639 |
-
<button className="btn-secondary-sm" onClick={() => setShowLog(true)}>π Log</button>
|
| 2640 |
<button className="btn-secondary-sm" onClick={onBack}>β Portal</button>
|
| 2641 |
<NotificationBell username={currentUser} onNavigate={onNavigate} />
|
| 2642 |
<ProfileMenu username={currentUser} role={currentRole} onLogout={onLogout} />
|
|
@@ -2820,12 +2822,10 @@ function TaskModal({ task: initTask, mode, currentUser, currentRole, products, a
|
|
| 2820 |
(isTester || task.productId) &&
|
| 2821 |
(isTester || (task.label === "daily" ? !!task.recurTime : !!task.dueDate));
|
| 2822 |
|
| 2823 |
-
const handleSubmit =
|
| 2824 |
e.preventDefault();
|
| 2825 |
if (!isValid) return;
|
| 2826 |
-
|
| 2827 |
-
await onSave(task);
|
| 2828 |
-
setSaving(false);
|
| 2829 |
};
|
| 2830 |
|
| 2831 |
return (
|
|
@@ -2992,8 +2992,8 @@ function TaskModal({ task: initTask, mode, currentUser, currentRole, products, a
|
|
| 2992 |
|
| 2993 |
<div className="modal-actions" style={{ margin: "16px 28px 24px" }}>
|
| 2994 |
<button type="button" className="btn-cancel" onClick={onClose}>Cancel</button>
|
| 2995 |
-
<button type="submit" className="btn-primary" disabled={
|
| 2996 |
-
{
|
| 2997 |
</button>
|
| 2998 |
</div>
|
| 2999 |
</form>
|
|
@@ -3068,18 +3068,20 @@ function TicketsPortal({ currentUser, currentRole, onBack, onLogout, onNavigate
|
|
| 3068 |
setModal({ mode: "edit", ticket: { ...ticket } });
|
| 3069 |
};
|
| 3070 |
|
| 3071 |
-
const handleSave =
|
| 3072 |
const orig = tickets.find(t => t.id === ticket.id);
|
| 3073 |
ticket.updatedAt = Date.now();
|
| 3074 |
-
await dbSaveTicket(ticket);
|
| 3075 |
|
| 3076 |
-
//
|
| 3077 |
setTickets(prev => {
|
| 3078 |
const idx = prev.findIndex(t => t.id === ticket.id);
|
| 3079 |
return idx >= 0 ? prev.map(t => t.id === ticket.id ? ticket : t) : [...prev, ticket];
|
| 3080 |
});
|
| 3081 |
setModal(null);
|
| 3082 |
|
|
|
|
|
|
|
|
|
|
| 3083 |
// Activity log + notification fire in background
|
| 3084 |
const isNew = !orig;
|
| 3085 |
const changes = [];
|
|
@@ -3171,7 +3173,7 @@ function TicketsPortal({ currentUser, currentRole, onBack, onLogout, onNavigate
|
|
| 3171 |
<div className="header-left"><h1>Tickets</h1></div>
|
| 3172 |
<div className="header-right">
|
| 3173 |
<button className="btn-view-report" onClick={openCreate}>+ New Ticket</button>
|
| 3174 |
-
<button className="btn-secondary-sm" onClick={() => setShowLog(true)}>π Log</button>
|
| 3175 |
<button className="btn-secondary-sm" onClick={onBack}>β Portal</button>
|
| 3176 |
<NotificationBell username={currentUser} onNavigate={onNavigate} />
|
| 3177 |
<ProfileMenu username={currentUser} role={currentRole} onLogout={onLogout} />
|
|
@@ -3293,12 +3295,10 @@ function TicketModal({ ticket: initTicket, mode, currentUser, currentRole, produ
|
|
| 3293 |
const canAssign = isAdmin || isReviewer;
|
| 3294 |
const canChangeStatus = isAdmin || isReviewer;
|
| 3295 |
|
| 3296 |
-
const handleSubmit =
|
| 3297 |
e.preventDefault();
|
| 3298 |
if (!ticket.title.trim()) return;
|
| 3299 |
-
|
| 3300 |
-
await onSave(ticket);
|
| 3301 |
-
setSaving(false);
|
| 3302 |
};
|
| 3303 |
|
| 3304 |
return (
|
|
@@ -3430,8 +3430,8 @@ function TicketModal({ ticket: initTicket, mode, currentUser, currentRole, produ
|
|
| 3430 |
|
| 3431 |
<div className="modal-actions" style={{ margin: "16px 28px 24px" }}>
|
| 3432 |
<button type="button" className="btn-cancel" onClick={onClose}>Cancel</button>
|
| 3433 |
-
<button type="submit" className="btn-primary" disabled={
|
| 3434 |
-
{
|
| 3435 |
</button>
|
| 3436 |
</div>
|
| 3437 |
</form>
|
|
@@ -3908,15 +3908,15 @@ export default function App() {
|
|
| 3908 |
if (!session) return <LoginPage onLogin={handleLogin} />;
|
| 3909 |
if (view === "review") return <ReviewPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} onNavigate={handleNavigate} />;
|
| 3910 |
if (view === "reports") return <ReportsPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} onNavigate={handleNavigate} />;
|
| 3911 |
-
if (view === "accounts") return <AccountsPortal currentUser={session.username} onBack={() => setView(null)} onLogout={handleLogout} />;
|
| 3912 |
-
if (view === "products") return <ProductsPortal currentUser={session.username} onBack={() => setView(null)} onLogout={handleLogout} />;
|
| 3913 |
if (view === "planner") return <KanbanPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} onNavigate={handleNavigate} />;
|
| 3914 |
if (view === "tickets") return <TicketsPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} onNavigate={handleNavigate} />;
|
| 3915 |
-
if (view === "profiles") return <TestingProfilesPortal currentUser={session.username}
|
| 3916 |
onEdit={p => { setEditingProfile(p); setView("profile-editor"); }}
|
| 3917 |
onBack={() => setView(null)} onLogout={handleLogout} />;
|
| 3918 |
if (view === "profile-editor" && editingProfile) return <ProfileEditorPortal
|
| 3919 |
-
profile={editingProfile} currentUser={session.username}
|
| 3920 |
onBack={() => setView("profiles")} onLogout={handleLogout} />;
|
| 3921 |
if (session.testingStart && session.testingEnd)
|
| 3922 |
return <TerminatedScreen session={session} rows={rows}
|
|
|
|
| 1565 |
}
|
| 1566 |
|
| 1567 |
// ββ TestingProfilesPortal βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1568 |
+
function TestingProfilesPortal({ currentUser, currentRole, onEdit, onBack, onLogout }) {
|
| 1569 |
const [profiles, setProfiles] = useState([]);
|
| 1570 |
const [loading, setLoading] = useState(true);
|
| 1571 |
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
|
|
| 1687 |
}
|
| 1688 |
|
| 1689 |
// ββ ProfileEditorPortal βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1690 |
+
function ProfileEditorPortal({ profile: initialProfile, currentUser, currentRole, onBack, onLogout }) {
|
| 1691 |
const [profile, setProfile] = useState(initialProfile);
|
| 1692 |
const [qs, setQs] = useState(() => initialProfile.questions ?? []);
|
| 1693 |
const [dirty, setDirty] = useState(false);
|
|
|
|
| 1971 |
}
|
| 1972 |
|
| 1973 |
// ββ ProductsPortal ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1974 |
+
function ProductsPortal({ currentUser, currentRole, onBack, onLogout }) {
|
| 1975 |
const [products, setProducts] = useState([]);
|
| 1976 |
const [loading, setLoading] = useState(true);
|
| 1977 |
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
|
|
| 2116 |
tester: "Tester portal only β upload screenshots per session",
|
| 2117 |
};
|
| 2118 |
|
| 2119 |
+
function AccountsPortal({ currentUser, currentRole, onBack, onLogout }) {
|
| 2120 |
const [users, setUsers] = useState([]);
|
| 2121 |
const [allProducts, setAllProducts] = useState([]);
|
| 2122 |
const [loading, setLoading] = useState(true);
|
|
|
|
| 2519 |
|
| 2520 |
const openEdit = (task) => setModal({ mode: "edit", task: { ...task } });
|
| 2521 |
|
| 2522 |
+
const handleSave = (task) => {
|
| 2523 |
const orig = tasks.find(t => t.id === task.id);
|
| 2524 |
task.updatedAt = Date.now();
|
| 2525 |
if (task.label === "daily" && task.status === "backlog") task.status = "in-progress";
|
|
|
|
| 2526 |
|
| 2527 |
+
// Optimistic update β show card and close modal instantly
|
| 2528 |
setTasks(prev => {
|
| 2529 |
const idx = prev.findIndex(t => t.id === task.id);
|
| 2530 |
return idx >= 0 ? prev.map(t => t.id === task.id ? task : t) : [...prev, task];
|
| 2531 |
});
|
| 2532 |
setModal(null);
|
| 2533 |
|
| 2534 |
+
// Persist to DB in background
|
| 2535 |
+
dbSaveTask(task).catch(() => {});
|
| 2536 |
+
|
| 2537 |
// Activity log + notifications fire in background
|
| 2538 |
const isNew = !orig;
|
| 2539 |
const changes = [];
|
|
|
|
| 2638 |
</div>
|
| 2639 |
<div className="header-right">
|
| 2640 |
{canCreate && <button className="btn-view-report" onClick={openCreate}>+ New Task</button>}
|
| 2641 |
+
{isAdmin && <button className="btn-secondary-sm" onClick={() => setShowLog(true)}>π Log</button>}
|
| 2642 |
<button className="btn-secondary-sm" onClick={onBack}>β Portal</button>
|
| 2643 |
<NotificationBell username={currentUser} onNavigate={onNavigate} />
|
| 2644 |
<ProfileMenu username={currentUser} role={currentRole} onLogout={onLogout} />
|
|
|
|
| 2822 |
(isTester || task.productId) &&
|
| 2823 |
(isTester || (task.label === "daily" ? !!task.recurTime : !!task.dueDate));
|
| 2824 |
|
| 2825 |
+
const handleSubmit = (e) => {
|
| 2826 |
e.preventDefault();
|
| 2827 |
if (!isValid) return;
|
| 2828 |
+
onSave(task);
|
|
|
|
|
|
|
| 2829 |
};
|
| 2830 |
|
| 2831 |
return (
|
|
|
|
| 2992 |
|
| 2993 |
<div className="modal-actions" style={{ margin: "16px 28px 24px" }}>
|
| 2994 |
<button type="button" className="btn-cancel" onClick={onClose}>Cancel</button>
|
| 2995 |
+
<button type="submit" className="btn-primary" disabled={!isValid}>
|
| 2996 |
+
{mode === "create" ? "Create Task" : "Save Changes"}
|
| 2997 |
</button>
|
| 2998 |
</div>
|
| 2999 |
</form>
|
|
|
|
| 3068 |
setModal({ mode: "edit", ticket: { ...ticket } });
|
| 3069 |
};
|
| 3070 |
|
| 3071 |
+
const handleSave = (ticket) => {
|
| 3072 |
const orig = tickets.find(t => t.id === ticket.id);
|
| 3073 |
ticket.updatedAt = Date.now();
|
|
|
|
| 3074 |
|
| 3075 |
+
// Optimistic update β show ticket and close modal instantly
|
| 3076 |
setTickets(prev => {
|
| 3077 |
const idx = prev.findIndex(t => t.id === ticket.id);
|
| 3078 |
return idx >= 0 ? prev.map(t => t.id === ticket.id ? ticket : t) : [...prev, ticket];
|
| 3079 |
});
|
| 3080 |
setModal(null);
|
| 3081 |
|
| 3082 |
+
// Persist to DB in background
|
| 3083 |
+
dbSaveTicket(ticket).catch(() => {});
|
| 3084 |
+
|
| 3085 |
// Activity log + notification fire in background
|
| 3086 |
const isNew = !orig;
|
| 3087 |
const changes = [];
|
|
|
|
| 3173 |
<div className="header-left"><h1>Tickets</h1></div>
|
| 3174 |
<div className="header-right">
|
| 3175 |
<button className="btn-view-report" onClick={openCreate}>+ New Ticket</button>
|
| 3176 |
+
{isAdmin && <button className="btn-secondary-sm" onClick={() => setShowLog(true)}>π Log</button>}
|
| 3177 |
<button className="btn-secondary-sm" onClick={onBack}>β Portal</button>
|
| 3178 |
<NotificationBell username={currentUser} onNavigate={onNavigate} />
|
| 3179 |
<ProfileMenu username={currentUser} role={currentRole} onLogout={onLogout} />
|
|
|
|
| 3295 |
const canAssign = isAdmin || isReviewer;
|
| 3296 |
const canChangeStatus = isAdmin || isReviewer;
|
| 3297 |
|
| 3298 |
+
const handleSubmit = (e) => {
|
| 3299 |
e.preventDefault();
|
| 3300 |
if (!ticket.title.trim()) return;
|
| 3301 |
+
onSave(ticket);
|
|
|
|
|
|
|
| 3302 |
};
|
| 3303 |
|
| 3304 |
return (
|
|
|
|
| 3430 |
|
| 3431 |
<div className="modal-actions" style={{ margin: "16px 28px 24px" }}>
|
| 3432 |
<button type="button" className="btn-cancel" onClick={onClose}>Cancel</button>
|
| 3433 |
+
<button type="submit" className="btn-primary" disabled={!ticket.title.trim()}>
|
| 3434 |
+
{mode === "create" ? "Create Ticket" : "Save Changes"}
|
| 3435 |
</button>
|
| 3436 |
</div>
|
| 3437 |
</form>
|
|
|
|
| 3908 |
if (!session) return <LoginPage onLogin={handleLogin} />;
|
| 3909 |
if (view === "review") return <ReviewPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} onNavigate={handleNavigate} />;
|
| 3910 |
if (view === "reports") return <ReportsPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} onNavigate={handleNavigate} />;
|
| 3911 |
+
if (view === "accounts") return <AccountsPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} />;
|
| 3912 |
+
if (view === "products") return <ProductsPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} />;
|
| 3913 |
if (view === "planner") return <KanbanPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} onNavigate={handleNavigate} />;
|
| 3914 |
if (view === "tickets") return <TicketsPortal currentUser={session.username} currentRole={session.role} onBack={() => setView(null)} onLogout={handleLogout} onNavigate={handleNavigate} />;
|
| 3915 |
+
if (view === "profiles") return <TestingProfilesPortal currentUser={session.username} currentRole={session.role}
|
| 3916 |
onEdit={p => { setEditingProfile(p); setView("profile-editor"); }}
|
| 3917 |
onBack={() => setView(null)} onLogout={handleLogout} />;
|
| 3918 |
if (view === "profile-editor" && editingProfile) return <ProfileEditorPortal
|
| 3919 |
+
profile={editingProfile} currentUser={session.username} currentRole={session.role}
|
| 3920 |
onBack={() => setView("profiles")} onLogout={handleLogout} />;
|
| 3921 |
if (session.testingStart && session.testingEnd)
|
| 3922 |
return <TerminatedScreen session={session} rows={rows}
|