Changes on Metric View and Campaign
This commit is contained in:
parent
23ce2eda41
commit
fa58c8d282
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
14
src/App.jsx
14
src/App.jsx
|
|
@ -20,6 +20,7 @@ import Rewards from "./pages/Rewards";
|
||||||
import Appreciations from "./pages/Appreciations/Appreciations";
|
import Appreciations from "./pages/Appreciations/Appreciations";
|
||||||
import Profile from "./pages/Profile";
|
import Profile from "./pages/Profile";
|
||||||
import MetricReports from "./pages/MetricReports";
|
import MetricReports from "./pages/MetricReports";
|
||||||
|
import CampaignDetails from "./pages/CampaignDetails";
|
||||||
import QuickActionsContent from "./pages/QuickActionsContent";
|
import QuickActionsContent from "./pages/QuickActionsContent";
|
||||||
import Magazines from "./pages/Magazines";
|
import Magazines from "./pages/Magazines";
|
||||||
import Magazine from "./components/Magazines/Magazine";
|
import Magazine from "./components/Magazines/Magazine";
|
||||||
|
|
@ -490,6 +491,19 @@ function App() {
|
||||||
<Route index element={<MetricReports />} />
|
<Route index element={<MetricReports />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/campaigns"
|
||||||
|
element={
|
||||||
|
<AuthGuard
|
||||||
|
auth={isLogin}
|
||||||
|
component={DashboardLayout}
|
||||||
|
page="campaigns"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route index element={<CampaignDetails />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/approve-reject-form-submission"
|
path="/approve-reject-form-submission"
|
||||||
element={
|
element={
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { Container, Card, Row, Col, Button } from "react-bootstrap";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import dashboardService from "../../services/Dashboard";
|
||||||
|
|
||||||
|
function DashboardCampaignsContainer() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data: campaignRes } = useQuery({
|
||||||
|
queryKey: ["dashboardCampaignList"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await dashboardService.getDashboardCampaignList();
|
||||||
|
return res?.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const campaigns = Array.isArray(campaignRes?.data)
|
||||||
|
? campaignRes.data
|
||||||
|
: campaignRes?.data?.items || campaignRes?.data?.campaigns || [];
|
||||||
|
const hasCampaigns = Array.isArray(campaigns) && campaigns.length > 0;
|
||||||
|
const campaign = hasCampaigns ? campaigns[0] : null;
|
||||||
|
|
||||||
|
if (!hasCampaigns) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="pt-1 pb-1 py-sm-3">
|
||||||
|
<div className="d-flex align-items-center justify-content-between">
|
||||||
|
<h4 className="section-title mb-0">Campaigns</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="card-theme border-0 shadow-sm p-3 rounded-4 mt-3">
|
||||||
|
<h5 className="mb-3 fw-bold">
|
||||||
|
{campaign?.name || campaign?.title || "Campaign"}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<Row className="g-2 mb-3">
|
||||||
|
<Col xs={4}>
|
||||||
|
<div className="border border-2 border-success rounded-3 p-2 text-center h-100 d-flex flex-column justify-content-center">
|
||||||
|
<div className="text-muted font-12 mb-1">Overall Rank</div>
|
||||||
|
<div className="fw-bold fs-4">
|
||||||
|
{campaign?.overall_rank ?? campaign?.overallRank ?? "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={4}>
|
||||||
|
<div className="border border-2 border-success rounded-3 p-2 text-center h-100 d-flex flex-column justify-content-center">
|
||||||
|
<div className="text-muted font-12 mb-1">Rank in Team</div>
|
||||||
|
<div className="fw-bold fs-4">
|
||||||
|
{campaign?.rank_in_team ?? campaign?.rankInTeam ?? "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={4}>
|
||||||
|
<div className="border border-2 border-success rounded-3 p-2 text-center h-100 d-flex flex-column justify-content-center">
|
||||||
|
<div className="text-muted font-12 mb-1">Team Rank</div>
|
||||||
|
<div className="fw-bold fs-4">
|
||||||
|
{campaign?.team_rank ?? campaign?.teamRank ?? "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="w-100 py-3 rounded-4 border-0 fw-semibold"
|
||||||
|
style={{ backgroundColor: "#5B7FC4" }}
|
||||||
|
onClick={() => navigate("/campaigns", { state: { campaign } })}
|
||||||
|
>
|
||||||
|
View Details
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashboardCampaignsContainer;
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ function DashboardHeader(props) {
|
||||||
}
|
}
|
||||||
// const backLinkPath = backLinkPathArr.join("/");
|
// const backLinkPath = backLinkPathArr.join("/");
|
||||||
// console.log("STATE =", location.state);
|
// console.log("STATE =", location.state);
|
||||||
}, [location.pathname]);
|
}, [location.pathname, location.state?.pageTitle]);
|
||||||
|
|
||||||
const [showLogoutConfirmationModal, setShowLogoutConfirmationModal] =
|
const [showLogoutConfirmationModal, setShowLogoutConfirmationModal] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,23 @@ import StatsCard from "../StatsCard/StatsCard";
|
||||||
//React Query
|
//React Query
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
//Capacitor
|
|
||||||
import { Browser } from "@capacitor/browser";
|
|
||||||
|
|
||||||
//Services
|
//Services
|
||||||
import dashboardService from "../../services/Dashboard";
|
import dashboardService from "../../services/Dashboard";
|
||||||
|
|
||||||
//Constants
|
function parseMetricViewIdFromReportUrl(reportViewUrl) {
|
||||||
import { APP_PRIMARY_COLOR } from "../../util/constants";
|
if (!reportViewUrl || typeof reportViewUrl !== "string") return null;
|
||||||
|
try {
|
||||||
|
const u = new URL(
|
||||||
|
reportViewUrl.startsWith("http")
|
||||||
|
? reportViewUrl
|
||||||
|
: `https://placeholder.local${reportViewUrl.startsWith("/") ? "" : "/"}${reportViewUrl}`
|
||||||
|
);
|
||||||
|
return u.searchParams.get("id");
|
||||||
|
} catch {
|
||||||
|
const m = String(reportViewUrl).match(/[?&]id=([^&]+)/);
|
||||||
|
return m ? decodeURIComponent(m[1]) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function PerformanceStatsContainer() {
|
function PerformanceStatsContainer() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -57,22 +66,29 @@ function PerformanceStatsContainer() {
|
||||||
// refetch({ isRefresh: isRefresh });
|
// refetch({ isRefresh: isRefresh });
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUrl = async (url) => {
|
const handleStatsCardClick = (item) => {
|
||||||
console.log("handleUrl is called...", url);
|
const metricViewId =
|
||||||
|
item?.id ??
|
||||||
|
item?.metric_view_id ??
|
||||||
|
parseMetricViewIdFromReportUrl(item?.report_view_url);
|
||||||
|
if (!metricViewId) {
|
||||||
|
console.warn("Performance stat has no metric view id in report_view_url");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const title = item?.value_title || item?.title || "Metric report";
|
||||||
|
const aggType = String(item?.agg_type || "").toLowerCase();
|
||||||
|
const defaultTab =
|
||||||
|
aggType === "team" ? "Detail" : aggType === "individual" ? "Trend" : "Detail";
|
||||||
navigate("/metric-reports", {
|
navigate("/metric-reports", {
|
||||||
state: { url: url },
|
state: {
|
||||||
|
metricViewId,
|
||||||
|
reportTitle: title,
|
||||||
|
pageTitle: title,
|
||||||
|
defaultTab,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStatsCardClick = async (url) => {
|
|
||||||
// navigate("/metric-reports", {
|
|
||||||
// state: { url: url },
|
|
||||||
// });
|
|
||||||
|
|
||||||
//Open url in In-App Browser
|
|
||||||
await Browser.open({ url: url, toolbarColor: APP_PRIMARY_COLOR });
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return (
|
||||||
<Container className="pt-1 pb-1 py-sm-3">
|
<Container className="pt-1 pb-1 py-sm-3">
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,14 @@
|
||||||
import { Card, Row, Col } from "react-bootstrap";
|
import { Card, Row, Col } from "react-bootstrap";
|
||||||
import authService from "../../services/Auth";
|
|
||||||
import { formatTextValue } from "../../util/util";
|
import { formatTextValue } from "../../util/util";
|
||||||
|
|
||||||
function StatsCard(props) {
|
function StatsCard(props) {
|
||||||
const title = props?.item?.title ? props?.item?.title : "Default title";
|
const title = props?.item?.title ? props?.item?.title : "Default title";
|
||||||
const sub_title = props?.item?.value_title;
|
const sub_title = props?.item?.value_title;
|
||||||
const card_type = props?.item?.type && "simple";
|
|
||||||
const sub_title_value = formatTextValue(props?.item?.text);
|
const sub_title_value = formatTextValue(props?.item?.text);
|
||||||
// const sub_title_value = props?.item?.text || 0;
|
|
||||||
const view_url = props?.item?.report_view_url;
|
|
||||||
let classes = props?.className ? props?.className : "";
|
let classes = props?.className ? props?.className : "";
|
||||||
|
|
||||||
function handleClick(data) {
|
function handleClick(data) {
|
||||||
console.log("HandleClick item =", data);
|
props.handleStatsCardClick(data);
|
||||||
// alert(data.report_view_url);
|
|
||||||
let url = `${data.report_view_url}&authorization=${authService.getToken()}`;
|
|
||||||
// props.handleStatsCardClick(data.report_view_url);
|
|
||||||
console.log("url = ", url);
|
|
||||||
props.handleStatsCardClick(url);
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Container, Card, Form } from "react-bootstrap";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import PageComponent from "../components/PageComponent/PageComponent";
|
||||||
|
import dashboardService from "../services/Dashboard";
|
||||||
|
|
||||||
|
const DEFAULT_PERIODS = [
|
||||||
|
{ label: "May Month", value: "month-0" },
|
||||||
|
{ label: "April Month", value: "month-1" },
|
||||||
|
{ label: "Last 3 Months", value: "month-3" },
|
||||||
|
{ label: "Overall", value: "all" },
|
||||||
|
{ label: "This week", value: "week-0" },
|
||||||
|
{ label: "Last week", value: "week-1" },
|
||||||
|
{ label: "Today", value: "day-0" },
|
||||||
|
{ label: "Yesterday", value: "day-1" },
|
||||||
|
{ label: "Last 7 Days", value: "day-7" },
|
||||||
|
{ label: "Last 30 Days", value: "day-30" },
|
||||||
|
];
|
||||||
|
|
||||||
|
function CampaignDetails() {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const campaign = location?.state?.campaign;
|
||||||
|
const campaignId = campaign?.campaignId || campaign?.campaign_id || campaign?.id;
|
||||||
|
|
||||||
|
const [selectedPeriod, setSelectedPeriod] = useState("month-0");
|
||||||
|
|
||||||
|
const { data: detailsRes, isFetching, isError } = useQuery({
|
||||||
|
queryKey: ["campaignDetails", campaignId, selectedPeriod],
|
||||||
|
enabled: !!campaignId,
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await dashboardService.getCampaignDetails(campaignId, selectedPeriod);
|
||||||
|
return res?.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const periods = Array.isArray(detailsRes?.data?.periods)
|
||||||
|
? detailsRes.data.periods
|
||||||
|
: Array.isArray(detailsRes?.periods)
|
||||||
|
? detailsRes.periods
|
||||||
|
: [];
|
||||||
|
const periodOptions = periods.length ? periods : DEFAULT_PERIODS;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (periodOptions.some((p) => p?.value === selectedPeriod)) return;
|
||||||
|
const first = periodOptions[0]?.value;
|
||||||
|
if (first) setSelectedPeriod(first);
|
||||||
|
}, [periodOptions, selectedPeriod]);
|
||||||
|
|
||||||
|
const screenTitle = detailsRes?.data?.title || detailsRes?.title || "Campaign";
|
||||||
|
useEffect(() => {
|
||||||
|
const currentTitle = location?.state?.pageTitle;
|
||||||
|
if (!screenTitle || currentTitle === screenTitle) return;
|
||||||
|
navigate(location.pathname, {
|
||||||
|
replace: true,
|
||||||
|
state: { ...(location.state || {}), pageTitle: screenTitle },
|
||||||
|
});
|
||||||
|
}, [navigate, location.pathname, location.state?.pageTitle, screenTitle]);
|
||||||
|
|
||||||
|
const cards = Array.isArray(detailsRes?.data)
|
||||||
|
? detailsRes.data
|
||||||
|
: Array.isArray(detailsRes?.data?.metrics)
|
||||||
|
? detailsRes.data.metrics
|
||||||
|
: detailsRes?.data?.items || detailsRes?.data?.campaigns || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageComponent className="metric-report-page">
|
||||||
|
<div className="metric-report-shell">
|
||||||
|
<Container fluid className="metric-inner px-3 pt-3 pb-5">
|
||||||
|
<div className="pt-3">
|
||||||
|
<div className="d-flex align-items-center justify-content-end">
|
||||||
|
<Form.Select
|
||||||
|
size="sm"
|
||||||
|
className="metric-period-select"
|
||||||
|
value={selectedPeriod}
|
||||||
|
onChange={(e) => setSelectedPeriod(e.target.value)}
|
||||||
|
>
|
||||||
|
{periodOptions.map((p, idx) => (
|
||||||
|
<option key={`${p?.value}-${idx}`} value={p?.value}>
|
||||||
|
{p?.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Form.Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3">
|
||||||
|
{!campaignId ? (
|
||||||
|
<div className="text-center text-muted small py-4">
|
||||||
|
Missing campaign id.
|
||||||
|
</div>
|
||||||
|
) : isError ? (
|
||||||
|
<div className="text-center text-danger small py-4">
|
||||||
|
Failed to load campaign details.
|
||||||
|
</div>
|
||||||
|
) : isFetching ? (
|
||||||
|
<div className="text-center text-muted small py-4">
|
||||||
|
Loading…
|
||||||
|
</div>
|
||||||
|
) : cards.length === 0 ? (
|
||||||
|
<div className="text-center text-muted small py-4">No data.</div>
|
||||||
|
) : (
|
||||||
|
<div className="d-flex flex-column gap-3">
|
||||||
|
{cards.map((c, idx) => {
|
||||||
|
const name = c?.name || c?.title || `Campaign ${idx + 1}`;
|
||||||
|
const value = c?.value ?? c?.achieved ?? c?.achieved_value ?? 0;
|
||||||
|
const units =
|
||||||
|
c?.units ??
|
||||||
|
c?.unit ??
|
||||||
|
c?.display_unit ??
|
||||||
|
c?.displayUnit ??
|
||||||
|
c?.value_unit ??
|
||||||
|
"Units";
|
||||||
|
const weight = c?.weight ?? c?.metric_weight ?? 1;
|
||||||
|
const points =
|
||||||
|
c?.points ??
|
||||||
|
c?.score ??
|
||||||
|
(Number.isFinite(Number(value)) && Number.isFinite(Number(weight))
|
||||||
|
? Number(value) * Number(weight)
|
||||||
|
: 0);
|
||||||
|
const valueColor =
|
||||||
|
c?.valueColor === "text-success" ||
|
||||||
|
c?.value_color === "green"
|
||||||
|
? "text-success"
|
||||||
|
: "text-danger";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card key={idx} className="border-0 shadow-sm overflow-hidden">
|
||||||
|
<div className="bg-light py-2 px-3 border-bottom">
|
||||||
|
<div className="text-center fw-semibold">{name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex">
|
||||||
|
<div className="flex-fill py-4 px-3 text-center border-end">
|
||||||
|
<div className={`fw-bold display-6 mb-1 ${valueColor}`}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
{units ? (
|
||||||
|
<div className="text-muted small">{units}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="flex-fill py-4 px-3 text-center">
|
||||||
|
<div
|
||||||
|
className="fw-bold display-6 mb-1"
|
||||||
|
style={{ color: "#00BCD4" }}
|
||||||
|
>
|
||||||
|
{points}
|
||||||
|
</div>
|
||||||
|
<div className="text-muted small">Points</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
</PageComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CampaignDetails;
|
||||||
|
|
||||||
|
|
@ -26,6 +26,7 @@ import DashboardCompetitionContestsContainer from "../components/CompetitionCont
|
||||||
import DashboardBanner from "../components/DashboardBanner/DashboardBanner";
|
import DashboardBanner from "../components/DashboardBanner/DashboardBanner";
|
||||||
import DashboardAdvertiseModal from "../components/DashboardAdvertiseModal/DashboardAdvertiseModal";
|
import DashboardAdvertiseModal from "../components/DashboardAdvertiseModal/DashboardAdvertiseModal";
|
||||||
import OverlayNotificationModal from "../components/Notifications/OverlayNotificationModal";
|
import OverlayNotificationModal from "../components/Notifications/OverlayNotificationModal";
|
||||||
|
import DashboardCampaignsContainer from "../components/DashboardCampaigns/DashboardCampaignsContainer";
|
||||||
|
|
||||||
//API
|
//API
|
||||||
import notificationServices from "../services/NotificationServices";
|
import notificationServices from "../services/NotificationServices";
|
||||||
|
|
@ -401,6 +402,8 @@ function Dashboard() {
|
||||||
|
|
||||||
<PerformanceStatsContainer />
|
<PerformanceStatsContainer />
|
||||||
|
|
||||||
|
<DashboardCampaignsContainer />
|
||||||
|
|
||||||
{otherBanners?.length > 0 && (
|
{otherBanners?.length > 0 && (
|
||||||
<section
|
<section
|
||||||
className={`overflow-hidden ${
|
className={`overflow-hidden ${
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,293 @@
|
||||||
|
/* Scoped metric report screen (Figma-inspired, Bootstrap-friendly) */
|
||||||
|
.metric-report-page {
|
||||||
|
--metric-accent: #00bcd4;
|
||||||
|
--metric-accent-rgb: 0, 188, 212;
|
||||||
|
background: #f8f9fa;
|
||||||
|
min-height: calc(100vh - 140px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .x-small {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-report-shell {
|
||||||
|
max-width: 28rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
background: #f8f9fa;
|
||||||
|
min-height: calc(100vh - 140px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-inner {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-period-header {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-menu-anchor-abs {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-menu-trigger {
|
||||||
|
min-width: 2.375rem;
|
||||||
|
height: 31px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-period-select {
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 9rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-content-card {
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-subtab-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-subtab-btn {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #6c757d;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-subtab-btn.active {
|
||||||
|
color: var(--metric-accent);
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom-color: var(--metric-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tile {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tile-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tile-value {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tile-delta {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tile--blue {
|
||||||
|
background: #e8f4fe;
|
||||||
|
}
|
||||||
|
.metric-report-page .metric-tile--green {
|
||||||
|
background: #e6f7ed;
|
||||||
|
}
|
||||||
|
.metric-report-page .metric-tile--purple {
|
||||||
|
background: #f3e8ff;
|
||||||
|
}
|
||||||
|
.metric-report-page .metric-tile--orange {
|
||||||
|
background: #fff4e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-design-table thead th {
|
||||||
|
background-color: var(--metric-accent);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-design-table tbody td {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-design-table th,
|
||||||
|
.metric-report-page .metric-design-table td {
|
||||||
|
width: auto;
|
||||||
|
max-width: 30vw;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-sticky-col {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-rank-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-rank-badge--gold {
|
||||||
|
background: linear-gradient(135deg, #facc15, #ca8a04);
|
||||||
|
box-shadow: 0 2px 6px rgba(202, 138, 4, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-rank-badge--silver {
|
||||||
|
background: linear-gradient(135deg, #d1d5db, #6b7280);
|
||||||
|
box-shadow: 0 2px 6px rgba(107, 114, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-rank-badge--bronze {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #c2410c);
|
||||||
|
box-shadow: 0 2px 6px rgba(194, 65, 12, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-rank-badge--muted {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #4b5563;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-overlay-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1040;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-menu-anchor-abs .metric-flyout-menu {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 100%;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
z-index: 1050;
|
||||||
|
width: 14rem;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-flyout-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #f1f3f5;
|
||||||
|
background: #fff;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-flyout-item:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-level-pill--on {
|
||||||
|
background-color: var(--metric-accent) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tab-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
gap: 0.25rem;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tab-row::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tab-btn {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0.65rem 0.85rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #6c757d;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-tab-btn.active {
|
||||||
|
color: var(--metric-accent);
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom-color: var(--metric-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-subchart-row .btn {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-table thead th {
|
||||||
|
background-color: var(--metric-accent);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-table tbody td {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-value-card {
|
||||||
|
background: #e3f8fb;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-period-row {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-report-page .metric-level-pill {
|
||||||
|
min-width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -47,6 +47,20 @@ const dashboardService = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//Dashboard Campaigns
|
||||||
|
getDashboardCampaignList: async () => {
|
||||||
|
console.log("Inside Service getDashboardCampaignList() ...");
|
||||||
|
return await axios.get("metric/campaign_view/campaign_list");
|
||||||
|
},
|
||||||
|
|
||||||
|
getCampaignDetails: async (id, period) => {
|
||||||
|
console.log("Inside Service getCampaignDetails() ...", id, period);
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (id) qs.set("id", id);
|
||||||
|
if (period) qs.set("period", period);
|
||||||
|
return await axios.get(`metric/campaign_view/campaign_details?${qs.toString()}`);
|
||||||
|
},
|
||||||
|
|
||||||
//Reloaded Dashboard User / Performance stats
|
//Reloaded Dashboard User / Performance stats
|
||||||
getReloadedDashboardUserStats: async () => {
|
getReloadedDashboardUserStats: async () => {
|
||||||
console.log("Inside Service getReloadedDashboardUserStats() ...");
|
console.log("Inside Service getReloadedDashboardUserStats() ...");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import axios from "./Axios";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metric report APIs (same paths Laravel calls via LambdaRequest).
|
||||||
|
* Auth: Axios interceptor attaches Authorization header.
|
||||||
|
*/
|
||||||
|
const metricReportService = {
|
||||||
|
/**
|
||||||
|
* @param {Record<string, unknown>} payload — e.g. { id } then merged post from responses (type, chart, period, level, …)
|
||||||
|
*/
|
||||||
|
postReportView: async (payload) => {
|
||||||
|
return await axios.post("metric/metric_report/view", payload);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metric report view helper for tab-specific loads.
|
||||||
|
* Example payload: { id, type: "details", chart: "tabular" }
|
||||||
|
*/
|
||||||
|
postMetricReportTab: async (payload) => {
|
||||||
|
return await axios.post("metric/metric_report/view", payload);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default metricReportService;
|
||||||
Loading…
Reference in New Issue