feat: add plug and organization
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user