Compare commits

..

No commits in common. "551bd7d10ca0623b458f27ea9224251a540445e1" and "e32b3853a3034754c686f84305c04e04b19a17d0" have entirely different histories.

10 changed files with 60 additions and 136 deletions

View File

@ -1,15 +0,0 @@
export const AppConfig: {
[key:string]: {
ldapApiUrl: string,
ldapApiKey: string
}
} = {
default:{
ldapApiUrl: 'https://test-api.hkchc.team',
ldapApiKey: 'MPCbsNa6l2RJ7D1Zo6D03qtVF1P93st3'
},
production:{
ldapApiUrl: 'https://test-api.hkchc.team',
ldapApiKey: 'MPCbsNa6l2RJ7D1Zo6D03qtVF1P93st3'
}
}

View File

@ -1,9 +1,9 @@
import {Button, Checkbox, CheckboxGroup, Space, Table, Tag, Typography} from "@douyinfe/semi-ui"; import {Button, Checkbox, CheckboxGroup, Space, Table, Typography} from "@douyinfe/semi-ui";
import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table"; import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table";
import React, {ReactNode, useMemo, useState} from "react"; import React, {ReactNode, useMemo, useState} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {IconCheckCircleStroked, IconSetting, IconTickCircle} from "@douyinfe/semi-icons"; import {IconCheckCircleStroked, IconSetting} from "@douyinfe/semi-icons";
import MoneyFormat from "@/components/money-format.tsx"; import MoneyFormat from "@/components/money-format.tsx";
import {Card} from "@/components/card"; import {Card} from "@/components/card";
@ -23,7 +23,7 @@ type BillListProps = {
loading?: boolean; loading?: boolean;
beforeTotalAmount?: React.ReactNode; beforeTotalAmount?: React.ReactNode;
} }
const CheckNumberCorrect = ({origin, confirmed}: { origin: string, confirmed?: string | null }) => { const CheckNumberCorrect = ({origin, confirmed}: { origin: string, confirmed?: string|null }) => {
if (origin == confirmed && origin) { if (origin == confirmed && origin) {
return (<Space style={{marginTop: 2, color: 'green'}}><span>{origin}</span><IconCheckCircleStroked/></Space>) return (<Space style={{marginTop: 2, color: 'green'}}><span>{origin}</span><IconCheckCircleStroked/></Space>)
} }
@ -42,7 +42,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
showCols: string[] showCols: string[]
}>({ }>({
showCols: [ showCols: [
"id", "merchant_ref", "student_number", "application_number", 'confirm_status', "initiated_paid_at", "delivered_at", "paid_at", "student_english_name", "student_email", "programme_english_name", "id", "merchant_ref", "student_number", "application_number", "initiated_paid_at", "delivered_at", "paid_at", "student_english_name", "student_email", "programme_english_name",
"intake_year", "detail", "detail_confirms", "amount", "pay_amount", "actual_payment_amount", "pay_method", "status", "apply_status" "intake_year", "detail", "detail_confirms", "amount", "pay_amount", "actual_payment_amount", "pay_method", "status", "apply_status"
] ]
}) })
@ -90,7 +90,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: t('base.student_number'), title: t('base.student_number'),
dataIndex: 'student_number', dataIndex: 'student_number',
width: 150, width: 150,
render: (value: string) => (value || 'N/A') render: (value:string) => (value|| 'N/A')
}, },
{ {
title: t('base.bill_number'), title: t('base.bill_number'),
@ -99,16 +99,13 @@ export const BillList: React.FC<BillListProps> = (props) => {
render: (value, record) => ( render: (value, record) => (
<CheckNumberCorrect origin={value} confirmed={record.confirm_application_number}/>) <CheckNumberCorrect origin={value} confirmed={record.confirm_application_number}/>)
}, },
{ // {
title: t('bill.title_bill_confirm_status'), // title: t('bill.title_application_number_confirmed'),
dataIndex: 'confirm_status', // dataIndex: 'application_number',
width: 160, // width: 150,
render: (value) => ( // render: (value, record) => (
<Tag // <CheckNumberCorrect origin={value} confirmed={value || record.application_number_confirm}/>)
shape='circle' prefixIcon={value == 'CONFIRMED' ? <IconTickCircle/> : null} // },
color={value == 'CONFIRMED' ? 'green' : 'grey'}>{value}</Tag>
)
},
{ {
title: <div className="table-header-title">{t('bill.title_initiated_paid_at')} title: <div className="table-header-title">{t('bill.title_initiated_paid_at')}
<div className="tips">(PPS Input Date)</div> <div className="tips">(PPS Input Date)</div>
@ -303,10 +300,9 @@ export const BillList: React.FC<BillListProps> = (props) => {
return <Card return <Card
title={<Space> title={<Space>
<span>{t('bill.title_bill_list')}</span> <span>{t('bill.title_bill_list')}</span>
<span <span className={'cursor-pointer'} onClick={() => setState({showColumnsConfig: true})}>
className={'cursor-pointer'} style={{color:'pink'}} <IconSetting size={'small'}/>
onClick={() => setState({showColumnsConfig: !state.showColumnsConfig})} </span>
><IconSetting /></span>
</Space>} </Space>}
headerRight={<Space spacing={20}> headerRight={<Space spacing={20}>
{props.beforeTotalAmount} {props.beforeTotalAmount}
@ -339,7 +335,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
</div> </div>
<div className="table-column-action" style={{marginTop: 20}}> <div className="table-column-action" style={{marginTop: 20}}>
<Space> <Space>
<Button onClick={() => setState({showColumnsConfig: false})}>{t('base.close')}</Button> <Button onClick={()=>setState({showColumnsConfig:false})}>{t('base.close')}</Button>
</Space> </Space>
</div> </div>

View File

@ -1,35 +0,0 @@
import {useEffect, useState} from "react";
function getRemoteUserNameList() {
return new Promise<string[][]>((resolve, reject) => {
fetch(`${AppConfig.ldapApiUrl}/api/v1/hkchc/user/ldap/get_staff_list`, {
method: 'GET',
headers: {
Apikey: AppConfig.ldapApiKey
},
redirect: 'follow'
})
.then(response => response.json())
.then(ret => {
const result = ret as APIResponse<string[][]>;
if (result.code === 0) {
resolve(result.data!)
} else {
reject(result.message)
}
})
.catch(reject);
})
}
export function useRemoteUserList() {
const [usernameList, setUserList] = useState<string[]>([])
useEffect(()=>{
getRemoteUserNameList().then(data=>{
setUserList(data.flat())
})
},[])
return usernameList
}

View File

@ -60,7 +60,6 @@
"status_unconfirmed": "UNCONFIRMED", "status_unconfirmed": "UNCONFIRMED",
"title_actual_payment_amount": "Actually Paid", "title_actual_payment_amount": "Actually Paid",
"title_amount": "Amount", "title_amount": "Amount",
"title_bill_confirm_status": "Confirm Status",
"title_bill_detail": "Bill Detail", "title_bill_detail": "Bill Detail",
"title_bill_list": "Bill List", "title_bill_list": "Bill List",
"title_bill_status": "Bill Status", "title_bill_status": "Bill Status",

View File

@ -60,7 +60,6 @@
"status_unconfirmed": "未确认", "status_unconfirmed": "未确认",
"title_actual_payment_amount": "实付金额", "title_actual_payment_amount": "实付金额",
"title_amount": "账单金额", "title_amount": "账单金额",
"title_bill_confirm_status": "确认状态",
"title_bill_detail": "账单详情", "title_bill_detail": "账单详情",
"title_bill_list": "账单列表", "title_bill_list": "账单列表",
"title_bill_status": "账单状态", "title_bill_status": "账单状态",

View File

@ -60,7 +60,6 @@
"status_unconfirmed": "未確認", "status_unconfirmed": "未確認",
"title_actual_payment_amount": "實付金額", "title_actual_payment_amount": "實付金額",
"title_amount": "帳單金額", "title_amount": "帳單金額",
"title_bill_confirm_status": "確認狀態",
"title_bill_detail": "帳單詳情", "title_bill_detail": "帳單詳情",
"title_bill_list": "帳單清單", "title_bill_list": "帳單清單",
"title_bill_status": "帳單狀態", "title_bill_status": "帳單狀態",

View File

@ -1,96 +1,86 @@
import {Card} from "@/components/card"; import {Card} from "@/components/card";
import {useSetState} from "ahooks"; import {useSetState} from "ahooks";
import {Button, Select, Space, Toast} from "@douyinfe/semi-ui"; import {Button, Space, TagInput, Toast} from "@douyinfe/semi-ui";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {useEffect, useMemo} from "react"; import {useEffect} from "react";
import {getPermissionList, savePermissionList} from "@/service/api/user.ts"; import {getPermissionList, savePermissionList} from "@/service/api/user.ts";
import {useRemoteUserList} from "@/hooks/useRemoteUserList.ts";
const DEFAULT_ROLES = [ const DEFAULT_ROLES = [
'root', 'ro', 'fo' 'root','ro','fo'
] ]
const Permission = () => { const Permission = ()=>{
const {t} = useTranslation() const {t} = useTranslation()
const usernameList = useRemoteUserList(); const [state,setState] = useSetState<{
const [state, setState] = useSetState<{ list:PermissionUserList[];
list: PermissionUserList[]; loading?:boolean;
loading?: boolean;
}>({ }>({
list: [] list:[]
}) })
const onUsernameChange = (role_name: string, username_list: string[]) => { const onUsernameChange = (role_name:string,username_list: string[])=>{
const index = state.list.findIndex(it => it.role_name == role_name) const index = state.list.findIndex(it=>it.role_name == role_name)
if (index != -1) { if(index != -1){
state.list[index] = { state.list[index] = {
role_name, username_list role_name,username_list
}; };
setState({ setState({
list: state.list list:state.list
}) })
} }
} }
const saveRoles = () => { const saveRoles = ()=>{
setState({ setState({
loading: true loading:true
}) })
savePermissionList(state.list).then(() => { savePermissionList(state.list).then(()=>{
Toast.success({content: `Save Success`, duration: 3,}) Toast.success({content:`Save Success`,duration: 3,})
}).catch(e => { }).catch(e=>{
Toast.error({ Toast.error({
content: `Save Error:${e.message}`, content:`Save Error:${e.message}`,
duration: 3, duration: 3,
}) })
}).finally(() => { }).finally(()=>{
setState({loading: false}) setState({loading:false})
}) })
} }
const loadAllPermission = () => { const loadAllPermission = ()=>{
setState({ setState({
loading: true loading:true
}) })
getPermissionList().then(list => { getPermissionList().then(list=>{
const roles: { const roles:{
[key: string]: string[] [key:string]:string[]
} = {} } = {}
// array to object // array to object
list.forEach(it => { list.forEach(it=>{
roles[it.role_name] = it.username_list roles[it.role_name] = it.username_list
}) })
const permissionList: PermissionUserList[] = []; const permissionList:PermissionUserList[] = [];
DEFAULT_ROLES.forEach(role_name => { DEFAULT_ROLES.forEach(role_name=>{
permissionList.push({ permissionList.push({
role_name, username_list: roles[role_name] role_name,username_list: roles[role_name]
}) })
}) })
setState({list: permissionList}) setState({list:permissionList})
}).finally(() => { }).finally(()=>{
setState({loading: false}) setState({loading:false})
}) })
} }
useEffect(loadAllPermission, []) useEffect(loadAllPermission,[])
const optionList = useMemo(() => (usernameList.map(name => ({label: name, value: name}))), [usernameList])
return (<Card style={{marginBottom: 20}}> return (<Card style={{marginBottom: 20}}>
{state.list.map(it => (<div key={it.role_name} style={{marginBottom: 20}}> {state.list.map(it=>(<div key={it.role_name} style={{marginBottom:20}}>
<div className="permission-title" style={{marginBottom: 5}}>{it.role_name.toUpperCase()}</div> <div className="permission-title" style={{marginBottom:5}}>{it.role_name.toUpperCase()}</div>
<Select <TagInput
style={{width: '100%', backgroundColor: 'var(--semi-color-fill-0)'}}
filter
multiple
size={'large'}
defaultValue={it.username_list} defaultValue={it.username_list}
optionList={optionList} size={'large'}
defaultActiveFirstOption addOnBlur={true} allowDuplicates={false}
allowCreate={true} placeholder={t('base.please_enter')}
placeholder={t('base.please_select')} onChange={users => onUsernameChange(it.role_name,users)}
onChange={(users) => onUsernameChange(it.role_name, users as string[])}
/> />
</div>))} </div>))}
<Space> <Space>
<Button <Button loading={state.loading} onClick={saveRoles} theme={'solid'}>{state.loading ? 'Loading' :t('base.save')}</Button>
loading={state.loading} onClick={saveRoles}
theme={'solid'}>{state.loading ? 'Loading' : t('base.save')}</Button>
{/*<div>{state.message||''}</div>*/} {/*<div>{state.message||''}</div>*/}
</Space> </Space>
</Card>) </Card>)

3
src/vite-env.d.ts vendored
View File

@ -18,11 +18,8 @@ declare const AppConfig: {
SSO_AUTH_CLIENT_KEY: string; SSO_AUTH_CLIENT_KEY: string;
// 登录凭证 token key // 登录凭证 token key
AUTH_TOKEN_KEY: string; AUTH_TOKEN_KEY: string;
ldapApiUrl:string;
ldapApiKey: string;
}; };
declare const AppMode: 'test' | 'production' | 'development'; declare const AppMode: 'test' | 'production' | 'development';
declare const AppMode: 'test' | 'production' | 'development';
declare type BasicComponentProps = { declare type BasicComponentProps = {
children?: React.ReactNode; children?: React.ReactNode;

View File

@ -8,7 +8,6 @@
"strict": true "strict": true
}, },
"include": [ "include": [
"config.ts", "vite.config.ts"
"vite.config.ts",
] ]
} }

View File

@ -1,14 +1,10 @@
import {defineConfig} from 'vite' import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import {resolve} from "path"; import {resolve} from "path";
import {AppConfig} from './config'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({mode}) => { export default defineConfig(({mode}) => {
let configs = AppConfig['default'];
if(AppConfig[mode]){
configs = {...configs,...AppConfig[mode]}
}
return { return {
plugins: [react()], plugins: [react()],
base: mode == 'for-wm' ? './' : '/', base: mode == 'for-wm' ? './' : '/',
@ -20,7 +16,6 @@ export default defineConfig(({mode}) => {
SSO_AUTH_URL: process.env.SSO_AUTH_URL || 'https://portal.chuhai.edu.hk', SSO_AUTH_URL: process.env.SSO_AUTH_URL || 'https://portal.chuhai.edu.hk',
SSO_AUTH_CLIENT_KEY: process.env.AUTH_CLIENT_KEY || 'payment', SSO_AUTH_CLIENT_KEY: process.env.AUTH_CLIENT_KEY || 'payment',
AUTH_TOKEN_KEY: process.env.AUTH_TOKEN_KEY || 'payment-auth-token', AUTH_TOKEN_KEY: process.env.AUTH_TOKEN_KEY || 'payment-auth-token',
...configs
}), }),
AppMode: JSON.stringify(mode) AppMode: JSON.stringify(mode)
}, },