mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Feature/stats finishoff (#1720)
* Added ability to click on genres, tags, and people to view all items in a modal. * Made it so we can click and open a filtered search from generic list * Fixed broken epub pagination area due to a typo in a query selector * Added day breakdown, wrapping up stats
This commit is contained in:
parent
dfbc8da427
commit
02daa5ed56
@ -121,6 +121,14 @@ public class StatsController : BaseApiController
|
|||||||
return Ok(await _statService.ReadCountByDay(userId, days));
|
return Ok(await _statService.ReadCountByDay(userId, days));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("day-breakdown")]
|
||||||
|
[Authorize("RequireAdminRole")]
|
||||||
|
[ResponseCache(CacheProfileName = "Statistics")]
|
||||||
|
public ActionResult<IEnumerable<StatCount<DayOfWeek>>> GetDayBreakdown()
|
||||||
|
{
|
||||||
|
return Ok(_statService.GetDayBreakdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("user/reading-history")]
|
[HttpGet("user/reading-history")]
|
||||||
[ResponseCache(CacheProfileName = "Statistics")]
|
[ResponseCache(CacheProfileName = "Statistics")]
|
||||||
|
@ -27,6 +27,7 @@ public interface IStatisticService
|
|||||||
Task<IEnumerable<TopReadDto>> GetTopUsers(int days);
|
Task<IEnumerable<TopReadDto>> GetTopUsers(int days);
|
||||||
Task<IEnumerable<ReadHistoryEvent>> GetReadingHistory(int userId);
|
Task<IEnumerable<ReadHistoryEvent>> GetReadingHistory(int userId);
|
||||||
Task<IEnumerable<PagesReadOnADayCount<DateTime>>> ReadCountByDay(int userId = 0, int days = 0);
|
Task<IEnumerable<PagesReadOnADayCount<DateTime>>> ReadCountByDay(int userId = 0, int days = 0);
|
||||||
|
IEnumerable<StatCount<DayOfWeek>> GetDayBreakdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -385,6 +386,17 @@ public class StatisticService : IStatisticService
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<StatCount<DayOfWeek>> GetDayBreakdown()
|
||||||
|
{
|
||||||
|
return _context.AppUserProgresses
|
||||||
|
.AsSplitQuery()
|
||||||
|
.AsNoTracking()
|
||||||
|
.GroupBy(p => p.LastModified.DayOfWeek)
|
||||||
|
.OrderBy(g => g.Key)
|
||||||
|
.Select(g => new StatCount<DayOfWeek>{ Value = g.Key, Count = g.Count() })
|
||||||
|
.AsEnumerable();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<TopReadDto>> GetTopUsers(int days)
|
public async Task<IEnumerable<TopReadDto>> GetTopUsers(int days)
|
||||||
{
|
{
|
||||||
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
||||||
|
78
UI/Web/package-lock.json
generated
78
UI/Web/package-lock.json
generated
@ -7433,7 +7433,7 @@
|
|||||||
"batch": {
|
"batch": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||||
"integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY="
|
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="
|
||||||
},
|
},
|
||||||
"bcrypt-pbkdf": {
|
"bcrypt-pbkdf": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@ -7534,7 +7534,7 @@
|
|||||||
"boolbase": {
|
"boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||||
},
|
},
|
||||||
"bootstrap": {
|
"bootstrap": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
@ -7656,7 +7656,7 @@
|
|||||||
"bytes": {
|
"bytes": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="
|
||||||
},
|
},
|
||||||
"cacache": {
|
"cacache": {
|
||||||
"version": "15.3.0",
|
"version": "15.3.0",
|
||||||
@ -7829,7 +7829,7 @@
|
|||||||
"clone": {
|
"clone": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
|
||||||
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
|
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="
|
||||||
},
|
},
|
||||||
"clone-deep": {
|
"clone-deep": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@ -7957,7 +7957,7 @@
|
|||||||
"commondir": {
|
"commondir": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
|
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
|
||||||
},
|
},
|
||||||
"compressible": {
|
"compressible": {
|
||||||
"version": "2.0.18",
|
"version": "2.0.18",
|
||||||
@ -7992,7 +7992,7 @@
|
|||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -8043,7 +8043,7 @@
|
|||||||
"cookie-signature": {
|
"cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||||
},
|
},
|
||||||
"copy-anything": {
|
"copy-anything": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
@ -8718,7 +8718,7 @@
|
|||||||
"dns-equal": {
|
"dns-equal": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
|
||||||
"integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0="
|
"integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg=="
|
||||||
},
|
},
|
||||||
"dns-packet": {
|
"dns-packet": {
|
||||||
"version": "1.3.4",
|
"version": "1.3.4",
|
||||||
@ -8813,7 +8813,7 @@
|
|||||||
"ee-first": {
|
"ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||||
},
|
},
|
||||||
"electron-to-chromium": {
|
"electron-to-chromium": {
|
||||||
"version": "1.4.68",
|
"version": "1.4.68",
|
||||||
@ -8839,7 +8839,7 @@
|
|||||||
"encodeurl": {
|
"encodeurl": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
|
||||||
},
|
},
|
||||||
"encoding": {
|
"encoding": {
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
@ -9080,7 +9080,7 @@
|
|||||||
"escape-html": {
|
"escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||||
},
|
},
|
||||||
"escape-string-regexp": {
|
"escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
@ -9157,7 +9157,7 @@
|
|||||||
"etag": {
|
"etag": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
||||||
},
|
},
|
||||||
"event-target-shim": {
|
"event-target-shim": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
@ -9590,7 +9590,7 @@
|
|||||||
"fresh": {
|
"fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
|
||||||
},
|
},
|
||||||
"fs-minipass": {
|
"fs-minipass": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@ -9860,7 +9860,7 @@
|
|||||||
"hpack.js": {
|
"hpack.js": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
|
||||||
"integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
|
"integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"obuf": "^1.0.0",
|
"obuf": "^1.0.0",
|
||||||
@ -9897,7 +9897,7 @@
|
|||||||
"http-deceiver": {
|
"http-deceiver": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
|
||||||
"integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc="
|
"integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw=="
|
||||||
},
|
},
|
||||||
"http-parser-js": {
|
"http-parser-js": {
|
||||||
"version": "0.5.5",
|
"version": "0.5.5",
|
||||||
@ -10049,7 +10049,7 @@
|
|||||||
"image-size": {
|
"image-size": {
|
||||||
"version": "0.5.5",
|
"version": "0.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||||
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
|
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"immediate": {
|
"immediate": {
|
||||||
@ -10251,7 +10251,7 @@
|
|||||||
"is-extglob": {
|
"is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
|
||||||
},
|
},
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@ -10368,7 +10368,7 @@
|
|||||||
"isobject": {
|
"isobject": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
|
||||||
},
|
},
|
||||||
"isstream": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
@ -12429,7 +12429,7 @@
|
|||||||
"lodash.debounce": {
|
"lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||||
},
|
},
|
||||||
"lodash.deburr": {
|
"lodash.deburr": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
@ -12731,7 +12731,7 @@
|
|||||||
"media-typer": {
|
"media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
|
||||||
},
|
},
|
||||||
"memfs": {
|
"memfs": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.1",
|
||||||
@ -12744,7 +12744,7 @@
|
|||||||
"merge-descriptors": {
|
"merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
||||||
},
|
},
|
||||||
"merge-stream": {
|
"merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@ -12759,7 +12759,7 @@
|
|||||||
"methods": {
|
"methods": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
|
||||||
},
|
},
|
||||||
"micromatch": {
|
"micromatch": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
@ -13173,7 +13173,7 @@
|
|||||||
"normalize-range": {
|
"normalize-range": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
||||||
"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI="
|
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="
|
||||||
},
|
},
|
||||||
"npm-bundled": {
|
"npm-bundled": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
@ -13829,7 +13829,7 @@
|
|||||||
"path-to-regexp": {
|
"path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||||
},
|
},
|
||||||
"path-type": {
|
"path-type": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@ -14300,7 +14300,7 @@
|
|||||||
"promise-inflight": {
|
"promise-inflight": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
||||||
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
|
"integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="
|
||||||
},
|
},
|
||||||
"promise-retry": {
|
"promise-retry": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@ -14692,7 +14692,7 @@
|
|||||||
"prr": {
|
"prr": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
|
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"psl": {
|
"psl": {
|
||||||
@ -15144,7 +15144,7 @@
|
|||||||
"select-hose": {
|
"select-hose": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||||
"integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
|
"integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg=="
|
||||||
},
|
},
|
||||||
"selenium-webdriver": {
|
"selenium-webdriver": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
@ -15211,7 +15211,7 @@
|
|||||||
"serve-index": {
|
"serve-index": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
|
||||||
"integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
|
"integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"accepts": "~1.3.4",
|
"accepts": "~1.3.4",
|
||||||
"batch": "0.6.1",
|
"batch": "0.6.1",
|
||||||
@ -15233,7 +15233,7 @@
|
|||||||
"http-errors": {
|
"http-errors": {
|
||||||
"version": "1.6.3",
|
"version": "1.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
"integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"depd": "~1.1.2",
|
"depd": "~1.1.2",
|
||||||
"inherits": "2.0.3",
|
"inherits": "2.0.3",
|
||||||
@ -15244,12 +15244,12 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
"setprototypeof": {
|
"setprototypeof": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@ -15814,7 +15814,7 @@
|
|||||||
"text-table": {
|
"text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
|
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
|
||||||
},
|
},
|
||||||
"throat": {
|
"throat": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
@ -15881,7 +15881,7 @@
|
|||||||
"tr46": {
|
"tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
},
|
},
|
||||||
"tree-kill": {
|
"tree-kill": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
@ -16112,7 +16112,7 @@
|
|||||||
"unpipe": {
|
"unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
|
||||||
},
|
},
|
||||||
"update-browserslist-db": {
|
"update-browserslist-db": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
@ -16148,7 +16148,7 @@
|
|||||||
"utils-merge": {
|
"utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.0",
|
||||||
@ -16203,7 +16203,7 @@
|
|||||||
"vary": {
|
"vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
|
||||||
},
|
},
|
||||||
"verror": {
|
"verror": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
@ -16263,7 +16263,7 @@
|
|||||||
"wcwidth": {
|
"wcwidth": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||||
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
|
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"defaults": "^1.0.3"
|
"defaults": "^1.0.3"
|
||||||
}
|
}
|
||||||
@ -16281,7 +16281,7 @@
|
|||||||
"webidl-conversions": {
|
"webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
},
|
},
|
||||||
"webpack": {
|
"webpack": {
|
||||||
"version": "5.73.0",
|
"version": "5.73.0",
|
||||||
@ -16608,7 +16608,7 @@
|
|||||||
"whatwg-url": {
|
"whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tr46": "~0.0.3",
|
"tr46": "~0.0.3",
|
||||||
"webidl-conversions": "^3.0.0"
|
"webidl-conversions": "^3.0.0"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { UserReadStatistics } from '../statistics/_models/user-read-statistics';
|
import { UserReadStatistics } from '../statistics/_models/user-read-statistics';
|
||||||
@ -13,6 +13,16 @@ import { StatCount } from '../statistics/_models/stat-count';
|
|||||||
import { PublicationStatus } from '../_models/metadata/publication-status';
|
import { PublicationStatus } from '../_models/metadata/publication-status';
|
||||||
import { MangaFormat } from '../_models/manga-format';
|
import { MangaFormat } from '../_models/manga-format';
|
||||||
|
|
||||||
|
export enum DayOfWeek
|
||||||
|
{
|
||||||
|
Sunday = 0,
|
||||||
|
Monday = 1,
|
||||||
|
Tuesday = 2,
|
||||||
|
Wednesday = 3,
|
||||||
|
Thursday = 4,
|
||||||
|
Friday = 5,
|
||||||
|
Saturday = 6,
|
||||||
|
}
|
||||||
|
|
||||||
const publicationStatusPipe = new PublicationStatusPipe();
|
const publicationStatusPipe = new PublicationStatusPipe();
|
||||||
const mangaFormatPipe = new MangaFormatPipe();
|
const mangaFormatPipe = new MangaFormatPipe();
|
||||||
@ -85,4 +95,8 @@ export class StatisticsService {
|
|||||||
getReadCountByDay(userId: number = 0, days: number = 0) {
|
getReadCountByDay(userId: number = 0, days: number = 0) {
|
||||||
return this.httpClient.get<Array<any>>(this.baseUrl + 'stats/reading-count-by-day?userId=' + userId + '&days=' + days);
|
return this.httpClient.get<Array<any>>(this.baseUrl + 'stats/reading-count-by-day?userId=' + userId + '&days=' + days);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDayBreakdown() {
|
||||||
|
return this.httpClient.get<Array<StatCount<DayOfWeek>>>(this.baseUrl + 'stats/day-breakdown');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { FormBuilder } from '@angular/forms';
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { Library } from 'src/app/_models/library';
|
import { Library } from 'src/app/_models/library';
|
||||||
import { Member } from 'src/app/_models/auth/member';
|
import { Member } from 'src/app/_models/auth/member';
|
||||||
@ -24,7 +23,7 @@ export class LibraryAccessModalComponent implements OnInit {
|
|||||||
return this.selections != null && this.selections.hasSomeSelected();
|
return this.selections != null && this.selections.hasSomeSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public modal: NgbActiveModal, private libraryService: LibraryService, private fb: FormBuilder) { }
|
constructor(public modal: NgbActiveModal, private libraryService: LibraryService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.libraryService.getLibraries().subscribe(libs => {
|
this.libraryService.getLibraries().subscribe(libs => {
|
||||||
|
@ -256,7 +256,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* book-content class
|
* book-content class
|
||||||
*/
|
*/
|
||||||
@ViewChild('bookContentElemRef', {static: false}) bookContentElemRef!: ElementRef<HTMLDivElement>;
|
@ViewChild('readingHtml', {static: false}) bookContentElemRef!: ElementRef<HTMLDivElement>;
|
||||||
@ViewChild('readingSection', {static: false}) readingSectionElemRef!: ElementRef<HTMLDivElement>;
|
@ViewChild('readingSection', {static: false}) readingSectionElemRef!: ElementRef<HTMLDivElement>;
|
||||||
@ViewChild('stickyTop', {static: false}) stickyTopElemRef!: ElementRef<HTMLDivElement>;
|
@ViewChild('stickyTop', {static: false}) stickyTopElemRef!: ElementRef<HTMLDivElement>;
|
||||||
@ViewChild('reader', {static: true}) reader!: ElementRef;
|
@ViewChild('reader', {static: true}) reader!: ElementRef;
|
||||||
@ -382,7 +382,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
get PageHeightForPagination() {
|
get PageHeightForPagination() {
|
||||||
if (this.layoutMode === BookPageLayoutMode.Default) {
|
if (this.layoutMode === BookPageLayoutMode.Default) {
|
||||||
|
|
||||||
// if the book content is less than the height of the container, override and return height of container for pagination area
|
// if the book content is less than the height of the container, override and return height of container for pagination area
|
||||||
if (this.bookContainerElemRef?.nativeElement?.clientHeight > this.bookContentElemRef?.nativeElement?.clientHeight) {
|
if (this.bookContainerElemRef?.nativeElement?.clientHeight > this.bookContentElemRef?.nativeElement?.clientHeight) {
|
||||||
return (this.bookContainerElemRef?.nativeElement?.clientHeight || 0) + 'px';
|
return (this.bookContainerElemRef?.nativeElement?.clientHeight || 0) + 'px';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { DashboardRoutingModule } from './dashboard-routing.module';
|
import { DashboardRoutingModule } from './dashboard-routing.module';
|
||||||
|
@ -60,6 +60,10 @@ export class AddToListModalComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
@ViewChild('title') inputElem!: ElementRef<HTMLInputElement>;
|
@ViewChild('title') inputElem!: ElementRef<HTMLInputElement>;
|
||||||
|
|
||||||
|
filterList = (listItem: ReadingList) => {
|
||||||
|
return listItem.title.toLowerCase().indexOf((this.listForm.value.filterQuery || '').toLowerCase()) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
constructor(private modal: NgbActiveModal, private readingListService: ReadingListService, private toastr: ToastrService) { }
|
constructor(private modal: NgbActiveModal, private readingListService: ReadingListService, private toastr: ToastrService) { }
|
||||||
|
|
||||||
@ -128,9 +132,4 @@ export class AddToListModalComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filterList = (listItem: ReadingList) => {
|
|
||||||
return listItem.title.toLowerCase().indexOf((this.listForm.value.filterQuery || '').toLowerCase()) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||||
|
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form style="width: 100%" [formGroup]="listForm">
|
||||||
|
<div class="mb-3" *ngIf="items.length >= 5">
|
||||||
|
<label for="filter" class="form-label">Filter</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="filter" autocomplete="off" class="form-control" formControlName="filterQuery" type="text" aria-describedby="reset-input">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" id="reset-input" (click)="listForm.get('filterQuery')?.setValue('');">Clear</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-group">
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center" *ngFor="let item of items | filter: filterList; let i = index">
|
||||||
|
{{item}}
|
||||||
|
<button class="btn btn-primary" [disabled]="clicked === undefined" (click)="handleClick(item)">
|
||||||
|
<i class="fa-solid fa-arrow-up-right-from-square" aria-hidden="true"></i>
|
||||||
|
<span class="visually-hidden">Open a filtered search for {{item}}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" (click)="close()">Close</button>
|
||||||
|
</div>
|
@ -0,0 +1,7 @@
|
|||||||
|
.list-group-item.no-click {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import { Component, EventEmitter, Input } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-generic-list-modal',
|
||||||
|
templateUrl: './generic-list-modal.component.html',
|
||||||
|
styleUrls: ['./generic-list-modal.component.scss']
|
||||||
|
})
|
||||||
|
export class GenericListModalComponent {
|
||||||
|
@Input() items: Array<string> = [];
|
||||||
|
@Input() title: string = '';
|
||||||
|
@Input() clicked: ((item: string) => void) | undefined = undefined;
|
||||||
|
|
||||||
|
listForm: FormGroup = new FormGroup({
|
||||||
|
'filterQuery': new FormControl('', [])
|
||||||
|
});
|
||||||
|
|
||||||
|
filterList = (listItem: string) => {
|
||||||
|
return listItem.toLowerCase().indexOf((this.listForm.value.filterQuery || '').toLowerCase()) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private modal: NgbActiveModal) {}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(item: string) {
|
||||||
|
if (this.clicked) {
|
||||||
|
this.clicked(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<div class="row g-0 mb-2">
|
||||||
|
<h4>Day Breakdown</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ngx-charts-bar-vertical
|
||||||
|
class="dark"
|
||||||
|
[view]="view"
|
||||||
|
[results]="dayBreakdown$ | async"
|
||||||
|
[xAxis]="true"
|
||||||
|
[yAxis]="true"
|
||||||
|
[legend]="showLegend"
|
||||||
|
[showXAxisLabel]="true"
|
||||||
|
[showYAxisLabel]="true"
|
||||||
|
xAxisLabel="Day of Week"
|
||||||
|
yAxisLabel="Reading Events">
|
||||||
|
</ngx-charts-bar-vertical>
|
@ -0,0 +1,3 @@
|
|||||||
|
::ng-deep .dark .ngx-charts text {
|
||||||
|
fill: #a0aabe;
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
import { LegendPosition } from '@swimlane/ngx-charts';
|
||||||
|
import { Subject, combineLatest, map, takeUntil, Observable } from 'rxjs';
|
||||||
|
import { DayOfWeek, StatisticsService } from 'src/app/_services/statistics.service';
|
||||||
|
import { compare } from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
||||||
|
import { PieDataItem } from '../../_models/pie-data-item';
|
||||||
|
import { StatCount } from '../../_models/stat-count';
|
||||||
|
import { DayOfWeekPipe } from '../../_pipes/day-of-week.pipe';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-day-breakdown',
|
||||||
|
templateUrl: './day-breakdown.component.html',
|
||||||
|
styleUrls: ['./day-breakdown.component.scss']
|
||||||
|
})
|
||||||
|
export class DayBreakdownComponent implements OnInit {
|
||||||
|
|
||||||
|
private readonly onDestroy = new Subject<void>();
|
||||||
|
|
||||||
|
view: [number, number] = [700, 400];
|
||||||
|
gradient: boolean = true;
|
||||||
|
showLegend: boolean = true;
|
||||||
|
showLabels: boolean = true;
|
||||||
|
isDoughnut: boolean = false;
|
||||||
|
legendPosition: LegendPosition = LegendPosition.Right;
|
||||||
|
colorScheme = {
|
||||||
|
domain: ['#5AA454', '#A10A28', '#C7B42C', '#AAAAAA']
|
||||||
|
};
|
||||||
|
|
||||||
|
formControl: FormControl = new FormControl(true, []);
|
||||||
|
dayBreakdown$!: Observable<Array<PieDataItem>>;
|
||||||
|
|
||||||
|
constructor(private statService: StatisticsService) {
|
||||||
|
const dayOfWeekPipe = new DayOfWeekPipe();
|
||||||
|
this.dayBreakdown$ = this.statService.getDayBreakdown().pipe(
|
||||||
|
map((data: Array<StatCount<DayOfWeek>>) => {
|
||||||
|
return data.map(d => {
|
||||||
|
return {name: dayOfWeekPipe.transform(d.value), value: d.count};
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
takeUntil(this.onDestroy)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.onDestroy.next();
|
||||||
|
this.onDestroy.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.onDestroy.next();
|
||||||
|
this.onDestroy.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -59,7 +59,7 @@ export class ReadByDayAndComponent implements OnInit, OnDestroy {
|
|||||||
shareReplay(),
|
shareReplay(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.data$.subscribe(_ => console.log('hi'));
|
this.data$.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="row g-0 mt-4 mb-3 d-flex justify-content-around" *ngIf="stats$ | async as stats">
|
<div class="row g-0 mt-4 mb-3 d-flex justify-content-around" *ngIf="stats$ | async as stats">
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="col-auto mb-2">
|
<div class="col-auto mb-2">
|
||||||
<app-icon-and-title label="Total Series" [clickable]="false" fontClasses="fa-regular fa-calendar" title="Total Series">
|
<app-icon-and-title label="Total Series" [clickable]="false" fontClasses="fa-solid fa-book-open" title="Total Series">
|
||||||
{{stats.seriesCount | compactNumber}} Series
|
{{stats.seriesCount | compactNumber}} Series
|
||||||
</app-icon-and-title>
|
</app-icon-and-title>
|
||||||
</div>
|
</div>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<ng-container >
|
<ng-container >
|
||||||
<div class="col-auto mb-2">
|
<div class="col-auto mb-2">
|
||||||
<app-icon-and-title label="Total Volumes" [clickable]="false" fontClasses="fas fa-eye" title="Total Volumes">
|
<app-icon-and-title label="Total Volumes" [clickable]="false" fontClasses="fas fa-book" title="Total Volumes">
|
||||||
{{stats.volumeCount | compactNumber}} Volumes
|
{{stats.volumeCount | compactNumber}} Volumes
|
||||||
</app-icon-and-title>
|
</app-icon-and-title>
|
||||||
</div>
|
</div>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="col-auto mb-2">
|
<div class="col-auto mb-2">
|
||||||
<app-icon-and-title label="Total Size" [clickable]="false" fontClasses="fa-solid fa-weight-scale" title="Total Size">
|
<app-icon-and-title label="Total Size" [clickable]="false" fontClasses="fa-solid fa-scale-unbalanced" title="Total Size">
|
||||||
{{stats.totalSize | bytes}}
|
{{stats.totalSize | bytes}}
|
||||||
</app-icon-and-title>
|
</app-icon-and-title>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="col-auto mb-2">
|
<div class="col-auto mb-2">
|
||||||
<app-icon-and-title label="Total Genres" [clickable]="false" fontClasses="fa-solid fa-tags" title="Total Genres">
|
<app-icon-and-title label="Total Genres" [clickable]="true" fontClasses="fa-solid fa-tags" title="Total Genres" (click)="openGenreList();$event.stopPropagation();">
|
||||||
{{stats.totalGenres | compactNumber}} Genres
|
{{stats.totalGenres | compactNumber}} Genres
|
||||||
</app-icon-and-title>
|
</app-icon-and-title>
|
||||||
</div>
|
</div>
|
||||||
@ -56,7 +56,7 @@
|
|||||||
|
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="col-auto mb-2">
|
<div class="col-auto mb-2">
|
||||||
<app-icon-and-title label="Total Tags" [clickable]="false" fontClasses="fa-solid fa-tags" title="Total Tags">
|
<app-icon-and-title label="Total Tags" [clickable]="true" fontClasses="fa-solid fa-tags" title="Total Tags" (click)="openTagList();$event.stopPropagation();">
|
||||||
{{stats.totalTags | compactNumber}} Tags
|
{{stats.totalTags | compactNumber}} Tags
|
||||||
</app-icon-and-title>
|
</app-icon-and-title>
|
||||||
</div>
|
</div>
|
||||||
@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="col-auto mb-2">
|
<div class="col-auto mb-2">
|
||||||
<app-icon-and-title label="Total People" [clickable]="false" fontClasses="fa-solid fa-user-tag" title="Total People">
|
<app-icon-and-title label="Total People" [clickable]="true" fontClasses="fa-solid fa-user-tag" title="Total People" (click)="openPeopleList();$event.stopPropagation();">
|
||||||
{{stats.totalPeople | compactNumber}} People
|
{{stats.totalPeople | compactNumber}} People
|
||||||
</app-icon-and-title>
|
</app-icon-and-title>
|
||||||
</div>
|
</div>
|
||||||
@ -74,7 +74,7 @@
|
|||||||
|
|
||||||
<div class="grid row g-0 pt-2 pb-2 d-flex justify-content-around">
|
<div class="grid row g-0 pt-2 pb-2 d-flex justify-content-around">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<app-stat-list [data$]="releaseYears$" title="Release Years" lable="series"></app-stat-list>
|
<app-stat-list [data$]="releaseYears$" title="Release Years" label="series"></app-stat-list>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<app-stat-list [data$]="mostActiveUsers$" title="Most Active Users" label="reads"></app-stat-list>
|
<app-stat-list [data$]="mostActiveUsers$" title="Most Active Users" label="reads"></app-stat-list>
|
||||||
@ -109,4 +109,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-0 pt-4 pb-2 " style="height: 242px">
|
||||||
|
<div class="col-md-12 col-sm-12 mt-4 pt-2">
|
||||||
|
<app-day-breakdown></app-day-breakdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { map, Observable, shareReplay, Subject, takeUntil, tap } from 'rxjs';
|
import { map, Observable, shareReplay, Subject, takeUntil, tap } from 'rxjs';
|
||||||
import { DownloadService } from 'src/app/shared/_services/download.service';
|
import { FilterQueryParam } from 'src/app/shared/_services/filter-utilities.service';
|
||||||
import { Series } from 'src/app/_models/series';
|
import { Series } from 'src/app/_models/series';
|
||||||
import { User } from 'src/app/_models/user';
|
|
||||||
import { ImageService } from 'src/app/_services/image.service';
|
import { ImageService } from 'src/app/_services/image.service';
|
||||||
|
import { MetadataService } from 'src/app/_services/metadata.service';
|
||||||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||||
import { PieDataItem } from '../../_models/pie-data-item';
|
import { PieDataItem } from '../../_models/pie-data-item';
|
||||||
import { ServerStatistics } from '../../_models/server-statistics';
|
import { ServerStatistics } from '../../_models/server-statistics';
|
||||||
|
import { GenericListModalComponent } from '../_modals/generic-list-modal/generic-list-modal.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-server-stats',
|
selector: 'app-server-stats',
|
||||||
@ -25,8 +27,13 @@ export class ServerStatsComponent implements OnInit, OnDestroy {
|
|||||||
stats$!: Observable<ServerStatistics>;
|
stats$!: Observable<ServerStatistics>;
|
||||||
seriesImage: (data: PieDataItem) => string;
|
seriesImage: (data: PieDataItem) => string;
|
||||||
private readonly onDestroy = new Subject<void>();
|
private readonly onDestroy = new Subject<void>();
|
||||||
|
openSeries = (data: PieDataItem) => {
|
||||||
|
const series = data.extra as Series;
|
||||||
|
this.router.navigate(['library', series.libraryId, 'series', series.id]);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private statService: StatisticsService, private router: Router, private imageService: ImageService) {
|
constructor(private statService: StatisticsService, private router: Router, private imageService: ImageService,
|
||||||
|
private metadataService: MetadataService, private modalService: NgbModal) {
|
||||||
this.seriesImage = (data: PieDataItem) => {
|
this.seriesImage = (data: PieDataItem) => {
|
||||||
if (data.extra) return this.imageService.getSeriesCoverImage(data.extra.id);
|
if (data.extra) return this.imageService.getSeriesCoverImage(data.extra.id);
|
||||||
return '';
|
return '';
|
||||||
@ -75,9 +82,40 @@ export class ServerStatsComponent implements OnInit, OnDestroy {
|
|||||||
this.onDestroy.complete();
|
this.onDestroy.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
openSeries = (data: PieDataItem) => {
|
openGenreList() {
|
||||||
const series = data.extra as Series;
|
this.metadataService.getAllGenres().subscribe(genres => {
|
||||||
this.router.navigate(['library', series.libraryId, 'series', series.id]);
|
const ref = this.modalService.open(GenericListModalComponent, { scrollable: true });
|
||||||
|
ref.componentInstance.items = genres.map(t => t.title);
|
||||||
|
ref.componentInstance.title = 'Genres';
|
||||||
|
ref.componentInstance.clicked = (item: string) => {
|
||||||
|
const params: any = {};
|
||||||
|
params[FilterQueryParam.Genres] = item;
|
||||||
|
params[FilterQueryParam.Page] = 1;
|
||||||
|
this.router.navigate(['all-series'], {queryParams: params});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openTagList() {
|
||||||
|
this.metadataService.getAllTags().subscribe(tags => {
|
||||||
|
const ref = this.modalService.open(GenericListModalComponent, { scrollable: true });
|
||||||
|
ref.componentInstance.items = tags.map(t => t.title);
|
||||||
|
ref.componentInstance.title = 'Tags';
|
||||||
|
ref.componentInstance.clicked = (item: string) => {
|
||||||
|
const params: any = {};
|
||||||
|
params[FilterQueryParam.Tags] = item;
|
||||||
|
params[FilterQueryParam.Page] = 1;
|
||||||
|
this.router.navigate(['all-series'], {queryParams: params});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openPeopleList() {
|
||||||
|
this.metadataService.getAllPeople().subscribe(people => {
|
||||||
|
const ref = this.modalService.open(GenericListModalComponent, { scrollable: true });
|
||||||
|
ref.componentInstance.items = [...new Set(people.map(person => person.name))];
|
||||||
|
ref.componentInstance.title = 'People';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
22
UI/Web/src/app/statistics/_pipes/day-of-week.pipe.ts
Normal file
22
UI/Web/src/app/statistics/_pipes/day-of-week.pipe.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { DayOfWeek } from 'src/app/_services/statistics.service';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'dayOfWeek'
|
||||||
|
})
|
||||||
|
export class DayOfWeekPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: DayOfWeek): string {
|
||||||
|
switch(value) {
|
||||||
|
case DayOfWeek.Monday: return 'Monday';
|
||||||
|
case DayOfWeek.Tuesday: return 'Tuesday';
|
||||||
|
case DayOfWeek.Wednesday: return 'Wednesday';
|
||||||
|
case DayOfWeek.Thursday: return 'Thursday';
|
||||||
|
case DayOfWeek.Friday: return 'Friday';
|
||||||
|
case DayOfWeek.Saturday: return 'Saturday';
|
||||||
|
case DayOfWeek.Sunday: return 'Sunday';
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,7 +7,7 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { ServerStatsComponent } from './_components/server-stats/server-stats.component';
|
import { ServerStatsComponent } from './_components/server-stats/server-stats.component';
|
||||||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||||
import { StatListComponent } from './_components/stat-list/stat-list.component';
|
import { StatListComponent } from './_components/stat-list/stat-list.component';
|
||||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModalModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { PublicationStatusStatsComponent } from './_components/publication-status-stats/publication-status-stats.component';
|
import { PublicationStatusStatsComponent } from './_components/publication-status-stats/publication-status-stats.component';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MangaFormatStatsComponent } from './_components/manga-format-stats/manga-format-stats.component';
|
import { MangaFormatStatsComponent } from './_components/manga-format-stats/manga-format-stats.component';
|
||||||
@ -15,6 +15,9 @@ import { FileBreakdownStatsComponent } from './_components/file-breakdown-stats/
|
|||||||
import { PipeModule } from '../pipe/pipe.module';
|
import { PipeModule } from '../pipe/pipe.module';
|
||||||
import { TopReadersComponent } from './_components/top-readers/top-readers.component';
|
import { TopReadersComponent } from './_components/top-readers/top-readers.component';
|
||||||
import { ReadByDayAndComponent } from './_components/read-by-day-and/read-by-day-and.component';
|
import { ReadByDayAndComponent } from './_components/read-by-day-and/read-by-day-and.component';
|
||||||
|
import { GenericListModalComponent } from './_components/_modals/generic-list-modal/generic-list-modal.component';
|
||||||
|
import { DayBreakdownComponent } from './_components/day-breakdown/day-breakdown.component';
|
||||||
|
import { DayOfWeekPipe } from './_pipes/day-of-week.pipe';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -28,13 +31,17 @@ import { ReadByDayAndComponent } from './_components/read-by-day-and/read-by-day
|
|||||||
MangaFormatStatsComponent,
|
MangaFormatStatsComponent,
|
||||||
FileBreakdownStatsComponent,
|
FileBreakdownStatsComponent,
|
||||||
TopReadersComponent,
|
TopReadersComponent,
|
||||||
ReadByDayAndComponent
|
ReadByDayAndComponent,
|
||||||
|
GenericListModalComponent,
|
||||||
|
DayBreakdownComponent,
|
||||||
|
DayOfWeekPipe
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
TableModule,
|
TableModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
|
NgbModalModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
PipeModule,
|
PipeModule,
|
||||||
|
|
||||||
|
66
openapi.json
66
openapi.json
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.6.1.18"
|
"version": "0.6.1.21"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
@ -8033,6 +8033,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/Stats/day-breakdown": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Stats"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/DayOfWeekStatCount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/DayOfWeekStatCount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/DayOfWeekStatCount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/Stats/user/reading-history": {
|
"/api/Stats/user/reading-history": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -10293,6 +10331,32 @@
|
|||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"DayOfWeek": {
|
||||||
|
"enum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"DayOfWeekStatCount": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"value": {
|
||||||
|
"$ref": "#/components/schemas/DayOfWeek"
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"DeleteSeriesDto": {
|
"DeleteSeriesDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user