feat(admin-center): implement organization calculation method settings
- Added functionality to update the organization's calculation method (hourly or man-days) in the Admin Center. - Created a new component for managing the calculation method, including UI elements for selection and saving changes. - Updated API service to handle the new endpoint for updating the calculation method. - Enhanced localization files to support new keys related to the calculation method settings. - Introduced a settings page to manage organization working days and hours alongside the calculation method.
This commit is contained in:
@@ -1,30 +1,45 @@
|
|||||||
import {IWorkLenzRequest} from "../interfaces/worklenz-request";
|
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
|
||||||
import {IWorkLenzResponse} from "../interfaces/worklenz-response";
|
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
|
||||||
|
|
||||||
import db from "../config/db";
|
import db from "../config/db";
|
||||||
import {ServerResponse} from "../models/server-response";
|
import { ServerResponse } from "../models/server-response";
|
||||||
import WorklenzControllerBase from "./worklenz-controller-base";
|
import WorklenzControllerBase from "./worklenz-controller-base";
|
||||||
import HandleExceptions from "../decorators/handle-exceptions";
|
import HandleExceptions from "../decorators/handle-exceptions";
|
||||||
import {calculateMonthDays, getColor, log_error, megabytesToBytes} from "../shared/utils";
|
import {
|
||||||
|
calculateMonthDays,
|
||||||
|
getColor,
|
||||||
|
log_error,
|
||||||
|
megabytesToBytes,
|
||||||
|
} from "../shared/utils";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import {calculateStorage} from "../shared/s3";
|
import { calculateStorage } from "../shared/s3";
|
||||||
import {checkTeamSubscriptionStatus, getActiveTeamMemberCount, getCurrentProjectsCount, getFreePlanSettings, getOwnerIdByTeam, getTeamMemberCount, getUsedStorage} from "../shared/paddle-utils";
|
import {
|
||||||
|
checkTeamSubscriptionStatus,
|
||||||
|
getActiveTeamMemberCount,
|
||||||
|
getCurrentProjectsCount,
|
||||||
|
getFreePlanSettings,
|
||||||
|
getOwnerIdByTeam,
|
||||||
|
getTeamMemberCount,
|
||||||
|
getUsedStorage,
|
||||||
|
} from "../shared/paddle-utils";
|
||||||
import {
|
import {
|
||||||
addModifier,
|
addModifier,
|
||||||
cancelSubscription,
|
cancelSubscription,
|
||||||
changePlan,
|
changePlan,
|
||||||
generatePayLinkRequest,
|
generatePayLinkRequest,
|
||||||
pauseOrResumeSubscription,
|
pauseOrResumeSubscription,
|
||||||
updateUsers
|
updateUsers,
|
||||||
} from "../shared/paddle-requests";
|
} from "../shared/paddle-requests";
|
||||||
import {statusExclude} from "../shared/constants";
|
import { statusExclude } from "../shared/constants";
|
||||||
import {NotificationsService} from "../services/notifications/notifications.service";
|
import { NotificationsService } from "../services/notifications/notifications.service";
|
||||||
import {SocketEvents} from "../socket.io/events";
|
import { SocketEvents } from "../socket.io/events";
|
||||||
import {IO} from "../shared/io";
|
import { IO } from "../shared/io";
|
||||||
|
|
||||||
export default class AdminCenterController extends WorklenzControllerBase {
|
export default class AdminCenterController extends WorklenzControllerBase {
|
||||||
|
public static async checkIfUserActiveInOtherTeams(
|
||||||
public static async checkIfUserActiveInOtherTeams(owner_id: string, email: string) {
|
owner_id: string,
|
||||||
|
email: string
|
||||||
|
) {
|
||||||
if (!owner_id) throw new Error("Owner not found.");
|
if (!owner_id) throw new Error("Owner not found.");
|
||||||
|
|
||||||
const q = `SELECT EXISTS(SELECT tmi.team_member_id
|
const q = `SELECT EXISTS(SELECT tmi.team_member_id
|
||||||
@@ -41,7 +56,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
|
|
||||||
// organization
|
// organization
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getOrganizationDetails(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getOrganizationDetails(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
// const q = `SELECT organization_name AS name,
|
// const q = `SELECT organization_name AS name,
|
||||||
// contact_number,
|
// contact_number,
|
||||||
// contact_number_secondary,
|
// contact_number_secondary,
|
||||||
@@ -62,7 +80,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getOrganizationAdmins(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getOrganizationAdmins(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT u.name, email, owner AS is_owner
|
const q = `SELECT u.name, email, owner AS is_owner
|
||||||
FROM users u
|
FROM users u
|
||||||
LEFT JOIN team_members tm ON u.id = tm.user_id
|
LEFT JOIN team_members tm ON u.id = tm.user_id
|
||||||
@@ -77,8 +98,14 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getOrganizationUsers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getOrganizationUsers(
|
||||||
const { searchQuery, size, offset } = this.toPaginationOptions(req.query, ["outer_tmiv.name", "outer_tmiv.email"]);
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { searchQuery, size, offset } = this.toPaginationOptions(req.query, [
|
||||||
|
"outer_tmiv.name",
|
||||||
|
"outer_tmiv.email",
|
||||||
|
]);
|
||||||
|
|
||||||
const q = `SELECT ROW_TO_JSON(rec) AS users
|
const q = `SELECT ROW_TO_JSON(rec) AS users
|
||||||
FROM (SELECT COUNT(*) AS total,
|
FROM (SELECT COUNT(*) AS total,
|
||||||
@@ -113,8 +140,11 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async updateOrganizationName(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async updateOrganizationName(
|
||||||
const {name} = req.body;
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { name } = req.body;
|
||||||
// const q = `UPDATE users_data
|
// const q = `UPDATE users_data
|
||||||
// SET organization_name = $1
|
// SET organization_name = $1
|
||||||
// WHERE user_id = $2;`;
|
// WHERE user_id = $2;`;
|
||||||
@@ -126,8 +156,11 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async updateOwnerContactNumber(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async updateOwnerContactNumber(
|
||||||
const {contact_number} = req.body;
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { contact_number } = req.body;
|
||||||
const q = `UPDATE organizations
|
const q = `UPDATE organizations
|
||||||
SET contact_number = $1
|
SET contact_number = $1
|
||||||
WHERE user_id = $2;`;
|
WHERE user_id = $2;`;
|
||||||
@@ -136,7 +169,59 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async create(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async updateOrganizationCalculationMethod(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { calculation_method, hours_per_day } = req.body;
|
||||||
|
|
||||||
|
// Validate calculation method
|
||||||
|
if (!["hourly", "man_days"].includes(calculation_method)) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
"Invalid calculation method. Must be \"hourly\" or \"man_days\""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateQuery = `
|
||||||
|
UPDATE organizations
|
||||||
|
SET calculation_method = $1,
|
||||||
|
hours_per_day = COALESCE($2, hours_per_day),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE user_id = $3
|
||||||
|
RETURNING id, organization_name, calculation_method, hours_per_day;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await db.query(updateQuery, [
|
||||||
|
calculation_method,
|
||||||
|
hours_per_day,
|
||||||
|
req.user?.owner_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.send(new ServerResponse(false, null, "Organization not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(
|
||||||
|
new ServerResponse(true, {
|
||||||
|
organization: result.rows[0],
|
||||||
|
message: "Organization calculation method updated successfully",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HandleExceptions()
|
||||||
|
public static async create(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = ``;
|
const q = ``;
|
||||||
const result = await db.query(q, []);
|
const result = await db.query(q, []);
|
||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
@@ -144,14 +229,21 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getOrganizationTeams(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getOrganizationTeams(
|
||||||
const {searchQuery, size, offset} = this.toPaginationOptions(req.query, ["name"]);
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { searchQuery, size, offset } = this.toPaginationOptions(req.query, [
|
||||||
|
"name",
|
||||||
|
]);
|
||||||
|
|
||||||
let size_changed = size;
|
let size_changed = size;
|
||||||
|
|
||||||
if (offset == 0) size_changed = size_changed - 1;
|
if (offset == 0) size_changed = size_changed - 1;
|
||||||
|
|
||||||
const currentTeamClosure = offset == 0 ? `,
|
const currentTeamClosure =
|
||||||
|
offset == 0
|
||||||
|
? `,
|
||||||
(SELECT COALESCE(ROW_TO_JSON(c), '{}'::JSON)
|
(SELECT COALESCE(ROW_TO_JSON(c), '{}'::JSON)
|
||||||
FROM (SELECT id,
|
FROM (SELECT id,
|
||||||
name,
|
name,
|
||||||
@@ -168,7 +260,8 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
LEFT JOIN users u on team_members.user_id = u.id
|
LEFT JOIN users u on team_members.user_id = u.id
|
||||||
WHERE team_id = teams.id) rec) AS team_members
|
WHERE team_id = teams.id) rec) AS team_members
|
||||||
FROM teams
|
FROM teams
|
||||||
WHERE user_id = $1 AND teams.id = $4) c) AS current_team_data` : ``;
|
WHERE user_id = $1 AND teams.id = $4) c) AS current_team_data`
|
||||||
|
: ``;
|
||||||
|
|
||||||
const q = `SELECT ROW_TO_JSON(rec) AS teams
|
const q = `SELECT ROW_TO_JSON(rec) AS teams
|
||||||
FROM (SELECT COUNT(*) AS total,
|
FROM (SELECT COUNT(*) AS total,
|
||||||
@@ -194,26 +287,38 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
${currentTeamClosure}
|
${currentTeamClosure}
|
||||||
FROM teams
|
FROM teams
|
||||||
WHERE user_id = $1 ${searchQuery}) rec;`;
|
WHERE user_id = $1 ${searchQuery}) rec;`;
|
||||||
const result = await db.query(q, [req.user?.owner_id, size_changed, offset, req.user?.team_id]);
|
const result = await db.query(q, [
|
||||||
|
req.user?.owner_id,
|
||||||
|
size_changed,
|
||||||
|
offset,
|
||||||
|
req.user?.team_id,
|
||||||
|
]);
|
||||||
|
|
||||||
const [obj] = result.rows;
|
const [obj] = result.rows;
|
||||||
|
|
||||||
for (const team of obj.teams?.data || []) {
|
for (const team of obj.teams?.data || []) {
|
||||||
team.names = this.createTagList(team?.team_members);
|
team.names = this.createTagList(team?.team_members);
|
||||||
team.names.map((a: any) => a.color_code = getColor(a.name));
|
team.names.map((a: any) => (a.color_code = getColor(a.name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj.teams.current_team_data) {
|
if (obj.teams.current_team_data) {
|
||||||
obj.teams.current_team_data.names = this.createTagList(obj.teams.current_team_data?.team_members);
|
obj.teams.current_team_data.names = this.createTagList(
|
||||||
obj.teams.current_team_data.names.map((a: any) => a.color_code = getColor(a.name));
|
obj.teams.current_team_data?.team_members
|
||||||
|
);
|
||||||
|
obj.teams.current_team_data.names.map(
|
||||||
|
(a: any) => (a.color_code = getColor(a.name))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, obj.teams || {}));
|
return res.status(200).send(new ServerResponse(true, obj.teams || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getTeamDetails(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getTeamDetails(
|
||||||
const {id} = req.params;
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
const q = `SELECT id,
|
const q = `SELECT id,
|
||||||
name,
|
name,
|
||||||
@@ -249,47 +354,63 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
const [obj] = result.rows;
|
const [obj] = result.rows;
|
||||||
|
|
||||||
obj.names = this.createTagList(obj?.team_members);
|
obj.names = this.createTagList(obj?.team_members);
|
||||||
obj.names.map((a: any) => a.color_code = getColor(a.name));
|
obj.names.map((a: any) => (a.color_code = getColor(a.name)));
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, obj || {}));
|
return res.status(200).send(new ServerResponse(true, obj || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async updateTeam(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async updateTeam(
|
||||||
const {id} = req.params;
|
req: IWorkLenzRequest,
|
||||||
const {name, teamMembers} = req.body;
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { name, teamMembers } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update team name
|
// Update team name
|
||||||
const updateNameQuery = `UPDATE teams SET name = $1 WHERE id = $2 RETURNING id;`;
|
const updateNameQuery = `UPDATE teams SET name = $1 WHERE id = $2 RETURNING id;`;
|
||||||
const nameResult = await db.query(updateNameQuery, [name, id]);
|
const nameResult = await db.query(updateNameQuery, [name, id]);
|
||||||
|
|
||||||
if (!nameResult.rows.length) {
|
if (!nameResult.rows.length) {
|
||||||
return res.status(404).send(new ServerResponse(false, null, "Team not found"));
|
return res
|
||||||
|
.status(404)
|
||||||
|
.send(new ServerResponse(false, null, "Team not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update team member roles if provided
|
// Update team member roles if provided
|
||||||
if (teamMembers?.length) {
|
if (teamMembers?.length) {
|
||||||
// Use Promise.all to handle all role updates concurrently
|
// Use Promise.all to handle all role updates concurrently
|
||||||
await Promise.all(teamMembers.map(async (member: { role_name: string; user_id: string; }) => {
|
await Promise.all(
|
||||||
const roleQuery = `
|
teamMembers.map(
|
||||||
|
async (member: { role_name: string; user_id: string }) => {
|
||||||
|
const roleQuery = `
|
||||||
UPDATE team_members
|
UPDATE team_members
|
||||||
SET role_id = (SELECT id FROM roles WHERE roles.team_id = $1 AND name = $2)
|
SET role_id = (SELECT id FROM roles WHERE roles.team_id = $1 AND name = $2)
|
||||||
WHERE user_id = $3 AND team_id = $1
|
WHERE user_id = $3 AND team_id = $1
|
||||||
RETURNING id;`;
|
RETURNING id;`;
|
||||||
await db.query(roleQuery, [id, member.role_name, member.user_id]);
|
await db.query(roleQuery, [id, member.role_name, member.user_id]);
|
||||||
}));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, null, "Team updated successfully"));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(new ServerResponse(true, null, "Team updated successfully"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log_error("Error updating team:", error);
|
log_error("Error updating team:", error);
|
||||||
return res.status(500).send(new ServerResponse(false, null, "Failed to update team"));
|
return res
|
||||||
|
.status(500)
|
||||||
|
.send(new ServerResponse(false, null, "Failed to update team"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getBillingInfo(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getBillingInfo(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT get_billing_info($1) AS billing_info;`;
|
const q = `SELECT get_billing_info($1) AS billing_info;`;
|
||||||
const result = await db.query(q, [req.user?.owner_id]);
|
const result = await db.query(q, [req.user?.owner_id]);
|
||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
@@ -297,7 +418,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
const validTillDate = moment(data.billing_info.trial_expire_date);
|
const validTillDate = moment(data.billing_info.trial_expire_date);
|
||||||
|
|
||||||
const daysDifference = validTillDate.diff(moment(), "days");
|
const daysDifference = validTillDate.diff(moment(), "days");
|
||||||
const dateString = calculateMonthDays(moment().format("YYYY-MM-DD"), data.billing_info.trial_expire_date);
|
const dateString = calculateMonthDays(
|
||||||
|
moment().format("YYYY-MM-DD"),
|
||||||
|
data.billing_info.trial_expire_date
|
||||||
|
);
|
||||||
|
|
||||||
data.billing_info.expire_date_string = dateString;
|
data.billing_info.expire_date_string = dateString;
|
||||||
|
|
||||||
@@ -309,10 +433,14 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
data.billing_info.expire_date_string = `Your trial plan expires in ${dateString}.`;
|
data.billing_info.expire_date_string = `Your trial plan expires in ${dateString}.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.billing_info.billing_type === "year") data.billing_info.unit_price_per_month = data.billing_info.unit_price / 12;
|
if (data.billing_info.billing_type === "year")
|
||||||
|
data.billing_info.unit_price_per_month =
|
||||||
|
data.billing_info.unit_price / 12;
|
||||||
|
|
||||||
const teamMemberData = await getTeamMemberCount(req.user?.owner_id ?? "");
|
const teamMemberData = await getTeamMemberCount(req.user?.owner_id ?? "");
|
||||||
const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id ?? "");
|
const subscriptionData = await checkTeamSubscriptionStatus(
|
||||||
|
req.user?.team_id ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
data.billing_info.total_used = teamMemberData.user_count;
|
data.billing_info.total_used = teamMemberData.user_count;
|
||||||
data.billing_info.total_seats = subscriptionData.quantity;
|
data.billing_info.total_seats = subscriptionData.quantity;
|
||||||
@@ -321,7 +449,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getBillingTransactions(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getBillingTransactions(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT subscription_payment_id,
|
const q = `SELECT subscription_payment_id,
|
||||||
event_time::date,
|
event_time::date,
|
||||||
(next_bill_date::DATE - INTERVAL '1 day')::DATE AS next_bill_date,
|
(next_bill_date::DATE - INTERVAL '1 day')::DATE AS next_bill_date,
|
||||||
@@ -339,7 +470,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getBillingCharges(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getBillingCharges(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT (SELECT name FROM licensing_pricing_plans lpp WHERE id = lus.plan_id),
|
const q = `SELECT (SELECT name FROM licensing_pricing_plans lpp WHERE id = lus.plan_id),
|
||||||
unit_price::numeric,
|
unit_price::numeric,
|
||||||
currency,
|
currency,
|
||||||
@@ -365,11 +499,21 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
LIMIT 1)::INT;`;
|
LIMIT 1)::INT;`;
|
||||||
const countResult = await db.query(countQ, [req.user?.owner_id]);
|
const countResult = await db.query(countQ, [req.user?.owner_id]);
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, {plan_charges: result.rows, modifiers: countResult.rows}));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(true, {
|
||||||
|
plan_charges: result.rows,
|
||||||
|
modifiers: countResult.rows,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getBillingModifiers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getBillingModifiers(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT created_at
|
const q = `SELECT created_at
|
||||||
FROM licensing_user_subscription_modifiers
|
FROM licensing_user_subscription_modifiers
|
||||||
WHERE subscription_id = (SELECT subscription_id
|
WHERE subscription_id = (SELECT subscription_id
|
||||||
@@ -383,7 +527,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getBillingConfiguration(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getBillingConfiguration(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT name,
|
const q = `SELECT name,
|
||||||
email,
|
email,
|
||||||
organization_name AS company_name,
|
organization_name AS company_name,
|
||||||
@@ -404,8 +551,20 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async updateBillingConfiguration(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async updateBillingConfiguration(
|
||||||
const {company_name, phone, address_line_1, address_line_2, city, state, postal_code, country} = req.body;
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const {
|
||||||
|
company_name,
|
||||||
|
phone,
|
||||||
|
address_line_1,
|
||||||
|
address_line_2,
|
||||||
|
city,
|
||||||
|
state,
|
||||||
|
postal_code,
|
||||||
|
country,
|
||||||
|
} = req.body;
|
||||||
const q = `UPDATE organizations
|
const q = `UPDATE organizations
|
||||||
SET organization_name = $1,
|
SET organization_name = $1,
|
||||||
contact_number = $2,
|
contact_number = $2,
|
||||||
@@ -416,24 +575,47 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
postal_code = $7,
|
postal_code = $7,
|
||||||
country = $8
|
country = $8
|
||||||
WHERE user_id = $9;`;
|
WHERE user_id = $9;`;
|
||||||
const result = await db.query(q, [company_name, phone, address_line_1, address_line_2, city, state, postal_code, country, req.user?.owner_id]);
|
const result = await db.query(q, [
|
||||||
|
company_name,
|
||||||
|
phone,
|
||||||
|
address_line_1,
|
||||||
|
address_line_2,
|
||||||
|
city,
|
||||||
|
state,
|
||||||
|
postal_code,
|
||||||
|
country,
|
||||||
|
req.user?.owner_id,
|
||||||
|
]);
|
||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, data, "Configuration Updated"));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(new ServerResponse(true, data, "Configuration Updated"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async upgradePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async upgradePlan(
|
||||||
const {plan} = req.query;
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { plan } = req.query;
|
||||||
|
|
||||||
const obj = await getTeamMemberCount(req.user?.owner_id ?? "");
|
const obj = await getTeamMemberCount(req.user?.owner_id ?? "");
|
||||||
const axiosResponse = await generatePayLinkRequest(obj, plan as string, req.user?.owner_id, req.user?.id);
|
const axiosResponse = await generatePayLinkRequest(
|
||||||
|
obj,
|
||||||
|
plan as string,
|
||||||
|
req.user?.owner_id,
|
||||||
|
req.user?.id
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getPlans(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getPlans(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT
|
const q = `SELECT
|
||||||
ls.default_monthly_plan AS monthly_plan_id,
|
ls.default_monthly_plan AS monthly_plan_id,
|
||||||
lp_monthly.name AS monthly_plan_name,
|
lp_monthly.name AS monthly_plan_name,
|
||||||
@@ -455,8 +637,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
|
|
||||||
const obj = await getTeamMemberCount(req.user?.owner_id ?? "");
|
const obj = await getTeamMemberCount(req.user?.owner_id ?? "");
|
||||||
|
|
||||||
data.team_member_limit = data.team_member_limit === 0 ? "Unlimited" : data.team_member_limit;
|
data.team_member_limit =
|
||||||
data.projects_limit = data.projects_limit === 0 ? "Unlimited" : data.projects_limit;
|
data.team_member_limit === 0 ? "Unlimited" : data.team_member_limit;
|
||||||
|
data.projects_limit =
|
||||||
|
data.projects_limit === 0 ? "Unlimited" : data.projects_limit;
|
||||||
data.free_tier_storage = `${data.free_tier_storage}MB`;
|
data.free_tier_storage = `${data.free_tier_storage}MB`;
|
||||||
data.current_user_count = obj.user_count;
|
data.current_user_count = obj.user_count;
|
||||||
data.annual_price = (data.annual_price / 12).toFixed(2);
|
data.annual_price = (data.annual_price / 12).toFixed(2);
|
||||||
@@ -465,7 +649,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async purchaseStorage(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async purchaseStorage(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT subscription_id
|
const q = `SELECT subscription_id
|
||||||
FROM licensing_user_subscriptions lus
|
FROM licensing_user_subscriptions lus
|
||||||
WHERE user_id = $1;`;
|
WHERE user_id = $1;`;
|
||||||
@@ -478,8 +665,11 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async changePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async changePlan(
|
||||||
const {plan} = req.query;
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { plan } = req.query;
|
||||||
|
|
||||||
const q = `SELECT subscription_id
|
const q = `SELECT subscription_id
|
||||||
FROM licensing_user_subscriptions lus
|
FROM licensing_user_subscriptions lus
|
||||||
@@ -487,14 +677,23 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
const result = await db.query(q, [req.user?.owner_id]);
|
const result = await db.query(q, [req.user?.owner_id]);
|
||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
|
|
||||||
const axiosResponse = await changePlan(plan as string, data.subscription_id);
|
const axiosResponse = await changePlan(
|
||||||
|
plan as string,
|
||||||
|
data.subscription_id
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async cancelPlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async cancelPlan(
|
||||||
if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request."));
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
if (!req.user?.owner_id)
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(new ServerResponse(false, "Invalid Request."));
|
||||||
|
|
||||||
const q = `SELECT subscription_id
|
const q = `SELECT subscription_id
|
||||||
FROM licensing_user_subscriptions lus
|
FROM licensing_user_subscriptions lus
|
||||||
@@ -502,14 +701,23 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
const result = await db.query(q, [req.user?.owner_id]);
|
const result = await db.query(q, [req.user?.owner_id]);
|
||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
|
|
||||||
const axiosResponse = await cancelSubscription(data.subscription_id, req.user?.owner_id);
|
const axiosResponse = await cancelSubscription(
|
||||||
|
data.subscription_id,
|
||||||
|
req.user?.owner_id
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async pauseSubscription(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async pauseSubscription(
|
||||||
if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request."));
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
if (!req.user?.owner_id)
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(new ServerResponse(false, "Invalid Request."));
|
||||||
|
|
||||||
const q = `SELECT subscription_id
|
const q = `SELECT subscription_id
|
||||||
FROM licensing_user_subscriptions lus
|
FROM licensing_user_subscriptions lus
|
||||||
@@ -517,14 +725,24 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
const result = await db.query(q, [req.user?.owner_id]);
|
const result = await db.query(q, [req.user?.owner_id]);
|
||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
|
|
||||||
const axiosResponse = await pauseOrResumeSubscription(data.subscription_id, req.user?.owner_id, true);
|
const axiosResponse = await pauseOrResumeSubscription(
|
||||||
|
data.subscription_id,
|
||||||
|
req.user?.owner_id,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async resumeSubscription(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async resumeSubscription(
|
||||||
if (!req.user?.owner_id) return res.status(200).send(new ServerResponse(false, "Invalid Request."));
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
if (!req.user?.owner_id)
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(new ServerResponse(false, "Invalid Request."));
|
||||||
|
|
||||||
const q = `SELECT subscription_id
|
const q = `SELECT subscription_id
|
||||||
FROM licensing_user_subscriptions lus
|
FROM licensing_user_subscriptions lus
|
||||||
@@ -532,13 +750,20 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
const result = await db.query(q, [req.user?.owner_id]);
|
const result = await db.query(q, [req.user?.owner_id]);
|
||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
|
|
||||||
const axiosResponse = await pauseOrResumeSubscription(data.subscription_id, req.user?.owner_id, false);
|
const axiosResponse = await pauseOrResumeSubscription(
|
||||||
|
data.subscription_id,
|
||||||
|
req.user?.owner_id,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getBillingStorageInfo(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getBillingStorageInfo(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT trial_in_progress,
|
const q = `SELECT trial_in_progress,
|
||||||
trial_expire_date,
|
trial_expire_date,
|
||||||
ud.storage,
|
ud.storage,
|
||||||
@@ -557,7 +782,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getAccountStorage(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getAccountStorage(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const teamsQ = `SELECT id
|
const teamsQ = `SELECT id
|
||||||
FROM teams
|
FROM teams
|
||||||
WHERE user_id = $1;`;
|
WHERE user_id = $1;`;
|
||||||
@@ -577,14 +805,19 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
storage.used += await calculateStorage(team.id);
|
storage.used += await calculateStorage(team.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.remaining = (storage.total * 1024 * 1024 * 1024) - storage.used;
|
storage.remaining = storage.total * 1024 * 1024 * 1024 - storage.used;
|
||||||
storage.used_percent = Math.ceil((storage.used / (storage.total * 1024 * 1024 * 1024)) * 10000) / 100;
|
storage.used_percent =
|
||||||
|
Math.ceil((storage.used / (storage.total * 1024 * 1024 * 1024)) * 10000) /
|
||||||
|
100;
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, storage));
|
return res.status(200).send(new ServerResponse(true, storage));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getCountries(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getCountries(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const q = `SELECT id, name, code
|
const q = `SELECT id, name, code
|
||||||
FROM countries
|
FROM countries
|
||||||
ORDER BY name;`;
|
ORDER BY name;`;
|
||||||
@@ -594,7 +827,10 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async switchToFreePlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async switchToFreePlan(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const { id: teamId } = req.params;
|
const { id: teamId } = req.params;
|
||||||
|
|
||||||
const limits = await getFreePlanSettings();
|
const limits = await getFreePlanSettings();
|
||||||
@@ -604,18 +840,45 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
if (parseInt(limits.team_member_limit) !== 0) {
|
if (parseInt(limits.team_member_limit) !== 0) {
|
||||||
const teamMemberCount = await getTeamMemberCount(ownerId);
|
const teamMemberCount = await getTeamMemberCount(ownerId);
|
||||||
if (parseInt(teamMemberCount) > parseInt(limits.team_member_limit)) {
|
if (parseInt(teamMemberCount) > parseInt(limits.team_member_limit)) {
|
||||||
return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${limits.team_member_limit} members.`));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
`Sorry, the free plan cannot have more than ${limits.team_member_limit} members.`
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectsCount = await getCurrentProjectsCount(ownerId);
|
const projectsCount = await getCurrentProjectsCount(ownerId);
|
||||||
if (parseInt(projectsCount) > parseInt(limits.projects_limit)) {
|
if (parseInt(projectsCount) > parseInt(limits.projects_limit)) {
|
||||||
return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot have more than ${limits.projects_limit} projects.`));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
`Sorry, the free plan cannot have more than ${limits.projects_limit} projects.`
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const usedStorage = await getUsedStorage(ownerId);
|
const usedStorage = await getUsedStorage(ownerId);
|
||||||
if (parseInt(usedStorage) > megabytesToBytes(parseInt(limits.free_tier_storage))) {
|
if (
|
||||||
return res.status(200).send(new ServerResponse(false, [], `Sorry, the free plan cannot exceed ${limits.free_tier_storage}MB of storage.`));
|
parseInt(usedStorage) >
|
||||||
|
megabytesToBytes(parseInt(limits.free_tier_storage))
|
||||||
|
) {
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
`Sorry, the free plan cannot exceed ${limits.free_tier_storage}MB of storage.`
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const update_q = `UPDATE organizations
|
const update_q = `UPDATE organizations
|
||||||
@@ -626,13 +889,32 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
WHERE user_id = $1;`;
|
WHERE user_id = $1;`;
|
||||||
await db.query(update_q, [ownerId]);
|
await db.query(update_q, [ownerId]);
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, [], "Your plan has been successfully switched to the Free Plan."));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
"Your plan has been successfully switched to the Free Plan."
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return res.status(200).send(new ServerResponse(false, [], "Failed to switch to the Free Plan. Please try again later."));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
"Failed to switch to the Free Plan. Please try again later."
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async redeem(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async redeem(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const { code } = req.body;
|
const { code } = req.body;
|
||||||
|
|
||||||
const q = `SELECT * FROM licensing_coupon_codes WHERE coupon_code = $1 AND is_redeemed IS FALSE AND is_refunded IS FALSE;`;
|
const q = `SELECT * FROM licensing_coupon_codes WHERE coupon_code = $1 AND is_redeemed IS FALSE AND is_refunded IS FALSE;`;
|
||||||
@@ -640,14 +922,26 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
|
|
||||||
if (!result.rows.length)
|
if (!result.rows.length)
|
||||||
return res.status(200).send(new ServerResponse(false, [], "Redeem Code verification Failed! Please try again."));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
"Redeem Code verification Failed! Please try again."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const checkQ = `SELECT sum(team_members_limit) AS team_member_total FROM licensing_coupon_codes WHERE redeemed_by = $1 AND is_redeemed IS TRUE;`;
|
const checkQ = `SELECT sum(team_members_limit) AS team_member_total FROM licensing_coupon_codes WHERE redeemed_by = $1 AND is_redeemed IS TRUE;`;
|
||||||
const checkResult = await db.query(checkQ, [req.user?.owner_id]);
|
const checkResult = await db.query(checkQ, [req.user?.owner_id]);
|
||||||
const [total] = checkResult.rows;
|
const [total] = checkResult.rows;
|
||||||
|
|
||||||
if (parseInt(total.team_member_total) > 50)
|
if (parseInt(total.team_member_total) > 50)
|
||||||
return res.status(200).send(new ServerResponse(false, [], "Maximum number of codes redeemed!"));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(false, [], "Maximum number of codes redeemed!")
|
||||||
|
);
|
||||||
|
|
||||||
const updateQ = `UPDATE licensing_coupon_codes
|
const updateQ = `UPDATE licensing_coupon_codes
|
||||||
SET is_redeemed = TRUE, redeemed_at = CURRENT_TIMESTAMP,
|
SET is_redeemed = TRUE, redeemed_at = CURRENT_TIMESTAMP,
|
||||||
@@ -663,16 +957,28 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
WHERE user_id = $1;`;
|
WHERE user_id = $1;`;
|
||||||
await db.query(updateQ2, [req.user?.owner_id]);
|
await db.query(updateQ2, [req.user?.owner_id]);
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, [], "Code redeemed successfully!"));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(new ServerResponse(true, [], "Code redeemed successfully!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async deleteTeam(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async deleteTeam(
|
||||||
const {id} = req.params;
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
if (id == req.user?.team_id) {
|
if (id == req.user?.team_id) {
|
||||||
return res.status(200).send(new ServerResponse(true, [], "Please switch to another team before attempting deletion.")
|
return res
|
||||||
.withTitle("Unable to remove the presently active team!"));
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
"Please switch to another team before attempting deletion."
|
||||||
|
).withTitle("Unable to remove the presently active team!")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const q = `DELETE FROM teams WHERE id = $1;`;
|
const q = `DELETE FROM teams WHERE id = $1;`;
|
||||||
@@ -682,16 +988,26 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async deleteById(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async deleteById(
|
||||||
const {id} = req.params;
|
req: IWorkLenzRequest,
|
||||||
const {teamId} = req.body;
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { teamId } = req.body;
|
||||||
|
|
||||||
if (!id || !teamId) return res.status(200).send(new ServerResponse(false, "Required fields are missing."));
|
if (!id || !teamId)
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(new ServerResponse(false, "Required fields are missing."));
|
||||||
|
|
||||||
// check subscription status
|
// check subscription status
|
||||||
const subscriptionData = await checkTeamSubscriptionStatus(teamId);
|
const subscriptionData = await checkTeamSubscriptionStatus(teamId);
|
||||||
if (statusExclude.includes(subscriptionData.subscription_status)) {
|
if (statusExclude.includes(subscriptionData.subscription_status)) {
|
||||||
return res.status(200).send(new ServerResponse(false, "Please check your subscription status."));
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(false, "Please check your subscription status.")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const q = `SELECT remove_team_member($1, $2, $3) AS member;`;
|
const q = `SELECT remove_team_member($1, $2, $3) AS member;`;
|
||||||
@@ -702,17 +1018,32 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
|
|
||||||
// if (subscriptionData.status === "trialing") break;
|
// if (subscriptionData.status === "trialing") break;
|
||||||
if (!subscriptionData.is_credit && !subscriptionData.is_custom) {
|
if (!subscriptionData.is_credit && !subscriptionData.is_custom) {
|
||||||
if (subscriptionData.subscription_status === "active" && subscriptionData.quantity > 0) {
|
if (
|
||||||
|
subscriptionData.subscription_status === "active" &&
|
||||||
|
subscriptionData.quantity > 0
|
||||||
|
) {
|
||||||
const obj = await getActiveTeamMemberCount(req.user?.owner_id ?? "");
|
const obj = await getActiveTeamMemberCount(req.user?.owner_id ?? "");
|
||||||
|
|
||||||
const userActiveInOtherTeams = await this.checkIfUserActiveInOtherTeams(req.user?.owner_id as string, req.query?.email as string);
|
const userActiveInOtherTeams = await this.checkIfUserActiveInOtherTeams(
|
||||||
|
req.user?.owner_id as string,
|
||||||
|
req.query?.email as string
|
||||||
|
);
|
||||||
|
|
||||||
if (!userActiveInOtherTeams) {
|
if (!userActiveInOtherTeams) {
|
||||||
const response = await updateUsers(subscriptionData.subscription_id, obj.user_count);
|
const response = await updateUsers(
|
||||||
if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, response.message || "Please check your subscription."));
|
subscriptionData.subscription_id,
|
||||||
|
obj.user_count
|
||||||
|
);
|
||||||
|
if (!response.body.subscription_id)
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(
|
||||||
|
new ServerResponse(
|
||||||
|
false,
|
||||||
|
response.message || "Please check your subscription."
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,26 +1051,39 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
receiver_socket_id: data.socket_id,
|
receiver_socket_id: data.socket_id,
|
||||||
message,
|
message,
|
||||||
team: data.team,
|
team: data.team,
|
||||||
team_id: id
|
team_id: id,
|
||||||
});
|
});
|
||||||
|
|
||||||
IO.emitByUserId(data.member.id, req.user?.id || null, SocketEvents.TEAM_MEMBER_REMOVED, {
|
IO.emitByUserId(
|
||||||
teamId: id,
|
data.member.id,
|
||||||
message
|
req.user?.id || null,
|
||||||
});
|
SocketEvents.TEAM_MEMBER_REMOVED,
|
||||||
|
{
|
||||||
|
teamId: id,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
);
|
||||||
return res.status(200).send(new ServerResponse(true, result.rows));
|
return res.status(200).send(new ServerResponse(true, result.rows));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getFreePlanLimits(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getFreePlanLimits(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
const limits = await getFreePlanSettings();
|
const limits = await getFreePlanSettings();
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, limits || {}));
|
return res.status(200).send(new ServerResponse(true, limits || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getOrganizationProjects(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getOrganizationProjects(
|
||||||
const { searchQuery, size, offset } = this.toPaginationOptions(req.query, ["p.name"]);
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { searchQuery, size, offset } = this.toPaginationOptions(req.query, [
|
||||||
|
"p.name",
|
||||||
|
]);
|
||||||
|
|
||||||
const countQ = `SELECT COUNT(*) AS total
|
const countQ = `SELECT COUNT(*) AS total
|
||||||
FROM projects p
|
FROM projects p
|
||||||
@@ -770,7 +1114,7 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
total: countResult.rows[0]?.total ?? 0,
|
total: countResult.rows[0]?.total ?? 0,
|
||||||
data: result.rows ?? []
|
data: result.rows ?? [],
|
||||||
};
|
};
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, response));
|
return res.status(200).send(new ServerResponse(true, response));
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ export default class ScheduleControllerV2 extends WorklenzControllerBase {
|
|||||||
const [workingDays] = workingDaysResults.rows;
|
const [workingDays] = workingDaysResults.rows;
|
||||||
|
|
||||||
// get organization working hours
|
// get organization working hours
|
||||||
const getDataHoursq = `SELECT working_hours FROM organizations WHERE user_id = $1 GROUP BY id LIMIT 1;`;
|
const getDataHoursq = `SELECT hours_per_day FROM organizations WHERE user_id = $1 GROUP BY id LIMIT 1;`;
|
||||||
|
|
||||||
const workingHoursResults = await db.query(getDataHoursq, [req.user?.owner_id]);
|
const workingHoursResults = await db.query(getDataHoursq, [req.user?.owner_id]);
|
||||||
|
|
||||||
const [workingHours] = workingHoursResults.rows;
|
const [workingHours] = workingHoursResults.rows;
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, { workingDays: workingDays?.working_days, workingHours: workingHours?.working_hours }));
|
return res.status(200).send(new ServerResponse(true, { workingDays: workingDays?.working_days, workingHours: workingHours?.hours_per_day }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
@@ -74,18 +74,13 @@ export default class ScheduleControllerV2 extends WorklenzControllerBase {
|
|||||||
.map(day => `${day.toLowerCase()} = ${workingDays.includes(day)}`)
|
.map(day => `${day.toLowerCase()} = ${workingDays.includes(day)}`)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
const updateQuery = `
|
const updateQuery = `UPDATE public.organization_working_days
|
||||||
UPDATE public.organization_working_days
|
|
||||||
SET ${setClause}, updated_at = CURRENT_TIMESTAMP
|
SET ${setClause}, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE organization_id IN (
|
WHERE organization_id IN (SELECT id FROM organizations WHERE user_id = $1);`;
|
||||||
SELECT organization_id FROM organizations
|
|
||||||
WHERE user_id = $1
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
await db.query(updateQuery, [req.user?.owner_id]);
|
await db.query(updateQuery, [req.user?.owner_id]);
|
||||||
|
|
||||||
const getDataHoursq = `UPDATE organizations SET working_hours = $1 WHERE user_id = $2;`;
|
const getDataHoursq = `UPDATE organizations SET hours_per_day = $1 WHERE user_id = $2;`;
|
||||||
|
|
||||||
await db.query(getDataHoursq, [workingHours, req.user?.owner_id]);
|
await db.query(getDataHoursq, [workingHours, req.user?.owner_id]);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const adminCenterApiRouter = express.Router();
|
|||||||
adminCenterApiRouter.get("/organization", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationDetails));
|
adminCenterApiRouter.get("/organization", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationDetails));
|
||||||
adminCenterApiRouter.get("/organization/admins", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationAdmins));
|
adminCenterApiRouter.get("/organization/admins", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationAdmins));
|
||||||
adminCenterApiRouter.put("/organization", teamOwnerOrAdminValidator, organizationSettingsValidator, safeControllerFunction(AdminCenterController.updateOrganizationName));
|
adminCenterApiRouter.put("/organization", teamOwnerOrAdminValidator, organizationSettingsValidator, safeControllerFunction(AdminCenterController.updateOrganizationName));
|
||||||
|
adminCenterApiRouter.put("/organization/calculation-method", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.updateOrganizationCalculationMethod));
|
||||||
adminCenterApiRouter.put("/organization/owner/contact-number", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.updateOwnerContactNumber));
|
adminCenterApiRouter.put("/organization/owner/contact-number", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.updateOwnerContactNumber));
|
||||||
|
|
||||||
// users
|
// users
|
||||||
|
|||||||
@@ -4,5 +4,29 @@
|
|||||||
"owner": "Pronari i Organizatës",
|
"owner": "Pronari i Organizatës",
|
||||||
"admins": "Administruesit e Organizatës",
|
"admins": "Administruesit e Organizatës",
|
||||||
"contactNumber": "Shto Numrin e Kontaktit",
|
"contactNumber": "Shto Numrin e Kontaktit",
|
||||||
"edit": "Redakto"
|
"edit": "Redakto",
|
||||||
|
"organizationWorkingDaysAndHours": "Ditët dhe Orët e Punës së Organizatës",
|
||||||
|
"workingDays": "Ditët e Punës",
|
||||||
|
"workingHours": "Orët e Punës",
|
||||||
|
"monday": "E Hënë",
|
||||||
|
"tuesday": "E Martë",
|
||||||
|
"wednesday": "E Mërkurë",
|
||||||
|
"thursday": "E Enjte",
|
||||||
|
"friday": "E Premte",
|
||||||
|
"saturday": "E Shtunë",
|
||||||
|
"sunday": "E Dielë",
|
||||||
|
"hours": "orë",
|
||||||
|
"saveButton": "Ruaj",
|
||||||
|
"saved": "Cilësimet u ruajtën me sukses",
|
||||||
|
"errorSaving": "Gabim gjatë ruajtjes së cilësimeve",
|
||||||
|
"organizationCalculationMethod": "Metoda e Llogaritjes së Organizatës",
|
||||||
|
"calculationMethod": "Metoda e Llogaritjes",
|
||||||
|
"hourlyRates": "Normat Orërore",
|
||||||
|
"manDays": "Ditët e Njeriut",
|
||||||
|
"saveChanges": "Ruaj Ndryshimet",
|
||||||
|
"hourlyCalculationDescription": "Të gjitha kostot e projektit do të llogariten duke përdorur orët e vlerësuara × normat orërore",
|
||||||
|
"manDaysCalculationDescription": "Të gjitha kostot e projektit do të llogariten duke përdorur ditët e vlerësuara të njeriut × normat ditore",
|
||||||
|
"calculationMethodTooltip": "Ky cilësim zbatohet për të gjitha projektet në organizatën tuaj",
|
||||||
|
"calculationMethodUpdated": "Metoda e llogaritjes së organizatës u përditësua me sukses",
|
||||||
|
"calculationMethodUpdateError": "Dështoi përditësimi i metodës së llogaritjes"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"settings": "Cilësimet",
|
||||||
|
"organizationWorkingDaysAndHours": "Ditët dhe Orët e Punës së Organizatës",
|
||||||
|
"workingDays": "Ditët e Punës",
|
||||||
|
"workingHours": "Orët e Punës",
|
||||||
|
"hours": "orë",
|
||||||
|
"monday": "E Hënë",
|
||||||
|
"tuesday": "E Martë",
|
||||||
|
"wednesday": "E Mërkurë",
|
||||||
|
"thursday": "E Enjte",
|
||||||
|
"friday": "E Premte",
|
||||||
|
"saturday": "E Shtunë",
|
||||||
|
"sunday": "E Dielë",
|
||||||
|
"saveButton": "Ruaj",
|
||||||
|
"saved": "Cilësimet u ruajtën me sukses",
|
||||||
|
"errorSaving": "Gabim gjatë ruajtjes së cilësimeve"
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@
|
|||||||
"teams": "Ekipet",
|
"teams": "Ekipet",
|
||||||
"billing": "Faturimi",
|
"billing": "Faturimi",
|
||||||
"projects": "Projektet",
|
"projects": "Projektet",
|
||||||
|
"settings": "Cilësimet",
|
||||||
"adminCenter": "Qendra Administrative"
|
"adminCenter": "Qendra Administrative"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,29 @@
|
|||||||
"owner": "Organisationsinhaber",
|
"owner": "Organisationsinhaber",
|
||||||
"admins": "Organisationsadministratoren",
|
"admins": "Organisationsadministratoren",
|
||||||
"contactNumber": "Kontaktnummer hinzufügen",
|
"contactNumber": "Kontaktnummer hinzufügen",
|
||||||
"edit": "Bearbeiten"
|
"edit": "Bearbeiten",
|
||||||
|
"organizationWorkingDaysAndHours": "Arbeitstage und -stunden der Organisation",
|
||||||
|
"workingDays": "Arbeitstage",
|
||||||
|
"workingHours": "Arbeitsstunden",
|
||||||
|
"monday": "Montag",
|
||||||
|
"tuesday": "Dienstag",
|
||||||
|
"wednesday": "Mittwoch",
|
||||||
|
"thursday": "Donnerstag",
|
||||||
|
"friday": "Freitag",
|
||||||
|
"saturday": "Samstag",
|
||||||
|
"sunday": "Sonntag",
|
||||||
|
"hours": "Stunden",
|
||||||
|
"saveButton": "Speichern",
|
||||||
|
"saved": "Einstellungen erfolgreich gespeichert",
|
||||||
|
"errorSaving": "Fehler beim Speichern der Einstellungen",
|
||||||
|
"organizationCalculationMethod": "Organisations-Berechnungsmethode",
|
||||||
|
"calculationMethod": "Berechnungsmethode",
|
||||||
|
"hourlyRates": "Stundensätze",
|
||||||
|
"manDays": "Mann-Tage",
|
||||||
|
"saveChanges": "Änderungen speichern",
|
||||||
|
"hourlyCalculationDescription": "Alle Projektkosten werden anhand geschätzter Stunden × Stundensätze berechnet",
|
||||||
|
"manDaysCalculationDescription": "Alle Projektkosten werden anhand geschätzter Mann-Tage × Tagessätze berechnet",
|
||||||
|
"calculationMethodTooltip": "Diese Einstellung gilt für alle Projekte in Ihrer Organisation",
|
||||||
|
"calculationMethodUpdated": "Organisations-Berechnungsmethode erfolgreich aktualisiert",
|
||||||
|
"calculationMethodUpdateError": "Fehler beim Aktualisieren der Berechnungsmethode"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"settings": "Einstellungen",
|
||||||
|
"organizationWorkingDaysAndHours": "Arbeitstage und -stunden der Organisation",
|
||||||
|
"workingDays": "Arbeitstage",
|
||||||
|
"workingHours": "Arbeitsstunden",
|
||||||
|
"hours": "Stunden",
|
||||||
|
"monday": "Montag",
|
||||||
|
"tuesday": "Dienstag",
|
||||||
|
"wednesday": "Mittwoch",
|
||||||
|
"thursday": "Donnerstag",
|
||||||
|
"friday": "Freitag",
|
||||||
|
"saturday": "Samstag",
|
||||||
|
"sunday": "Sonntag",
|
||||||
|
"saveButton": "Speichern",
|
||||||
|
"saved": "Einstellungen erfolgreich gespeichert",
|
||||||
|
"errorSaving": "Fehler beim Speichern der Einstellungen"
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@
|
|||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
"billing": "Abrechnung",
|
"billing": "Abrechnung",
|
||||||
"projects": "Projekte",
|
"projects": "Projekte",
|
||||||
|
"settings": "Einstellungen",
|
||||||
"adminCenter": "Admin-Center"
|
"adminCenter": "Admin-Center"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,29 @@
|
|||||||
"owner": "Organization Owner",
|
"owner": "Organization Owner",
|
||||||
"admins": "Organization Admins",
|
"admins": "Organization Admins",
|
||||||
"contactNumber": "Add Contact Number",
|
"contactNumber": "Add Contact Number",
|
||||||
"edit": "Edit"
|
"edit": "Edit",
|
||||||
|
"organizationWorkingDaysAndHours": "Organization Working Days & Hours",
|
||||||
|
"workingDays": "Working Days",
|
||||||
|
"workingHours": "Working Hours",
|
||||||
|
"monday": "Monday",
|
||||||
|
"tuesday": "Tuesday",
|
||||||
|
"wednesday": "Wednesday",
|
||||||
|
"thursday": "Thursday",
|
||||||
|
"friday": "Friday",
|
||||||
|
"saturday": "Saturday",
|
||||||
|
"sunday": "Sunday",
|
||||||
|
"hours": "hours",
|
||||||
|
"saveButton": "Save",
|
||||||
|
"saved": "Settings saved successfully",
|
||||||
|
"errorSaving": "Error saving settings",
|
||||||
|
"organizationCalculationMethod": "Organization Calculation Method",
|
||||||
|
"calculationMethod": "Calculation Method",
|
||||||
|
"hourlyRates": "Hourly Rates",
|
||||||
|
"manDays": "Man Days",
|
||||||
|
"saveChanges": "Save Changes",
|
||||||
|
"hourlyCalculationDescription": "All project costs will be calculated using estimated hours × hourly rates",
|
||||||
|
"manDaysCalculationDescription": "All project costs will be calculated using estimated man days × daily rates",
|
||||||
|
"calculationMethodTooltip": "This setting applies to all projects in your organization",
|
||||||
|
"calculationMethodUpdated": "Organization calculation method updated successfully",
|
||||||
|
"calculationMethodUpdateError": "Failed to update calculation method"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"settings": "Settings",
|
||||||
|
"organizationWorkingDaysAndHours": "Organization Working Days & Hours",
|
||||||
|
"workingDays": "Working Days",
|
||||||
|
"workingHours": "Working Hours",
|
||||||
|
"hours": "hours",
|
||||||
|
"monday": "Monday",
|
||||||
|
"tuesday": "Tuesday",
|
||||||
|
"wednesday": "Wednesday",
|
||||||
|
"thursday": "Thursday",
|
||||||
|
"friday": "Friday",
|
||||||
|
"saturday": "Saturday",
|
||||||
|
"sunday": "Sunday",
|
||||||
|
"saveButton": "Save",
|
||||||
|
"saved": "Settings saved successfully",
|
||||||
|
"errorSaving": "Error saving settings"
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@
|
|||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
"projects": "Projects",
|
"projects": "Projects",
|
||||||
|
"settings": "Settings",
|
||||||
"adminCenter": "Admin Center"
|
"adminCenter": "Admin Center"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,29 @@
|
|||||||
"owner": "Propietario de la Organización",
|
"owner": "Propietario de la Organización",
|
||||||
"admins": "Administradores de la Organización",
|
"admins": "Administradores de la Organización",
|
||||||
"contactNumber": "Agregar Número de Contacto",
|
"contactNumber": "Agregar Número de Contacto",
|
||||||
"edit": "Editar"
|
"edit": "Editar",
|
||||||
|
"organizationWorkingDaysAndHours": "Días y Horas Laborales de la Organización",
|
||||||
|
"workingDays": "Días Laborales",
|
||||||
|
"workingHours": "Horas Laborales",
|
||||||
|
"monday": "Lunes",
|
||||||
|
"tuesday": "Martes",
|
||||||
|
"wednesday": "Miércoles",
|
||||||
|
"thursday": "Jueves",
|
||||||
|
"friday": "Viernes",
|
||||||
|
"saturday": "Sábado",
|
||||||
|
"sunday": "Domingo",
|
||||||
|
"hours": "horas",
|
||||||
|
"saveButton": "Guardar",
|
||||||
|
"saved": "Configuración guardada exitosamente",
|
||||||
|
"errorSaving": "Error al guardar la configuración",
|
||||||
|
"organizationCalculationMethod": "Método de Cálculo de la Organización",
|
||||||
|
"calculationMethod": "Método de Cálculo",
|
||||||
|
"hourlyRates": "Tarifas por Hora",
|
||||||
|
"manDays": "Días Hombre",
|
||||||
|
"saveChanges": "Guardar Cambios",
|
||||||
|
"hourlyCalculationDescription": "Todos los costos del proyecto se calcularán usando horas estimadas × tarifas por hora",
|
||||||
|
"manDaysCalculationDescription": "Todos los costos del proyecto se calcularán usando días hombre estimados × tarifas diarias",
|
||||||
|
"calculationMethodTooltip": "Esta configuración se aplica a todos los proyectos en su organización",
|
||||||
|
"calculationMethodUpdated": "Método de cálculo de la organización actualizado exitosamente",
|
||||||
|
"calculationMethodUpdateError": "Error al actualizar el método de cálculo"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"settings": "Configuración",
|
||||||
|
"organizationWorkingDaysAndHours": "Días y Horas Laborales de la Organización",
|
||||||
|
"workingDays": "Días Laborales",
|
||||||
|
"workingHours": "Horas Laborales",
|
||||||
|
"hours": "horas",
|
||||||
|
"monday": "Lunes",
|
||||||
|
"tuesday": "Martes",
|
||||||
|
"wednesday": "Miércoles",
|
||||||
|
"thursday": "Jueves",
|
||||||
|
"friday": "Viernes",
|
||||||
|
"saturday": "Sábado",
|
||||||
|
"sunday": "Domingo",
|
||||||
|
"saveButton": "Guardar",
|
||||||
|
"saved": "Configuración guardada exitosamente",
|
||||||
|
"errorSaving": "Error al guardar la configuración"
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@
|
|||||||
"teams": "Equipos",
|
"teams": "Equipos",
|
||||||
"billing": "Facturación",
|
"billing": "Facturación",
|
||||||
"projects": "Proyectos",
|
"projects": "Proyectos",
|
||||||
|
"settings": "Configuración",
|
||||||
"adminCenter": "Centro de Administración"
|
"adminCenter": "Centro de Administración"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,29 @@
|
|||||||
"owner": "Proprietário da Organização",
|
"owner": "Proprietário da Organização",
|
||||||
"admins": "Administradores da Organização",
|
"admins": "Administradores da Organização",
|
||||||
"contactNumber": "Adicione o Número de Contato",
|
"contactNumber": "Adicione o Número de Contato",
|
||||||
"edit": "Editar"
|
"edit": "Editar",
|
||||||
|
"organizationWorkingDaysAndHours": "Dias e Horas de Trabalho da Organização",
|
||||||
|
"workingDays": "Dias de Trabalho",
|
||||||
|
"workingHours": "Horas de Trabalho",
|
||||||
|
"monday": "Segunda-feira",
|
||||||
|
"tuesday": "Terça-feira",
|
||||||
|
"wednesday": "Quarta-feira",
|
||||||
|
"thursday": "Quinta-feira",
|
||||||
|
"friday": "Sexta-feira",
|
||||||
|
"saturday": "Sábado",
|
||||||
|
"sunday": "Domingo",
|
||||||
|
"hours": "horas",
|
||||||
|
"saveButton": "Salvar",
|
||||||
|
"saved": "Configurações salvas com sucesso",
|
||||||
|
"errorSaving": "Erro ao salvar configurações",
|
||||||
|
"organizationCalculationMethod": "Método de Cálculo da Organização",
|
||||||
|
"calculationMethod": "Método de Cálculo",
|
||||||
|
"hourlyRates": "Taxas por Hora",
|
||||||
|
"manDays": "Dias Homem",
|
||||||
|
"saveChanges": "Salvar Alterações",
|
||||||
|
"hourlyCalculationDescription": "Todos os custos do projeto serão calculados usando horas estimadas × taxas por hora",
|
||||||
|
"manDaysCalculationDescription": "Todos os custos do projeto serão calculados usando dias homem estimados × taxas diárias",
|
||||||
|
"calculationMethodTooltip": "Esta configuração se aplica a todos os projetos em sua organização",
|
||||||
|
"calculationMethodUpdated": "Método de cálculo da organização atualizado com sucesso",
|
||||||
|
"calculationMethodUpdateError": "Erro ao atualizar o método de cálculo"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"settings": "Configurações",
|
||||||
|
"organizationWorkingDaysAndHours": "Dias e Horas de Trabalho da Organização",
|
||||||
|
"workingDays": "Dias de Trabalho",
|
||||||
|
"workingHours": "Horas de Trabalho",
|
||||||
|
"hours": "horas",
|
||||||
|
"monday": "Segunda-feira",
|
||||||
|
"tuesday": "Terça-feira",
|
||||||
|
"wednesday": "Quarta-feira",
|
||||||
|
"thursday": "Quinta-feira",
|
||||||
|
"friday": "Sexta-feira",
|
||||||
|
"saturday": "Sábado",
|
||||||
|
"sunday": "Domingo",
|
||||||
|
"saveButton": "Salvar",
|
||||||
|
"saved": "Configurações salvas com sucesso",
|
||||||
|
"errorSaving": "Erro ao salvar configurações"
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@
|
|||||||
"teams": "Equipes",
|
"teams": "Equipes",
|
||||||
"billing": "Faturamento",
|
"billing": "Faturamento",
|
||||||
"projects": "Projetos",
|
"projects": "Projetos",
|
||||||
|
"settings": "Configurações",
|
||||||
"adminCenter": "Central Administrativa"
|
"adminCenter": "Central Administrativa"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,29 @@
|
|||||||
"owner": "组织所有者",
|
"owner": "组织所有者",
|
||||||
"admins": "组织管理员",
|
"admins": "组织管理员",
|
||||||
"contactNumber": "添加联系电话",
|
"contactNumber": "添加联系电话",
|
||||||
"edit": "编辑"
|
"edit": "编辑",
|
||||||
|
"organizationWorkingDaysAndHours": "组织工作日和工作时间",
|
||||||
|
"workingDays": "工作日",
|
||||||
|
"workingHours": "工作时间",
|
||||||
|
"monday": "星期一",
|
||||||
|
"tuesday": "星期二",
|
||||||
|
"wednesday": "星期三",
|
||||||
|
"thursday": "星期四",
|
||||||
|
"friday": "星期五",
|
||||||
|
"saturday": "星期六",
|
||||||
|
"sunday": "星期日",
|
||||||
|
"hours": "小时",
|
||||||
|
"saveButton": "保存",
|
||||||
|
"saved": "设置保存成功",
|
||||||
|
"errorSaving": "保存设置时出错",
|
||||||
|
"organizationCalculationMethod": "组织计算方法",
|
||||||
|
"calculationMethod": "计算方法",
|
||||||
|
"hourlyRates": "小时费率",
|
||||||
|
"manDays": "人天",
|
||||||
|
"saveChanges": "保存更改",
|
||||||
|
"hourlyCalculationDescription": "所有项目成本将使用估算小时数 × 小时费率计算",
|
||||||
|
"manDaysCalculationDescription": "所有项目成本将使用估算人天数 × 日费率计算",
|
||||||
|
"calculationMethodTooltip": "此设置适用于您组织中的所有项目",
|
||||||
|
"calculationMethodUpdated": "组织计算方法更新成功",
|
||||||
|
"calculationMethodUpdateError": "更新计算方法失败"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"settings": "设置",
|
||||||
|
"organizationWorkingDaysAndHours": "组织工作日和工作时间",
|
||||||
|
"workingDays": "工作日",
|
||||||
|
"workingHours": "工作时间",
|
||||||
|
"hours": "小时",
|
||||||
|
"monday": "星期一",
|
||||||
|
"tuesday": "星期二",
|
||||||
|
"wednesday": "星期三",
|
||||||
|
"thursday": "星期四",
|
||||||
|
"friday": "星期五",
|
||||||
|
"saturday": "星期六",
|
||||||
|
"sunday": "星期日",
|
||||||
|
"saveButton": "保存",
|
||||||
|
"saved": "设置保存成功",
|
||||||
|
"errorSaving": "保存设置时出错"
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@
|
|||||||
"teams": "团队",
|
"teams": "团队",
|
||||||
"billing": "账单",
|
"billing": "账单",
|
||||||
"projects": "项目",
|
"projects": "项目",
|
||||||
|
"settings": "设置",
|
||||||
"adminCenter": "管理中心"
|
"adminCenter": "管理中心"
|
||||||
}
|
}
|
||||||
@@ -280,4 +280,16 @@ export const adminCenterApiService = {
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async updateOrganizationCalculationMethod(
|
||||||
|
calculationMethod: 'hourly' | 'man_days'
|
||||||
|
): Promise<IServerResponse<any>> {
|
||||||
|
const response = await apiClient.put<IServerResponse<any>>(
|
||||||
|
`${rootUrl}/organization/calculation-method`,
|
||||||
|
{
|
||||||
|
calculation_method: calculationMethod,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Card, Select, Space, Typography, Tooltip, message, Button } from '@/shared/antd-imports';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { InfoCircleOutlined, CalculatorOutlined, SaveOutlined } from '@ant-design/icons';
|
||||||
|
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
||||||
|
import { IOrganization } from '@/types/admin-center/admin-center.types';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
|
interface OrganizationCalculationMethodProps {
|
||||||
|
organization: IOrganization | null;
|
||||||
|
refetch: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrganizationCalculationMethod: React.FC<OrganizationCalculationMethodProps> = ({
|
||||||
|
organization,
|
||||||
|
refetch
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation('admin-center/overview');
|
||||||
|
const [updating, setUpdating] = useState(false);
|
||||||
|
const [currentMethod, setCurrentMethod] = useState<'hourly' | 'man_days'>(
|
||||||
|
organization?.calculation_method || 'hourly'
|
||||||
|
);
|
||||||
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (organization) {
|
||||||
|
setCurrentMethod(organization.calculation_method || 'hourly');
|
||||||
|
setHasChanges(false);
|
||||||
|
}
|
||||||
|
}, [organization]);
|
||||||
|
|
||||||
|
const handleMethodChange = (newMethod: 'hourly' | 'man_days') => {
|
||||||
|
setCurrentMethod(newMethod);
|
||||||
|
setHasChanges(newMethod !== organization?.calculation_method);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setUpdating(true);
|
||||||
|
try {
|
||||||
|
await adminCenterApiService.updateOrganizationCalculationMethod(currentMethod);
|
||||||
|
|
||||||
|
message.success(t('calculationMethodUpdated'));
|
||||||
|
|
||||||
|
setHasChanges(false);
|
||||||
|
refetch();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update organization calculation method:', error);
|
||||||
|
message.error(t('calculationMethodUpdateError'));
|
||||||
|
} finally {
|
||||||
|
setUpdating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||||
|
<Space align="center">
|
||||||
|
<CalculatorOutlined />
|
||||||
|
<Title level={5} style={{ margin: 0 }}>
|
||||||
|
{t('organizationCalculationMethod')}
|
||||||
|
</Title>
|
||||||
|
<Tooltip title={t('calculationMethodTooltip')}>
|
||||||
|
<InfoCircleOutlined style={{ color: '#666' }} />
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||||
|
<Space align="center" wrap>
|
||||||
|
<Text strong>{t('calculationMethod')}:</Text>
|
||||||
|
<Select
|
||||||
|
value={currentMethod}
|
||||||
|
onChange={handleMethodChange}
|
||||||
|
disabled={updating}
|
||||||
|
style={{ width: 150 }}
|
||||||
|
>
|
||||||
|
<Option value="hourly">
|
||||||
|
<Space>
|
||||||
|
<span>{t('hourlyRates')}</span>
|
||||||
|
</Space>
|
||||||
|
</Option>
|
||||||
|
<Option value="man_days">
|
||||||
|
<Space>
|
||||||
|
<span>{t('manDays')}</span>
|
||||||
|
</Space>
|
||||||
|
</Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{hasChanges && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SaveOutlined />}
|
||||||
|
onClick={handleSave}
|
||||||
|
loading={updating}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{t('saveChanges')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
{currentMethod === 'hourly' && (
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||||
|
{t('hourlyCalculationDescription')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentMethod === 'man_days' && (
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||||
|
{t('manDaysCalculationDescription')} ({organization?.hours_per_day}h/day)
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrganizationCalculationMethod;
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ProfileOutlined,
|
ProfileOutlined,
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
|
SettingOutlined,
|
||||||
} from '@/shared/antd-imports';
|
} from '@/shared/antd-imports';
|
||||||
import React, { ReactNode, lazy } from 'react';
|
import React, { ReactNode, lazy } from 'react';
|
||||||
const Overview = lazy(() => import('./overview/overview'));
|
const Overview = lazy(() => import('./overview/overview'));
|
||||||
@@ -11,6 +12,7 @@ const Users = lazy(() => import('./users/users'));
|
|||||||
const Teams = lazy(() => import('./teams/teams'));
|
const Teams = lazy(() => import('./teams/teams'));
|
||||||
const Billing = lazy(() => import('./billing/billing'));
|
const Billing = lazy(() => import('./billing/billing'));
|
||||||
const Projects = lazy(() => import('./projects/projects'));
|
const Projects = lazy(() => import('./projects/projects'));
|
||||||
|
const Settings = lazy(() => import('./settings/settings'));
|
||||||
|
|
||||||
// type of a menu item in admin center sidebar
|
// type of a menu item in admin center sidebar
|
||||||
type AdminCenterMenuItems = {
|
type AdminCenterMenuItems = {
|
||||||
@@ -57,4 +59,11 @@ export const adminCenterItems: AdminCenterMenuItems[] = [
|
|||||||
icon: React.createElement(CreditCardOutlined),
|
icon: React.createElement(CreditCardOutlined),
|
||||||
element: React.createElement(Billing),
|
element: React.createElement(Billing),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'settings',
|
||||||
|
name: 'settings',
|
||||||
|
endpoint: 'settings',
|
||||||
|
icon: React.createElement(SettingOutlined),
|
||||||
|
element: React.createElement(Settings),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { EditOutlined, MailOutlined, PhoneOutlined } from '@/shared/antd-imports';
|
|
||||||
import { PageHeader } from '@ant-design/pro-components';
|
|
||||||
import { Button, Card, Input, Space, Tooltip, Typography } from '@/shared/antd-imports';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
} from '@/shared/antd-imports';
|
||||||
|
import { PageHeader } from '@ant-design/pro-components';
|
||||||
import OrganizationAdminsTable from '@/components/admin-center/overview/organization-admins-table/organization-admins-table';
|
import OrganizationAdminsTable from '@/components/admin-center/overview/organization-admins-table/organization-admins-table';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import { RootState } from '@/app/store';
|
import { RootState } from '@/app/store';
|
||||||
@@ -11,9 +14,6 @@ import OrganizationOwner from '@/components/admin-center/overview/organization-o
|
|||||||
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
||||||
import { IOrganization, IOrganizationAdmin } from '@/types/admin-center/admin-center.types';
|
import { IOrganization, IOrganizationAdmin } from '@/types/admin-center/admin-center.types';
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
import { tr } from 'date-fns/locale';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
const Overview: React.FC = () => {
|
const Overview: React.FC = () => {
|
||||||
const [organization, setOrganization] = useState<IOrganization | null>(null);
|
const [organization, setOrganization] = useState<IOrganization | null>(null);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './settings';
|
||||||
156
worklenz-frontend/src/pages/admin-center/settings/settings.tsx
Normal file
156
worklenz-frontend/src/pages/admin-center/settings/settings.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Input,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
Checkbox,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
Row,
|
||||||
|
message,
|
||||||
|
} from '@/shared/antd-imports';
|
||||||
|
import { PageHeader } from '@ant-design/pro-components';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
||||||
|
import { IOrganization } from '@/types/admin-center/admin-center.types';
|
||||||
|
import logger from '@/utils/errorLogger';
|
||||||
|
import { scheduleAPIService } from '@/api/schedule/schedule.api.service';
|
||||||
|
import { Settings } from '@/types/schedule/schedule-v2.types';
|
||||||
|
import OrganizationCalculationMethod from '@/components/admin-center/overview/organization-calculation-method/organization-calculation-method';
|
||||||
|
|
||||||
|
const SettingsPage: React.FC = () => {
|
||||||
|
const [organization, setOrganization] = useState<IOrganization | null>(null);
|
||||||
|
const [workingDays, setWorkingDays] = useState<Settings['workingDays']>([]);
|
||||||
|
const [workingHours, setWorkingHours] = useState<Settings['workingHours']>(8);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const { t } = useTranslation('admin-center/settings');
|
||||||
|
|
||||||
|
const getOrganizationDetails = async () => {
|
||||||
|
try {
|
||||||
|
const res = await adminCenterApiService.getOrganizationDetails();
|
||||||
|
if (res.done) {
|
||||||
|
setOrganization(res.body);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error getting organization details', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOrgWorkingSettings = async () => {
|
||||||
|
try {
|
||||||
|
const res = await scheduleAPIService.fetchScheduleSettings();
|
||||||
|
if (res && res.done) {
|
||||||
|
setWorkingDays(
|
||||||
|
res.body.workingDays || ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
|
||||||
|
);
|
||||||
|
setWorkingHours(res.body.workingHours || 8);
|
||||||
|
form.setFieldsValue({
|
||||||
|
workingDays: res.body.workingDays || [
|
||||||
|
'Monday',
|
||||||
|
'Tuesday',
|
||||||
|
'Wednesday',
|
||||||
|
'Thursday',
|
||||||
|
'Friday',
|
||||||
|
],
|
||||||
|
workingHours: res.body.workingHours || 8,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error getting organization working settings', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async (values: any) => {
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const res = await scheduleAPIService.updateScheduleSettings({
|
||||||
|
workingDays: values.workingDays,
|
||||||
|
workingHours: values.workingHours,
|
||||||
|
});
|
||||||
|
if (res && res.done) {
|
||||||
|
message.success(t('saved'));
|
||||||
|
setWorkingDays(values.workingDays);
|
||||||
|
setWorkingHours(values.workingHours);
|
||||||
|
getOrgWorkingSettings();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error updating organization working days/hours', error);
|
||||||
|
message.error(t('errorSaving'));
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getOrganizationDetails();
|
||||||
|
getOrgWorkingSettings();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<PageHeader title={<span>{t('settings')}</span>} style={{ padding: '16px 0' }} />
|
||||||
|
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size={22}>
|
||||||
|
<Card>
|
||||||
|
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||||
|
{t('organizationWorkingDaysAndHours') || 'Organization Working Days & Hours'}
|
||||||
|
</Typography.Title>
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
form={form}
|
||||||
|
initialValues={{ workingDays, workingHours }}
|
||||||
|
onFinish={handleSave}
|
||||||
|
style={{ marginTop: 16 }}
|
||||||
|
>
|
||||||
|
<Form.Item label={t('workingDays')} name="workingDays">
|
||||||
|
<Checkbox.Group>
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>
|
||||||
|
<Checkbox value="Monday">{t('monday')}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Checkbox value="Tuesday">{t('tuesday')}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Checkbox value="Wednesday">{t('wednesday')}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Checkbox value="Thursday">{t('thursday')}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Checkbox value="Friday">{t('friday')}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Checkbox value="Saturday">{t('saturday')}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Checkbox value="Sunday">{t('sunday')}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Checkbox.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t('workingHours')} name="workingHours">
|
||||||
|
<Input type="number" min={1} max={24} suffix={t('hours')} width={100} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" loading={saving}>
|
||||||
|
{t('saveButton') || 'Save'}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<OrganizationCalculationMethod
|
||||||
|
organization={organization}
|
||||||
|
refetch={getOrganizationDetails}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsPage;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ISUBSCRIPTION_TYPE } from '@/shared/constants';
|
import { ISUBSCRIPTION_TYPE } from "@/shared/constants";
|
||||||
|
|
||||||
export interface IOrganization {
|
export interface IOrganization {
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -6,6 +6,8 @@ export interface IOrganization {
|
|||||||
email?: string;
|
email?: string;
|
||||||
contact_number?: string;
|
contact_number?: string;
|
||||||
contact_number_secondary?: string;
|
contact_number_secondary?: string;
|
||||||
|
calculation_method?: 'hourly' | 'man_days';
|
||||||
|
hours_per_day?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOrganizationAdmin {
|
export interface IOrganizationAdmin {
|
||||||
@@ -79,7 +81,6 @@ export interface IBillingAccountInfo {
|
|||||||
unit_price?: number;
|
unit_price?: number;
|
||||||
unit_price_per_month?: number;
|
unit_price_per_month?: number;
|
||||||
usedPercentage?: number;
|
usedPercentage?: number;
|
||||||
used_percent?: number;
|
|
||||||
usedStorage?: number;
|
usedStorage?: number;
|
||||||
is_custom?: boolean;
|
is_custom?: boolean;
|
||||||
is_ltd_user?: boolean;
|
is_ltd_user?: boolean;
|
||||||
@@ -230,4 +231,4 @@ export interface IFreePlanSettings {
|
|||||||
export interface IOrganizationProjectsGetResponse {
|
export interface IOrganizationProjectsGetResponse {
|
||||||
total?: number;
|
total?: number;
|
||||||
data?: IOrganizationProject[];
|
data?: IOrganizationProject[];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user