Files
Frontend/src/modules/organization/components/OrganizationMembers.tsx
T

308 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from "react";
import { useOrganization } from "../hooks/useOrganization";
export const OrganizationMembers = () => {
const { members, isLoading, updateMemberRole, removeMember, addMember } =
useOrganization();
const [showAddModal, setShowAddModal] = useState(false);
const [newMemberEmail, setNewMemberEmail] = useState("");
const [newMemberRole, setNewMemberRole] = useState<"admin" | "member">(
"member",
);
const [actionLoading, setActionLoading] = useState<number | null>(null);
const handleRoleChange = async (userId: number, newRole: string) => {
setActionLoading(userId);
await updateMemberRole(userId, newRole);
setActionLoading(null);
};
const handleRemoveMember = async (userId: number, memberName: string) => {
if (
confirm(
`Вы уверены, что хотите удалить пользователя "${memberName}" из организации?`,
)
) {
setActionLoading(userId);
await removeMember(userId);
setActionLoading(null);
}
};
const handleAddMember = async (e: React.FormEvent) => {
e.preventDefault();
const success = await addMember(newMemberEmail, newMemberRole);
if (success) {
setShowAddModal(false);
setNewMemberEmail("");
setNewMemberRole("member");
}
};
const getRoleLabel = (role: string) => {
switch (role) {
case "owner":
return "Владелец";
case "admin":
return "Администратор";
case "member":
return "Участник";
default:
return role;
}
};
const getRoleColor = (role: string) => {
switch (role) {
case "owner":
return "text-yellow-600 dark:text-yellow-400";
case "admin":
return "text-blue-600 dark:text-blue-400";
case "member":
return "text-green-600 dark:text-green-400";
default:
return "";
}
};
if (isLoading && members.length === 0) {
return (
<div className="flex justify-center py-12">
<div style={{ color: "var(--text-secondary)" }}>
Загрузка участников...
</div>
</div>
);
}
return (
<div>
<div className="flex justify-between items-center mb-6">
<h2
className="text-2xl font-bold"
style={{ color: "var(--text-primary)" }}
>
Участники организации
</h2>
<button
onClick={() => setShowAddModal(true)}
className="px-4 py-2 rounded-lg font-medium transition-all transform hover:scale-[1.02] active:scale-[0.98]"
style={{
backgroundColor: "var(--accent)",
color: "var(--button-primary-text)",
}}
>
+ Добавить участника
</button>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b" style={{ borderColor: "var(--border)" }}>
<th
className="text-left py-3 px-4"
style={{ color: "var(--text-secondary)" }}
>
Имя
</th>
<th
className="text-left py-3 px-4"
style={{ color: "var(--text-secondary)" }}
>
Email
</th>
<th
className="text-left py-3 px-4"
style={{ color: "var(--text-secondary)" }}
>
Роль
</th>
<th
className="text-left py-3 px-4"
style={{ color: "var(--text-secondary)" }}
>
Действия
</th>
</tr>
</thead>
<tbody>
{members.map((member) => (
<tr
key={member.id}
className="border-b"
style={{ borderColor: "var(--border)" }}
>
<td
className="py-3 px-4"
style={{ color: "var(--text-primary)" }}
>
{member.first_name} {member.last_name}
<div
className="text-xs"
style={{ color: "var(--text-muted)" }}
>
@{member.username}
</div>
</td>
<td
className="py-3 px-4"
style={{ color: "var(--text-primary)" }}
>
{member.email}
</td>
<td className="py-3 px-4">
{member.role === "owner" ? (
<span
className={`font-medium ${getRoleColor(member.role)}`}
>
{getRoleLabel(member.role)}
</span>
) : (
<select
value={member.role}
onChange={(e) =>
handleRoleChange(member.user_id, e.target.value)
}
disabled={actionLoading === member.user_id}
className="px-2 py-1 rounded border focus:outline-none focus:ring-2"
style={{
backgroundColor: "var(--input-bg)",
color: "var(--text-primary)",
borderColor: "var(--border)",
"--tw-ring-color": "var(--accent)",
}}
>
<option value="admin">{getRoleLabel("admin")}</option>
<option value="member">{getRoleLabel("member")}</option>
</select>
)}
</td>
<td className="py-3 px-4">
{member.role !== "owner" && (
<button
onClick={() =>
handleRemoveMember(
member.user_id,
`${member.first_name} ${member.last_name}`,
)
}
disabled={actionLoading === member.user_id}
className="px-3 py-1 rounded text-sm transition-all hover:scale-105 disabled:opacity-50"
style={{
backgroundColor: "var(--button-danger)",
color: "var(--button-danger-text)",
}}
>
{actionLoading === member.user_id ? "..." : "Удалить"}
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
{members.length === 0 && !isLoading && (
<div
className="text-center py-12"
style={{ color: "var(--text-secondary)" }}
>
В организации пока нет участников
</div>
)}
{showAddModal && (
<div className="fixed inset-0 bg-[#00000055] bg-opacity-50 flex items-center justify-center z-50">
<div
className="rounded-2xl p-6 max-w-md w-full mx-4"
style={{
backgroundColor: "var(--card-bg)",
boxShadow: `0 20px 25px -5px var(--shadow-color)`,
}}
>
<h3
className="text-xl font-bold mb-4"
style={{ color: "var(--text-primary)" }}
>
Добавить участника
</h3>
<form onSubmit={handleAddMember}>
<div className="mb-4">
<label
className="block text-sm font-medium mb-2"
style={{ color: "var(--text-secondary)" }}
>
Email пользователя
</label>
<input
type="email"
value={newMemberEmail}
onChange={(e) => setNewMemberEmail(e.target.value)}
className="w-full px-4 py-2 rounded-lg border focus:outline-none focus:ring-2"
style={{
backgroundColor: "var(--input-bg)",
color: "var(--text-primary)",
borderColor: "var(--border)",
"--tw-ring-color": "var(--accent)",
}}
required
placeholder="user@example.com"
/>
</div>
<div className="mb-6">
<label
className="block text-sm font-medium mb-2"
style={{ color: "var(--text-secondary)" }}
>
Роль
</label>
<select
value={newMemberRole}
onChange={(e) =>
setNewMemberRole(e.target.value as "admin" | "member")
}
className="w-full px-4 py-2 rounded-lg border focus:outline-none focus:ring-2"
style={{
backgroundColor: "var(--input-bg)",
color: "var(--text-primary)",
borderColor: "var(--border)",
"--tw-ring-color": "var(--accent)",
}}
>
<option value="admin">Администратор</option>
<option value="member">Участник</option>
</select>
</div>
<div className="flex gap-3">
<button
type="button"
onClick={() => setShowAddModal(false)}
className="flex-1 py-2 px-4 rounded-lg font-medium transition-all"
style={{
backgroundColor: "var(--bg-secondary)",
color: "var(--text-secondary)",
}}
>
Отмена
</button>
<button
type="submit"
className="flex-1 py-2 px-4 rounded-lg font-medium transition-all transform hover:scale-[1.02]"
style={{
backgroundColor: "var(--accent)",
color: "var(--button-primary-text)",
}}
>
Добавить
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
};