控制首页展示的权限添加

main
kangwenjing 2026-04-16 10:55:37 +08:00
parent 7b1c424ba2
commit 04e7422438
9 changed files with 376 additions and 120 deletions

View File

@ -12,6 +12,7 @@ public class DashboardActivityDTO {
private String content;
private Long operatorUserId;
private String operatorName;
private String targetTab;
private OffsetDateTime createdAt;
private String timeText;
@ -79,6 +80,14 @@ public class DashboardActivityDTO {
this.operatorName = operatorName;
}
public String getTargetTab() {
return targetTab;
}
public void setTargetTab(String targetTab) {
this.targetTab = targetTab;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}

View File

@ -9,6 +9,8 @@ public class DashboardHomeDTO {
private String jobTitle;
private String deptName;
private Long onboardingDays;
private Boolean todoCardVisible;
private Boolean activityCardVisible;
private List<DashboardStatDTO> stats;
private List<DashboardTodoDTO> todos;
private List<DashboardActivityDTO> activities;
@ -17,6 +19,7 @@ public class DashboardHomeDTO {
}
public DashboardHomeDTO(Long userId, String realName, String jobTitle, String deptName, Long onboardingDays,
Boolean todoCardVisible, Boolean activityCardVisible,
List<DashboardStatDTO> stats, List<DashboardTodoDTO> todos,
List<DashboardActivityDTO> activities) {
this.userId = userId;
@ -24,6 +27,8 @@ public class DashboardHomeDTO {
this.jobTitle = jobTitle;
this.deptName = deptName;
this.onboardingDays = onboardingDays;
this.todoCardVisible = todoCardVisible;
this.activityCardVisible = activityCardVisible;
this.stats = stats;
this.todos = todos;
this.activities = activities;
@ -69,6 +74,22 @@ public class DashboardHomeDTO {
this.onboardingDays = onboardingDays;
}
public Boolean getTodoCardVisible() {
return todoCardVisible;
}
public void setTodoCardVisible(Boolean todoCardVisible) {
this.todoCardVisible = todoCardVisible;
}
public Boolean getActivityCardVisible() {
return activityCardVisible;
}
public void setActivityCardVisible(Boolean activityCardVisible) {
this.activityCardVisible = activityCardVisible;
}
public List<DashboardStatDTO> getStats() {
return stats;
}

View File

@ -26,5 +26,12 @@ public interface DashboardMapper {
int markTodoDone(@Param("userId") Long userId, @Param("todoId") Long todoId);
List<DashboardActivityDTO> selectLatestActivities(@Param("userId") Long userId);
@DataScope(tableAlias = "a", ownerColumn = "owner_user_id")
List<DashboardActivityDTO> selectLatestOpportunityActivities(@Param("userId") Long userId);
@DataScope(tableAlias = "a", ownerColumn = "owner_user_id")
List<DashboardActivityDTO> selectLatestSalesActivities(@Param("userId") Long userId);
@DataScope(tableAlias = "a", ownerColumn = "owner_user_id")
List<DashboardActivityDTO> selectLatestChannelActivities(@Param("userId") Long userId);
}

View File

@ -8,21 +8,34 @@ import com.unis.crm.dto.dashboard.DashboardTodoDTO;
import com.unis.crm.dto.dashboard.UserWelcomeDTO;
import com.unis.crm.mapper.DashboardMapper;
import com.unis.crm.service.DashboardService;
import com.unisbase.service.SysPermissionService;
import com.unisbase.spi.UnisBaseTenantProvider;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import org.springframework.stereotype.Service;
@Service
public class DashboardServiceImpl implements DashboardService {
private final DashboardMapper dashboardMapper;
private static final String DASHBOARD_TODO_CARD_VIEW_PERMISSION = "dashboard_todo_card:view";
private static final String DASHBOARD_ACTIVITY_CARD_VIEW_PERMISSION = "dashboard_activity_card:view";
public DashboardServiceImpl(DashboardMapper dashboardMapper) {
private final DashboardMapper dashboardMapper;
private final SysPermissionService sysPermissionService;
private final UnisBaseTenantProvider tenantProvider;
public DashboardServiceImpl(DashboardMapper dashboardMapper,
SysPermissionService sysPermissionService,
UnisBaseTenantProvider tenantProvider) {
this.dashboardMapper = dashboardMapper;
this.sysPermissionService = sysPermissionService;
this.tenantProvider = tenantProvider;
}
@Override
@ -41,8 +54,11 @@ public class DashboardServiceImpl implements DashboardService {
if (monthlyChannelStat != null) {
stats.add(monthlyChannelStat);
}
List<DashboardTodoDTO> todos = dashboardMapper.selectTodos(userId);
List<DashboardActivityDTO> activities = dashboardMapper.selectLatestActivities(userId);
Set<String> permissionCodes = loadPermissionCodes(userId);
boolean todoCardVisible = permissionCodes.contains(DASHBOARD_TODO_CARD_VIEW_PERMISSION);
boolean activityCardVisible = permissionCodes.contains(DASHBOARD_ACTIVITY_CARD_VIEW_PERMISSION);
List<DashboardTodoDTO> todos = todoCardVisible ? dashboardMapper.selectTodos(userId) : List.of();
List<DashboardActivityDTO> activities = activityCardVisible ? loadLatestActivities(userId) : List.of();
enrichActivityTimeText(activities);
long onboardingDays = 0;
@ -57,6 +73,8 @@ public class DashboardServiceImpl implements DashboardService {
user.getJobTitle(),
user.getDeptName(),
onboardingDays,
todoCardVisible,
activityCardVisible,
stats,
todos,
activities
@ -77,6 +95,35 @@ public class DashboardServiceImpl implements DashboardService {
}
}
private Set<String> loadPermissionCodes(Long userId) {
try {
Long tenantId = tenantProvider.getCurrentTenantId();
if (tenantId == null) {
return Set.of();
}
Set<String> permissionCodes = sysPermissionService.listPermissionCodesByUserId(userId, tenantId);
if (permissionCodes == null) {
return Set.of();
}
return permissionCodes;
} catch (Exception ignored) {
return Set.of();
}
}
private List<DashboardActivityDTO> loadLatestActivities(Long userId) {
List<DashboardActivityDTO> activities = new ArrayList<>();
activities.addAll(dashboardMapper.selectLatestOpportunityActivities(userId));
activities.addAll(dashboardMapper.selectLatestSalesActivities(userId));
activities.addAll(dashboardMapper.selectLatestChannelActivities(userId));
activities.sort(Comparator.comparing(DashboardActivityDTO::getCreatedAt,
Comparator.nullsLast(Comparator.reverseOrder())));
if (activities.size() > 8) {
return new ArrayList<>(activities.subList(0, 8));
}
return activities;
}
private void enrichActivityTimeText(List<DashboardActivityDTO> activities) {
if (activities == null || activities.isEmpty()) {
return;

View File

@ -116,110 +116,7 @@
and status in ('todo', 'done')
</update>
<select id="selectLatestActivities" resultType="com.unis.crm.dto.dashboard.DashboardActivityDTO">
with latest_report_comment as (
select distinct on (c.report_id)
c.report_id,
c.reviewer_user_id,
c.score,
c.comment_content,
c.reviewed_at
from work_daily_report_comment c
order by c.report_id, c.reviewed_at desc, c.id desc
),
activity_union as (
select
l.id,
l.biz_type as bizType,
l.biz_id as bizId,
l.action_type as actionType,
l.title,
l.content,
l.operator_user_id as operatorUserId,
l.created_at as createdAt
from sys_activity_log l
where l.operator_user_id = #{userId}
or l.operator_user_id is null
union all
select
(1000000000 + o.id) as id,
'opportunity' as bizType,
o.id as bizId,
'stage_update' as actionType,
'商机阶段更新' as title,
o.opportunity_name || ' 已推进至' ||
case o.stage
when 'initial_contact' then '初步沟通'
when 'solution_discussion' then '方案交流'
when 'bidding' then '招投标'
when 'business_negotiation' then '商务谈判'
when 'won' then '已成交'
when 'lost' then '已输单'
else o.stage
end || '阶段' as content,
o.owner_user_id as operatorUserId,
o.updated_at as createdAt
from crm_opportunity o
where o.owner_user_id = #{userId}
and o.updated_at > o.created_at
union all
select
(2000000000 + r.id) as id,
'report' as bizType,
r.id as bizId,
case
when r.status = 'reviewed' or lc.score is not null then 'report_reviewed'
else 'report_read'
end as actionType,
case
when r.status = 'reviewed' or lc.score is not null then '日报已点评'
else '日报已阅'
end as title,
case
when lc.score is not null then '主管对你' || to_char(r.report_date, 'MM-DD') || '的日报给出了 ' || lc.score || ' 分'
when r.status = 'reviewed' then '你的' || to_char(r.report_date, 'MM-DD') || '日报已完成主管点评'
else '你的' || to_char(r.report_date, 'MM-DD') || '日报已被查阅'
end as content,
coalesce(lc.reviewer_user_id, r.user_id) as operatorUserId,
coalesce(lc.reviewed_at, r.updated_at, r.created_at) as createdAt
from work_daily_report r
left join latest_report_comment lc on lc.report_id = r.id
where r.user_id = #{userId}
and r.status in ('read', 'reviewed')
union all
select
(3000000000 + c.id) as id,
'channel' as bizType,
c.id as bizId,
'channel_created' as actionType,
'新渠道录入' as title,
'成功录入 ' || c.channel_name || ' 渠道商信息' as content,
c.owner_user_id as operatorUserId,
c.created_at as createdAt
from crm_channel_expansion c
where c.owner_user_id = #{userId}
union all
select
(4000000000 + f.id) as id,
'opportunity_followup' as bizType,
f.opportunity_id as bizId,
'opportunity_followup' as actionType,
'商机跟进新增' as title,
o.opportunity_name || ' 新增了一条' || f.followup_type || '跟进记录' as content,
f.followup_user_id as operatorUserId,
f.followup_time as createdAt
from crm_opportunity_followup f
join crm_opportunity o on o.id = f.opportunity_id
where f.followup_user_id = #{userId}
)
<select id="selectLatestOpportunityActivities" resultType="com.unis.crm.dto.dashboard.DashboardActivityDTO">
select
a.id,
a.bizType,
@ -229,10 +126,180 @@
a.content,
a.operatorUserId,
u.display_name as operatorName,
a.targetTab,
a.createdAt
from activity_union a
from (
select
(1000000000 + o.id) as id,
'opportunity' as bizType,
o.id as bizId,
'status_update' as actionType,
'商机状态更新' as title,
o.opportunity_name || ' · 当前阶段:' ||
case o.stage
when 'initial_contact' then '初步沟通'
when 'solution_discussion' then '方案交流'
when 'bidding' then '招投标'
when 'business_negotiation' then '商务谈判'
when 'won' then '已成交'
when 'lost' then '已放弃'
else coalesce(o.stage, '未知')
end ||
case when coalesce(o.archived, false) then ' · 已签单' else '' end ||
case when coalesce(o.pushed_to_oms, false) then ' · 已推送OMS' else '' end as content,
o.owner_user_id as operatorUserId,
case when coalesce(o.archived, false) then 'archived' else 'active' end as targetTab,
o.owner_user_id as owner_user_id,
o.updated_at as createdAt
from crm_opportunity o
where o.updated_at > o.created_at
union all
select
(1100000000 + f.id) as id,
'opportunity' as bizType,
f.opportunity_id as bizId,
'followup_update' as actionType,
'商机跟进更新' as title,
o.opportunity_name || ' · ' || coalesce(f.followup_type, '跟进') || ' · ' ||
left(coalesce(nullif(btrim(f.next_action), ''), nullif(btrim(f.content), ''), '已新增跟进记录'), 80) as content,
f.followup_user_id as operatorUserId,
case when coalesce(o.archived, false) then 'archived' else 'active' end as targetTab,
o.owner_user_id as owner_user_id,
f.followup_time as createdAt
from crm_opportunity_followup f
join crm_opportunity o on o.id = f.opportunity_id
) a
left join sys_user u on u.user_id = a.operatorUserId
where 1 = 1
order by a.createdAt desc nulls last
limit 8
limit 12
</select>
<select id="selectLatestSalesActivities" resultType="com.unis.crm.dto.dashboard.DashboardActivityDTO">
select
a.id,
a.bizType,
a.bizId,
a.actionType,
a.title,
a.content,
a.operatorUserId,
u.display_name as operatorName,
a.targetTab,
a.createdAt
from (
select
(2000000000 + s.id) as id,
'sales' as bizType,
s.id as bizId,
'status_update' as actionType,
'销售拓展状态更新' as title,
s.candidate_name || ' · 当前阶段:' ||
case s.stage
when 'initial_contact' then '初步沟通'
when 'solution_discussion' then '方案交流'
when 'bidding' then '招投标'
when 'business_negotiation' then '商务谈判'
when 'won' then '已成交'
when 'lost' then '已放弃'
else coalesce(s.stage, '未知')
end ||
' · ' || case when coalesce(s.employment_status, 'active') = 'active' then '在职' else '离职' end as content,
s.owner_user_id as operatorUserId,
'sales' as targetTab,
s.owner_user_id as owner_user_id,
s.updated_at as createdAt
from crm_sales_expansion s
where s.updated_at > s.created_at
union all
select
(2100000000 + f.id) as id,
'sales' as bizType,
f.biz_id as bizId,
'followup_update' as actionType,
'销售拓展跟进更新' as title,
s.candidate_name || ' · ' || coalesce(f.followup_type, '跟进') || ' · ' ||
left(coalesce(nullif(btrim(f.next_plan), ''), nullif(btrim(f.content), ''), '已新增跟进记录'), 80) as content,
f.followup_user_id as operatorUserId,
'sales' as targetTab,
s.owner_user_id as owner_user_id,
f.followup_time as createdAt
from crm_expansion_followup f
join crm_sales_expansion s on s.id = f.biz_id and f.biz_type = 'sales'
) a
left join sys_user u on u.user_id = a.operatorUserId
where 1 = 1
order by a.createdAt desc nulls last
limit 12
</select>
<select id="selectLatestChannelActivities" resultType="com.unis.crm.dto.dashboard.DashboardActivityDTO">
select
a.id,
a.bizType,
a.bizId,
a.actionType,
a.title,
a.content,
a.operatorUserId,
u.display_name as operatorName,
a.targetTab,
a.createdAt
from (
select
(3000000000 + c.id) as id,
'channel' as bizType,
c.id as bizId,
'status_update' as actionType,
'渠道拓展状态更新' as title,
c.channel_name || ' · 当前阶段:' ||
case c.stage
when 'initial_contact' then '初步接触'
when 'solution_discussion' then '方案交流'
when 'bidding' then '招投标'
when 'business_negotiation' then '合作洽谈'
when 'won' then '已合作'
when 'lost' then '已终止'
else coalesce(c.stage, '未知')
end ||
' · 合作意向:' ||
case c.intent_level
when 'high' then '高'
when 'medium' then '中'
when 'low' then '低'
else '未知'
end as content,
c.owner_user_id as operatorUserId,
'channel' as targetTab,
c.owner_user_id as owner_user_id,
c.updated_at as createdAt
from crm_channel_expansion c
where c.updated_at > c.created_at
union all
select
(3100000000 + f.id) as id,
'channel' as bizType,
f.biz_id as bizId,
'followup_update' as actionType,
'渠道拓展跟进更新' as title,
c.channel_name || ' · ' || coalesce(f.followup_type, '跟进') || ' · ' ||
left(coalesce(nullif(btrim(f.next_plan), ''), nullif(btrim(f.content), ''), '已新增跟进记录'), 80) as content,
f.followup_user_id as operatorUserId,
'channel' as targetTab,
c.owner_user_id as owner_user_id,
f.followup_time as createdAt
from crm_expansion_followup f
join crm_channel_expansion c on c.id = f.biz_id and f.biz_type = 'channel'
) a
left join sys_user u on u.user_id = a.operatorUserId
where 1 = 1
order by a.createdAt desc nulls last
limit 12
</select>
</mapper>

View File

@ -94,6 +94,7 @@ export interface DashboardActivity {
content?: string;
operatorUserId?: number;
operatorName?: string;
targetTab?: string;
createdAt?: string;
timeText?: string;
}
@ -104,6 +105,8 @@ export interface DashboardHome {
jobTitle?: string;
deptName?: string;
onboardingDays?: number;
todoCardVisible?: boolean;
activityCardVisible?: boolean;
stats?: DashboardStat[];
todos?: DashboardTodo[];
activities?: DashboardActivity[];
@ -650,6 +653,11 @@ function applyAuthHeaders(headers: Headers) {
headers.set("X-User-Id", String(userId));
}
const activeTenantId = localStorage.getItem("activeTenantId");
if (activeTenantId?.trim()) {
headers.set("X-Tenant-Id", activeTenantId.trim());
}
return headers;
}

View File

@ -120,6 +120,9 @@ export default function Dashboard() {
const visibleActivities = showAllActivities ? activities : activities.slice(0, DASHBOARD_PREVIEW_COUNT);
const hasMoreActivities = activities.length > DASHBOARD_PREVIEW_COUNT && activities[0]?.id !== 0;
const hasMoreHistoryTodos = historyTodos.length > DASHBOARD_HISTORY_PREVIEW_COUNT;
const showTodoCard = home.todoCardVisible !== false;
const showActivityCard = home.activityCardVisible !== false;
const dashboardPanelCount = Number(showTodoCard) + Number(showActivityCard);
const handleCompleteTodo = async (todoId: string) => {
if (!todoId || completingTodoId === todoId) {
@ -151,6 +154,23 @@ export default function Dashboard() {
navigate(target.pathname, target.state ? { state: target.state } : undefined);
};
const handleActivityClick = (activity: DashboardActivity) => {
if (!activity?.bizId || !activity.bizType) {
return;
}
if (activity.bizType === "opportunity") {
const archiveTab = activity.targetTab === "archived" ? "archived" : "active";
navigate("/opportunities", { state: { selectedId: activity.bizId, archiveTab } });
return;
}
if (activity.bizType === "sales" || activity.bizType === "channel") {
const tab = activity.targetTab === "channel" ? "channel" : "sales";
navigate("/expansion", { state: { tab, selectedId: activity.bizId } });
}
};
return (
<div className="crm-page-stack">
<header className="crm-page-header">
@ -223,7 +243,8 @@ export default function Dashboard() {
})}
</div>
<div className="mt-5 grid gap-5 md:mt-6 md:grid-cols-2 md:gap-6">
<div className={`mt-5 grid gap-5 md:mt-6 ${dashboardPanelCount > 1 ? "md:grid-cols-2" : "md:grid-cols-1"} md:gap-6`}>
{showTodoCard ? (
<motion.div
initial={disableMobileMotion ? false : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
@ -320,7 +341,9 @@ export default function Dashboard() {
</div>
</div>
</motion.div>
) : null}
{showActivityCard ? (
<motion.div
initial={disableMobileMotion ? false : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
@ -330,17 +353,35 @@ export default function Dashboard() {
<h2 className="mb-4 text-lg font-semibold text-slate-900 dark:text-white"></h2>
<div className="crm-section-stack sm:gap-5">
{visibleActivities.map((news: DashboardActivity, i: number) => (
<div key={news.id ?? i} className="flex gap-4">
<div className="relative mt-1 flex h-3 w-3 items-center justify-center">
<button
key={news.id ?? i}
type="button"
onClick={() => handleActivityClick(news)}
disabled={!news?.bizId || !news?.bizType}
className="group flex w-full gap-4 rounded-xl border border-transparent px-2 py-2 text-left transition-colors hover:border-violet-100 hover:bg-violet-50/60 disabled:cursor-default disabled:hover:border-transparent disabled:hover:bg-transparent dark:hover:border-violet-900/40 dark:hover:bg-slate-900/50"
>
<div className="relative mt-1 flex h-3 w-3 shrink-0 items-center justify-center">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-violet-400 opacity-20"></span>
<span className="relative inline-flex h-2 w-2 rounded-full bg-violet-500"></span>
</div>
<div>
<p className="text-sm font-medium text-slate-900 dark:text-white">{news.title || "无"}</p>
<p className="crm-field-note mt-0.5">{news.content || "无"}</p>
<p className="mt-1 text-[10px] text-slate-400 dark:text-slate-500">{news.timeText || "无"}</p>
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-3">
<p className="text-sm font-medium text-slate-900 transition-colors group-hover:text-violet-700 dark:text-white dark:group-hover:text-violet-300">
{news.title || "无"}
</p>
{news.bizId ? (
<span className="shrink-0 rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-medium text-slate-500 group-hover:bg-violet-100 group-hover:text-violet-700 dark:bg-slate-800 dark:text-slate-400 dark:group-hover:bg-violet-500/10 dark:group-hover:text-violet-300">
</span>
) : null}
</div>
<p className="crm-field-note mt-0.5 line-clamp-2">{news.content || "无"}</p>
<div className="mt-1 flex items-center gap-2 text-[10px] text-slate-400 dark:text-slate-500">
<span>{news.timeText || "无"}</span>
{news.operatorName ? <span>· {news.operatorName}</span> : null}
</div>
</div>
</button>
))}
</div>
{hasMoreActivities && !showAllActivities ? (
@ -353,6 +394,7 @@ export default function Dashboard() {
</button>
) : null}
</motion.div>
) : null}
</div>
</motion.div>
)}

View File

@ -29,6 +29,7 @@ import { cn } from "@/lib/utils";
type ExpansionItem = SalesExpansionItem | ChannelExpansionItem;
type ExpansionTab = "sales" | "channel";
type ExpansionLocationState = { tab?: ExpansionTab; selectedId?: number } | null;
type ExpansionExportFilters = {
keyword?: string;
intent?: string;
@ -1038,12 +1039,42 @@ export default function Expansion() {
}, []);
useEffect(() => {
const requestedTab = (location.state as { tab?: ExpansionTab } | null)?.tab;
const requestedTab = (location.state as ExpansionLocationState)?.tab;
if (requestedTab === "sales" || requestedTab === "channel") {
setActiveTab(requestedTab);
}
}, [location.state]);
useEffect(() => {
const requestedState = location.state as ExpansionLocationState;
const requestedTab = requestedState?.tab;
const requestedId = requestedState?.selectedId;
if (!requestedId) {
return;
}
if (requestedTab === "sales") {
const matched = salesData.find((item) => item.id === requestedId);
if (matched) {
setSelectedItem(matched);
}
return;
}
if (requestedTab === "channel") {
const matched = channelData.find((item) => item.id === requestedId);
if (matched) {
setSelectedItem(matched);
}
return;
}
const matched = salesData.find((item) => item.id === requestedId) ?? channelData.find((item) => item.id === requestedId) ?? null;
if (matched) {
setSelectedItem(matched);
}
}, [location.state, salesData, channelData]);
useEffect(() => {
let cancelled = false;

View File

@ -2,6 +2,7 @@ import { useEffect, useRef, useState, type ReactNode } from "react";
import { Search, Plus, Download, ChevronDown, Check, Building, Calendar, DollarSign, Activity, X, Clock, FileText, User, Tag, AlertTriangle, ListFilter } from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
import { createPortal } from "react-dom";
import { useLocation } from "react-router-dom";
import { createOpportunity, getExpansionOverview, getOpportunityMeta, getOpportunityOmsPreSalesOptions, getOpportunityOverview, getStoredCurrentUserId, pushOpportunityToOms, updateOpportunity, type ChannelExpansionItem, type CreateOpportunityPayload, type OmsPreSalesOption, type OpportunityDictOption, type OpportunityFollowUp, type OpportunityItem, type PushOpportunityToOmsPayload, type SalesExpansionItem } from "@/lib/auth";
import { AdaptiveSelect } from "@/components/AdaptiveSelect";
import { useIsWecomBrowser } from "@/hooks/useIsWecomBrowser";
@ -32,6 +33,7 @@ const COMPETITOR_OPTIONS = [
type CompetitorOption = (typeof COMPETITOR_OPTIONS)[number];
type OperatorMode = "none" | "h3c" | "channel" | "both";
type OpportunityArchiveTab = "active" | "archived";
type OpportunityLocationState = { selectedId?: number; archiveTab?: OpportunityArchiveTab } | null;
type OpportunityExportFilters = {
keyword?: string;
expectedStartDate?: string;
@ -1342,6 +1344,7 @@ function CompetitorMultiSelect({
}
export default function Opportunities() {
const location = useLocation();
const currentUserId = getStoredCurrentUserId();
const isMobileViewport = useIsMobileViewport();
const isWecomBrowser = useIsWecomBrowser();
@ -1404,6 +1407,27 @@ export default function Opportunities() {
};
}, [keyword, filter]);
useEffect(() => {
const requestedState = location.state as OpportunityLocationState;
const requestedArchiveTab = requestedState?.archiveTab;
if (requestedArchiveTab === "active" || requestedArchiveTab === "archived") {
setArchiveTab(requestedArchiveTab);
}
}, [location.state]);
useEffect(() => {
const requestedState = location.state as OpportunityLocationState;
const requestedId = requestedState?.selectedId;
if (!requestedId) {
return;
}
const matched = items.find((item) => item.id === requestedId);
if (matched) {
setSelectedItem(matched);
}
}, [location.state, items]);
useEffect(() => {
let cancelled = false;