Merge branch 'main' of http://192.168.1.200:3000/AmeyBhagwat/Rapport-Mobileapp
* 'main' of http://192.168.1.200:3000/AmeyBhagwat/Rapport-Mobileapp: Changes on Metric View and Campaign
This commit is contained in:
commit
eecbb3c948
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 Profile from "./pages/Profile";
|
||||
import MetricReports from "./pages/MetricReports";
|
||||
import CampaignDetails from "./pages/CampaignDetails";
|
||||
import QuickActionsContent from "./pages/QuickActionsContent";
|
||||
import Magazines from "./pages/Magazines";
|
||||
import Magazine from "./components/Magazines/Magazine";
|
||||
|
|
@ -490,6 +491,19 @@ function App() {
|
|||
<Route index element={<MetricReports />} />
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
path="/campaigns"
|
||||
element={
|
||||
<AuthGuard
|
||||
auth={isLogin}
|
||||
component={DashboardLayout}
|
||||
page="campaigns"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Route index element={<CampaignDetails />} />
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
path="/approve-reject-form-submission"
|
||||
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("/");
|
||||
// console.log("STATE =", location.state);
|
||||
}, [location.pathname]);
|
||||
}, [location.pathname, location.state?.pageTitle]);
|
||||
|
||||
const [showLogoutConfirmationModal, setShowLogoutConfirmationModal] =
|
||||
useState(false);
|
||||
|
|
|
|||
|
|
@ -8,14 +8,23 @@ import StatsCard from "../StatsCard/StatsCard";
|
|||
//React Query
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
//Capacitor
|
||||
import { Browser } from "@capacitor/browser";
|
||||
|
||||
//Services
|
||||
import dashboardService from "../../services/Dashboard";
|
||||
|
||||
//Constants
|
||||
import { APP_PRIMARY_COLOR } from "../../util/constants";
|
||||
function parseMetricViewIdFromReportUrl(reportViewUrl) {
|
||||
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() {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -57,22 +66,29 @@ function PerformanceStatsContainer() {
|
|||
// refetch({ isRefresh: isRefresh });
|
||||
}
|
||||
|
||||
const handleUrl = async (url) => {
|
||||
console.log("handleUrl is called...", url);
|
||||
const handleStatsCardClick = (item) => {
|
||||
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", {
|
||||
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) {
|
||||
return (
|
||||
<Container className="pt-1 pb-1 py-sm-3">
|
||||
|
|
|
|||
|
|
@ -1,23 +1,14 @@
|
|||
import { Card, Row, Col } from "react-bootstrap";
|
||||
import authService from "../../services/Auth";
|
||||
import { formatTextValue } from "../../util/util";
|
||||
|
||||
function StatsCard(props) {
|
||||
const title = props?.item?.title ? props?.item?.title : "Default 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 = props?.item?.text || 0;
|
||||
const view_url = props?.item?.report_view_url;
|
||||
let classes = props?.className ? props?.className : "";
|
||||
|
||||
function handleClick(data) {
|
||||
console.log("HandleClick item =", 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);
|
||||
props.handleStatsCardClick(data);
|
||||
}
|
||||
return (
|
||||
<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 DashboardAdvertiseModal from "../components/DashboardAdvertiseModal/DashboardAdvertiseModal";
|
||||
import OverlayNotificationModal from "../components/Notifications/OverlayNotificationModal";
|
||||
import DashboardCampaignsContainer from "../components/DashboardCampaigns/DashboardCampaignsContainer";
|
||||
|
||||
//API
|
||||
import notificationServices from "../services/NotificationServices";
|
||||
|
|
@ -401,6 +402,8 @@ function Dashboard() {
|
|||
|
||||
<PerformanceStatsContainer />
|
||||
|
||||
<DashboardCampaignsContainer />
|
||||
|
||||
{otherBanners?.length > 0 && (
|
||||
<section
|
||||
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
|
||||
getReloadedDashboardUserStats: async () => {
|
||||
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