feat: cancel deal bill, date range in timetable
This commit is contained in:
@@ -27,6 +27,8 @@ export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
|
||||
export type { BillPaymentStatus } from './models/BillPaymentStatus';
|
||||
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
|
||||
export type { Body_upload_product_image } from './models/Body_upload_product_image';
|
||||
export type { CancelDealBillRequest } from './models/CancelDealBillRequest';
|
||||
export type { CancelDealBillResponse } from './models/CancelDealBillResponse';
|
||||
export type { ClientCreateRequest } from './models/ClientCreateRequest';
|
||||
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
||||
export type { ClientDeleteRequest } from './models/ClientDeleteRequest';
|
||||
|
||||
8
src/client/models/CancelDealBillRequest.ts
Normal file
8
src/client/models/CancelDealBillRequest.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type CancelDealBillRequest = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
9
src/client/models/CancelDealBillResponse.ts
Normal file
9
src/client/models/CancelDealBillResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type CancelDealBillResponse = {
|
||||
ok: boolean;
|
||||
message: string;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { BillStatusUpdateRequest } from '../models/BillStatusUpdateRequest';
|
||||
import type { CancelDealBillRequest } from '../models/CancelDealBillRequest';
|
||||
import type { CancelDealBillResponse } from '../models/CancelDealBillResponse';
|
||||
import type { CreateDealBillRequest } from '../models/CreateDealBillRequest';
|
||||
import type { CreateDealBillResponse } from '../models/CreateDealBillResponse';
|
||||
import type { GetDealBillById } from '../models/GetDealBillById';
|
||||
@@ -50,6 +52,26 @@ export class BillingService {
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Create Deal Bill
|
||||
* @returns CancelDealBillResponse Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static cancelDealBill({
|
||||
requestBody,
|
||||
}: {
|
||||
requestBody: CancelDealBillRequest,
|
||||
}): CancelablePromise<CancelDealBillResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/billing/cancel-deal-bill',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get Deal Bill By Id
|
||||
* @returns GetDealBillById Successful Response
|
||||
|
||||
@@ -1,35 +1,75 @@
|
||||
import {useMemo} from "react";
|
||||
import {MRT_ColumnDef} from "mantine-react-table";
|
||||
import {getDatesInMonth, getDayOfWeek} from "../../../../../shared/lib/date.ts";
|
||||
import {Box, Flex, NumberInput} from "@mantine/core";
|
||||
import {isNumber} from "lodash";
|
||||
import {getDayOfWeek} from "../../../../../shared/lib/date.ts";
|
||||
import {Box, Flex, NumberInput, rem} from "@mantine/core";
|
||||
import {isNumber, last} from "lodash";
|
||||
import {processSelectedCells} from "../../../../../shared/lib/interpolateCells.ts";
|
||||
import {TimeTrackingData} from "../../../../../client";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export type EmployeeData = {
|
||||
name: string;
|
||||
userId: number;
|
||||
totalAmount: number;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
data?: TimeTrackingData[],
|
||||
[key: string]: number | string;
|
||||
};
|
||||
type Props = {
|
||||
month: Date;
|
||||
data: EmployeeData[];
|
||||
onUpdate: (date: Date, userId: number, value: number) => void
|
||||
onUpdate: (date: Date, userId: number, value: number) => void,
|
||||
selectedCells: string[];
|
||||
setSelectedCells: (cells: string[]) => void
|
||||
selectedBoundaries: [Date | null, Date | null];
|
||||
range: dayjs.Dayjs[];
|
||||
}
|
||||
const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
|
||||
const totalAmount = useMemo(() => data.reduce((acc, value) => acc + value.totalAmount, 0), [data]);
|
||||
const useWorkTableColumns = ({
|
||||
month,
|
||||
onUpdate,
|
||||
data,
|
||||
selectedCells,
|
||||
setSelectedCells,
|
||||
selectedBoundaries,
|
||||
range
|
||||
}: Props) => {
|
||||
const totalAmount = useMemo(() => {
|
||||
return data.reduce((acc, value) => {
|
||||
if (value.data) {
|
||||
const sum = value.data.reduce((innerAcc, item) => innerAcc + item.amount, 0);
|
||||
return acc + sum;
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
}, [data, month, selectedCells, selectedBoundaries]);
|
||||
const getBorderStyles = (cellId: string) => {
|
||||
if (selectedCells.length <= 1) return {}
|
||||
if (selectedCells[0] === cellId)
|
||||
return {
|
||||
borderTopLeftRadius: rem(20),
|
||||
borderBottomLeftRadius: rem(20),
|
||||
}
|
||||
else if (last(selectedCells) === cellId)
|
||||
return {
|
||||
borderTopRightRadius: rem(20),
|
||||
borderBottomRightRadius: rem(20),
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
return useMemo<MRT_ColumnDef<EmployeeData>[]>(() => [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "ФИО"
|
||||
},
|
||||
|
||||
...getDatesInMonth(month).map(date => ({
|
||||
...range.map(date => ({
|
||||
size: 80,
|
||||
accessorKey: date.date().toString(),
|
||||
header: date.date().toString(),
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
|
||||
Header: (
|
||||
<Flex
|
||||
align={"center"}
|
||||
@@ -44,6 +84,19 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
|
||||
),
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
mantineTableBodyCellProps: ({cell}) => ({
|
||||
style: selectedCells.includes(cell.id) ? {
|
||||
backgroundColor: "var(--mantine-primary-color-filled)",
|
||||
...getBorderStyles(cell.id)
|
||||
} : {},
|
||||
onClick: () => {
|
||||
const result = processSelectedCells(selectedCells, cell.id);
|
||||
setSelectedCells(result);
|
||||
},
|
||||
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
Cell: ({cell, row}) => {
|
||||
return (
|
||||
<Flex direction={"column"}>
|
||||
@@ -71,9 +124,13 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
|
||||
{
|
||||
accessorKey: "totalAmount",
|
||||
header: "Итоговая сумма заработка",
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
Cell: ({cell}) => cell.renderValue().toLocaleString("ru-RU"),
|
||||
|
||||
Cell: ({row}) => {
|
||||
return (row.original.data || []).reduce((acc, value) => {
|
||||
acc += value.amount;
|
||||
return acc;
|
||||
}, 0);
|
||||
},
|
||||
Footer: (
|
||||
<Flex>
|
||||
Всего: {totalAmount.toLocaleString("ru-RU")}
|
||||
@@ -82,7 +139,7 @@ const useWorkTableColumns = ({month, onUpdate, data}: Props) => {
|
||||
},
|
||||
|
||||
|
||||
], [month]);
|
||||
], [month, selectedCells, selectedBoundaries, totalAmount]);
|
||||
}
|
||||
|
||||
export default useWorkTableColumns;
|
||||
@@ -1,10 +1,16 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {TimeTrackingRecord, TimeTrackingService} from "../../../../../client";
|
||||
import {dateWithoutTimezone} from "../../../../../shared/lib/date.ts";
|
||||
import {dateWithoutTimezone, getDatesInMonth} from "../../../../../shared/lib/date.ts";
|
||||
import {last} from "lodash";
|
||||
|
||||
const getDateBoundaries = (month: Date) => {
|
||||
return [getDatesInMonth(month)[0].toDate(), last(getDatesInMonth(month))?.toDate()]
|
||||
}
|
||||
const useWorkTableState = () => {
|
||||
const [month, setMonth] = useState<Date>(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
|
||||
const [trackingRecords, setTrackingRecords] = useState<TimeTrackingRecord[]>([]);
|
||||
const [dateBoundaries, setDateBoundaries] = useState(getDateBoundaries(month));
|
||||
|
||||
const refetch = async () => {
|
||||
return TimeTrackingService.getTimeTrackingRecords({
|
||||
requestBody: {
|
||||
@@ -13,16 +19,20 @@ const useWorkTableState = () => {
|
||||
}
|
||||
}).then((response) => setTrackingRecords(response.records));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
refetch().then(_ => {
|
||||
setDateBoundaries(getDateBoundaries(month));
|
||||
});
|
||||
|
||||
}, [month])
|
||||
return {
|
||||
month,
|
||||
setMonth,
|
||||
refetch,
|
||||
trackingRecords
|
||||
trackingRecords,
|
||||
dateBoundaries
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,58 @@
|
||||
import {ActionIcon, Flex, MultiSelect, rem, Tooltip} from "@mantine/core";
|
||||
import {MonthPickerInput} from "@mantine/dates";
|
||||
import {DatePickerInput, MonthPickerInput} from "@mantine/dates";
|
||||
import useWorkTableState from "../hooks/useWorkTableState.tsx";
|
||||
import {BaseTable} from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import {useEffect, useState} from "react";
|
||||
import useWorkTableColumns, {EmployeeData} from "../hooks/useWorkTableColumns.tsx";
|
||||
import {TimeTrackingRecord, TimeTrackingService, UserSchema} from "../../../../../client";
|
||||
import {dateWithoutTimezone, getDatesInMonth} from "../../../../../shared/lib/date.ts";
|
||||
import {dateWithoutTimezone, getDatesBetween, getDatesInMonth} from "../../../../../shared/lib/date.ts";
|
||||
import useUsersList from "../../../hooks/useUsersList.tsx";
|
||||
import {notifications} from "../../../../../shared/lib/notifications.ts";
|
||||
import {PaySchemeType} from "../../../../../shared/enums/PaySchemeType.ts";
|
||||
import {IconEyeOff} from "@tabler/icons-react";
|
||||
import {MRT_TableOptions} from "mantine-react-table";
|
||||
import {difference, omit} from "lodash";
|
||||
|
||||
|
||||
const WorkTimeTable = () => {
|
||||
const [data, setData] = useState<EmployeeData[]>([]);
|
||||
const {
|
||||
dateBoundaries,
|
||||
month,
|
||||
setMonth,
|
||||
trackingRecords,
|
||||
refetch
|
||||
} = useWorkTableState();
|
||||
const [hiddenUsers, setHiddenUsers] = useState<UserSchema[]>([]);
|
||||
const [selectedBoundaries, setSelectedBoundaries] = useState<[Date | null, Date | null]>([null, null]);
|
||||
|
||||
const users = useUsersList().objects.filter(user => user.payRate?.payrollScheme.key === PaySchemeType.HOURLY);
|
||||
|
||||
const getRange = () => {
|
||||
const startDate = selectedBoundaries[0];
|
||||
const endDate = selectedBoundaries[1];
|
||||
if (startDate && endDate) {
|
||||
return getDatesBetween(startDate, endDate);
|
||||
} else {
|
||||
return getDatesInMonth(month);
|
||||
}
|
||||
}
|
||||
const range = getRange();
|
||||
|
||||
const transformTrackingRecordsToData = (trackingRecords: TimeTrackingRecord[]): EmployeeData[] => {
|
||||
if (!month) return [];
|
||||
const rangeDays = range.map(r => r.date());
|
||||
|
||||
trackingRecords = trackingRecords.map(tr => ({
|
||||
...tr,
|
||||
data: tr.data.filter(d => rangeDays.includes((new Date(d.date)).getDate()))
|
||||
}));
|
||||
const existingUserIds = trackingRecords.map(tr => tr.user.id);
|
||||
const firstResult = trackingRecords.map((record) => ({
|
||||
name: `${record.user.firstName} ${record.user.secondName}`,
|
||||
userId: record.user.id,
|
||||
totalAmount: record.totalAmount,
|
||||
data: record.data,
|
||||
...Object.fromEntries(getDatesInMonth(month).reduce((acc, day) => {
|
||||
return acc.set(day.date().toString(), 0);
|
||||
}, new Map<string, number>)),
|
||||
@@ -45,7 +69,23 @@ const WorkTimeTable = () => {
|
||||
}, new Map<string, number>)),
|
||||
}))
|
||||
const hiddenUserIds = hiddenUsers.map(user => user.id);
|
||||
return (firstResult.concat(restUsersResult) as unknown as EmployeeData[]).filter(r => !hiddenUserIds.includes(r.userId));
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
const result = (firstResult.concat(restUsersResult)).filter(r => !hiddenUserIds.includes(r.userId));
|
||||
const firstDate = selectedBoundaries[0];
|
||||
const lastDate = selectedBoundaries[1];
|
||||
if (firstDate && lastDate) {
|
||||
const allDays = getDatesInMonth(month).map(d => d.date().toString());
|
||||
const allowedDays = getDatesBetween(firstDate, lastDate).map(d => d.date().toString());
|
||||
const omitDays = difference(allDays, allowedDays);
|
||||
|
||||
return result.map(r => {
|
||||
return omit(r, omitDays);
|
||||
}) as unknown as EmployeeData[];
|
||||
}
|
||||
return result as unknown as EmployeeData[];
|
||||
}
|
||||
|
||||
const optimisticUpdate = (date: Date, userId: number, value: number) => {
|
||||
@@ -56,6 +96,8 @@ const WorkTimeTable = () => {
|
||||
record[date.getDate()] = value;
|
||||
return record;
|
||||
}))
|
||||
|
||||
|
||||
TimeTrackingService.updateTimeTrackingRecord({
|
||||
requestBody: {
|
||||
date: dateWithoutTimezone(date),
|
||||
@@ -71,11 +113,21 @@ const WorkTimeTable = () => {
|
||||
})
|
||||
}
|
||||
const columns = useWorkTableColumns({
|
||||
month, data, onUpdate: optimisticUpdate
|
||||
month,
|
||||
selectedBoundaries,
|
||||
data,
|
||||
onUpdate: optimisticUpdate,
|
||||
selectedCells: [],
|
||||
setSelectedCells: () => {
|
||||
},
|
||||
range
|
||||
});
|
||||
useEffect(() => {
|
||||
setData(transformTrackingRecordsToData(trackingRecords));
|
||||
}, [trackingRecords, hiddenUsers])
|
||||
}, [trackingRecords, hiddenUsers, selectedBoundaries]);
|
||||
useEffect(() => {
|
||||
setSelectedBoundaries([null, null]);
|
||||
}, [month])
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
@@ -83,9 +135,11 @@ const WorkTimeTable = () => {
|
||||
gap={rem(10)}
|
||||
>
|
||||
<Flex
|
||||
justify={"flex-end"}
|
||||
align={"center"}
|
||||
justify={"space-between"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
|
||||
<MultiSelect
|
||||
data={users.map(user => ({
|
||||
label: `${user.firstName} ${user.secondName}`,
|
||||
@@ -95,12 +149,31 @@ const WorkTimeTable = () => {
|
||||
value={hiddenUsers.map(user => user.id.toString())}
|
||||
placeholder={hiddenUsers.length > 0 ? "" : "Скрытые пользователи"}
|
||||
/>
|
||||
<MonthPickerInput
|
||||
allowDeselect={false}
|
||||
onChange={(event) => event && setMonth(event)}
|
||||
value={month}
|
||||
placeholder={"Выберите месяц"}
|
||||
/>
|
||||
<Flex gap={rem(10)}>
|
||||
|
||||
<DatePickerInput
|
||||
styles={{
|
||||
input: {
|
||||
textAlign: "center"
|
||||
}
|
||||
}}
|
||||
miw={rem(80)}
|
||||
valueFormat={"DD"}
|
||||
type={"range"}
|
||||
minDate={dateBoundaries[0]}
|
||||
maxDate={dateBoundaries[1]}
|
||||
value={selectedBoundaries}
|
||||
onChange={setSelectedBoundaries}
|
||||
placeholder={"Даты"}
|
||||
/>
|
||||
<MonthPickerInput
|
||||
allowDeselect={false}
|
||||
onChange={(event) => event && setMonth(event)}
|
||||
value={month}
|
||||
placeholder={"Выберите месяц"}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
</Flex>
|
||||
<Flex>
|
||||
<BaseTable
|
||||
@@ -125,7 +198,7 @@ const WorkTimeTable = () => {
|
||||
</Tooltip>
|
||||
|
||||
</Flex>
|
||||
)
|
||||
),
|
||||
} as MRT_TableOptions<EmployeeData>}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -156,6 +156,32 @@ const ProductAndServiceTab: FC = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
const onCancelBillClick = () => {
|
||||
if (!dealState.deal) return;
|
||||
const dealId = dealState.deal.id;
|
||||
modals.openConfirmModal({
|
||||
withCloseButton: false,
|
||||
children:
|
||||
<Text style={{textAlign: "justify"}}>
|
||||
Вы уверены что хотите отозвать заявку на оплату?
|
||||
</Text>,
|
||||
onConfirm: () => {
|
||||
BillingService.cancelDealBill({
|
||||
requestBody: {
|
||||
dealId
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message});
|
||||
await dealState.refetch();
|
||||
})
|
||||
},
|
||||
labels: {
|
||||
confirm: "Отозвать",
|
||||
cancel: "Отмена"
|
||||
},
|
||||
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
@@ -209,11 +235,19 @@ const ProductAndServiceTab: FC = () => {
|
||||
</div>
|
||||
<Divider my={rem(15)}/>
|
||||
<div className={styles['deal-container-buttons']}>
|
||||
<Button
|
||||
disabled={isLocked}
|
||||
onClick={onCreateBillClick}
|
||||
variant={"default"}
|
||||
fullWidth>Выставить счет</Button>
|
||||
{isLocked ? <Button
|
||||
onClick={onCancelBillClick}
|
||||
color={"red"}
|
||||
>
|
||||
Отозвать счет
|
||||
</Button> :
|
||||
<Button
|
||||
disabled={isLocked}
|
||||
onClick={onCreateBillClick}
|
||||
variant={"default"}
|
||||
fullWidth>Выставить счет</Button>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
</Flex>
|
||||
|
||||
@@ -23,9 +23,8 @@ const ServicesKitsTable: FC<Props> = ({items, onDelete, onChange}) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
const onDeleteClick = (kit: GetServiceKitSchema) => {
|
||||
const onDeleteClick = () => {
|
||||
if (!onDelete) return;
|
||||
console.log(kit)
|
||||
}
|
||||
return (
|
||||
<BaseTable
|
||||
@@ -46,7 +45,7 @@ const ServicesKitsTable: FC<Props> = ({items, onDelete, onChange}) => {
|
||||
</Tooltip>
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => {
|
||||
if (onDelete) onDeleteClick(row.original);
|
||||
if (onDelete) onDeleteClick();
|
||||
}} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
|
||||
@@ -43,4 +43,17 @@ export const getDayOfWeek = (day: number): string => {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function getDatesBetween(startDate: Date, endDate: Date): dayjs.Dayjs[] {
|
||||
const dates: dayjs.Dayjs[] = [];
|
||||
const currentDate = new Date(startDate);
|
||||
|
||||
while (currentDate <= endDate) {
|
||||
dates.push(dayjs(new Date(currentDate)));
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
}
|
||||
|
||||
return dates;
|
||||
}
|
||||
|
||||
export const isWeekend = (day: number) => (day === 6) || (day === 0);
|
||||
47
src/shared/lib/interpolateCells.ts
Normal file
47
src/shared/lib/interpolateCells.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {maxBy, minBy} from "lodash";
|
||||
|
||||
type Cell = { row: number, col: number };
|
||||
|
||||
function parseCell(cell: string): Cell {
|
||||
const [row, col] = cell.split('_').map(Number);
|
||||
return {row, col};
|
||||
}
|
||||
|
||||
function stringifyCell(cell: Cell): string {
|
||||
return `${cell.row}_${cell.col}`;
|
||||
}
|
||||
|
||||
function intropolate(cells: Cell[]) {
|
||||
const interpolatedCells: Cell[] = [];
|
||||
for (let i = 0; i < cells.length - 1; i++) {
|
||||
const current = cells[i];
|
||||
const next = cells[i + 1];
|
||||
interpolatedCells.push(current);
|
||||
if (current.row === next.row) {
|
||||
for (let col = current.col + 1; col < next.col; col++) {
|
||||
interpolatedCells.push({row: current.row, col});
|
||||
}
|
||||
}
|
||||
}
|
||||
interpolatedCells.push(cells[cells.length - 1]);
|
||||
return interpolatedCells;
|
||||
}
|
||||
|
||||
export function processSelectedCells(prevCells: string[], newSelection: string): string[] {
|
||||
const newCell = parseCell(newSelection);
|
||||
let cells = intropolate([...prevCells, newSelection].map(parseCell).sort((a, b) => a.col - b.col));
|
||||
const maxCell = maxBy(cells, cell => cell.col);
|
||||
const minCell = minBy(cells, cell => cell.col);
|
||||
const indexOfNewCell = cells.findIndex(cell => (cell.col === newCell.col && cell.row === newCell.row));
|
||||
|
||||
if (minCell && maxCell) {
|
||||
if (newCell.col > minCell.col && newCell.col < maxCell.col) {
|
||||
if (indexOfNewCell >= (cells.length / 2))
|
||||
cells = cells.slice(0, indexOfNewCell);
|
||||
else
|
||||
cells = cells.slice(indexOfNewCell);
|
||||
}
|
||||
}
|
||||
|
||||
return cells.map(stringifyCell);
|
||||
}
|
||||
Reference in New Issue
Block a user