import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; // Users Slice for managing users and deleted users import { fetchClient } from "@/lib/api/fetchClient"; import { User } from "@/lib/features/auth/authSlice"; /** API'den gelen kullanıcıyı frontend User tipine çevirir (avatar -> avatar_url, permissions null ise []). */ function mapApiUserToUser(apiUser: Record): User & { deleted_at?: string } { const roles = (apiUser.roles as User["roles"]) || []; return { id: String(apiUser.id), username: String(apiUser.username ?? ""), email: String(apiUser.email ?? ""), roles: roles.map((r) => ({ ...r, permissions: r.permissions ?? [] })), avatar_url: apiUser.avatar ? String(apiUser.avatar) : (apiUser.avatar_url as string | undefined), deleted_at: apiUser.deleted_at ? String(apiUser.deleted_at) : undefined, }; } // Types export interface CreateUserRequest { username: string; email: string; password?: string; roles?: string[]; avatar?: File; } export interface UpdateUserRequest { id: string; username?: string; email?: string; password?: string; roles?: string[]; avatar?: File; } interface UsersState { users: User[]; // Active users deletedUsers: (User & { deleted_at?: string })[]; // Soft-deleted users isLoading: boolean; error: string | null; } const initialState: UsersState = { users: [], deletedUsers: [], isLoading: false, error: null, }; // Async Thunks export const fetchUsers = createAsyncThunk( "users/fetchAll", async (_, { rejectWithValue }) => { try { const response = await fetchClient("/admin/users") as { users?: unknown[]; data?: unknown[]; pagination?: unknown }; const rawList = response.users ?? response.data ?? (Array.isArray(response) ? response : null); const list = Array.isArray(rawList) ? rawList : []; return list.map((u) => mapApiUserToUser(u as Record)); } catch (error: any) { return rejectWithValue(error.message); } } ); export const fetchDeletedUsers = createAsyncThunk( "users/fetchDeleted", async (_, { rejectWithValue }) => { try { const response = await fetchClient("/admin/users/deleted") as { users?: unknown[]; data?: unknown[]; pagination?: unknown }; const rawList = response.users ?? response.data ?? (Array.isArray(response) ? response : null); const list = Array.isArray(rawList) ? rawList : []; return list.map((u) => mapApiUserToUser(u as Record)); } catch (error: any) { return rejectWithValue(error.message); } } ); export const createUser = createAsyncThunk( "users/create", async (userData: CreateUserRequest, { rejectWithValue }) => { try { const formData = new FormData(); formData.append("user_name", userData.username); formData.append("email", userData.email); if (userData.password) formData.append("password", userData.password); if (userData.roles && userData.roles.length > 0) { userData.roles.forEach((r) => formData.append("roles", r)); } if (userData.avatar) { formData.append("avatar", userData.avatar); } const response = await fetchClient("/admin/users", { method: "POST", body: formData, }); const raw = (response as { user?: Record }).user ?? response; return mapApiUserToUser(raw as Record); } catch (error: any) { return rejectWithValue(error.message); } } ); export const updateUser = createAsyncThunk( "users/update", async (userData: UpdateUserRequest, { rejectWithValue }) => { try { const { id, username, email, password, roles, avatar } = userData; const formData = new FormData(); if (username !== undefined) formData.append("user_name", username); if (email !== undefined) formData.append("email", email); if (password) formData.append("password", password); if (roles && roles.length > 0) { roles.forEach((r) => formData.append("roles", r)); } if (avatar) { formData.append("avatar", avatar); } const response = await fetchClient(`/admin/users/${id}`, { method: "PUT", body: formData, }); const raw = (response as { user?: Record }).user ?? response; return mapApiUserToUser(raw as Record); } catch (error: any) { return rejectWithValue(error.message); } } ); export const deleteUser = createAsyncThunk( "users/delete", async (payload: string | { id: string; hard: boolean }, { rejectWithValue }) => { try { const id = typeof payload === "string" ? payload : payload.id; const isHard = typeof payload === "object" && payload.hard; const endpoint = `/admin/users/${id}${isHard ? "?hard=true" : ""}`; await fetchClient(endpoint, { method: "DELETE", }); return { id, isHard }; } catch (error: any) { return rejectWithValue(error.message); } } ); export const restoreUser = createAsyncThunk( "users/restore", async (id: string, { rejectWithValue }) => { try { await fetchClient(`/admin/users/${id}/restore`, { method: "POST", }); return id; } catch (error: any) { return rejectWithValue(error.message); } } ); const usersSlice = createSlice({ name: "users", initialState, reducers: {}, extraReducers: (builder) => { // Fetch Active builder.addCase(fetchUsers.pending, (state) => { state.isLoading = true; state.error = null; }); builder.addCase(fetchUsers.fulfilled, (state, action: PayloadAction) => { state.isLoading = false; state.users = action.payload; }); builder.addCase(fetchUsers.rejected, (state, action) => { state.isLoading = false; state.error = action.payload as string; }); // Fetch Deleted builder.addCase(fetchDeletedUsers.pending, (state) => { state.isLoading = true; state.error = null; }); builder.addCase(fetchDeletedUsers.fulfilled, (state, action: PayloadAction) => { state.isLoading = false; state.deletedUsers = action.payload; }); // Create builder.addCase(createUser.fulfilled, (state, action: PayloadAction) => { state.isLoading = false; if (action.payload && action.payload.id) { state.users.push(action.payload); } }); // Update builder.addCase(updateUser.fulfilled, (state, action: PayloadAction) => { state.isLoading = false; if (action.payload && action.payload.id) { const index = state.users.findIndex(u => u.id === action.payload.id); if (index !== -1) { state.users[index] = action.payload; } } }); // Delete builder.addCase(deleteUser.fulfilled, (state, action: PayloadAction<{ id: string, isHard: boolean }>) => { state.isLoading = false; const { id, isHard } = action.payload; // Remove from active users list (always happens) const deletedUser = state.users.find(u => u.id === id); state.users = state.users.filter(u => u.id !== id); // If Soft Delete, add to deletedUsers list (mechanically we should re-fetch, but optimistically we can add if we had the full object) if (!isHard && deletedUser) { state.deletedUsers.unshift({ ...deletedUser, deleted_at: new Date().toISOString() }); } // If Hard Delete, remove from deletedUsers list too (checking if it was there) if (isHard) { state.deletedUsers = state.deletedUsers.filter(u => u.id !== id); } }); // Restore builder.addCase(restoreUser.fulfilled, (state, action: PayloadAction) => { const id = action.payload; const restoredUser = state.deletedUsers.find(u => u.id === id); // Remove from deleted list state.deletedUsers = state.deletedUsers.filter(u => u.id !== id); // Add to active list if (restoredUser) { const { deleted_at, ...user } = restoredUser; state.users.push(user); } }); }, }); export default usersSlice.reducer;