Compare commits
1 Commits
317a1e3145
...
91bd25b5b8
Author | SHA1 | Date | |
---|---|---|---|
91bd25b5b8 |
@ -126,12 +126,12 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
|||||||
}
|
}
|
||||||
|
|
||||||
statistics := gin.H{
|
statistics := gin.H{
|
||||||
"TotalTimeLeftSeconds": (document.TotalPages - document.CurrentPage) * document.SecondsPerPage,
|
"TotalTimeLeftSeconds": (document.Pages - document.Page) * document.SecondsPerPage,
|
||||||
"WordsPerMinute": "N/A",
|
"WordsPerMinute": "N/A",
|
||||||
}
|
}
|
||||||
|
|
||||||
if document.Words != nil && *document.Words != 0 {
|
if document.Words != nil && *document.Words != 0 {
|
||||||
statistics["WordsPerMinute"] = (*document.Words / document.TotalPages * document.ReadPages) / (document.TotalTimeSeconds / 60.0)
|
statistics["WordsPerMinute"] = (*document.Words / document.Pages * document.ReadPages) / (document.TotalTimeSeconds / 60.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
templateVars["RelBase"] = "../"
|
templateVars["RelBase"] = "../"
|
||||||
@ -513,12 +513,12 @@ func (api *API) identifyDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
statistics := gin.H{
|
statistics := gin.H{
|
||||||
"TotalTimeLeftSeconds": (document.TotalPages - document.CurrentPage) * document.SecondsPerPage,
|
"TotalTimeLeftSeconds": (document.Pages - document.Page) * document.SecondsPerPage,
|
||||||
"WordsPerMinute": "N/A",
|
"WordsPerMinute": "N/A",
|
||||||
}
|
}
|
||||||
|
|
||||||
if document.Words != nil && *document.Words != 0 {
|
if document.Words != nil && *document.Words != 0 {
|
||||||
statistics["WordsPerMinute"] = (*document.Words / document.TotalPages * document.ReadPages) / (document.TotalTimeSeconds / 60.0)
|
statistics["WordsPerMinute"] = (*document.Words / document.Pages * document.ReadPages) / (document.TotalTimeSeconds / 60.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
templateVars["Data"] = document
|
templateVars["Data"] = document
|
||||||
|
@ -22,11 +22,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type activityItem struct {
|
type activityItem struct {
|
||||||
DocumentID string `json:"document"`
|
DocumentID string `json:"document"`
|
||||||
StartTime int64 `json:"start_time"`
|
StartTime int64 `json:"start_time"`
|
||||||
Duration int64 `json:"duration"`
|
Duration int64 `json:"duration"`
|
||||||
CurrentPage int64 `json:"current_page"`
|
Page int64 `json:"page"`
|
||||||
TotalPages int64 `json:"total_pages"`
|
Pages int64 `json:"pages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type requestActivity struct {
|
type requestActivity struct {
|
||||||
@ -256,13 +256,13 @@ func (api *API) addActivities(c *gin.Context) {
|
|||||||
// Add All Activity
|
// Add All Activity
|
||||||
for _, item := range rActivity.Activity {
|
for _, item := range rActivity.Activity {
|
||||||
if _, err := qtx.AddActivity(api.DB.Ctx, database.AddActivityParams{
|
if _, err := qtx.AddActivity(api.DB.Ctx, database.AddActivityParams{
|
||||||
UserID: rUser.(string),
|
UserID: rUser.(string),
|
||||||
DocumentID: item.DocumentID,
|
DocumentID: item.DocumentID,
|
||||||
DeviceID: rActivity.DeviceID,
|
DeviceID: rActivity.DeviceID,
|
||||||
StartTime: time.Unix(int64(item.StartTime), 0).UTC(),
|
StartTime: time.Unix(int64(item.StartTime), 0).UTC(),
|
||||||
Duration: int64(item.Duration),
|
Duration: int64(item.Duration),
|
||||||
CurrentPage: int64(item.CurrentPage),
|
Page: int64(item.Page),
|
||||||
TotalPages: int64(item.TotalPages),
|
Pages: int64(item.Pages),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("[addActivities] AddActivity DB Error:", err)
|
log.Error("[addActivities] AddActivity DB Error:", err)
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Activity"})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Activity"})
|
||||||
|
0
book_manager.db
Normal file
0
book_manager.db
Normal file
@ -919,8 +919,8 @@ function SyncNinja:getStatisticsActivity(timestamp)
|
|||||||
document = rows[1][i],
|
document = rows[1][i],
|
||||||
start_time = tonumber(rows[2][i]),
|
start_time = tonumber(rows[2][i]),
|
||||||
duration = tonumber(rows[3][i]),
|
duration = tonumber(rows[3][i]),
|
||||||
current_page = tonumber(rows[4][i]),
|
page = tonumber(rows[4][i]),
|
||||||
total_pages = tonumber(rows[5][i])
|
pages = tonumber(rows[5][i])
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -9,15 +9,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Activity struct {
|
type Activity struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
DocumentID string `json:"document_id"`
|
DocumentID string `json:"document_id"`
|
||||||
DeviceID string `json:"device_id"`
|
DeviceID string `json:"device_id"`
|
||||||
StartTime time.Time `json:"start_time"`
|
StartTime time.Time `json:"start_time"`
|
||||||
Duration int64 `json:"duration"`
|
Duration int64 `json:"duration"`
|
||||||
CurrentPage int64 `json:"current_page"`
|
Page int64 `json:"page"`
|
||||||
TotalPages int64 `json:"total_pages"`
|
Pages int64 `json:"pages"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
@ -85,6 +85,7 @@ type RescaledActivity struct {
|
|||||||
DeviceID string `json:"device_id"`
|
DeviceID string `json:"device_id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
StartTime time.Time `json:"start_time"`
|
StartTime time.Time `json:"start_time"`
|
||||||
|
Pages int64 `json:"pages"`
|
||||||
Page int64 `json:"page"`
|
Page int64 `json:"page"`
|
||||||
Duration int64 `json:"duration"`
|
Duration int64 `json:"duration"`
|
||||||
}
|
}
|
||||||
|
@ -141,8 +141,8 @@ INSERT INTO activity (
|
|||||||
device_id,
|
device_id,
|
||||||
start_time,
|
start_time,
|
||||||
duration,
|
duration,
|
||||||
current_page,
|
page,
|
||||||
total_pages
|
pages
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
@ -192,15 +192,15 @@ WITH true_progress AS (
|
|||||||
start_time AS last_read,
|
start_time AS last_read,
|
||||||
SUM(duration) AS total_time_seconds,
|
SUM(duration) AS total_time_seconds,
|
||||||
document_id,
|
document_id,
|
||||||
current_page,
|
page,
|
||||||
total_pages,
|
pages,
|
||||||
|
|
||||||
-- Determine Read Pages
|
-- Determine Read Pages
|
||||||
COUNT(DISTINCT current_page) AS read_pages,
|
COUNT(DISTINCT page) AS read_pages,
|
||||||
|
|
||||||
-- Derive Percentage of Book
|
-- Derive Percentage of Book
|
||||||
ROUND(CAST(current_page AS REAL) / CAST(total_pages AS REAL) * 100, 2) AS percentage
|
ROUND(CAST(page AS REAL) / CAST(pages AS REAL) * 100, 2) AS percentage
|
||||||
FROM activity
|
FROM rescaled_activity
|
||||||
WHERE user_id = $user_id
|
WHERE user_id = $user_id
|
||||||
AND document_id = $document_id
|
AND document_id = $document_id
|
||||||
GROUP BY document_id
|
GROUP BY document_id
|
||||||
@ -210,8 +210,8 @@ WITH true_progress AS (
|
|||||||
SELECT
|
SELECT
|
||||||
documents.*,
|
documents.*,
|
||||||
|
|
||||||
CAST(IFNULL(current_page, 0) AS INTEGER) AS current_page,
|
CAST(IFNULL(page, 0) AS INTEGER) AS page,
|
||||||
CAST(IFNULL(total_pages, 0) AS INTEGER) AS total_pages,
|
CAST(IFNULL(pages, 0) AS INTEGER) AS pages,
|
||||||
CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds,
|
CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds,
|
||||||
CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read,
|
CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read,
|
||||||
CAST(IFNULL(read_pages, 0) AS INTEGER) AS read_pages,
|
CAST(IFNULL(read_pages, 0) AS INTEGER) AS read_pages,
|
||||||
@ -244,9 +244,9 @@ WITH true_progress AS (
|
|||||||
start_time AS last_read,
|
start_time AS last_read,
|
||||||
SUM(duration) AS total_time_seconds,
|
SUM(duration) AS total_time_seconds,
|
||||||
document_id,
|
document_id,
|
||||||
current_page,
|
page,
|
||||||
total_pages,
|
pages,
|
||||||
ROUND(CAST(current_page AS REAL) / CAST(total_pages AS REAL) * 100, 2) AS percentage
|
ROUND(CAST(page AS REAL) / CAST(pages AS REAL) * 100, 2) AS percentage
|
||||||
FROM activity
|
FROM activity
|
||||||
WHERE user_id = $user_id
|
WHERE user_id = $user_id
|
||||||
GROUP BY document_id
|
GROUP BY document_id
|
||||||
@ -255,8 +255,8 @@ WITH true_progress AS (
|
|||||||
SELECT
|
SELECT
|
||||||
documents.*,
|
documents.*,
|
||||||
|
|
||||||
CAST(IFNULL(current_page, 0) AS INTEGER) AS current_page,
|
CAST(IFNULL(page, 0) AS INTEGER) AS page,
|
||||||
CAST(IFNULL(total_pages, 0) AS INTEGER) AS total_pages,
|
CAST(IFNULL(pages, 0) AS INTEGER) AS pages,
|
||||||
CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds,
|
CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds,
|
||||||
CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read,
|
CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read,
|
||||||
|
|
||||||
@ -295,8 +295,8 @@ SELECT
|
|||||||
title,
|
title,
|
||||||
author,
|
author,
|
||||||
duration,
|
duration,
|
||||||
current_page,
|
page,
|
||||||
total_pages
|
pages
|
||||||
FROM activity
|
FROM activity
|
||||||
LEFT JOIN documents ON documents.id = activity.document_id
|
LEFT JOIN documents ON documents.id = activity.document_id
|
||||||
LEFT JOIN users ON users.id = activity.user_id
|
LEFT JOIN users ON users.id = activity.user_id
|
||||||
@ -324,8 +324,8 @@ GROUP BY activity.device_id;
|
|||||||
|
|
||||||
-- name: GetDocumentReadStats :one
|
-- name: GetDocumentReadStats :one
|
||||||
SELECT
|
SELECT
|
||||||
count(DISTINCT page) AS pages_read,
|
COUNT(DISTINCT page) AS pages_read,
|
||||||
sum(duration) AS total_time
|
SUM(duration) AS total_time
|
||||||
FROM rescaled_activity
|
FROM rescaled_activity
|
||||||
WHERE document_id = $document_id
|
WHERE document_id = $document_id
|
||||||
AND user_id = $user_id
|
AND user_id = $user_id
|
||||||
@ -333,7 +333,7 @@ AND start_time >= $start_time;
|
|||||||
|
|
||||||
-- name: GetDocumentReadStatsCapped :one
|
-- name: GetDocumentReadStatsCapped :one
|
||||||
WITH capped_stats AS (
|
WITH capped_stats AS (
|
||||||
SELECT min(sum(duration), CAST($page_duration_cap AS INTEGER)) AS durations
|
SELECT MIN(SUM(duration), CAST($page_duration_cap AS INTEGER)) AS durations
|
||||||
FROM rescaled_activity
|
FROM rescaled_activity
|
||||||
WHERE document_id = $document_id
|
WHERE document_id = $document_id
|
||||||
AND user_id = $user_id
|
AND user_id = $user_id
|
||||||
@ -341,20 +341,20 @@ WITH capped_stats AS (
|
|||||||
GROUP BY page
|
GROUP BY page
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
CAST(count(*) AS INTEGER) AS pages_read,
|
CAST(COUNT(*) AS INTEGER) AS pages_read,
|
||||||
CAST(sum(durations) AS INTEGER) AS total_time
|
CAST(SUM(durations) AS INTEGER) AS total_time
|
||||||
FROM capped_stats;
|
FROM capped_stats;
|
||||||
|
|
||||||
-- name: GetDocumentDaysRead :one
|
-- name: GetDocumentDaysRead :one
|
||||||
WITH document_days AS (
|
WITH document_days AS (
|
||||||
SELECT DATE(start_time, time_offset) AS dates
|
SELECT DATE(start_time, time_offset) AS dates
|
||||||
FROM rescaled_activity
|
FROM activity
|
||||||
JOIN users ON users.id = rescaled_activity.user_id
|
JOIN users ON users.id = activity.user_id
|
||||||
WHERE document_id = $document_id
|
WHERE document_id = $document_id
|
||||||
AND user_id = $user_id
|
AND user_id = $user_id
|
||||||
GROUP BY dates
|
GROUP BY dates
|
||||||
)
|
)
|
||||||
SELECT CAST(count(*) AS INTEGER) AS days_read
|
SELECT CAST(COUNT(*) AS INTEGER) AS days_read
|
||||||
FROM document_days;
|
FROM document_days;
|
||||||
|
|
||||||
-- name: GetUserWindowStreaks :one
|
-- name: GetUserWindowStreaks :one
|
||||||
@ -381,7 +381,7 @@ partitions AS (
|
|||||||
),
|
),
|
||||||
streaks AS (
|
streaks AS (
|
||||||
SELECT
|
SELECT
|
||||||
count(*) AS streak,
|
COUNT(*) AS streak,
|
||||||
MIN(read_window) AS start_date,
|
MIN(read_window) AS start_date,
|
||||||
MAX(read_window) AS end_date,
|
MAX(read_window) AS end_date,
|
||||||
time_offset
|
time_offset
|
||||||
@ -431,10 +431,10 @@ LIMIT 1;
|
|||||||
|
|
||||||
-- name: GetDatabaseInfo :one
|
-- name: GetDatabaseInfo :one
|
||||||
SELECT
|
SELECT
|
||||||
(SELECT count(rowid) FROM activity WHERE activity.user_id = $user_id) AS activity_size,
|
(SELECT COUNT(rowid) FROM activity WHERE activity.user_id = $user_id) AS activity_size,
|
||||||
(SELECT count(rowid) FROM documents) AS documents_size,
|
(SELECT COUNT(rowid) FROM documents) AS documents_size,
|
||||||
(SELECT count(rowid) FROM document_progress WHERE document_progress.user_id = $user_id) AS progress_size,
|
(SELECT COUNT(rowid) FROM document_progress WHERE document_progress.user_id = $user_id) AS progress_size,
|
||||||
(SELECT count(rowid) FROM devices WHERE devices.user_id = $user_id) AS devices_size
|
(SELECT COUNT(rowid) FROM devices WHERE devices.user_id = $user_id) AS devices_size
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: GetDailyReadStats :many
|
-- name: GetDailyReadStats :many
|
||||||
@ -448,7 +448,7 @@ WITH RECURSIVE last_30_days AS (
|
|||||||
),
|
),
|
||||||
activity_records AS (
|
activity_records AS (
|
||||||
SELECT
|
SELECT
|
||||||
sum(duration) AS seconds_read,
|
SUM(duration) AS seconds_read,
|
||||||
DATE(start_time, time_offset) AS day
|
DATE(start_time, time_offset) AS day
|
||||||
FROM activity
|
FROM activity
|
||||||
LEFT JOIN users ON users.id = activity.user_id
|
LEFT JOIN users ON users.id = activity.user_id
|
||||||
|
@ -19,21 +19,21 @@ INSERT INTO activity (
|
|||||||
device_id,
|
device_id,
|
||||||
start_time,
|
start_time,
|
||||||
duration,
|
duration,
|
||||||
current_page,
|
page,
|
||||||
total_pages
|
pages
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id, user_id, document_id, device_id, start_time, duration, current_page, total_pages, created_at
|
RETURNING id, user_id, document_id, device_id, start_time, duration, page, pages, created_at
|
||||||
`
|
`
|
||||||
|
|
||||||
type AddActivityParams struct {
|
type AddActivityParams struct {
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
DocumentID string `json:"document_id"`
|
DocumentID string `json:"document_id"`
|
||||||
DeviceID string `json:"device_id"`
|
DeviceID string `json:"device_id"`
|
||||||
StartTime time.Time `json:"start_time"`
|
StartTime time.Time `json:"start_time"`
|
||||||
Duration int64 `json:"duration"`
|
Duration int64 `json:"duration"`
|
||||||
CurrentPage int64 `json:"current_page"`
|
Page int64 `json:"page"`
|
||||||
TotalPages int64 `json:"total_pages"`
|
Pages int64 `json:"pages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) AddActivity(ctx context.Context, arg AddActivityParams) (Activity, error) {
|
func (q *Queries) AddActivity(ctx context.Context, arg AddActivityParams) (Activity, error) {
|
||||||
@ -43,8 +43,8 @@ func (q *Queries) AddActivity(ctx context.Context, arg AddActivityParams) (Activ
|
|||||||
arg.DeviceID,
|
arg.DeviceID,
|
||||||
arg.StartTime,
|
arg.StartTime,
|
||||||
arg.Duration,
|
arg.Duration,
|
||||||
arg.CurrentPage,
|
arg.Page,
|
||||||
arg.TotalPages,
|
arg.Pages,
|
||||||
)
|
)
|
||||||
var i Activity
|
var i Activity
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@ -54,8 +54,8 @@ func (q *Queries) AddActivity(ctx context.Context, arg AddActivityParams) (Activ
|
|||||||
&i.DeviceID,
|
&i.DeviceID,
|
||||||
&i.StartTime,
|
&i.StartTime,
|
||||||
&i.Duration,
|
&i.Duration,
|
||||||
&i.CurrentPage,
|
&i.Page,
|
||||||
&i.TotalPages,
|
&i.Pages,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
@ -155,8 +155,8 @@ SELECT
|
|||||||
title,
|
title,
|
||||||
author,
|
author,
|
||||||
duration,
|
duration,
|
||||||
current_page,
|
page,
|
||||||
total_pages
|
pages
|
||||||
FROM activity
|
FROM activity
|
||||||
LEFT JOIN documents ON documents.id = activity.document_id
|
LEFT JOIN documents ON documents.id = activity.document_id
|
||||||
LEFT JOIN users ON users.id = activity.user_id
|
LEFT JOIN users ON users.id = activity.user_id
|
||||||
@ -181,13 +181,13 @@ type GetActivityParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetActivityRow struct {
|
type GetActivityRow struct {
|
||||||
DocumentID string `json:"document_id"`
|
DocumentID string `json:"document_id"`
|
||||||
StartTime string `json:"start_time"`
|
StartTime string `json:"start_time"`
|
||||||
Title *string `json:"title"`
|
Title *string `json:"title"`
|
||||||
Author *string `json:"author"`
|
Author *string `json:"author"`
|
||||||
Duration int64 `json:"duration"`
|
Duration int64 `json:"duration"`
|
||||||
CurrentPage int64 `json:"current_page"`
|
Page int64 `json:"page"`
|
||||||
TotalPages int64 `json:"total_pages"`
|
Pages int64 `json:"pages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetActivity(ctx context.Context, arg GetActivityParams) ([]GetActivityRow, error) {
|
func (q *Queries) GetActivity(ctx context.Context, arg GetActivityParams) ([]GetActivityRow, error) {
|
||||||
@ -211,8 +211,8 @@ func (q *Queries) GetActivity(ctx context.Context, arg GetActivityParams) ([]Get
|
|||||||
&i.Title,
|
&i.Title,
|
||||||
&i.Author,
|
&i.Author,
|
||||||
&i.Duration,
|
&i.Duration,
|
||||||
&i.CurrentPage,
|
&i.Page,
|
||||||
&i.TotalPages,
|
&i.Pages,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -238,7 +238,7 @@ WITH RECURSIVE last_30_days AS (
|
|||||||
),
|
),
|
||||||
activity_records AS (
|
activity_records AS (
|
||||||
SELECT
|
SELECT
|
||||||
sum(duration) AS seconds_read,
|
SUM(duration) AS seconds_read,
|
||||||
DATE(start_time, time_offset) AS day
|
DATE(start_time, time_offset) AS day
|
||||||
FROM activity
|
FROM activity
|
||||||
LEFT JOIN users ON users.id = activity.user_id
|
LEFT JOIN users ON users.id = activity.user_id
|
||||||
@ -290,10 +290,10 @@ func (q *Queries) GetDailyReadStats(ctx context.Context, userID string) ([]GetDa
|
|||||||
|
|
||||||
const getDatabaseInfo = `-- name: GetDatabaseInfo :one
|
const getDatabaseInfo = `-- name: GetDatabaseInfo :one
|
||||||
SELECT
|
SELECT
|
||||||
(SELECT count(rowid) FROM activity WHERE activity.user_id = ?1) AS activity_size,
|
(SELECT COUNT(rowid) FROM activity WHERE activity.user_id = ?1) AS activity_size,
|
||||||
(SELECT count(rowid) FROM documents) AS documents_size,
|
(SELECT COUNT(rowid) FROM documents) AS documents_size,
|
||||||
(SELECT count(rowid) FROM document_progress WHERE document_progress.user_id = ?1) AS progress_size,
|
(SELECT COUNT(rowid) FROM document_progress WHERE document_progress.user_id = ?1) AS progress_size,
|
||||||
(SELECT count(rowid) FROM devices WHERE devices.user_id = ?1) AS devices_size
|
(SELECT COUNT(rowid) FROM devices WHERE devices.user_id = ?1) AS devices_size
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -451,13 +451,13 @@ func (q *Queries) GetDocument(ctx context.Context, documentID string) (Document,
|
|||||||
const getDocumentDaysRead = `-- name: GetDocumentDaysRead :one
|
const getDocumentDaysRead = `-- name: GetDocumentDaysRead :one
|
||||||
WITH document_days AS (
|
WITH document_days AS (
|
||||||
SELECT DATE(start_time, time_offset) AS dates
|
SELECT DATE(start_time, time_offset) AS dates
|
||||||
FROM rescaled_activity
|
FROM activity
|
||||||
JOIN users ON users.id = rescaled_activity.user_id
|
JOIN users ON users.id = activity.user_id
|
||||||
WHERE document_id = ?1
|
WHERE document_id = ?1
|
||||||
AND user_id = ?2
|
AND user_id = ?2
|
||||||
GROUP BY dates
|
GROUP BY dates
|
||||||
)
|
)
|
||||||
SELECT CAST(count(*) AS INTEGER) AS days_read
|
SELECT CAST(COUNT(*) AS INTEGER) AS days_read
|
||||||
FROM document_days
|
FROM document_days
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -475,8 +475,8 @@ func (q *Queries) GetDocumentDaysRead(ctx context.Context, arg GetDocumentDaysRe
|
|||||||
|
|
||||||
const getDocumentReadStats = `-- name: GetDocumentReadStats :one
|
const getDocumentReadStats = `-- name: GetDocumentReadStats :one
|
||||||
SELECT
|
SELECT
|
||||||
count(DISTINCT page) AS pages_read,
|
COUNT(DISTINCT page) AS pages_read,
|
||||||
sum(duration) AS total_time
|
SUM(duration) AS total_time
|
||||||
FROM rescaled_activity
|
FROM rescaled_activity
|
||||||
WHERE document_id = ?1
|
WHERE document_id = ?1
|
||||||
AND user_id = ?2
|
AND user_id = ?2
|
||||||
@ -503,7 +503,7 @@ func (q *Queries) GetDocumentReadStats(ctx context.Context, arg GetDocumentReadS
|
|||||||
|
|
||||||
const getDocumentReadStatsCapped = `-- name: GetDocumentReadStatsCapped :one
|
const getDocumentReadStatsCapped = `-- name: GetDocumentReadStatsCapped :one
|
||||||
WITH capped_stats AS (
|
WITH capped_stats AS (
|
||||||
SELECT min(sum(duration), CAST(?1 AS INTEGER)) AS durations
|
SELECT MIN(SUM(duration), CAST(?1 AS INTEGER)) AS durations
|
||||||
FROM rescaled_activity
|
FROM rescaled_activity
|
||||||
WHERE document_id = ?2
|
WHERE document_id = ?2
|
||||||
AND user_id = ?3
|
AND user_id = ?3
|
||||||
@ -511,8 +511,8 @@ WITH capped_stats AS (
|
|||||||
GROUP BY page
|
GROUP BY page
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
CAST(count(*) AS INTEGER) AS pages_read,
|
CAST(COUNT(*) AS INTEGER) AS pages_read,
|
||||||
CAST(sum(durations) AS INTEGER) AS total_time
|
CAST(SUM(durations) AS INTEGER) AS total_time
|
||||||
FROM capped_stats
|
FROM capped_stats
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -546,15 +546,15 @@ WITH true_progress AS (
|
|||||||
start_time AS last_read,
|
start_time AS last_read,
|
||||||
SUM(duration) AS total_time_seconds,
|
SUM(duration) AS total_time_seconds,
|
||||||
document_id,
|
document_id,
|
||||||
current_page,
|
page,
|
||||||
total_pages,
|
pages,
|
||||||
|
|
||||||
-- Determine Read Pages
|
-- Determine Read Pages
|
||||||
COUNT(DISTINCT current_page) AS read_pages,
|
COUNT(DISTINCT page) AS read_pages,
|
||||||
|
|
||||||
-- Derive Percentage of Book
|
-- Derive Percentage of Book
|
||||||
ROUND(CAST(current_page AS REAL) / CAST(total_pages AS REAL) * 100, 2) AS percentage
|
ROUND(CAST(page AS REAL) / CAST(pages AS REAL) * 100, 2) AS percentage
|
||||||
FROM activity
|
FROM rescaled_activity
|
||||||
WHERE user_id = ?1
|
WHERE user_id = ?1
|
||||||
AND document_id = ?2
|
AND document_id = ?2
|
||||||
GROUP BY document_id
|
GROUP BY document_id
|
||||||
@ -564,8 +564,8 @@ WITH true_progress AS (
|
|||||||
SELECT
|
SELECT
|
||||||
documents.id, documents.md5, documents.filepath, documents.coverfile, documents.title, documents.author, documents.series, documents.series_index, documents.lang, documents.description, documents.words, documents.gbid, documents.olid, documents.isbn10, documents.isbn13, documents.synced, documents.deleted, documents.updated_at, documents.created_at,
|
documents.id, documents.md5, documents.filepath, documents.coverfile, documents.title, documents.author, documents.series, documents.series_index, documents.lang, documents.description, documents.words, documents.gbid, documents.olid, documents.isbn10, documents.isbn13, documents.synced, documents.deleted, documents.updated_at, documents.created_at,
|
||||||
|
|
||||||
CAST(IFNULL(current_page, 0) AS INTEGER) AS current_page,
|
CAST(IFNULL(page, 0) AS INTEGER) AS page,
|
||||||
CAST(IFNULL(total_pages, 0) AS INTEGER) AS total_pages,
|
CAST(IFNULL(pages, 0) AS INTEGER) AS pages,
|
||||||
CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds,
|
CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds,
|
||||||
CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read,
|
CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read,
|
||||||
CAST(IFNULL(read_pages, 0) AS INTEGER) AS read_pages,
|
CAST(IFNULL(read_pages, 0) AS INTEGER) AS read_pages,
|
||||||
@ -618,8 +618,8 @@ type GetDocumentWithStatsRow struct {
|
|||||||
Deleted bool `json:"-"`
|
Deleted bool `json:"-"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
CurrentPage int64 `json:"current_page"`
|
Page int64 `json:"page"`
|
||||||
TotalPages int64 `json:"total_pages"`
|
Pages int64 `json:"pages"`
|
||||||
TotalTimeSeconds int64 `json:"total_time_seconds"`
|
TotalTimeSeconds int64 `json:"total_time_seconds"`
|
||||||
LastRead string `json:"last_read"`
|
LastRead string `json:"last_read"`
|
||||||
ReadPages int64 `json:"read_pages"`
|
ReadPages int64 `json:"read_pages"`
|
||||||
@ -650,8 +650,8 @@ func (q *Queries) GetDocumentWithStats(ctx context.Context, arg GetDocumentWithS
|
|||||||
&i.Deleted,
|
&i.Deleted,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.CurrentPage,
|
&i.Page,
|
||||||
&i.TotalPages,
|
&i.Pages,
|
||||||
&i.TotalTimeSeconds,
|
&i.TotalTimeSeconds,
|
||||||
&i.LastRead,
|
&i.LastRead,
|
||||||
&i.ReadPages,
|
&i.ReadPages,
|
||||||
@ -722,9 +722,9 @@ WITH true_progress AS (
|
|||||||
start_time AS last_read,
|
start_time AS last_read,
|
||||||
SUM(duration) AS total_time_seconds,
|
SUM(duration) AS total_time_seconds,
|
||||||
document_id,
|
document_id,
|
||||||
current_page,
|
page,
|
||||||
total_pages,
|
pages,
|
||||||
ROUND(CAST(current_page AS REAL) / CAST(total_pages AS REAL) * 100, 2) AS percentage
|
ROUND(CAST(page AS REAL) / CAST(pages AS REAL) * 100, 2) AS percentage
|
||||||
FROM activity
|
FROM activity
|
||||||
WHERE user_id = ?1
|
WHERE user_id = ?1
|
||||||
GROUP BY document_id
|
GROUP BY document_id
|
||||||
@ -733,8 +733,8 @@ WITH true_progress AS (
|
|||||||
SELECT
|
SELECT
|
||||||
documents.id, documents.md5, documents.filepath, documents.coverfile, documents.title, documents.author, documents.series, documents.series_index, documents.lang, documents.description, documents.words, documents.gbid, documents.olid, documents.isbn10, documents.isbn13, documents.synced, documents.deleted, documents.updated_at, documents.created_at,
|
documents.id, documents.md5, documents.filepath, documents.coverfile, documents.title, documents.author, documents.series, documents.series_index, documents.lang, documents.description, documents.words, documents.gbid, documents.olid, documents.isbn10, documents.isbn13, documents.synced, documents.deleted, documents.updated_at, documents.created_at,
|
||||||
|
|
||||||
CAST(IFNULL(current_page, 0) AS INTEGER) AS current_page,
|
CAST(IFNULL(page, 0) AS INTEGER) AS page,
|
||||||
CAST(IFNULL(total_pages, 0) AS INTEGER) AS total_pages,
|
CAST(IFNULL(pages, 0) AS INTEGER) AS pages,
|
||||||
CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds,
|
CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds,
|
||||||
CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read,
|
CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read,
|
||||||
|
|
||||||
@ -779,8 +779,8 @@ type GetDocumentsWithStatsRow struct {
|
|||||||
Deleted bool `json:"-"`
|
Deleted bool `json:"-"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
CurrentPage int64 `json:"current_page"`
|
Page int64 `json:"page"`
|
||||||
TotalPages int64 `json:"total_pages"`
|
Pages int64 `json:"pages"`
|
||||||
TotalTimeSeconds int64 `json:"total_time_seconds"`
|
TotalTimeSeconds int64 `json:"total_time_seconds"`
|
||||||
LastRead string `json:"last_read"`
|
LastRead string `json:"last_read"`
|
||||||
Percentage float64 `json:"percentage"`
|
Percentage float64 `json:"percentage"`
|
||||||
@ -815,8 +815,8 @@ func (q *Queries) GetDocumentsWithStats(ctx context.Context, arg GetDocumentsWit
|
|||||||
&i.Deleted,
|
&i.Deleted,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.CurrentPage,
|
&i.Page,
|
||||||
&i.TotalPages,
|
&i.Pages,
|
||||||
&i.TotalTimeSeconds,
|
&i.TotalTimeSeconds,
|
||||||
&i.LastRead,
|
&i.LastRead,
|
||||||
&i.Percentage,
|
&i.Percentage,
|
||||||
@ -1002,7 +1002,7 @@ partitions AS (
|
|||||||
),
|
),
|
||||||
streaks AS (
|
streaks AS (
|
||||||
SELECT
|
SELECT
|
||||||
count(*) AS streak,
|
COUNT(*) AS streak,
|
||||||
MIN(read_window) AS start_date,
|
MIN(read_window) AS start_date,
|
||||||
MAX(read_window) AS end_date,
|
MAX(read_window) AS end_date,
|
||||||
time_offset
|
time_offset
|
||||||
|
@ -110,8 +110,8 @@ CREATE TABLE IF NOT EXISTS activity (
|
|||||||
|
|
||||||
start_time DATETIME NOT NULL,
|
start_time DATETIME NOT NULL,
|
||||||
duration INTEGER NOT NULL,
|
duration INTEGER NOT NULL,
|
||||||
current_page INTEGER NOT NULL,
|
page INTEGER NOT NULL,
|
||||||
total_pages INTEGER NOT NULL,
|
pages INTEGER NOT NULL,
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users (id),
|
FOREIGN KEY (user_id) REFERENCES users (id),
|
||||||
@ -119,6 +119,13 @@ CREATE TABLE IF NOT EXISTS activity (
|
|||||||
FOREIGN KEY (device_id) REFERENCES devices (id)
|
FOREIGN KEY (device_id) REFERENCES devices (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX IF NOT EXISTS activity_start_time ON activity (start_time);
|
||||||
|
CREATE INDEX IF NOT EXISTS activity_user_id_document_id ON activity (
|
||||||
|
user_id,
|
||||||
|
document_id
|
||||||
|
);
|
||||||
|
|
||||||
-- Update Trigger
|
-- Update Trigger
|
||||||
CREATE TRIGGER IF NOT EXISTS update_documents_updated_at
|
CREATE TRIGGER IF NOT EXISTS update_documents_updated_at
|
||||||
BEFORE UPDATE ON documents BEGIN
|
BEFORE UPDATE ON documents BEGIN
|
||||||
@ -130,20 +137,21 @@ END;
|
|||||||
-- Rescaled Activity View (Adapted from KOReader)
|
-- Rescaled Activity View (Adapted from KOReader)
|
||||||
CREATE VIEW IF NOT EXISTS rescaled_activity AS
|
CREATE VIEW IF NOT EXISTS rescaled_activity AS
|
||||||
|
|
||||||
WITH RECURSIVE numbers (idx) AS (
|
WITH RECURSIVE nums (idx) AS (
|
||||||
SELECT 1 AS idx
|
SELECT 1 AS idx
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT idx + 1
|
SELECT idx + 1
|
||||||
FROM numbers
|
FROM nums
|
||||||
LIMIT 1000
|
LIMIT 1000
|
||||||
),
|
),
|
||||||
|
|
||||||
total_pages AS (
|
current_pages AS (
|
||||||
SELECT
|
SELECT
|
||||||
document_id,
|
document_id,
|
||||||
total_pages AS pages
|
user_id,
|
||||||
|
pages
|
||||||
FROM activity
|
FROM activity
|
||||||
GROUP BY document_id
|
GROUP BY document_id, user_id
|
||||||
HAVING MAX(start_time)
|
HAVING MAX(start_time)
|
||||||
ORDER BY start_time DESC
|
ORDER BY start_time DESC
|
||||||
),
|
),
|
||||||
@ -153,25 +161,50 @@ intermediate AS (
|
|||||||
activity.document_id,
|
activity.document_id,
|
||||||
activity.device_id,
|
activity.device_id,
|
||||||
activity.user_id,
|
activity.user_id,
|
||||||
activity.current_page,
|
|
||||||
activity.total_pages,
|
|
||||||
total_pages.pages,
|
|
||||||
activity.start_time,
|
activity.start_time,
|
||||||
activity.duration,
|
activity.duration,
|
||||||
numbers.idx,
|
activity.page,
|
||||||
-- Derive First Page
|
current_pages.pages,
|
||||||
((activity.current_page - 1) * total_pages.pages) / activity.total_pages
|
|
||||||
|
-- Derive first page
|
||||||
|
((activity.page - 1) * current_pages.pages) / activity.pages
|
||||||
+ 1 AS first_page,
|
+ 1 AS first_page,
|
||||||
-- Derive Last Page
|
|
||||||
|
-- Derive last page
|
||||||
MAX(
|
MAX(
|
||||||
((activity.current_page - 1) * total_pages.pages)
|
((activity.page - 1) * current_pages.pages)
|
||||||
/ activity.total_pages
|
/ activity.pages
|
||||||
+ 1,
|
+ 1,
|
||||||
(activity.current_page * total_pages.pages) / activity.total_pages
|
(activity.page * current_pages.pages) / activity.pages
|
||||||
) AS last_page
|
) AS last_page
|
||||||
|
|
||||||
FROM activity
|
FROM activity
|
||||||
INNER JOIN total_pages ON total_pages.document_id = activity.document_id
|
INNER JOIN current_pages ON
|
||||||
INNER JOIN numbers ON numbers.idx <= (last_page - first_page + 1)
|
current_pages.document_id = activity.document_id
|
||||||
|
AND current_pages.user_id = activity.user_id
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Improves performance
|
||||||
|
num_limit AS (
|
||||||
|
SELECT * FROM nums
|
||||||
|
LIMIT (SELECT MAX(last_page - first_page + 1) FROM intermediate)
|
||||||
|
),
|
||||||
|
|
||||||
|
rescaled_raw AS (
|
||||||
|
SELECT
|
||||||
|
document_id,
|
||||||
|
device_id,
|
||||||
|
user_id,
|
||||||
|
start_time,
|
||||||
|
last_page,
|
||||||
|
pages,
|
||||||
|
first_page + num_limit.idx - 1 AS page,
|
||||||
|
duration / (
|
||||||
|
last_page - first_page + 1.0
|
||||||
|
) AS duration
|
||||||
|
FROM intermediate
|
||||||
|
JOIN num_limit ON
|
||||||
|
num_limit.idx <= (last_page - first_page + 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
@ -179,6 +212,13 @@ SELECT
|
|||||||
device_id,
|
device_id,
|
||||||
user_id,
|
user_id,
|
||||||
start_time,
|
start_time,
|
||||||
first_page + idx - 1 AS page,
|
pages,
|
||||||
duration / (last_page - first_page + 1) AS duration
|
page,
|
||||||
FROM intermediate;
|
|
||||||
|
-- Round up if last page (maintains total duration)
|
||||||
|
CAST(CASE
|
||||||
|
WHEN page = last_page AND duration != CAST(duration AS INTEGER)
|
||||||
|
THEN duration + 1
|
||||||
|
ELSE duration
|
||||||
|
END AS INTEGER) AS duration
|
||||||
|
FROM rescaled_raw;
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
<p>{{ $activity.Duration }}</p>
|
<p>{{ $activity.Duration }}</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-3 border-b border-gray-200">
|
<td class="p-3 border-b border-gray-200">
|
||||||
<p>{{ $activity.CurrentPage }} / {{ $activity.TotalPages }}</p>
|
<p>{{ $activity.Page }} / {{ $activity.Pages }}</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="text-gray-400">Progress</p>
|
<p class="text-gray-400">Progress</p>
|
||||||
<p class="font-medium text-lg">
|
<p class="font-medium text-lg">
|
||||||
{{ .Data.CurrentPage }} / {{ .Data.TotalPages }} ({{ .Data.Percentage }}%)
|
{{ .Data.Page }} / {{ .Data.Pages }} ({{ .Data.Percentage }}%)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -344,7 +344,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="text-gray-500">Progress</p>
|
<p class="text-gray-500">Progress</p>
|
||||||
<p class="font-medium text-lg">
|
<p class="font-medium text-lg">
|
||||||
{{ .Data.CurrentPage }} / {{ .Data.TotalPages }} ({{ .Data.Percentage }}%)
|
{{ .Data.Page }} / {{ .Data.Pages }} ({{ .Data.Percentage }}%)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<!--
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{{range $doc := .Data }}
|
{{range $doc := .Data }}
|
||||||
<div class="w-full relative">
|
<div class="w-full relative">
|
||||||
<div class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded">
|
<div class="flex gap-4 w-full h-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded">
|
||||||
<div class="min-w-fit h-48 relative">
|
<div class="min-w-fit my-auto h-48 relative">
|
||||||
<a href="./documents/{{$doc.ID}}">
|
<a href="./documents/{{$doc.ID}}">
|
||||||
<img class="rounded object-cover h-full" src="./documents/{{$doc.ID}}/cover"></img>
|
<img class="rounded object-cover h-full" src="./documents/{{$doc.ID}}/cover"></img>
|
||||||
</a>
|
</a>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="text-gray-400">Progress</p>
|
<p class="text-gray-400">Progress</p>
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
{{ $doc.CurrentPage }} / {{ $doc.TotalPages }} ({{ $doc.Percentage }}%)
|
{{ $doc.Page }} / {{ $doc.Pages }} ({{ $doc.Percentage }}%)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4 my-4 md:grid-cols-4">
|
<div class="grid grid-cols-2 gap-4 my-4 md:grid-cols-4">
|
||||||
<div class="w-full">
|
<a href="./documents" class="w-full">
|
||||||
<div
|
<div
|
||||||
class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"
|
class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"
|
||||||
>
|
>
|
||||||
@ -111,9 +111,8 @@
|
|||||||
<p class="text-sm text-gray-400">Documents</p>
|
<p class="text-sm text-gray-400">Documents</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
|
<a href="./activity" class="w-full">
|
||||||
<div class="w-full">
|
|
||||||
<div
|
<div
|
||||||
class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"
|
class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"
|
||||||
>
|
>
|
||||||
@ -124,8 +123,7 @@
|
|||||||
<p class="text-sm text-gray-400">Activity Records</p>
|
<p class="text-sm text-gray-400">Activity Records</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div
|
<div
|
||||||
class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"
|
class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"
|
||||||
@ -138,7 +136,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div
|
<div
|
||||||
class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"
|
class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded"
|
||||||
|
Loading…
Reference in New Issue
Block a user