301 lines
8.5 KiB
TypeScript
301 lines
8.5 KiB
TypeScript
'use client';
|
|
|
|
import {useState} from 'react';
|
|
import {proportional, pixel} from '@astryxdesign/core/Table';
|
|
import {ResponsiveTable} from '@/shared/components/patterns/ResponsiveTable';
|
|
import type {TableColumn} from '@astryxdesign/core/Table';
|
|
import {VStack, HStack} from '@astryxdesign/core/Layout';
|
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
import {Badge} from '@astryxdesign/core/Badge';
|
|
import {Button} from '@astryxdesign/core/Button';
|
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
import {Avatar} from '@astryxdesign/core/Avatar';
|
|
import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
|
|
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
import {Selector} from '@astryxdesign/core/Selector';
|
|
import {useToast} from '@astryxdesign/core/Toast';
|
|
import {StaticPanel} from '@/shared/components/patterns/PanelCard';
|
|
|
|
export interface StaffUser extends Record<string, unknown> {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
role: 'Owner' | 'Manager' | 'Cashier' | 'Marketing' | 'Support';
|
|
storeName: string;
|
|
status: 'active' | 'suspended' | 'invited';
|
|
lastLogin: string;
|
|
}
|
|
|
|
const INITIAL_STAFF: StaffUser[] = [
|
|
{
|
|
id: 'usr-1',
|
|
name: 'Aravind',
|
|
email: 'aravind@nearle.in',
|
|
role: 'Owner',
|
|
storeName: 'All stores',
|
|
status: 'active',
|
|
lastLogin: 'Just now',
|
|
},
|
|
{
|
|
id: 'usr-2',
|
|
name: 'Vikram Seth',
|
|
email: 'vikram@nearle.in',
|
|
role: 'Manager',
|
|
storeName: 'Indiranagar Flagship',
|
|
status: 'active',
|
|
lastLogin: 'Today, 14:20',
|
|
},
|
|
{
|
|
id: 'usr-3',
|
|
name: 'Priya Sharma',
|
|
email: 'priya@nearle.in',
|
|
role: 'Cashier',
|
|
storeName: 'Koramangala 80ft',
|
|
status: 'active',
|
|
lastLogin: 'Today, 09:15',
|
|
},
|
|
{
|
|
id: 'usr-4',
|
|
name: 'Rahul Verma',
|
|
email: 'rahul@nearle.in',
|
|
role: 'Marketing',
|
|
storeName: 'All stores',
|
|
status: 'invited',
|
|
lastLogin: 'Pending accept',
|
|
},
|
|
{
|
|
id: 'usr-5',
|
|
name: 'Deepa Nair',
|
|
email: 'deepa@nearle.in',
|
|
role: 'Cashier',
|
|
storeName: 'Whitefield Main',
|
|
status: 'suspended',
|
|
lastLogin: '3 days ago',
|
|
},
|
|
];
|
|
|
|
export function TeamManagement() {
|
|
const toast = useToast();
|
|
const [staffList, setStaffList] = useState<StaffUser[]>(INITIAL_STAFF);
|
|
const [isAdding, setIsAdding] = useState(false);
|
|
const [name, setName] = useState('');
|
|
const [email, setEmail] = useState('');
|
|
const [role, setRole] = useState<'Owner' | 'Manager' | 'Cashier' | 'Marketing' | 'Support'>('Manager');
|
|
const [storeName, setStoreName] = useState('Indiranagar Flagship');
|
|
|
|
const handleAddStaff = () => {
|
|
if (!name.trim() || !email.trim()) {
|
|
toast({type: 'error', body: 'Name and Email are required'});
|
|
return;
|
|
}
|
|
const newUser: StaffUser = {
|
|
id: `usr-${Date.now()}`,
|
|
name,
|
|
email,
|
|
role,
|
|
storeName,
|
|
status: 'invited',
|
|
lastLogin: 'Pending accept',
|
|
};
|
|
setStaffList((prev) => [newUser, ...prev]);
|
|
setName('');
|
|
setEmail('');
|
|
setIsAdding(false);
|
|
toast({body: `Invitation sent to ${email}`});
|
|
};
|
|
|
|
const handleToggleStatus = (id: string) => {
|
|
setStaffList((prev) =>
|
|
prev.map((s) => {
|
|
if (s.id !== id) return s;
|
|
const nextStatus = s.status === 'suspended' ? 'active' : 'suspended';
|
|
toast({body: `${s.name} status updated to ${nextStatus}`});
|
|
return {...s, status: nextStatus};
|
|
}),
|
|
);
|
|
};
|
|
|
|
const handleResetPassword = (emailStr: string) => {
|
|
toast({body: `Password reset link sent to ${emailStr}`});
|
|
};
|
|
|
|
const handleRemove = (id: string, nameStr: string) => {
|
|
setStaffList((prev) => prev.filter((s) => s.id !== id));
|
|
toast({body: `${nameStr} removed from team`});
|
|
};
|
|
|
|
const columns: TableColumn<StaffUser>[] = [
|
|
{
|
|
key: 'name',
|
|
header: 'Staff Member',
|
|
width: proportional(2),
|
|
renderCell: (row) => (
|
|
<HStack gap={3} vAlign="center">
|
|
<Avatar name={row.name} size="sm" tooltip={false} />
|
|
<VStack gap={0}>
|
|
<Text size="sm" weight="medium">
|
|
{row.name}
|
|
</Text>
|
|
<Text size="sm" color="secondary">
|
|
{row.email}
|
|
</Text>
|
|
</VStack>
|
|
</HStack>
|
|
),
|
|
},
|
|
{
|
|
key: 'role',
|
|
header: 'Role',
|
|
width: proportional(1),
|
|
renderCell: (row) => (
|
|
<Badge
|
|
variant={row.role === 'Owner' ? 'info' : 'neutral'}
|
|
label={row.role}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
key: 'storeName',
|
|
header: 'Assigned Store',
|
|
width: proportional(1.5),
|
|
renderCell: (row) => <Text size="sm">{row.storeName}</Text>,
|
|
},
|
|
{
|
|
key: 'status',
|
|
header: 'Status',
|
|
width: pixel(110),
|
|
renderCell: (row) => {
|
|
const v =
|
|
row.status === 'active'
|
|
? 'success'
|
|
: row.status === 'suspended'
|
|
? 'error'
|
|
: 'warning';
|
|
return <Badge variant={v} label={row.status} />;
|
|
},
|
|
},
|
|
{
|
|
key: 'lastLogin',
|
|
header: 'Last Login',
|
|
width: proportional(1.2),
|
|
renderCell: (row) => (
|
|
<Text size="sm" color="secondary">
|
|
{row.lastLogin}
|
|
</Text>
|
|
),
|
|
},
|
|
{
|
|
key: 'actions',
|
|
header: 'Actions',
|
|
align: 'center',
|
|
width: pixel(160),
|
|
renderCell: (row) =>
|
|
row.role === 'Owner' ? (
|
|
<HStack hAlign="center">
|
|
<Text size="sm" color="secondary">
|
|
Primary Owner
|
|
</Text>
|
|
</HStack>
|
|
) : (
|
|
<HStack hAlign="center">
|
|
<DropdownMenu
|
|
button={{
|
|
variant: 'secondary',
|
|
size: 'sm',
|
|
label: 'Manage',
|
|
icon: <Icon icon="moreHorizontal" size="sm" />,
|
|
}}
|
|
menuWidth={180}
|
|
items={[
|
|
{
|
|
label: row.status === 'suspended' ? 'Reactivate staff' : 'Suspend staff',
|
|
onClick: () => handleToggleStatus(row.id),
|
|
},
|
|
{
|
|
label: 'Reset password',
|
|
onClick: () => handleResetPassword(row.email),
|
|
},
|
|
{type: 'divider'},
|
|
{
|
|
label: 'Remove from team',
|
|
onClick: () => handleRemove(row.id, row.name),
|
|
},
|
|
]}
|
|
/>
|
|
</HStack>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<VStack gap={5}>
|
|
<StaticPanel
|
|
title="Team Directory & Staff Access"
|
|
subtitle="Manage store assignments, role permissions, password resets and account statuses."
|
|
actions={
|
|
<Button
|
|
variant="primary"
|
|
size="sm"
|
|
label={isAdding ? 'Cancel' : 'Add Staff Member'}
|
|
onClick={() => setIsAdding(!isAdding)}
|
|
/>
|
|
}
|
|
>
|
|
<VStack gap={4}>
|
|
{isAdding ? (
|
|
<VStack gap={3} padding={4}>
|
|
<Heading level={3}>Invite New Staff Member</Heading>
|
|
<HStack gap={3} vAlign="end" wrap="wrap">
|
|
<TextInput
|
|
label="Full Name"
|
|
value={name}
|
|
onChange={setName}
|
|
placeholder="e.g. Ananya Rao"
|
|
/>
|
|
<TextInput
|
|
label="Work Email"
|
|
value={email}
|
|
onChange={setEmail}
|
|
placeholder="ananya@nearle.in"
|
|
/>
|
|
<Selector
|
|
label="Role"
|
|
value={role}
|
|
onChange={(v) =>
|
|
setRole(v as 'Owner' | 'Manager' | 'Cashier' | 'Marketing' | 'Support')
|
|
}
|
|
options={['Manager', 'Cashier', 'Marketing', 'Support']}
|
|
/>
|
|
<Selector
|
|
label="Assigned Store"
|
|
value={storeName}
|
|
onChange={(v) => setStoreName(v)}
|
|
options={[
|
|
'All stores',
|
|
'Indiranagar Flagship',
|
|
'Koramangala 80ft',
|
|
'Whitefield Main',
|
|
'Jayanagar 4th Block',
|
|
]}
|
|
/>
|
|
<Button
|
|
variant="primary"
|
|
size="sm"
|
|
label="Send Invite"
|
|
onClick={handleAddStaff}
|
|
/>
|
|
</HStack>
|
|
</VStack>
|
|
) : null}
|
|
|
|
<ResponsiveTable
|
|
columns={columns}
|
|
data={staffList}
|
|
primaryKey="name"
|
|
/>
|
|
</VStack>
|
|
</StaticPanel>
|
|
</VStack>
|
|
);
|
|
}
|