Files
cn-rdms-web/src/views/workbench/modules/workbench-todo-panel.vue

891 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { computed, markRaw, onMounted, ref, watch } from 'vue';
import type { RouteKey } from '@elegant-router/types';
import {
fetchApproveOvertimeApplication,
fetchGetOvertimeApplicationApprovalPage,
fetchRejectOvertimeApplication
} from '@/service/api';
import { useRouterPush } from '@/hooks/common/router';
import PersonalItemOperateDialog from '@/views/personal-center/my-item/modules/personal-item-operate-dialog.vue';
import OvertimeApplicationActionDialog from '@/views/personal-center/overtime-application/modules/overtime-application-action-dialog.vue';
import OvertimeApplicationDetailDialog from '@/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue';
import OvertimeApplicationStatusLogDialog from '@/views/personal-center/overtime-application/modules/overtime-application-status-log-dialog.vue';
import {
type WorkbenchTodoDeadlineFilter,
type WorkbenchTodoItem,
type WorkbenchTodoMainTab,
buildWorkbenchTodoItems,
filterWorkbenchTodoItemsByCategory,
filterWorkbenchTodoItemsByDeadline,
isWorkbenchTodoOverdue,
sortWorkbenchTodoItemsByPriority
} from '../homepage';
import { workbenchTodoMock } from '../mock';
import { useWorkbenchRefresh } from '../composables/use-workbench-refresh';
import WorkbenchModuleCard from './workbench-module-card.vue';
import IconMdiCheckCircleOutline from '~icons/mdi/check-circle-outline';
import IconMdiCloseCircleOutline from '~icons/mdi/close-circle-outline';
import IconMdiEyeOutline from '~icons/mdi/eye-outline';
import IconMdiHistory from '~icons/mdi/history';
type SortKey = 'created' | 'priority' | 'deadline';
type OvertimeApprovalActionType = 'approve' | 'reject';
type ApprovalBizType = 'overtime_application';
defineOptions({ name: 'WorkbenchTodoPanel' });
interface Props {
editing?: boolean;
}
withDefaults(defineProps<Props>(), { editing: false });
defineEmits<{
(e: 'hide'): void;
}>();
const { routerPushByKey } = useRouterPush();
const { loading, refresh } = useWorkbenchRefresh();
const PAGE_SIZE = 5;
const activeTab = ref<WorkbenchTodoMainTab>('all');
const activeDeadlineFilter = ref<WorkbenchTodoDeadlineFilter>(null);
const activeApprovalBizType = ref<ApprovalBizType>('overtime_application');
const activeSort = ref<SortKey>('deadline');
const currentPage = ref(1);
const sortOptions = computed<Array<{ key: SortKey; label: string }>>(() => {
const base: Array<{ key: SortKey; label: string }> = [
{ key: 'deadline', label: '截止时间' },
{ key: 'created', label: '创建时间' }
];
if (activeTab.value === 'task') {
base.push({ key: 'priority', label: '优先级' });
}
return base;
});
const mainTabs: Array<{ key: WorkbenchTodoMainTab; label: string }> = [
{ key: 'all', label: '全部' },
{ key: 'task', label: '任务' },
{ key: 'ticket', label: '工单' },
{ key: 'personal', label: '个人事项' },
{ key: 'approval', label: '待审批' }
];
const deadlineFilters: Array<{ key: Exclude<WorkbenchTodoDeadlineFilter, null>; label: string }> = [
{ key: 'overdue', label: '已逾期' },
{ key: 'today', label: '今日到期' },
{ key: 'week', label: '本周到期' }
];
const approvalBizTabs: Array<{ key: ApprovalBizType; label: string }> = [
{ key: 'overtime_application', label: '加班申请' }
];
const allItems = computed(() => buildWorkbenchTodoItems(workbenchTodoMock));
const overtimeApprovalItems = ref<WorkbenchTodoItem[]>([]);
const overtimeApprovalRows = ref<Api.OvertimeApplication.OvertimeApplication[]>([]);
const mergedItems = computed(() => {
const mockItems = allItems.value.filter(item => item.category !== 'approval');
return [...mockItems, ...overtimeApprovalItems.value];
});
const addDialogVisible = ref(false);
const overtimeDetailVisible = ref(false);
const overtimeStatusLogVisible = ref(false);
const overtimeActionVisible = ref(false);
const overtimeActionSubmitting = ref(false);
const currentOvertimeApplication = ref<Api.OvertimeApplication.OvertimeApplication | null>(null);
const currentOvertimeActionType = ref<OvertimeApprovalActionType>('approve');
const OVERTIME_APPROVAL_ACTION_ICONS = {
detail: markRaw(IconMdiEyeOutline),
approve: markRaw(IconMdiCheckCircleOutline),
reject: markRaw(IconMdiCloseCircleOutline),
statusLog: markRaw(IconMdiHistory)
};
function handleOpenAdd() {
addDialogVisible.value = true;
}
function handleAddSubmitted() {
activeTab.value = 'personal';
activeDeadlineFilter.value = null;
}
const tabCounts = computed(() => {
const counts: Record<WorkbenchTodoMainTab, number> = {
all: mergedItems.value.length,
task: 0,
ticket: 0,
personal: 0,
approval: 0
};
mergedItems.value.forEach(item => {
counts[item.category] += 1;
});
return counts;
});
const tabOverdueCount = computed(() => {
const map: Record<WorkbenchTodoMainTab, number> = {
all: 0,
task: 0,
ticket: 0,
personal: 0,
approval: 0
};
mergedItems.value.forEach(item => {
if (!isWorkbenchTodoOverdue(item)) return;
map.all += 1;
map[item.category] += 1;
});
return map;
});
const itemsInTab = computed(() => filterWorkbenchTodoItemsByCategory(mergedItems.value, activeTab.value));
const filteredItems = computed(() => {
if (activeTab.value === 'approval') {
return itemsInTab.value.filter(item => item.approvalBizType === activeApprovalBizType.value);
}
return filterWorkbenchTodoItemsByDeadline(itemsInTab.value, activeDeadlineFilter.value);
});
const approvalBizTabCounts = computed(() => {
const counts: Record<ApprovalBizType, number> = {
overtime_application: 0
};
itemsInTab.value.forEach(item => {
if (item.approvalBizType === 'overtime_application') {
counts.overtime_application += 1;
}
});
return counts;
});
const sortedItems = computed(() => {
const base = filteredItems.value;
if (activeSort.value === 'priority') {
return sortWorkbenchTodoItemsByPriority(base, 'desc');
}
if (activeSort.value === 'deadline') {
return [...base].sort((left, right) => {
const leftValue = left.remainingDays === null ? Number.POSITIVE_INFINITY : left.remainingDays;
const rightValue = right.remainingDays === null ? Number.POSITIVE_INFINITY : right.remainingDays;
return leftValue - rightValue;
});
}
return base;
});
const currentSortLabel = computed(
() => sortOptions.value.find(option => option.key === activeSort.value)?.label ?? '排序'
);
const pagedItems = computed(() => {
const start = (currentPage.value - 1) * PAGE_SIZE;
return sortedItems.value.slice(start, start + PAGE_SIZE);
});
watch([activeTab, activeDeadlineFilter, activeSort], () => {
currentPage.value = 1;
});
function handleSelectTab(key: WorkbenchTodoMainTab) {
if (activeTab.value === key) return;
activeTab.value = key;
if (key === 'approval') activeDeadlineFilter.value = null;
activeDeadlineFilter.value = null;
if (key !== 'task' && activeSort.value === 'priority') {
activeSort.value = 'deadline';
}
}
function handleSelectDeadlineFilter(key: Exclude<WorkbenchTodoDeadlineFilter, null>) {
activeDeadlineFilter.value = activeDeadlineFilter.value === key ? null : key;
}
function handleSelectApprovalBizType(key: ApprovalBizType) {
activeApprovalBizType.value = key;
}
function handleSelectSort(key: SortKey) {
activeSort.value = key;
}
function handleClickItem(item: WorkbenchTodoItem) {
if (item.approvalBizType === 'overtime_application') {
openOvertimeDetail(item);
return;
}
if (!item.routeKey) return;
routerPushByKey(item.routeKey as RouteKey);
}
function findOvertimeApprovalRow(item: WorkbenchTodoItem) {
if (!item.approvalBizId) {
return null;
}
return overtimeApprovalRows.value.find(row => row.id === item.approvalBizId) || null;
}
function openOvertimeDetail(item: WorkbenchTodoItem) {
const row = findOvertimeApprovalRow(item);
if (!row) return;
currentOvertimeApplication.value = row;
overtimeDetailVisible.value = true;
}
function openOvertimeStatusLog(item: WorkbenchTodoItem) {
const row = findOvertimeApprovalRow(item);
if (!row) return;
currentOvertimeApplication.value = row;
overtimeStatusLogVisible.value = true;
}
function openOvertimeAction(item: WorkbenchTodoItem, actionType: OvertimeApprovalActionType) {
const row = findOvertimeApprovalRow(item);
if (!row) return;
currentOvertimeApplication.value = row;
currentOvertimeActionType.value = actionType;
overtimeActionVisible.value = true;
}
async function handleOvertimeActionSubmit(reason: string | null) {
if (!currentOvertimeApplication.value) {
return;
}
overtimeActionSubmitting.value = true;
const result =
currentOvertimeActionType.value === 'approve'
? await fetchApproveOvertimeApplication(currentOvertimeApplication.value.id, { reason })
: await fetchRejectOvertimeApplication(currentOvertimeApplication.value.id, { reason });
overtimeActionSubmitting.value = false;
if (result.error) {
return;
}
overtimeActionVisible.value = false;
overtimeDetailVisible.value = false;
window.$message?.success(currentOvertimeActionType.value === 'approve' ? '加班申请已通过' : '加班申请已退回');
await loadOvertimeApprovalItems();
}
async function loadOvertimeApprovalItems() {
const { error, data } = await fetchGetOvertimeApplicationApprovalPage({
pageNo: 1,
pageSize: 20,
statusCode: 'pending',
keyword: undefined,
applicantName: undefined,
approverId: undefined,
approverName: undefined,
overtimeDate: undefined,
createTime: undefined
});
if (error || !data) {
overtimeApprovalRows.value = [];
overtimeApprovalItems.value = [];
return;
}
overtimeApprovalRows.value = data.list;
overtimeApprovalItems.value = buildWorkbenchTodoItems(
data.list.map(item => ({
id: `overtime-application-${item.id}`,
category: 'approval',
title: `${item.applicantName} · ${item.overtimeDate.slice(5, 7)} 月加班 ${item.overtimeDuration} 申请待审批`,
createdTime: item.submitTime || item.createTime,
deadline: item.submitTime || item.createTime,
source: `加班申请 · ${item.applicantName}`,
priority: 'mid',
approvalBizType: 'overtime_application',
approvalBizId: item.id
}))
);
}
function getDeadlineToneClass(item: WorkbenchTodoItem) {
if (isWorkbenchTodoOverdue(item)) return 'workbench-todo__deadline--rose';
if (item.remainingDays === 0) return 'workbench-todo__deadline--amber';
return 'workbench-todo__deadline--slate';
}
onMounted(loadOvertimeApprovalItems);
</script>
<template>
<WorkbenchModuleCard
v-loading="loading"
title="我的待办"
icon="mdi:clipboard-text-clock-outline"
:editing="editing"
@hide="$emit('hide')"
@refresh="refresh"
>
<div class="workbench-todo__tabs">
<div class="workbench-todo__tabs-group">
<ElTooltip
v-for="tab in mainTabs"
:key="tab.key"
:content="`已逾期 ${tabOverdueCount[tab.key]} 项,建议尽快处理`"
:disabled="tabOverdueCount[tab.key] === 0"
placement="top"
effect="dark"
>
<button
type="button"
class="workbench-todo__tab"
:class="{ 'workbench-todo__tab--active': activeTab === tab.key }"
@click="handleSelectTab(tab.key)"
>
<span>{{ tab.label }}</span>
<span class="workbench-todo__tab-count">{{ tabCounts[tab.key] }}</span>
<span v-if="tabOverdueCount[tab.key] > 0" class="workbench-todo__tab-dot" />
</button>
</ElTooltip>
</div>
<button type="button" class="workbench-todo__add" @click="handleOpenAdd">
<SvgIcon icon="mdi:plus" class="workbench-todo__add-icon" />
<span>个人事项</span>
</button>
</div>
<div class="workbench-todo__filters">
<div v-if="activeTab !== 'approval'" class="workbench-todo__filters-left">
<button
v-for="filter in deadlineFilters"
:key="filter.key"
type="button"
class="workbench-todo__filter"
:class="{ 'workbench-todo__filter--active': activeDeadlineFilter === filter.key }"
@click="handleSelectDeadlineFilter(filter.key)"
>
{{ filter.label }}
</button>
</div>
<div v-else class="workbench-todo__filters-left">
<button
v-for="tab in approvalBizTabs"
:key="tab.key"
type="button"
class="workbench-todo__filter"
:class="{ 'workbench-todo__filter--active': activeApprovalBizType === tab.key }"
@click="handleSelectApprovalBizType(tab.key)"
>
{{ tab.label }}
<span class="workbench-todo__filter-count">{{ approvalBizTabCounts[tab.key] }}</span>
</button>
</div>
<ElDropdown trigger="click" placement="bottom-end" @command="handleSelectSort">
<span class="workbench-todo__sort">
排序{{ currentSortLabel }}
<SvgIcon icon="mdi:chevron-down" class="workbench-todo__sort-icon" />
</span>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem
v-for="option in sortOptions"
:key="option.key"
:command="option.key"
:class="{ 'is-active': activeSort === option.key }"
>
{{ option.label }}
</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
</div>
<div class="workbench-todo__content">
<div v-if="pagedItems.length" class="workbench-todo__list">
<article
v-for="item in pagedItems"
:key="item.id"
class="workbench-todo__item"
:class="{ 'workbench-todo__item--clickable': Boolean(item.routeKey || item.approvalBizType) }"
@click="handleClickItem(item)"
>
<div class="workbench-todo__leading">
<span class="workbench-todo__category" :class="`workbench-todo__category--${item.categoryTone}`">
{{ item.categoryLabel }}
</span>
<span v-if="item.priority === 'high'" class="workbench-todo__priority"></span>
</div>
<div class="workbench-todo__body">
<h4 class="workbench-todo__item-title">{{ item.title }}</h4>
<div class="workbench-todo__meta">
<span class="workbench-todo__source">{{ item.source }}</span>
</div>
</div>
<div class="workbench-todo__trailing">
<div v-if="item.approvalBizType === 'overtime_application'" class="workbench-todo__actions" @click.stop>
<ElTooltip content="详情">
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openOvertimeDetail(item)">
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.detail" class="text-15px" />
</ElButton>
</ElTooltip>
<ElTooltip content="通过">
<ElButton
link
type="success"
class="workbench-todo__action-btn"
@click="openOvertimeAction(item, 'approve')"
>
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.approve" class="text-15px" />
</ElButton>
</ElTooltip>
<ElTooltip content="退回">
<ElButton
link
type="danger"
class="workbench-todo__action-btn"
@click="openOvertimeAction(item, 'reject')"
>
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.reject" class="text-15px" />
</ElButton>
</ElTooltip>
<ElTooltip content="状态日志">
<ElButton link type="info" class="workbench-todo__action-btn" @click="openOvertimeStatusLog(item)">
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.statusLog" class="text-15px" />
</ElButton>
</ElTooltip>
</div>
<span class="workbench-todo__deadline" :class="getDeadlineToneClass(item)">
{{ item.deadlineLabel }}
</span>
</div>
</article>
</div>
<ElEmpty v-else description="当前筛选下暂无待办" :image-size="72" />
</div>
<div class="workbench-todo__pager">
<ElPagination
v-if="filteredItems.length > PAGE_SIZE"
v-model:current-page="currentPage"
:page-size="PAGE_SIZE"
:total="filteredItems.length"
background
small
layout="prev, pager, next"
/>
</div>
<!-- append-to-body脱离 grid item transform 容器弹窗才能正常全屏居中 -->
<PersonalItemOperateDialog
v-model:visible="addDialogVisible"
operate-type="add"
:row-data="null"
append-to-body
@submitted="handleAddSubmitted"
/>
<OvertimeApplicationDetailDialog v-model:visible="overtimeDetailVisible" :row-data="currentOvertimeApplication" />
<OvertimeApplicationStatusLogDialog
v-model:visible="overtimeStatusLogVisible"
:row-data="currentOvertimeApplication"
/>
<OvertimeApplicationActionDialog
v-model:visible="overtimeActionVisible"
:action-type="currentOvertimeActionType"
:loading="overtimeActionSubmitting"
@submit="handleOvertimeActionSubmit"
/>
</WorkbenchModuleCard>
</template>
<style scoped>
.workbench-todo__tabs {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.workbench-todo__tabs-group {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.workbench-todo__add {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 12px;
border: 1px solid rgb(14 116 144 / 60%);
border-radius: 999px;
background-color: rgb(240 253 250 / 80%);
color: rgb(14 116 144 / 96%);
font-size: 12px;
font-weight: 600;
white-space: nowrap;
cursor: pointer;
transition: all 160ms ease;
}
.workbench-todo__add:hover {
background-color: rgb(14 116 144 / 96%);
border-color: rgb(14 116 144 / 96%);
color: white;
}
.workbench-todo__add-icon {
font-size: 14px;
}
.workbench-todo__tab {
position: relative;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border: 1px solid rgb(226 232 240 / 92%);
border-radius: 999px;
background-color: rgb(255 255 255 / 96%);
color: rgb(71 85 105 / 94%);
font-size: 13px;
cursor: pointer;
transition: all 160ms ease;
}
.workbench-todo__tab:hover {
border-color: rgb(14 116 144 / 64%);
color: rgb(14 116 144 / 96%);
}
.workbench-todo__tab--active {
border-color: rgb(14 116 144 / 92%);
background-color: rgb(14 116 144 / 96%);
color: white;
}
.workbench-todo__tab--active:hover {
color: white;
}
.workbench-todo__tab-count {
padding: 1px 6px;
border-radius: 999px;
background-color: rgb(241 245 249 / 96%);
color: rgb(71 85 105 / 94%);
font-size: 11px;
font-weight: 600;
}
.workbench-todo__tab--active .workbench-todo__tab-count {
background-color: rgb(255 255 255 / 22%);
color: white;
}
.workbench-todo__tab-dot {
position: absolute;
top: 4px;
right: 6px;
width: 7px;
height: 7px;
border-radius: 50%;
background-color: rgb(225 29 72 / 96%);
box-shadow: 0 0 0 2px rgb(255 255 255 / 96%);
}
.workbench-todo__tab--active .workbench-todo__tab-dot {
box-shadow: 0 0 0 2px rgb(14 116 144 / 96%);
}
.workbench-todo__filters {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 14px;
}
.workbench-todo__filters-left {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.workbench-todo__sort {
display: inline-flex;
align-items: center;
gap: 2px;
padding: 4px 8px;
border-radius: 6px;
color: rgb(100 116 139 / 92%);
font-size: 12px;
white-space: nowrap;
cursor: pointer;
transition: all 160ms ease;
}
.workbench-todo__sort:hover {
background-color: rgb(241 245 249 / 96%);
color: rgb(14 116 144 / 96%);
}
.workbench-todo__sort-icon {
font-size: 14px;
}
:deep(.el-dropdown-menu__item.is-active) {
color: var(--el-color-primary);
font-weight: 600;
}
.workbench-todo__filter {
padding: 3px 10px;
border: 1px dashed rgb(203 213 225 / 92%);
border-radius: 999px;
background-color: transparent;
color: rgb(100 116 139 / 92%);
font-size: 12px;
cursor: pointer;
transition: all 160ms ease;
}
.workbench-todo__filter:hover {
border-style: solid;
border-color: rgb(14 116 144 / 60%);
color: rgb(14 116 144 / 96%);
}
.workbench-todo__filter--active {
border-style: solid;
border-color: rgb(190 18 60 / 80%);
background-color: rgb(255 228 230 / 96%);
color: rgb(190 18 60 / 96%);
}
.workbench-todo__filter--active:hover {
border-color: rgb(190 18 60 / 92%);
color: rgb(190 18 60 / 96%);
}
.workbench-todo__filter-count {
margin-left: 4px;
font-size: 11px;
font-weight: 700;
}
.workbench-todo__content {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
overflow: auto;
}
.workbench-todo__content :deep(.el-empty) {
margin: auto;
}
.workbench-todo__list {
display: flex;
flex-direction: column;
gap: 10px;
}
.workbench-todo__item {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 14px;
padding: 14px 16px;
border: 1px solid rgb(226 232 240 / 90%);
border-radius: 16px;
background-color: rgb(255 255 255 / 98%);
transition:
border-color 160ms ease,
background-color 160ms ease;
}
.workbench-todo__item--clickable {
cursor: pointer;
}
.workbench-todo__item--clickable:hover {
border-color: rgb(14 116 144 / 60%);
background-color: rgb(240 253 250 / 84%);
}
.workbench-todo__leading {
display: flex;
align-items: center;
gap: 8px;
}
.workbench-todo__category {
display: inline-flex;
align-items: center;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
}
.workbench-todo__category--sky {
background-color: rgb(224 242 254 / 96%);
color: rgb(14 116 144 / 96%);
}
.workbench-todo__category--emerald {
background-color: rgb(220 252 231 / 96%);
color: rgb(5 150 105 / 96%);
}
.workbench-todo__category--amber {
background-color: rgb(254 243 199 / 96%);
color: rgb(180 83 9 / 96%);
}
.workbench-todo__category--rose {
background-color: rgb(255 228 230 / 96%);
color: rgb(190 18 60 / 96%);
}
.workbench-todo__category--violet {
background-color: rgb(237 233 254 / 96%);
color: rgb(109 40 217 / 96%);
}
.workbench-todo__priority {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 6px;
background-color: rgb(254 226 226 / 96%);
color: rgb(220 38 38 / 96%);
font-size: 11px;
font-weight: 800;
}
.workbench-todo__body {
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.workbench-todo__item-title {
margin: 0;
color: rgb(15 23 42 / 98%);
font-size: 14px;
font-weight: 600;
line-height: 1.5;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.workbench-todo__meta {
display: flex;
align-items: center;
gap: 8px;
}
.workbench-todo__source {
color: rgb(100 116 139 / 92%);
font-size: 12px;
line-height: 1.5;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.workbench-todo__trailing {
display: flex;
align-items: center;
gap: 12px;
}
.workbench-todo__actions {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.workbench-todo__actions :deep(.el-button + .el-button) {
margin-left: 0;
}
:deep(.workbench-todo__action-btn) {
min-width: auto;
height: auto;
padding: 3px;
line-height: 1;
}
.workbench-todo__deadline {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.workbench-todo__deadline--slate {
background-color: rgb(241 245 249 / 96%);
color: rgb(71 85 105 / 94%);
}
.workbench-todo__deadline--amber {
background-color: rgb(254 243 199 / 96%);
color: rgb(180 83 9 / 96%);
}
.workbench-todo__deadline--rose {
background-color: rgb(255 228 230 / 96%);
color: rgb(190 18 60 / 96%);
}
.workbench-todo__pager {
display: flex;
justify-content: flex-end;
align-items: center;
min-height: 32px;
margin-top: 12px;
}
@media (width <= 600px) {
.workbench-todo__item {
grid-template-columns: auto minmax(0, 1fr);
}
.workbench-todo__trailing {
grid-column: 1 / -1;
justify-content: flex-end;
}
}
</style>