refactor: clean up code formatting and improve readability
- Standardized table formatting in `api.md` for privilege keys, enhancing clarity. - Removed unnecessary line breaks and improved inline formatting in various components, including `right-section.tsx`, `company-settings.remote.service.ts`, and `timeline-helpers.tsx`, to streamline code readability. - Updated test cases in `privilege-key-hierarchy.test.ts` and `filter-menu-by-view-privilege.test.ts` for better alignment and consistency in assertions. These changes improve the overall code quality and maintainability, making it easier for developers to navigate and understand the codebase.
This commit is contained in:
@@ -243,7 +243,7 @@ HTTP mapping:
|
||||
Catalog (`GET /privilege-keys`, needs `ADMIN.SETTINGS.USER.PRIVILEGES` `view`). Keys use `Group.Parent.Module` or `Group.Parent.Module.Submodule`:
|
||||
|
||||
| code | label |
|
||||
| ---- | ----- |
|
||||
| ----------------------------------------- | ----------------------- |
|
||||
| `ADMIN.SETTINGS.USER.PRIVILEGES` | Privileges |
|
||||
| `ADMIN.SETTINGS.USER.USERS` | Users |
|
||||
| `ADMIN.SETTINGS.DATA.DIVISION` | Divisions |
|
||||
@@ -274,7 +274,7 @@ Catalog (`GET /privilege-keys`, needs `ADMIN.SETTINGS.USER.PRIVILEGES` `view`).
|
||||
Cycles and plans do **not** use a single key. Privilege is resolved from `purpose`:
|
||||
|
||||
| purpose | cycle keys | plan keys |
|
||||
| ----------- | ---------- | --------- |
|
||||
| ----------- | ---------------------------- | ---------------------------------------------------------- |
|
||||
| `sales` | `ADMIN.SALES.DATA.CYCLE` | `ADMIN.SALES.ACTIVITIES.PLAN`, `MOBILE.SALES.PLAN` |
|
||||
| `logistics` | `ADMIN.LOGISTICS.DATA.CYCLE` | `ADMIN.LOGISTICS.ACTIVITIES.PLAN`, `MOBILE.LOGISTICS.PLAN` |
|
||||
|
||||
|
||||
@@ -104,12 +104,6 @@ export function RightSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[13px] text-center text-gray-600 mt-6 max-w-[85%] z-10 leading-[1.6]">
|
||||
Compatible with{' '}
|
||||
<span className="font-bold italic text-gray-900">Gmail, Outlook Web, LinkedIn and most web editors</span> for a
|
||||
smooth writing experience anywhere online.
|
||||
</p>
|
||||
|
||||
{/* Pagination Indicators */}
|
||||
<div className="flex gap-1.5 mt-8 z-10 mb-2">
|
||||
<div className="w-6 h-1 rounded-full bg-brand-500"></div>
|
||||
|
||||
+1
-4
@@ -1,8 +1,5 @@
|
||||
import type { AxiosInstance } from '@repo/core-api/http-client';
|
||||
import type {
|
||||
CompanySettingsEntity,
|
||||
UpdateCompanySettingsPayload,
|
||||
} from '../domain/entities/company-settings.entity';
|
||||
import type { CompanySettingsEntity, UpdateCompanySettingsPayload } from '../domain/entities/company-settings.entity';
|
||||
|
||||
export class CompanySettingsRemoteService {
|
||||
constructor(private readonly client: AxiosInstance) {}
|
||||
|
||||
+1
-4
@@ -1,10 +1,7 @@
|
||||
import { BaseDataTransformer } from '@repo/core-api/data-services';
|
||||
import { apiClient } from '../../../../../../../core/lib/api-client';
|
||||
import { TrackGoRemoteDataServices } from '../../../../../../../core/lib/trackgo-remote-data-services';
|
||||
import {
|
||||
companySettingsModuleConfig,
|
||||
type CompanySettingsShellEntity,
|
||||
} from '../constants/company-settings.constants';
|
||||
import { companySettingsModuleConfig, type CompanySettingsShellEntity } from '../constants/company-settings.constants';
|
||||
import { CompanySettingsRemoteService } from '../../data/company-settings.remote.service';
|
||||
|
||||
class CompanySettingsShellTransformer extends BaseDataTransformer<CompanySettingsShellEntity> {
|
||||
|
||||
+1
-4
@@ -25,10 +25,7 @@ export default function CompanySettingsModule() {
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/index" element={<IndexPage />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={<Navigate to={`${companySettingsModuleConfig.webUrl}/index`} replace />}
|
||||
/>
|
||||
<Route path="/" element={<Navigate to={`${companySettingsModuleConfig.webUrl}/index`} replace />} />
|
||||
<Route path="*" element={<Navigate to="/404" replace />} />
|
||||
</Routes>
|
||||
</EnterpriseModuleProvider>
|
||||
|
||||
+1
-5
@@ -91,11 +91,7 @@ export default function CompanySettingsPage() {
|
||||
<Stack gap="md" component="form" onSubmit={onSubmit}>
|
||||
<Grid gutter="md">
|
||||
<Grid.Col span={{ base: 12, md: 6 }}>
|
||||
<FieldDatePicker
|
||||
control={form.control}
|
||||
name="cycleStartDate"
|
||||
label={t('fields.cycleStartDate')}
|
||||
/>
|
||||
<FieldDatePicker control={form.control} name="cycleStartDate" label={t('fields.cycleStartDate')} />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6 }}>
|
||||
<FieldNumberInput
|
||||
|
||||
+1
-3
@@ -49,9 +49,7 @@ export function TimelineActivityList({
|
||||
radius="md"
|
||||
shadow={selected ? 'sm' : undefined}
|
||||
style={{
|
||||
borderColor: selected
|
||||
? 'var(--mantine-color-blue-filled)'
|
||||
: 'var(--mantine-color-default-border)',
|
||||
borderColor: selected ? 'var(--mantine-color-blue-filled)' : 'var(--mantine-color-default-border)',
|
||||
borderWidth: selected ? 2 : 1,
|
||||
}}
|
||||
>
|
||||
|
||||
+1
-8
@@ -1,12 +1,5 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import {
|
||||
Paper,
|
||||
ScrollArea,
|
||||
SegmentedControl,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from '@repo/ui/components';
|
||||
import { Paper, ScrollArea, SegmentedControl, Stack, Text, TextInput } from '@repo/ui/components';
|
||||
import { Search } from 'lucide-react';
|
||||
import { TimelineActivityList } from './timeline-activity-list';
|
||||
import type { TimelineActivityGroup, TimelineActivityTab } from './timeline-helpers';
|
||||
|
||||
+3
-1
@@ -115,7 +115,9 @@ describe('timeline helpers', () => {
|
||||
'visit-1',
|
||||
'ungrouped',
|
||||
]);
|
||||
expect(filterActivityGroups(groups, { tab: 'completed', query: '' }).map((group) => group.key)).toEqual(['visit-2']);
|
||||
expect(filterActivityGroups(groups, { tab: 'completed', query: '' }).map((group) => group.key)).toEqual([
|
||||
'visit-2',
|
||||
]);
|
||||
expect(filterActivityGroups(groups, { tab: 'on_the_way', query: 'maju' }).map((group) => group.title)).toEqual([
|
||||
'Toko Maju',
|
||||
]);
|
||||
|
||||
+4
-16
@@ -1,7 +1,4 @@
|
||||
import type {
|
||||
TimelineActivityEntity,
|
||||
TimelineFootprintEntity,
|
||||
} from '../../domain/entities/timeline.entity';
|
||||
import type { TimelineActivityEntity, TimelineFootprintEntity } from '../../domain/entities/timeline.entity';
|
||||
|
||||
export type TimelineActivityGroup = {
|
||||
key: string;
|
||||
@@ -46,9 +43,7 @@ export function groupActivities(activities: TimelineActivityEntity[]): TimelineA
|
||||
const sorted = sortByRecordedAt(items);
|
||||
const last = sorted[sorted.length - 1];
|
||||
const first = sorted[0];
|
||||
const title =
|
||||
last?.customer?.name ??
|
||||
(key === 'ungrouped' ? 'ungrouped' : (last?.customer?.code ?? key));
|
||||
const title = last?.customer?.name ?? (key === 'ungrouped' ? 'ungrouped' : (last?.customer?.code ?? key));
|
||||
|
||||
return {
|
||||
key,
|
||||
@@ -95,10 +90,7 @@ export function resolvePlaybackPositions(
|
||||
return [];
|
||||
}
|
||||
|
||||
const latestByEmployee = new Map<
|
||||
string,
|
||||
{ latitude: number; longitude: number; label: string }
|
||||
>();
|
||||
const latestByEmployee = new Map<string, { latitude: number; longitude: number; label: string }>();
|
||||
|
||||
for (const footprint of footprints) {
|
||||
if (footprint.recordedAt > playbackTime) {
|
||||
@@ -155,10 +147,6 @@ export function shouldRestartPlayback(
|
||||
return playbackTime === null || playbackTime >= bounds.max;
|
||||
}
|
||||
|
||||
export function advancePlaybackTime(
|
||||
current: number,
|
||||
bounds: { min: number; max: number },
|
||||
step: number,
|
||||
): number {
|
||||
export function advancePlaybackTime(current: number, bounds: { min: number; max: number }, step: number): number {
|
||||
return Math.min(bounds.max, current + step);
|
||||
}
|
||||
|
||||
+1
-4
@@ -104,10 +104,7 @@ export function TimelinePlaybackOverlay({
|
||||
value={last ? `${formatClock(last.recordedAt)} · ${activityLabel(last.type)}` : '—'}
|
||||
/>
|
||||
<OverlayMetric label={currentLocationLabel} value={currentLocation || '—'} />
|
||||
<OverlayMetric
|
||||
label={activitiesLabel}
|
||||
value={group ? String(group.activities.length) : '—'}
|
||||
/>
|
||||
<OverlayMetric label={activitiesLabel} value={group ? String(group.activities.length) : '—'} />
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -25,10 +25,7 @@ export default function SalesTimelineModule() {
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/index" element={<IndexPage />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={<Navigate to={`${salesTimelineModuleConfig.webUrl}/index`} replace />}
|
||||
/>
|
||||
<Route path="/" element={<Navigate to={`${salesTimelineModuleConfig.webUrl}/index`} replace />} />
|
||||
<Route path="*" element={<Navigate to="/404" replace />} />
|
||||
</Routes>
|
||||
</EnterpriseModuleProvider>
|
||||
|
||||
+4
-18
@@ -153,10 +153,7 @@ export default function TimelinePage() {
|
||||
[footprints, playbackTime],
|
||||
);
|
||||
const groups = useMemo(() => groupActivities(visibleActivities), [visibleActivities]);
|
||||
const filteredGroups = useMemo(
|
||||
() => filterActivityGroups(groups, { tab, query: search }),
|
||||
[groups, search, tab],
|
||||
);
|
||||
const filteredGroups = useMemo(() => filterActivityGroups(groups, { tab, query: search }), [groups, search, tab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (filteredGroups.length === 0) {
|
||||
@@ -179,9 +176,7 @@ export default function TimelinePage() {
|
||||
);
|
||||
const focusPositions = useMemo(() => {
|
||||
if (selectedGroup && selectedGroup.activities.length > 0) {
|
||||
return selectedGroup.activities.map(
|
||||
(activity): [number, number] => [activity.latitude, activity.longitude],
|
||||
);
|
||||
return selectedGroup.activities.map((activity): [number, number] => [activity.latitude, activity.longitude]);
|
||||
}
|
||||
return dayPositions;
|
||||
}, [dayPositions, selectedGroup]);
|
||||
@@ -195,10 +190,7 @@ export default function TimelinePage() {
|
||||
})),
|
||||
[visibleFootprints],
|
||||
);
|
||||
const activityLabel = useCallback(
|
||||
(type: string) => t(`activities.types.${type}`, { defaultValue: type }),
|
||||
[t],
|
||||
);
|
||||
const activityLabel = useCallback((type: string) => t(`activities.types.${type}`, { defaultValue: type }), [t]);
|
||||
const mapActivities = useMemo(
|
||||
() =>
|
||||
visibleActivities.map((activity) => ({
|
||||
@@ -282,13 +274,7 @@ export default function TimelinePage() {
|
||||
</Box>
|
||||
</FormProvider>
|
||||
|
||||
<Box
|
||||
pos="absolute"
|
||||
bottom={16}
|
||||
left={{ base: 12, md: 432 }}
|
||||
right={{ base: 12, md: 16 }}
|
||||
style={{ zIndex: 2 }}
|
||||
>
|
||||
<Box pos="absolute" bottom={16} left={{ base: 12, md: 432 }} right={{ base: 12, md: 16 }} style={{ zIndex: 2 }}>
|
||||
<TimelinePlaybackOverlay
|
||||
group={selectedGroup}
|
||||
ungroupedLabel={t('activities.ungrouped')}
|
||||
|
||||
+3
-11
@@ -52,14 +52,9 @@ describe('groupPrivilegeKeys', () => {
|
||||
|
||||
expect(grouped.map((tab) => tab.group)).toEqual(['ADMIN', 'MOBILE']);
|
||||
expect(grouped[0]?.parents[0]?.parent).toBe('SALES');
|
||||
expect(grouped[0]?.parents[0]?.modules.map((m) => m.module)).toEqual([
|
||||
'ACTIVITIES',
|
||||
'DATA',
|
||||
]);
|
||||
expect(grouped[0]?.parents[0]?.modules.map((m) => m.module)).toEqual(['ACTIVITIES', 'DATA']);
|
||||
expect(grouped[1]?.parents[0]?.modules[0]?.directRow?.code).toBe('MOBILE.SALES.PLAN');
|
||||
expect(grouped[1]?.parents[0]?.modules[0]?.submodules[0]?.code).toBe(
|
||||
'MOBILE.SALES.PLAN.ATTENDANCE',
|
||||
);
|
||||
expect(grouped[1]?.parents[0]?.modules[0]?.submodules[0]?.code).toBe('MOBILE.SALES.PLAN.ATTENDANCE');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,10 +67,7 @@ describe('flattenParentSectionRows', () => {
|
||||
expect(salesParent).toBeDefined();
|
||||
const tableRows = flattenParentSectionRows(salesParent!);
|
||||
|
||||
expect(tableRows.map((r) => r.code)).toEqual([
|
||||
'ADMIN.SALES.DATA.CYCLE',
|
||||
'ADMIN.SALES.ACTIVITIES.PLAN',
|
||||
]);
|
||||
expect(tableRows.map((r) => r.code)).toEqual(['ADMIN.SALES.DATA.CYCLE', 'ADMIN.SALES.ACTIVITIES.PLAN']);
|
||||
expect(tableRows[0]).toMatchObject({ module: 'DATA', submodule: 'CYCLE' });
|
||||
expect(tableRows[1]).toMatchObject({ module: 'ACTIVITIES', submodule: 'PLAN' });
|
||||
});
|
||||
|
||||
+3
-15
@@ -28,10 +28,7 @@ function segmentLabel(t: (key: string) => string, kind: string, value: string) {
|
||||
return translated === key ? formatPrivilegeSegment(value) : translated;
|
||||
}
|
||||
|
||||
function submoduleCellLabel(
|
||||
t: (key: string) => string,
|
||||
row: PrivilegeTableRow,
|
||||
) {
|
||||
function submoduleCellLabel(t: (key: string) => string, row: PrivilegeTableRow) {
|
||||
if (row.submodule) {
|
||||
return row.label || segmentLabel(t, 'submodule', row.submodule);
|
||||
}
|
||||
@@ -53,11 +50,7 @@ function MatrixActionCells({
|
||||
<Table.Td key={action} ta="center" w={ACTION_COL_WIDTH} miw={ACTION_COL_WIDTH} maw={ACTION_COL_WIDTH}>
|
||||
<Box display="flex" style={{ justifyContent: 'center', alignItems: 'center' }}>
|
||||
{mode === 'edit' && formControl ? (
|
||||
<FieldCheckbox
|
||||
control={formControl.control}
|
||||
name={`matrix.${row.keyId}.${action}`}
|
||||
label=""
|
||||
/>
|
||||
<FieldCheckbox control={formControl.control} name={`matrix.${row.keyId}.${action}`} label="" />
|
||||
) : row.cell[action] ? (
|
||||
<Check size={16} />
|
||||
) : (
|
||||
@@ -137,12 +130,7 @@ function ParentMatrixTable({
|
||||
);
|
||||
}
|
||||
|
||||
export function PrivilegeMatrixLayout({
|
||||
groups,
|
||||
t,
|
||||
mode,
|
||||
formControl,
|
||||
}: PrivilegeMatrixLayoutProps) {
|
||||
export function PrivilegeMatrixLayout({ groups, t, mode, formControl }: PrivilegeMatrixLayoutProps) {
|
||||
if (groups.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ describe('enterpriseStorageAdapter', () => {
|
||||
return null;
|
||||
});
|
||||
|
||||
await expect(enterpriseStorageAdapter.getPrivileges('ADMIN.SETTINGS.USER.PRIVILEGES')).resolves.toEqual(defaultPrivileges);
|
||||
await expect(enterpriseStorageAdapter.getPrivileges('ADMIN.SETTINGS.USER.PRIVILEGES')).resolves.toEqual(
|
||||
defaultPrivileges,
|
||||
);
|
||||
await expect(enterpriseStorageAdapter.getPrivileges('UNKNOWN')).resolves.toEqual(defaultPrivileges);
|
||||
});
|
||||
|
||||
|
||||
@@ -94,7 +94,11 @@ describe('filterMenuByViewPrivilege', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const filtered = filterMenuByViewPrivilege(menu, { 'ADMIN.SALES.ACTIVITIES.ORDER': { ...noPrivileges, ALLOW_VIEW: true } }, false);
|
||||
const filtered = filterMenuByViewPrivilege(
|
||||
menu,
|
||||
{ 'ADMIN.SALES.ACTIVITIES.ORDER': { ...noPrivileges, ALLOW_VIEW: true } },
|
||||
false,
|
||||
);
|
||||
|
||||
expect(filtered.find((item) => item.key === 'sales')?.children?.map((child) => child.key)).toEqual([
|
||||
'orders',
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import {
|
||||
CircleMarker,
|
||||
MapContainer,
|
||||
Polyline,
|
||||
TileLayer,
|
||||
Tooltip,
|
||||
ZoomControl,
|
||||
useMap,
|
||||
} from 'react-leaflet';
|
||||
import { CircleMarker, MapContainer, Polyline, TileLayer, Tooltip, ZoomControl, useMap } from 'react-leaflet';
|
||||
import { Box, Text } from '@mantine/core';
|
||||
import { OSM_ATTRIBUTION, OSM_TILE_URL } from './osm';
|
||||
import { DEFAULT_MAP_CENTER, DEFAULT_MAP_ZOOM } from './location-point';
|
||||
@@ -76,11 +68,7 @@ function InvalidateSize() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function FitTimelineBounds({
|
||||
positions,
|
||||
}: {
|
||||
positions: Array<[number, number]>;
|
||||
}) {
|
||||
function FitTimelineBounds({ positions }: { positions: Array<[number, number]> }) {
|
||||
const map = useMap();
|
||||
const boundsKey = positions.map(([lat, lng]) => `${lat.toFixed(6)},${lng.toFixed(6)}`).join('|');
|
||||
useEffect(() => {
|
||||
@@ -97,9 +85,7 @@ function FitTimelineBounds({
|
||||
return null;
|
||||
}
|
||||
|
||||
function groupFootprintsByEmployee(
|
||||
footprints: readonly TimelineMapFootprint[],
|
||||
): Map<string, TimelineMapFootprint[]> {
|
||||
function groupFootprintsByEmployee(footprints: readonly TimelineMapFootprint[]): Map<string, TimelineMapFootprint[]> {
|
||||
const grouped = new Map<string, TimelineMapFootprint[]>();
|
||||
for (const point of footprints) {
|
||||
const existing = grouped.get(point.employeeId) ?? [];
|
||||
@@ -125,10 +111,7 @@ export function TimelineMap({
|
||||
fullBleed = false,
|
||||
emptyLabel = 'No timeline data',
|
||||
}: TimelineMapProps) {
|
||||
const groupedTracks = useMemo(
|
||||
() => groupFootprintsByEmployee(footprints),
|
||||
[footprints],
|
||||
);
|
||||
const groupedTracks = useMemo(() => groupFootprintsByEmployee(footprints), [footprints]);
|
||||
|
||||
const positions = useMemo(() => {
|
||||
const points: Array<[number, number]> = [];
|
||||
@@ -144,18 +127,12 @@ export function TimelineMap({
|
||||
return points;
|
||||
}, [activities, footprints, playbackPositions]);
|
||||
|
||||
const boundsPositions =
|
||||
focusPositions && focusPositions.length > 0 ? focusPositions : positions;
|
||||
const boundsPositions = focusPositions && focusPositions.length > 0 ? focusPositions : positions;
|
||||
const employeeIds = [...groupedTracks.keys()];
|
||||
const hasData = positions.length > 0;
|
||||
|
||||
return (
|
||||
<Box
|
||||
h={height}
|
||||
bdrs={fullBleed ? 0 : radius}
|
||||
className="tg-map-viewport"
|
||||
pos="relative"
|
||||
>
|
||||
<Box h={height} bdrs={fullBleed ? 0 : radius} className="tg-map-viewport" pos="relative">
|
||||
<MapContainer
|
||||
center={boundsPositions[0] ?? DEFAULT_MAP_CENTER}
|
||||
zoom={hasData ? 12 : DEFAULT_MAP_ZOOM}
|
||||
@@ -221,13 +198,7 @@ export function TimelineMap({
|
||||
<InvalidateSize />
|
||||
</MapContainer>
|
||||
{!hasData ? (
|
||||
<Box
|
||||
pos="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
style={{ zIndex: 1, transform: 'translate(-50%, -50%)' }}
|
||||
p="sm"
|
||||
>
|
||||
<Box pos="absolute" top="50%" left="50%" style={{ zIndex: 1, transform: 'translate(-50%, -50%)' }} p="sm">
|
||||
<Text size="sm" c="dimmed">
|
||||
{emptyLabel}
|
||||
</Text>
|
||||
|
||||
Reference in New Issue
Block a user