feat(dimentorin): backoffice admin panel (dashboard, user mgmt, session mgmt, content/roadmap, feedback, settings)

This commit is contained in:
asepharyana
2026-08-06 01:03:50 +07:00
parent 7901fc3f82
commit 567d237651
13 changed files with 1979 additions and 11 deletions
+69
View File
@@ -1,5 +1,7 @@
import { api } from '../';
import {
TAdminSessionListResponse,
TAdminSessionsParams,
TArticleDetail,
TArticleListItem,
TArticleListParams,
@@ -16,8 +18,12 @@ import {
TMentorRegisterRequest,
TMentorStats,
TPayment,
TRoadmapItem,
TRoadmapRequest,
TSessionFeedbackRequest,
TSessionListResponse,
TUpdateSessionStatusRequest,
TUpdateSessionStatusResponse,
} from '../../types/dimentorin';
import { TResponseMessage } from '../../types/common';
@@ -263,3 +269,66 @@ export const postChat = async (
});
return data.data;
};
// ---- Admin Backoffice: sessions + status + roadmap ----
export const getAdminSessions = async (
params?: TAdminSessionsParams
): Promise<TAdminSessionListResponse> => {
const { data } = await api({
method: 'GET',
url: '/dimentorin/admin/sessions',
params,
});
return data;
};
export const putUpdateSessionStatus = async (
id: string,
payload: TUpdateSessionStatusRequest
): Promise<TUpdateSessionStatusResponse> => {
const { data } = await api({
method: 'PUT',
url: `/dimentorin/sessions/update/${id}/status`,
data: payload,
});
return data.data;
};
export const getRoadmapList = async (): Promise<TRoadmapItem[]> => {
const { data } = await api({
method: 'GET',
url: '/landing/cms/roadmap',
});
return data.data;
};
export const postCreateRoadmap = async (
payload: TRoadmapRequest
): Promise<TResponseMessage> => {
const { data } = await api({
method: 'POST',
url: '/landing/cms/roadmap/create',
data: payload,
});
return data;
};
export const patchUpdateRoadmap = async (
id: string,
payload: TRoadmapRequest
): Promise<TResponseMessage> => {
const { data } = await api({
method: 'PATCH',
url: `/landing/cms/roadmap/update/${id}`,
data: payload,
});
return data;
};
export const deleteRoadmap = async (id: string): Promise<void> => {
await api({
method: 'DELETE',
url: `/landing/cms/roadmap/delete/${id}`,
});
};
+100 -1
View File
@@ -1 +1,100 @@
export {};
import { api } from '../';
import {
TActivateUserRequest,
TCreateUserRequest,
TUpdateUserRequest,
TUserDetail,
TUserListParams,
TUserListResponse,
} from '../../types/users';
import { TRolesListItem } from '../../types/roles';
import { TResponseMessage } from '../../types/common';
export const getUserMe = async (): Promise<TUserDetail> => {
const { data } = await api({
method: 'GET',
url: '/iam/users/me',
});
return data.data;
};
export const putUpdateUserMe = async (
payload: TUpdateUserRequest
): Promise<TResponseMessage> => {
const { data } = await api({
method: 'PUT',
url: '/iam/users/update/me',
data: payload,
});
return data;
};
export const getUsersList = async (
params?: TUserListParams
): Promise<TUserListResponse> => {
const { data } = await api({
method: 'GET',
url: '/iam/users',
params,
});
return data;
};
export const getUserById = async (id: string): Promise<TUserDetail> => {
const { data } = await api({
method: 'GET',
url: `/iam/users/detail/${id}`,
});
return data.data;
};
export const postCreateUser = async (
payload: TCreateUserRequest
): Promise<TUserDetail> => {
const { data } = await api({
method: 'POST',
url: '/iam/users/create',
data: payload,
});
return data.data;
};
export const putUpdateUser = async (
id: string,
payload: TUpdateUserRequest
): Promise<TResponseMessage> => {
const { data } = await api({
method: 'PUT',
url: `/iam/users/update/${id}`,
data: payload,
});
return data;
};
export const putActivateUser = async (
id: string,
payload: TActivateUserRequest
): Promise<TResponseMessage> => {
const { data } = await api({
method: 'PUT',
url: `/iam/users/activate/${id}`,
data: payload,
});
return data;
};
export const deleteUser = async (id: string): Promise<TResponseMessage> => {
const { data } = await api({
method: 'DELETE',
url: `/iam/users/delete/${id}`,
});
return data;
};
export const getRolesList = async (): Promise<TRolesListItem[]> => {
const { data } = await api({
method: 'GET',
url: '/iam/roles',
});
return data.data;
};
+77 -2
View File
@@ -1,5 +1,7 @@
import { useMutation, useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
import {
deleteRoadmap,
getAdminSessions,
getArticleById,
getArticleBySlug,
getArticleCategories,
@@ -12,12 +14,15 @@ import {
getMentorStats,
getMentors,
getMyPayments,
getMaterialById,
getMaterialById,
getMaterialBySlug,
getMaterialCategories,
getMaterialsList,
getMySessions,
getRoadmapList,
patchUpdateRoadmap,
postChat,
postCreateRoadmap,
getPaymentById,
getSessionPayments,
postBookSession,
@@ -26,12 +31,15 @@ import {
postRefreshPayment,
postRegisterMentor,
postSessionFeedback,
putUpdateSessionStatus,
} from '../../api/dimentorin';
import {
TAdminSessionListResponse,
TAdminSessionsParams,
TArticleDetail,
TArticleListItem,
TArticleListParams,
TBookSessionRequest,
TBookSessionRequest,
TChatRequest,
TChatResponse,
TMaterialDetail,
@@ -44,8 +52,12 @@ import {
TMentorRegisterRequest,
TMentorStats,
TPayment,
TRoadmapItem,
TRoadmapRequest,
TSessionFeedbackRequest,
TSessionListResponse,
TUpdateSessionStatusRequest,
TUpdateSessionStatusResponse,
} from '../../types/dimentorin';
import { TResponseError, TResponseMessage } from '../../types/common';
@@ -307,3 +319,66 @@ export const usePostChat = () => {
};
export type { TResponseMessage };
// ---- Admin Backoffice ----
export const useGetAdminSessions = (
params?: TAdminSessionsParams
): UseQueryResult<TAdminSessionListResponse, TResponseError> => {
return useQuery({
queryKey: ['admin-sessions', params],
queryFn: async () => await getAdminSessions(params),
});
};
export const usePutUpdateSessionStatus = () => {
const qc = useQueryClient();
return useMutation<
TUpdateSessionStatusResponse,
TResponseError,
{ id: string; payload: TUpdateSessionStatusRequest }
>({
mutationFn: async ({ id, payload }) =>
await putUpdateSessionStatus(id, payload),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin-sessions'] });
},
});
};
export const useGetRoadmapList = (): UseQueryResult<TRoadmapItem[], TResponseError> => {
return useQuery({
queryKey: ['admin-roadmap'],
queryFn: async () => await getRoadmapList(),
});
};
export const usePostCreateRoadmap = () => {
const qc = useQueryClient();
return useMutation<TResponseMessage, TResponseError, TRoadmapRequest>({
mutationFn: async (payload) => await postCreateRoadmap(payload),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin-roadmap'] });
},
});
};
export const usePatchUpdateRoadmap = () => {
const qc = useQueryClient();
return useMutation<TResponseMessage, TResponseError, { id: string; payload: TRoadmapRequest }>({
mutationFn: async ({ id, payload }) => await patchUpdateRoadmap(id, payload),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin-roadmap'] });
},
});
};
export const useDeleteRoadmap = () => {
const qc = useQueryClient();
return useMutation<void, TResponseError, string>({
mutationFn: async (id) => await deleteRoadmap(id),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin-roadmap'] });
},
});
};
+108 -1
View File
@@ -1 +1,108 @@
export {};
import { useMutation, useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
import {
deleteUser,
getRolesList,
getUserById,
getUserMe,
getUsersList,
postCreateUser,
putActivateUser,
putUpdateUser,
putUpdateUserMe,
} from '../../api/users';
import {
TActivateUserRequest,
TCreateUserRequest,
TUpdateUserRequest,
TUserDetail,
TUserListParams,
TUserListResponse,
} from '../../types/users';
import { TRolesListItem } from '../../types/roles';
import { TResponseError, TResponseMessage } from '../../types/common';
const USERS_KEY = ['admin-users'];
const ROLES_KEY = ['admin-roles'];
export const useGetUsersList = (
params?: TUserListParams
): UseQueryResult<TUserListResponse, TResponseError> => {
return useQuery({
queryKey: [...USERS_KEY, params],
queryFn: async () => await getUsersList(params),
});
};
export const useGetUserById = (
id: string
): UseQueryResult<TUserDetail, TResponseError> => {
return useQuery({
queryKey: [...USERS_KEY, 'detail', id],
queryFn: async () => await getUserById(id),
enabled: !!id,
});
};
export const usePostCreateUser = () => {
const qc = useQueryClient();
return useMutation<TUserDetail, TResponseError, TCreateUserRequest>({
mutationFn: async (payload) => await postCreateUser(payload),
onSuccess: () => {
qc.invalidateQueries({ queryKey: USERS_KEY });
},
});
};
export const usePutUpdateUser = () => {
const qc = useQueryClient();
return useMutation<TResponseMessage, TResponseError, { id: string; payload: TUpdateUserRequest }>({
mutationFn: async ({ id, payload }) => await putUpdateUser(id, payload),
onSuccess: () => {
qc.invalidateQueries({ queryKey: USERS_KEY });
},
});
};
export const usePutActivateUser = () => {
const qc = useQueryClient();
return useMutation<TResponseMessage, TResponseError, { id: string; payload: TActivateUserRequest }>({
mutationFn: async ({ id, payload }) => await putActivateUser(id, payload),
onSuccess: () => {
qc.invalidateQueries({ queryKey: USERS_KEY });
},
});
};
export const useDeleteUser = () => {
const qc = useQueryClient();
return useMutation<TResponseMessage, TResponseError, string>({
mutationFn: async (id) => await deleteUser(id),
onSuccess: () => {
qc.invalidateQueries({ queryKey: USERS_KEY });
},
});
};
export const useGetUserMe = (): UseQueryResult<TUserDetail, TResponseError> => {
return useQuery({
queryKey: [USERS_KEY, 'me'],
queryFn: async () => await getUserMe(),
});
};
export const usePutUpdateUserMe = () => {
const qc = useQueryClient();
return useMutation<TResponseMessage, TResponseError, TUpdateUserRequest>({
mutationFn: async (payload) => await putUpdateUserMe(payload),
onSuccess: () => {
qc.invalidateQueries({ queryKey: USERS_KEY });
},
});
};
export const useGetRolesList = (): UseQueryResult<TRolesListItem[], TResponseError> => {
return useQuery({
queryKey: ROLES_KEY,
queryFn: async () => await getRolesList(),
});
};
@@ -229,3 +229,72 @@ export type TChatResponse = {
answer: string;
sources: TChatSource[];
};
// ---- Admin Backoffice ----
export type TAdminSession = {
id: string;
mentor_id: string;
mentor_fullname: string | null;
mentee_id: string;
mentee_fullname: string | null;
mentee_email: string | null;
topic: string;
scheduled_at: string;
duration_minutes: number;
meeting_link: string | null;
session_type: string;
status: string;
rating: number | null;
feedback: string | null;
feedback_submitted_at: string | null;
created_at: string;
};
export type TAdminSessionListResponse = {
data: TAdminSession[];
meta: {
page: number;
per_page: number;
total: number;
total_pages?: number;
has_next?: boolean;
has_prev?: boolean;
};
};
export type TAdminSessionsParams = {
page?: number;
per_page?: number;
status?: string;
};
export type TUpdateSessionStatusRequest = {
status: string;
meeting_link?: string | null;
};
export type TUpdateSessionStatusResponse = {
id: string;
status: string;
meeting_link: string | null;
updated_at: string;
};
// ---- CMS Roadmap ----
export type TRoadmapItem = {
id: string;
title: string;
description: string;
status: string; // upcoming | in_progress | shipped
votes: number;
is_deleted: boolean;
created_at: string;
updated_at?: string;
};
export type TRoadmapRequest = {
title: string;
description: string;
status: string;
};
+81 -7
View File
@@ -1,13 +1,87 @@
import { TRoleDetailItem } from '../roles';
export type TUserRole = {
id: string;
name: string;
permissions: Array<{ id: string; name: string }>;
created_at: string;
updated_at: string;
};
export type TUserItem = {
id: string;
avatar: string;
birthdate: string;
email: string;
role: string; // role NAME in list view
fullname: string;
gender: string;
email: string;
avatar: string | null;
is_active: boolean;
phone_number: string;
role: TRoleDetailItem;
created_at: string;
updated_at: string;
};
export type TUserDetail = {
id: string;
role: TUserRole;
fullname: string;
legal_name: string | null;
email: string;
avatar: string | null;
is_active: boolean;
profile_extension: unknown | null;
created_at: string;
updated_at: string;
};
export type TUserListParams = {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
order?: string;
filter?: string;
filter_by?: string;
};
export type TUserListResponse = {
data: TUserItem[];
meta: {
page: number;
per_page: number;
total: number;
total_pages?: number;
has_next?: boolean;
has_prev?: boolean;
};
};
export type TCreateUserRequest = {
email: string;
password: string;
fullname: string;
is_active?: boolean;
role_id: string;
avatar?: string | null;
};
export type TUpdateUserRequest = {
email?: string;
password?: string;
fullname?: string;
legal_name?: string;
is_active?: boolean;
avatar?: string | null;
role_id?: string;
};
export type TActivateUserRequest = {
is_active: boolean;
};
export type TUsersMeProfile = {
id: string;
fullname: string;
email: string;
avatar: string | null;
is_active: boolean;
role: TUserRole;
created_at: string;
updated_at: string;
};