diff --git a/judge/views/user.py b/judge/views/user.py
index 71ac35b..927ece8 100644
--- a/judge/views/user.py
+++ b/judge/views/user.py
@@ -11,6 +11,8 @@ from django.contrib.auth.views import redirect_to_login
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.db.models import Count, Max, Min
+from django.db.models.fields import DateField
+from django.db.models.functions import Cast, ExtractYear
from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
@@ -149,6 +151,25 @@ class UserAboutPage(UserPage):
ratio = (max_ever - max_user) / (max_ever - min_ever) if max_ever != min_ever else 1.0
context['max_graph'] = max_user + ratio * delta
context['min_graph'] = min_user + ratio * delta - delta
+
+
+ submissions = (
+ self.object.submission_set
+ .annotate(date_only=Cast('date', DateField()))
+ .values('date_only').annotate(cnt=Count('id'))
+ )
+
+ context['submission_data'] = mark_safe(json.dumps({
+ date_counts['date_only'].isoformat(): date_counts['cnt'] for date_counts in submissions
+ }))
+ context['submission_metadata'] = mark_safe(json.dumps({
+ 'min_year': (
+ self.object.submission_set
+ .annotate(year_only=ExtractYear('date'))
+ .aggregate(min_year=Min('year_only'))['min_year']
+ ),
+ }))
+
return context
# follow/unfollow user
diff --git a/resources/table.scss b/resources/table.scss
index 5570ff2..dbc8c4a 100644
--- a/resources/table.scss
+++ b/resources/table.scss
@@ -1,7 +1,5 @@
@import "vars";
-$table_header_rounding: 6px;
-
.table {
border-spacing: 0;
width: 100%;
diff --git a/resources/users.scss b/resources/users.scss
index 7e64116..cd189fb 100644
--- a/resources/users.scss
+++ b/resources/users.scss
@@ -1,3 +1,5 @@
+@import "vars";
+
@media(min-width: 400px) {
#content-right {
&.users {
@@ -293,4 +295,101 @@ a.edit-profile {
}
.unfollow:hover {
background: darkred;
+}
+
+#submission-activity {
+ #submission-activity-actions {
+ text-align: center;
+ #prev-year-action, #next-year-action {
+ font-size: 1.75em;
+ }
+ #year {
+ font-size: 1.25em;
+ color: #444;
+ }
+ }
+
+ #submission-activity-display {
+ border: 1px solid $border_gray;
+ border-radius: $table_header_rounding;
+
+ .info-bar {
+ display: flex;
+ justify-content: space-between;
+
+ .info-table {
+ width: 15%;
+ min-width: 130px;
+
+ .info-table-text {
+ width: 8%;
+ }
+ }
+ }
+
+ .info-text {
+ font-size: 0.75em;
+ line-height: 1;
+ font-weight: 100;
+ color: #444;
+ }
+
+ #submission-total-count {
+ align-self: center;
+ padding-left: 8%;
+ font-size: 0.85em;
+ }
+
+ @media(max-width: 1000px) {
+ #submission-total-count {
+ padding-left: 5px;
+ }
+ }
+
+ table {
+ width: 100%;
+ padding: 5px;
+
+ th.submission-date-col {
+ width: 8%;
+ }
+
+ @media (max-width: 1000px) {
+ th.submission-date-col {
+ display: none;
+ }
+ }
+ td {
+ border-radius: 20%;
+
+ div {
+ margin-top: 100%;
+ }
+
+ &.activity-label {
+ position: relative;
+ white-space: nowrap;
+ }
+
+ &.activity-blank {
+ background-color: white;
+ }
+ &.activity-0 {
+ background-color: #ddd;
+ }
+ &.activity-1 {
+ background-color: #9be9a8;
+ }
+ &.activity-2 {
+ background-color: #40c463;
+ }
+ &.activity-3 {
+ background-color: #2f9c4c;
+ }
+ &.activity-4 {
+ background-color: #216e39;
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/resources/vars.scss b/resources/vars.scss
index bd43b26..4bcda31 100644
--- a/resources/vars.scss
+++ b/resources/vars.scss
@@ -7,5 +7,6 @@ $announcement_red: #ae0000;
$base_font_size: 14px;
$widget_border_radius: 4px;
+$table_header_rounding: 6px;
$monospace-fonts: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace;
diff --git a/templates/user/user-about.html b/templates/user/user-about.html
index 9a40c04..96740e2 100644
--- a/templates/user/user-about.html
+++ b/templates/user/user-about.html
@@ -59,6 +59,69 @@
{% endif %}
+