-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: filter action log by operators
- Loading branch information
Showing
8 changed files
with
355 additions
and
257 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { BadRequestError, Controller, Get, Query, UseMiddlewares } from '@/common/http'; | ||
import { ParseBoolPipe, ParseCsvPipe, ParseDatePipe, ParseIntPipe } from '@/common/pipe'; | ||
import { adminOnly, auth } from '@/middleware'; | ||
import { | ||
customerServiceActionLogService, | ||
CustomerServiceActionLogType, | ||
} from '@/service/customer-service-action-log'; | ||
import { Ticket } from '@/model/Ticket'; | ||
import { User } from '@/model/User'; | ||
import { OpsLogResponse } from '@/response/ops-log'; | ||
import { ReplyResponse } from '@/response/reply'; | ||
import { ReplyRevisionResponse } from '@/response/reply-revision'; | ||
import { TicketListItemResponse } from '@/response/ticket'; | ||
import { UserResponse } from '@/response/user'; | ||
|
||
@Controller('customer-service-action-logs') | ||
@UseMiddlewares(auth, adminOnly) | ||
export class CustomerServiceActionLogController { | ||
@Get() | ||
async getLogs( | ||
@Query('from', ParseDatePipe) from: Date | undefined, | ||
@Query('to', ParseDatePipe) to: Date | undefined, | ||
@Query('operatorIds', ParseCsvPipe) operatorIds: string[] | undefined, | ||
@Query('pageSize', new ParseIntPipe({ min: 1, max: 100 })) pageSize = 10, | ||
@Query('desc', ParseBoolPipe) desc: boolean | undefined | ||
) { | ||
if (!from || !to) { | ||
throw new BadRequestError('Date range params "from" and "to" are required'); | ||
} | ||
if (operatorIds && operatorIds.length > 20) { | ||
throw new BadRequestError('The size of operatorIds must less than 20'); | ||
} | ||
|
||
const logs = await customerServiceActionLogService.getLogs({ | ||
from, | ||
to, | ||
operatorIds, | ||
limit: pageSize, | ||
desc, | ||
}); | ||
|
||
const ticketIds = new Set<string>(); | ||
const userIds = new Set<string>(); | ||
|
||
const logResult = logs.map((log) => { | ||
switch (log.type) { | ||
case CustomerServiceActionLogType.Reply: | ||
if (log.ticketId) { | ||
ticketIds.add(log.ticketId); | ||
} | ||
userIds.add(log.operatorId); | ||
return { | ||
type: 'reply', | ||
ticketId: log.ticketId, | ||
operatorId: log.operatorId, | ||
reply: log.reply && new ReplyResponse(log.reply), | ||
revision: new ReplyRevisionResponse(log.revision), | ||
ts: log.ts.toISOString(), | ||
}; | ||
case CustomerServiceActionLogType.OpsLog: | ||
ticketIds.add(log.ticketId); | ||
userIds.add(log.operatorId); | ||
if (log.opsLog.data.assignee) { | ||
userIds.add(log.opsLog.data.assignee.objectId); | ||
} | ||
return { | ||
type: 'opsLog', | ||
ticketId: log.ticketId, | ||
operatorId: log.operatorId, | ||
opsLog: new OpsLogResponse(log.opsLog), | ||
ts: log.ts.toISOString(), | ||
}; | ||
} | ||
}); | ||
|
||
const tickets = ticketIds.size | ||
? await Ticket.queryBuilder() | ||
.where('objectId', 'in', Array.from(ticketIds)) | ||
.find({ useMasterKey: true }) | ||
: []; | ||
|
||
const users = userIds.size | ||
? await User.queryBuilder() | ||
.where('objectId', 'in', Array.from(userIds)) | ||
.find({ useMasterKey: true }) | ||
: []; | ||
|
||
return { | ||
logs: logResult, | ||
tickets: tickets.map((ticket) => new TicketListItemResponse(ticket)), | ||
users: users.map((user) => new UserResponse(user)), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
next/web/src/App/Admin/Stats/CustomerServiceAction/components/FilterForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { forwardRef } from 'react'; | ||
import { Button, DatePicker, Form, FormInstance } from 'antd'; | ||
import { Moment } from 'moment'; | ||
|
||
import { CustomerServiceSelect } from '@/components/common'; | ||
|
||
export interface FilterFormData { | ||
dateRange: [Moment, Moment]; | ||
operatorIds?: string[]; | ||
} | ||
|
||
export interface FilterFormProps { | ||
initData?: Partial<FilterFormData>; | ||
onSubmit?: (data: FilterFormData) => void; | ||
loading?: boolean; | ||
} | ||
|
||
export const FilterForm = forwardRef<FormInstance, FilterFormProps>( | ||
({ initData, onSubmit, loading }, ref) => { | ||
const handleSubmit = (data: FilterFormData) => { | ||
if (onSubmit) { | ||
if (data.operatorIds && data.operatorIds.length === 0) { | ||
delete data.operatorIds; | ||
} | ||
onSubmit(data); | ||
} | ||
}; | ||
|
||
return ( | ||
<Form | ||
ref={ref} | ||
className="flex flex-wrap gap-2" | ||
initialValues={initData} | ||
onFinish={handleSubmit} | ||
> | ||
<Form.Item noStyle name="dateRange"> | ||
<DatePicker.RangePicker allowClear={false} /> | ||
</Form.Item> | ||
|
||
<Form.Item noStyle name="operatorIds"> | ||
<CustomerServiceSelect | ||
allowClear | ||
showArrow | ||
mode="multiple" | ||
placeholder="客服" | ||
style={{ minWidth: 200 }} | ||
/> | ||
</Form.Item> | ||
|
||
<Button type="primary" htmlType="submit" loading={loading}> | ||
查询 | ||
</Button> | ||
</Form> | ||
); | ||
} | ||
); |
Oops, something went wrong.