Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
thecybernanny-webapp
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
asranov0003
thecybernanny-webapp
Commits
abaf3077
Commit
abaf3077
authored
Jul 22, 2025
by
asranov0003
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: render call history
parent
c66190ac
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
232 additions
and
11 deletions
+232
-11
en.json
src/locales/en/en.json
+4
-0
ru.json
src/locales/ru/ru.json
+4
-0
BrowserHistory.css
src/pages/BrowserHistory/BrowserHistory.css
+1
-1
BrowserHistory.tsx
src/pages/BrowserHistory/BrowserHistory.tsx
+5
-1
Calls.css
src/pages/Calls/Calls.css
+30
-0
Calls.tsx
src/pages/Calls/Calls.tsx
+102
-9
callHistorySlice.ts
src/stores/slices/callHistorySlice.ts
+78
-0
store.ts
src/stores/store.ts
+2
-0
global.css
src/styles/global.css
+6
-0
No files found.
src/locales/en/en.json
View file @
abaf3077
...
...
@@ -118,6 +118,10 @@
"website"
:
"Website"
,
"date"
:
"Date"
},
"callHistory"
:
{
"title"
:
"Call History"
,
"empty"
:
"No call history found."
},
"pincode"
:
{
"enterTitle"
:
"Enter your PIN Code"
,
"createTitle"
:
"Create your PIN Code"
,
...
...
src/locales/ru/ru.json
View file @
abaf3077
...
...
@@ -118,6 +118,10 @@
"website"
:
"Вебсайт"
,
"date"
:
"Дата"
},
"callHistory"
:
{
"title"
:
"История звонков"
,
"empty"
:
"История звонков не найдена."
},
"pincode"
:
{
"enterTitle"
:
"Введите PIN-код"
,
"createTitle"
:
"Создайте PIN-код"
,
...
...
src/pages/BrowserHistory/BrowserHistory.css
View file @
abaf3077
...
...
@@ -45,7 +45,7 @@
display
:
flex
;
flex-direction
:
column
;
gap
:
0.5rem
;
max-height
:
70
vh
;
max-height
:
67
vh
;
overflow-y
:
auto
;
margin
:
0.5rem
0
;
}
...
...
src/pages/BrowserHistory/BrowserHistory.tsx
View file @
abaf3077
...
...
@@ -103,7 +103,11 @@ const BrowserHistory: React.FC = () => {
</
div
>
</
div
>
<
CButton
title=
{
t
(
"button.search"
)
}
onClick=
{
filterBrowserHistory
}
/>
<
CButton
title=
{
t
(
"button.search"
)
}
onClick=
{
filterBrowserHistory
}
isLoading=
{
loading
}
/>
</
div
>
</
div
>
</
div
>
...
...
src/pages/Calls/Calls.css
View file @
abaf3077
...
...
@@ -39,4 +39,33 @@
.calls__filter__date
label
{
margin-right
:
0.5rem
;
font-weight
:
bold
;
}
.callhistory__list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
0.5rem
;
max-height
:
67vh
;
overflow-y
:
auto
;
margin
:
0.5rem
0
;
}
.callhistory__item
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
padding
:
1rem
;
border-radius
:
10px
;
background
:
var
(
--content-bg-color
);
font-size
:
0.875rem
;
}
.callhistory__item
div
{
display
:
flex
;
align-items
:
center
;
gap
:
0.5rem
;
}
.callhistory__item__title
{
font-weight
:
bold
;
}
\ No newline at end of file
src/pages/Calls/Calls.tsx
View file @
abaf3077
import
React
from
"react"
;
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
"./Calls.css"
;
import
SectionHeader
from
"../../layouts/SectionHeader"
;
import
{
MdInfoOutline
}
from
"react-icons/md"
;
import
CButton
from
"../../components/CButton"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useAppDispatch
,
useAppSelector
,
type
RootState
,
}
from
"../../stores/store"
;
import
{
fetchCallHistory
}
from
"../../stores/slices/callHistorySlice"
;
import
{
HiOutlinePhoneMissedCall
}
from
"react-icons/hi"
;
import
{
VscCallOutgoing
}
from
"react-icons/vsc"
;
import
{
IoCallOutline
}
from
"react-icons/io5"
;
import
CLoading
from
"../../components/CLoading"
;
const
Calls
:
React
.
FC
=
()
=>
{
const
today
=
new
Date
().
toISOString
().
split
(
"T"
)[
0
];
const
[
fromDate
,
setFromDate
]
=
useState
(
today
);
const
[
toDate
,
setToDate
]
=
useState
(
today
);
const
{
t
}
=
useTranslation
();
const
{
selectedDevice
}
=
useAppSelector
((
state
:
RootState
)
=>
state
.
device
);
const
{
callHistories
,
loading
,
error
}
=
useAppSelector
(
(
state
:
RootState
)
=>
state
.
callHistory
);
const
dispatch
=
useAppDispatch
();
const
getCallIcon
=
(
callType
:
string
)
=>
{
if
(
callType
==
"3"
)
{
return
(
<
HiOutlinePhoneMissedCall
style=
{
{
color
:
"#F44336"
,
fontSize
:
"18px"
}
}
/>
);
}
else
if
(
callType
==
"2"
)
{
return
<
VscCallOutgoing
style=
{
{
color
:
"#4CAF50"
,
fontSize
:
"18px"
}
}
/>;
}
else
{
return
<
IoCallOutline
style=
{
{
color
:
"#448AFF"
,
fontSize
:
"18px"
}
}
/>;
}
};
useEffect
(()
=>
{
if
(
selectedDevice
?.
id
)
{
filterBrowserHistory
();
}
},
[
dispatch
,
selectedDevice
]);
const
filterBrowserHistory
=
()
=>
{
if
(
selectedDevice
?.
id
)
{
dispatch
(
fetchCallHistory
({
deviceId
:
selectedDevice
.
id
,
dateFrom
:
new
Date
(
fromDate
),
dateTo
:
new
Date
(
toDate
),
recStart
:
0
,
})
);
}
};
return
(
<
div
className=
"calls wrapper"
>
<
SectionHeader
to=
"/home"
/>
...
...
@@ -12,29 +66,68 @@ const Calls: React.FC = () => {
<
div
className=
"calls__content"
>
<
div
>
<
div
className=
"calls__content__header"
>
<
h3
>
Calls
</
h3
>
<
h3
>
{
t
(
"callHistory.title"
)
}
</
h3
>
<
MdInfoOutline
className=
"calls__content__header__icon"
/>
</
div
>
<
p
className=
"calls__content__empty"
>
No data
</
p
>
{
loading
&&
(
<
div
className=
"cloading__center"
>
<
CLoading
/>
</
div
>
)
}
{
error
&&
<
p
className=
"text-danger"
>
{
error
}
</
p
>
}
{
callHistories
.
length
===
0
&&
!
loading
&&
(
<
p
>
{
t
(
"callHistory.empty"
)
}
</
p
>
)
}
<
div
className=
"callhistory__list"
>
{
callHistories
.
map
((
history
,
index
)
=>
{
return
(
<
div
className=
"callhistory__item"
key=
{
index
}
>
<
div
>
{
getCallIcon
(
history
.
call_type
)
}
<
p
className=
"callhistory__item__title"
>
{
history
.
name
}
</
p
>
</
div
>
<
p
>
{
history
.
date
}
</
p
>
</
div
>
);
})
}
</
div
>
</
div
>
<
div
className=
"calls__filter"
>
<
p
className=
"calls__filter__title"
>
Select Dates
</
p
>
<
p
className=
"calls__filter__title"
>
{
t
(
"common.selectDates"
)
}
</
p
>
<
div
className=
"calls__filter__dates"
>
<
div
className=
"calls__filter__date"
>
<
label
htmlFor=
"from"
>
From
</
label
>
<
input
type=
"date"
id=
"from"
/>
<
label
htmlFor=
"from"
>
{
t
(
"common.from"
)
}
</
label
>
<
input
type=
"date"
id=
"from"
value=
{
fromDate
}
onChange=
{
(
e
)
=>
setFromDate
(
e
.
target
.
value
)
}
/>
</
div
>
<
div
className=
"calls__filter__date"
>
<
label
htmlFor=
"to"
>
To
</
label
>
<
input
type=
"date"
id=
"to"
/>
<
label
htmlFor=
"to"
>
{
t
(
"common.to"
)
}
</
label
>
<
input
type=
"date"
id=
"to"
value=
{
toDate
}
onChange=
{
(
e
)
=>
setToDate
(
e
.
target
.
value
)
}
/>
</
div
>
</
div
>
<
CButton
title=
"Search"
/>
<
CButton
title=
{
t
(
"button.search"
)
}
onClick=
{
filterBrowserHistory
}
isLoading=
{
loading
}
/>
</
div
>
</
div
>
</
div
>
...
...
src/stores/slices/callHistorySlice.ts
0 → 100644
View file @
abaf3077
import
{
createAsyncThunk
,
createSlice
}
from
"@reduxjs/toolkit"
;
import
{
sendRpcRequest
}
from
"../../services/apiClient"
;
interface
ICallHistory
{
name
:
string
;
call_type
:
string
;
date
:
string
;
}
interface
CallHistoryState
{
callHistories
:
ICallHistory
[];
loading
:
boolean
;
error
:
string
|
null
;
}
const
initialState
:
CallHistoryState
=
{
callHistories
:
[],
loading
:
false
,
error
:
null
,
};
export
const
fetchCallHistory
=
createAsyncThunk
(
"callHistory/fetchCallHistory"
,
async
(
{
deviceId
,
dateFrom
,
dateTo
,
recStart
,
}:
{
deviceId
:
string
;
dateFrom
:
Date
;
dateTo
:
Date
;
recStart
:
number
},
{
rejectWithValue
}
)
=>
{
try
{
const
response
=
await
sendRpcRequest
<
{
list
:
ICallHistory
[]
}
>
(
"data.getcontent"
,
{
deviceId
,
type
:
3
,
dateFrom
,
dateTo
,
recStart
,
recLimit
:
50
,
}
);
return
response
.
list
;
}
catch
(
error
:
unknown
)
{
if
(
typeof
error
===
"object"
&&
error
!==
null
&&
"message"
in
error
)
{
return
rejectWithValue
(
error
.
message
);
}
return
rejectWithValue
(
"Unknown error occurred"
);
}
}
);
const
callHistorySlice
=
createSlice
({
name
:
"callHistory"
,
initialState
,
reducers
:
{},
extraReducers
:
(
builder
)
=>
{
builder
.
addCase
(
fetchCallHistory
.
pending
,
(
state
)
=>
{
state
.
loading
=
true
;
state
.
error
=
null
;
})
.
addCase
(
fetchCallHistory
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
callHistories
=
action
.
payload
;
state
.
loading
=
false
;
})
.
addCase
(
fetchCallHistory
.
rejected
,
(
state
,
action
)
=>
{
state
.
loading
=
false
;
state
.
error
=
action
.
payload
as
string
;
});
},
});
export
default
callHistorySlice
.
reducer
;
src/stores/store.ts
View file @
abaf3077
...
...
@@ -6,6 +6,7 @@ import deviceSlice from "./slices/deviceSlice";
import
commandSlice
from
"./slices/commandSlice"
;
import
appsSlice
from
"./slices/appsSlice"
;
import
browserHistorySlice
from
"./slices/browserHistorySlice"
;
import
callHistorySlice
from
"./slices/callHistorySlice"
;
import
{
useDispatch
,
useSelector
,
...
...
@@ -21,6 +22,7 @@ export const store = configureStore({
command
:
commandSlice
,
apps
:
appsSlice
,
browserHistory
:
browserHistorySlice
,
callHistory
:
callHistorySlice
,
},
});
...
...
src/styles/global.css
View file @
abaf3077
...
...
@@ -70,4 +70,9 @@ html[data-theme='dark'] {
.modal__box__actions
{
display
:
flex
;
gap
:
0.5rem
;
}
.cloading__center
{
display
:
flex
;
justify-content
:
center
;
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment