Compare commits

...

1046 Commits

Author SHA1 Message Date
Nicolai
ebcf9d5042 feat(i18n): add Russian translations + replace Frappe branding with IIE
Branding:
- 'Powered by Learning' -> 'Powered by IIE' (sidebar tooltip)
- 'Frappe Learning' -> 'IIE' (HelpModal title, email templates)
- frappe.io URLs -> enlightrussia.ru
- 'Install Frappe Learning' -> 'Установить приложение IIE'
- PersonaForm question translated

Translations (ru.po):
- Added 125 new Russian translations for client-facing strings
- Covers: sidebar nav, profiles, leaderboard, points, lessons,
  batches, assignments, error messages, video stats, MPGU-specific

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 19:51:43 +03:00
Nicolai
8ac7eb5a29 refactor(sidebar): remove external URLs from hardcoded items
External web pages (chatgpt-schoolchild, ai-teachers, my-child)
removed from addEnlightCustomItems() — they should be managed
via the new "More +" sidebar page manager in v2.46.0.

Only Vue-router pages remain hardcoded:
- LeaderBoard (/leaderboard)
- MyPoints (/mypoints)
- Role-based profiles (StudentProfile, SchoolchildrenProfile,
  CourseCreatorProfile, ParentProfile)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:51:01 +03:00
Nicolai
8bb7a61f56 fix(sidebar): restore custom Enlight sidebar items after v2.46.0 merge
Two bugs fixed:
1. updateSidebarLinks() was called AFTER custom add functions,
   resetting sidebarLinks.value and erasing all custom items
2. Custom items were pushed as flat objects but the template
   expects grouped structure { label, hideLabel, items: [] }

Refactored all custom functions into a single addEnlightCustomItems()
that builds a proper group and pushes it AFTER updateSidebarLinks().
Items: LeaderBoard, MyPoints, ChatGPT (role-based), MyChild, Profile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:43:34 +03:00
Nicolai
ba99a48c88 Merge v2.46.0 into develop — resolve all conflicts
- yarn.lock, components.d.ts, ru.po: accepted upstream v2.46.0
- AppSidebar.vue: kept custom sidebar links (MyPoints, LeaderBoard,
  ChatGPT, MyChild, Profile) + adopted async watch from v2.46.0
- MobileLayout.vue: merged onMounted with sidebarSettings.reload +
  custom addSideBar() for role-based mobile links
- EditProfile.vue: adopted new Dialog options structure (size only)
- Courses/Courses.vue: unified tab values to lowercase (enrolled,
  upcoming, new, created, unpublished)
- CourseDetail.vue (old): removed — logic migrated to Courses/CourseDetail.vue;
  transferred custom tag flex-wrap styling to CourseOverview.vue
- LessonForm.vue: kept Rutube video support
- App.vue: clean (no conflict markers)
- user.py: merged imports + kept custom sign_up params (phone, user_role)
- utils.py: kept render_html (Rutube), is_mentor, is_eligible_to_review;
  added type hints for get_course_progress from v2.46.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:25:58 +03:00
37d408f762 Merge branch 'develop' of https://git.nefor.net/MIDNIGHT/enlight-lms into develop 2026-03-09 11:00:49 +00:00
6d7c91ceeb Before update v2.46.0 2026-03-09 10:54:44 +00:00
Frappe PR Bot
5933a59a14 chore(release): Bumped to Version 2.46.0 2026-03-04 05:08:05 +00:00
Jannat Patel
5c3834cbbe Merge pull request #2161 from frappe/main-hotfix
chore: merge 'main-hotfix' into 'main'
2026-03-04 10:37:22 +05:30
Jannat Patel
c77fdf55b3 Merge branch 'main' into main-hotfix 2026-03-04 10:22:29 +05:30
Frappe PR Bot
2c2e8ca112 chore(release): Bumped to Version 2.45.2 2026-03-03 07:19:36 +00:00
Jannat Patel
4771ebbcfd fix: enrollment error during course progress 2026-03-03 12:48:51 +05:30
Jannat Patel
08fbcc963d Merge pull request #2158 from frappe/mergify/bp/main-hotfix/pr-2157
fix: pricing section issue in course form (backport #2157)
2026-03-02 18:32:05 +05:30
Jannat Patel
54e9396fdb fix: pricing section issue in course form
(cherry picked from commit a0d6b2b6b626040572ddefb47f9ef09beec1a801)
2026-03-02 12:54:01 +00:00
Frappe PR Bot
315ec3d655 chore(release): Bumped to Version 2.45.1 2026-02-25 12:36:10 +00:00
Jannat Patel
484c3d7402 Merge pull request #2128 from frappe/mergify/bp/main/pr-2126
fix: permission issue during quiz submission (backport #2126)
2026-02-25 18:05:27 +05:30
Jannat Patel
e7ce850691 fix: removed trailing comma at the end of permission 2026-02-25 16:45:02 +05:30
Jannat Patel
62b5715b98 Merge pull request #2127 from frappe/mergify/bp/main-hotfix/pr-2126
fix: permission issue during quiz submission (backport #2126)
2026-02-25 13:08:14 +05:30
Jannat Patel
593c70affb chore: resolved conflicts 2026-02-25 13:07:05 +05:30
Jannat Patel
3a1a7db386 chore: resolved conflicts 2026-02-25 13:06:37 +05:30
Jannat Patel
a5e948bba8 fix: permission issue during quiz submission
(cherry picked from commit af5bce9e34df945cdc368eceb2a1c3e56023fb34)

# Conflicts:
#	lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json
2026-02-25 07:21:47 +00:00
Jannat Patel
e63d83beb5 fix: permission issue during quiz submission
(cherry picked from commit af5bce9e34df945cdc368eceb2a1c3e56023fb34)
2026-02-25 07:21:40 +00:00
Jannat Patel
1ea8705552 Merge pull request #2125 from frappe/develop
chore: merge `develop` into `main-hotfix`
2026-02-25 11:24:11 +05:30
Jannat Patel
61193b71f4 Merge branch 'main-hotfix' into develop 2026-02-25 11:09:48 +05:30
Jannat Patel
2331ddfc67 Merge pull request #2123 from frappe/main-hotfix
chore: merge 'main-hotfix' into 'main'
2026-02-25 10:55:53 +05:30
Jannat Patel
afe9674a6a Merge pull request #2124 from frappe/mergify/bp/main-hotfix/pr-2121
chore: sync translations from crowdin (backport #2121)
2026-02-25 10:47:30 +05:30
Jannat Patel
5b22ef46c0 chore: resolved conflicts 2026-02-25 10:40:06 +05:30
MochaMind
8f1604e237 chore: Persian translations
(cherry picked from commit 63321fe2c8)
2026-02-25 05:08:21 +00:00
MochaMind
a9f4eb1291 chore: Spanish translations
(cherry picked from commit 68848fc642)

# Conflicts:
#	lms/locale/es.po
2026-02-25 05:08:20 +00:00
Jannat Patel
26301c26e9 Merge pull request #2121 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-25 10:37:32 +05:30
MochaMind
63321fe2c8 chore: Persian translations 2026-02-24 22:48:48 +05:30
MochaMind
68848fc642 chore: Spanish translations 2026-02-24 22:48:29 +05:30
Jannat Patel
aa7ec019bc Merge pull request #2120 from pateljannat/issues-192
fix: misc issues
2026-02-24 18:22:57 +05:30
Jannat Patel
eb33155db2 fix: enqueue progress calculation after validating enrollments 2026-02-24 18:13:02 +05:30
Jannat Patel
3088b14d83 fix: recalculate course progress when lesson is inserted or deleted 2026-02-24 17:40:58 +05:30
Jannat Patel
bf89f3ba2f fix: show system timezone in certificate request 2026-02-24 13:01:29 +05:30
Jannat Patel
2198adf902 fix: sidebar settings issue if guest access was not allowed 2026-02-24 12:36:46 +05:30
Jannat Patel
c5145c6c24 Merge pull request #2119 from pateljannat/issues-191
fix: misc issues
2026-02-24 12:17:41 +05:30
Jannat Patel
499bcd5281 chore: resolved conflicts 2026-02-24 12:05:44 +05:30
Jannat Patel
dc4bbdaa55 Merge pull request #2116 from raizasafeel/fix/codesandbox-embed
fix(lesson): render codesandbox
2026-02-24 12:02:52 +05:30
Jannat Patel
bf19ebd3a8 fix: assignment submission issue 2026-02-24 12:02:10 +05:30
Jannat Patel
fa7e59b4ad Merge pull request #2118 from frappe/mergify/bp/main-hotfix/pr-2117
chore: capture more events for analytics (backport #2117)
2026-02-23 16:59:02 +05:30
Jannat Patel
5fcd3ddabe revert: removed new batch modal changes 2026-02-23 16:45:37 +05:30
Jannat Patel
a9dd43d0ea chore: resolved conflicts 2026-02-23 16:44:04 +05:30
Jannat Patel
22e005f19c chore: capture more events for analytics
(cherry picked from commit b3c8fbd833)

# Conflicts:
#	frontend/src/pages/Batches/components/NewBatchModal.vue
#	lms/lms/doctype/lms_course_review/lms_course_review.json
2026-02-23 11:11:44 +00:00
Jannat Patel
5a6a7ff646 Merge pull request #2117 from pateljannat/issues-189
chore: capture more events for analytics
2026-02-23 16:41:23 +05:30
Jannat Patel
b3c8fbd833 chore: capture more events for analytics 2026-02-23 16:31:58 +05:30
Jannat Patel
f828c76a0f Merge pull request #2115 from pateljannat/issues-188
fix: misc issues
2026-02-23 15:13:02 +05:30
raizasafeel
2634a4e316 fix(lesson): render codesandbox properly 2026-02-23 15:12:08 +05:30
Jannat Patel
fb0517caa0 fix: show only instructor tab for admins on home page 2026-02-23 14:54:54 +05:30
Jannat Patel
90151be166 fix: updated app name in workspace and desktop 2026-02-23 12:35:13 +05:30
Jannat Patel
9b0a7f5fa5 Merge pull request #2112 from pateljannat/issues-187
fix: lesson progress issue
2026-02-23 12:03:30 +05:30
Jannat Patel
aa93375e6c Merge pull request #2110 from frappe/mergify/bp/main-hotfix/pr-2109
fix: check permission of session user during batch enrollment (backport #2109)
2026-02-23 11:49:25 +05:30
Jannat Patel
e8edf33be6 fix: lesson progress issue 2026-02-23 11:49:06 +05:30
Jannat Patel
619f02a74b fix: check permission of session user during batch enrollment
(cherry picked from commit c1260edb00)
2026-02-23 06:08:16 +00:00
Jannat Patel
b77c4867e1 Merge pull request #2109 from pateljannat/issues-186
fix: check permission of session user during batch enrollment
2026-02-23 11:36:49 +05:30
Jannat Patel
c1260edb00 fix: check permission of session user during batch enrollment 2026-02-23 11:28:58 +05:30
Jannat Patel
41d5ef5fd5 Merge pull request #2108 from pateljannat/issues-185
fix: misc permission issues
2026-02-23 11:22:52 +05:30
Jannat Patel
14937fd4fc fix: check ptype for permission if not admin 2026-02-23 11:06:34 +05:30
Jannat Patel
58826fe30f fix: removed badge page reference from the router 2026-02-23 10:41:13 +05:30
Jannat Patel
0da9eec0af Merge pull request #2105 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-23 10:29:32 +05:30
Jannat Patel
bb47fd5ba9 fix: misc permission issues 2026-02-23 10:29:13 +05:30
MochaMind
db49cb2d64 chore: Thai translations 2026-02-21 22:13:22 +05:30
MochaMind
58732148e2 chore: Vietnamese translations 2026-02-21 22:13:18 +05:30
MochaMind
08eb7ef17b chore: Dutch translations 2026-02-21 22:13:10 +05:30
MochaMind
8bda7edb7b chore: Spanish translations 2026-02-21 22:13:03 +05:30
Jannat Patel
49d596216d fix: zoom account link issue 2026-02-21 12:58:19 +05:30
Jannat Patel
faa9c94970 fix: zoom account link issue 2026-02-21 12:28:55 +05:30
Jannat Patel
c596d1e215 fix: misc permission issues 2026-02-21 12:25:47 +05:30
Jannat Patel
235958e432 Merge pull request #2103 from raizasafeel/fix/lesson-body-filter
fix(security): remove ignore_xss_filter to enable HTML sanitization
2026-02-20 14:20:07 +05:30
raizasafeel
7f85dbccec fix: added timestamps for bench migrate 2026-02-20 13:52:13 +05:30
Jannat Patel
5b69ddf9b5 Merge pull request #2099 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-20 13:39:16 +05:30
Jannat Patel
dfb7152aa3 fix: video watch time permission and other issues 2026-02-20 13:08:27 +05:30
Raizaaa
2a311bfb6f Merge pull request #2104 from raizasafeel/fix/lms-enrollment
fix: duplicate enrollment validation on update
2026-02-20 12:21:45 +05:30
raizasafeel
0e90627144 fix: duplicate enrollment validation on update 2026-02-20 12:09:08 +05:30
Raizaaa
aac1692058 Merge branch 'frappe:develop' into fix/lesson-body-filter 2026-02-20 11:58:57 +05:30
raizasafeel
d58d362c72 fix(security): remove ignore_xss_filter to enable HTML sanitization 2026-02-20 11:54:17 +05:30
MochaMind
e7f2386d14 chore: Thai translations 2026-02-19 21:23:50 +05:30
MochaMind
79a50d2454 chore: Portuguese, Brazilian translations 2026-02-19 21:23:45 +05:30
MochaMind
936f82c477 chore: Spanish translations 2026-02-19 21:23:24 +05:30
Jannat Patel
133037698c fix: remove read permission on lms settings for lms student 2026-02-19 16:30:13 +05:30
Jannat Patel
07c58251a1 fix: lms certificate request will allow students to read only if they are owner 2026-02-19 16:27:35 +05:30
Jannat Patel
c88d36df1e fix: permission issues on badges 2026-02-19 15:59:03 +05:30
Jannat Patel
08373dc2ab fix: refactored job form and permissions 2026-02-19 15:58:44 +05:30
Jannat Patel
44ca59c64a fix: return profile details only if the profile is of an LMS user 2026-02-19 12:51:30 +05:30
Jannat Patel
c961923fa0 fix: verify enrollment and admin access before returing batch assessment data 2026-02-19 12:43:50 +05:30
Jannat Patel
72cee75474 fix: only allow lms roles to be modified by moderator 2026-02-19 12:39:55 +05:30
Jannat Patel
cb3af6fa63 fix: sanitised badge assignment api 2026-02-19 12:24:47 +05:30
Jannat Patel
0ff14a959d Merge pull request #2096 from pateljannat/issues-184
fix: course form issues
2026-02-19 11:58:09 +05:30
Jannat Patel
35adf49015 fix: course form overflow issue 2026-02-19 11:47:17 +05:30
Jannat Patel
e5f0d55ff0 fix: course form permission issue 2026-02-19 11:46:56 +05:30
Jannat Patel
ba395fe982 Merge pull request #2081 from pateljannat/batch-dashboard-update
Batch dashboard update
2026-02-18 15:45:01 +05:30
Jannat Patel
8ab6776fa9 fix: redirect from batch/details to batch 2026-02-18 15:36:55 +05:30
Jannat Patel
61d13aeb12 Merge pull request #2094 from frappe/develop
chore: merge `develop` into `main-hotfix`
2026-02-18 12:11:10 +05:30
Jannat Patel
24bfe69985 chore: resolved conflicts 2026-02-18 11:16:00 +05:30
Jannat Patel
7b2a4fe24a Merge pull request #2092 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-18 11:10:40 +05:30
Jannat Patel
6f86b822bf fix: mobile view for batch dashboard 2026-02-18 10:58:49 +05:30
MochaMind
c3b2907ebf chore: Portuguese, Brazilian translations 2026-02-17 21:06:06 +05:30
MochaMind
48c5b82c73 chore: Spanish translations 2026-02-17 21:06:04 +05:30
Jannat Patel
af273a9a1c refactor: batch student progress 2026-02-17 19:38:19 +05:30
Jannat Patel
3b80ccd8db Merge pull request #2083 from raizasafeel/fix/dark-mode
fix: dark mode ui
2026-02-17 17:41:40 +05:30
Jannat Patel
6484d551d1 Merge pull request #2091 from frappe/mergify/bp/main-hotfix/pr-2087
chore: project URLs (backport #2087)
2026-02-17 17:41:05 +05:30
Ankush Menat
719d7b5e88 chore: project URLs (#2087)
(cherry picked from commit 3cd9d89f0b)
2026-02-17 11:38:09 +00:00
Ankush Menat
3cd9d89f0b chore: project URLs (#2087) 2026-02-17 15:54:23 +05:30
raizasafeel
6e44da1993 test: update batch creation test to match Badge component 2026-02-17 15:13:13 +05:30
Jannat Patel
44b7a210ce chore: resolved conflicts 2026-02-17 15:04:31 +05:30
Jannat Patel
0e382f77ef Merge pull request #2048 from raizasafeel/fix/video-embedding
fix(lesson): vimeo player rendered for private and unsanitized content
2026-02-17 14:57:00 +05:30
raizasafeel
7a1a247113 Merge upstream/develop into fix/dark-mode 2026-02-17 14:28:46 +05:30
raizasafeel
2c6ab3c331 Merge remote-tracking branch 'upstream/develop' into fix/video-embedding 2026-02-17 14:21:22 +05:30
Jannat Patel
79dba165c5 Merge pull request #2084 from pateljannat/issues-183
fix: misc issues
2026-02-17 13:57:09 +05:30
Jannat Patel
d83f3464cd fix: permission issue when cancelling evaluation 2026-02-17 13:35:15 +05:30
Jannat Patel
e2ef8f732d test: fixed category input selection in course creation test 2026-02-17 12:09:29 +05:30
Jannat Patel
919904a7f1 refactor: autocomplete component 2026-02-17 12:05:40 +05:30
raizasafeel
8453226f29 fix: add vimeo emded URL to extract hash properly 2026-02-17 11:38:26 +05:30
Jannat Patel
03759ca3c3 fix: spacing issue on course overview page 2026-02-16 19:53:33 +05:30
Jannat Patel
c1608f8cc4 refactor: Link component 2026-02-16 19:48:58 +05:30
Jannat Patel
73b20653f0 chore: updated release process with main-hotfix 2026-02-16 18:33:45 +05:30
Jannat Patel
7e683f8b44 fix: permission checks for api 2026-02-16 18:20:02 +05:30
Jannat Patel
eba1815390 fix: permission issues when adding new members 2026-02-16 18:05:53 +05:30
raizasafeel
7564f0418b fix: dark mode text and divider colors in course dashboard 2026-02-16 17:03:35 +05:30
raizasafeel
7e9cca2782 refactor: replace deprecated input with formcontrol in announcement modal 2026-02-16 17:00:18 +05:30
raizasafeel
dbc7e7d6d4 fix: dark mode styling for controls and modals 2026-02-16 16:58:32 +05:30
raizasafeel
ae25cfae6e fix: text editor border color in dark mode across forms and modals 2026-02-16 16:58:32 +05:30
raizasafeel
970635430b fix(settings): dark mode divider and text colors 2026-02-16 16:58:32 +05:30
raizasafeel
fe869a5988 fix(batch): use frappe-ui badge for dark mode compatibility 2026-02-16 16:58:32 +05:30
raizasafeel
7ea8040790 fix(sidebar): configuration popover and dark mode fixes 2026-02-16 16:58:32 +05:30
Jannat Patel
9f6f717585 fix: discussions reply endpoint permission check 2026-02-16 12:18:41 +05:30
Jannat Patel
641d729bd1 refactor: student batch dashboard 2026-02-16 12:17:13 +05:30
Jannat Patel
ee73d8db86 Merge pull request #2077 from pateljannat/issues-182
refactor: MultiSelect field
2026-02-16 09:10:46 +05:30
Jannat Patel
c7b5f9a04d Merge pull request #2078 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-16 09:10:28 +05:30
MochaMind
fa4c3a8ad7 chore: Persian translations 2026-02-15 19:54:51 +05:30
MochaMind
71318bff04 chore: Portuguese translations 2026-02-15 19:54:42 +05:30
MochaMind
186ddc93c8 chore: Portuguese, Brazilian translations 2026-02-14 19:58:13 +05:30
Jannat Patel
2f1d9a8690 refactor: MultiSelect field 2026-02-13 13:04:02 +05:30
Jannat Patel
5fc7c52bfe Merge pull request #2073 from UmakanthKaspa/fix/categories-dark-mode-text
fix: add text color for category names in dark mode
2026-02-13 10:18:29 +05:30
Jannat Patel
d0da6e7401 Merge pull request #2076 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-13 10:17:53 +05:30
UmakanthKaspa
a437c197a5 fix: format code with pre-commit 2026-02-12 21:41:19 +05:30
Jannat Patel
944fd6d013 refactor: new batch quick entry modal 2026-02-12 19:52:46 +05:30
MochaMind
80a9f2abe2 chore: Spanish translations 2026-02-12 19:28:48 +05:30
Jannat Patel
c0298f0a70 Merge branch 'develop' of https://github.com/frappe/lms into batch-dashboard-update 2026-02-12 11:09:19 +05:30
Jannat Patel
c30b21e5ae Merge pull request #2075 from pateljannat/issues-181
fix: issues on home page
2026-02-12 11:07:12 +05:30
Jannat Patel
3e3afa63c2 fix: issues on home page 2026-02-12 10:44:01 +05:30
Jannat Patel
7ef8aad2c8 fix: dirty state of batch form 2026-02-12 10:33:21 +05:30
Jannat Patel
c00cb100a9 Merge pull request #2072 from UmakanthKaspa/fix/job-details-missing-type-hint
fix: add missing type annotation to get_job_details
2026-02-11 21:41:28 +05:30
Jannat Patel
f824ac3c28 Merge pull request #2070 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-11 21:40:28 +05:30
UmakanthKaspa
2dea096fa0 fix: use semantic bg class for MultiSelect "Create New" button 2026-02-11 21:39:30 +05:30
UmakanthKaspa
f1853a3c97 fix: add text color for category names in dark mode 2026-02-11 21:13:48 +05:30
UmakanthKaspa
4995f8e3fd fix: add missing type annotation to get_job_details 2026-02-11 20:38:08 +05:30
Jannat Patel
f59eecd34e fix: circular dependency issues 2026-02-11 19:24:50 +05:30
MochaMind
560ac8d5c4 chore: Burmese translations 2026-02-11 18:52:08 +05:30
Jannat Patel
eab929da47 refactor: batch form 2026-02-11 18:42:42 +05:30
Jannat Patel
d370ca796f Merge pull request #2067 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-10 19:53:48 +05:30
Jannat Patel
e9f0b12550 feat: batch page new look 2026-02-10 19:53:26 +05:30
MochaMind
a4035168be chore: Italian translations 2026-02-10 17:32:47 +05:30
Jannat Patel
70872857d1 fix: show console error if course creation fails 2026-02-10 10:55:59 +05:30
Jannat Patel
332334b556 Merge pull request #2066 from UmakanthKaspa/fix/course-creation-error-toast
fix: show error toast when course creation fails
2026-02-10 10:48:34 +05:30
UmakanthKaspa
1d91baa9c5 fix: show error toast when course creation fails 2026-02-09 13:41:12 +00:00
Jannat Patel
1e8040ef7b Merge pull request #2036 from Aradhya-Tripathi/setup-fix
fix(setup): Add frappe dependency and build utils
2026-02-09 17:21:05 +05:30
Jannat Patel
ad6e0a3b80 Merge pull request #2064 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-09 15:32:59 +05:30
Jannat Patel
8f6810923d fix: only published courses should be added to batch 2026-02-09 14:42:32 +05:30
Jannat Patel
990db83ab3 chore: Italian translations 2026-02-09 13:02:11 +05:30
Jannat Patel
01f08ba449 chore: fixed typing for non mandatory fields of certificate 2026-02-09 12:44:54 +05:30
Jannat Patel
7a3701cc10 Merge pull request #2063 from pateljannat/issues-180
chore: added type hints to all whitelisted functions
2026-02-06 17:29:57 +05:30
Jannat Patel
f021ddd84c chore: fixed type annotations for doc_event methods 2026-02-06 17:08:49 +05:30
Jannat Patel
0e3157c57e chore: fixed type check for batch flows 2026-02-06 16:53:25 +05:30
Raizaaa
22eb8b9f3f Merge branch 'frappe:develop' into fix/video-embedding 2026-02-06 16:51:52 +05:30
Jannat Patel
9609398643 chore: fixed type check for batch flows 2026-02-06 16:18:37 +05:30
Jannat Patel
cd0d4c413d chore: added type hints to all whitelisted functions 2026-02-06 16:01:49 +05:30
Jannat Patel
1bbdff9aaf Merge pull request #2060 from raizasafeel/translations-fix
fix(profile): translations in tab are now rendered
2026-02-06 15:00:20 +05:30
Jannat Patel
8754d0498c Merge pull request #2062 from pateljannat/issues-179
fix: improved the default print format
2026-02-06 14:59:08 +05:30
raizasafeel
395ac52740 fix(assessments): translation render in list heading 2026-02-06 14:46:02 +05:30
Jannat Patel
29cdbe5b8b Merge pull request #2061 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-06 14:38:40 +05:30
Raizaaa
0677c21dc7 Merge branch 'frappe:develop' into translations-fix 2026-02-06 14:37:22 +05:30
Jannat Patel
1a58e2669f fix: improved the default print format 2026-02-06 14:33:24 +05:30
raizasafeel
3fa27024f9 fix(breadcrumbs): translations are now rendered 2026-02-06 12:53:17 +05:30
Jannat Patel
04c4069c75 chore: Serbian (Latin) translations 2026-02-06 12:08:55 +05:30
Jannat Patel
dd77b01ff1 chore: Bosnian translations 2026-02-06 12:08:52 +05:30
Jannat Patel
085614bca6 chore: Croatian translations 2026-02-06 12:08:50 +05:30
Jannat Patel
ef2606c41a chore: Swedish translations 2026-02-06 12:08:41 +05:30
Jannat Patel
1d95361587 chore: Serbian (Cyrillic) translations 2026-02-06 12:08:40 +05:30
raizasafeel
6ead16edf0 fix(profile): translations in tab are now rendered 2026-02-06 10:10:29 +05:30
Jannat Patel
31d21bf689 Merge pull request #2056 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-02-05 13:07:15 +05:30
Jannat Patel
c5ee140551 Merge pull request #2054 from pateljannat/issues-178
feat: student progress in course dashboard
2026-02-05 12:40:26 +05:30
Jannat Patel
8e97b2f5bb chore: codecov config 2026-02-05 12:33:38 +05:30
Jannat Patel
19171a8019 chore: removed files that are no longer used 2026-02-05 12:24:56 +05:30
Jannat Patel
3f49cf0c9c chore: added type hints to course assessment progress functions 2026-02-05 12:24:36 +05:30
Jannat Patel
8f9cc536e2 chore: Esperanto translations 2026-02-05 12:13:55 +05:30
Jannat Patel
573bc74a41 chore: Serbian (Latin) translations 2026-02-05 12:13:53 +05:30
Jannat Patel
bb2552b30c chore: Norwegian Bokmal translations 2026-02-05 12:13:52 +05:30
Jannat Patel
20ac312f57 chore: Bosnian translations 2026-02-05 12:13:50 +05:30
Jannat Patel
f58842438b chore: Burmese translations 2026-02-05 12:13:49 +05:30
Jannat Patel
a34f99ed49 chore: Croatian translations 2026-02-05 12:13:48 +05:30
Jannat Patel
44b7243f75 chore: Thai translations 2026-02-05 12:13:46 +05:30
Jannat Patel
d2f7d80114 chore: Persian translations 2026-02-05 12:13:45 +05:30
Jannat Patel
192b246381 chore: Indonesian translations 2026-02-05 12:13:43 +05:30
Jannat Patel
17d9a3991e chore: Portuguese, Brazilian translations 2026-02-05 12:13:42 +05:30
Jannat Patel
3407a02046 chore: Vietnamese translations 2026-02-05 12:13:41 +05:30
Jannat Patel
be3546e79c chore: Chinese Simplified translations 2026-02-05 12:13:39 +05:30
Jannat Patel
556067de7a chore: Turkish translations 2026-02-05 12:13:38 +05:30
Jannat Patel
4ce08af516 chore: Swedish translations 2026-02-05 12:13:36 +05:30
Jannat Patel
8a4477a01f chore: Serbian (Cyrillic) translations 2026-02-05 12:13:35 +05:30
Jannat Patel
21d868a355 chore: Slovenian translations 2026-02-05 12:13:33 +05:30
Jannat Patel
68a2cc1003 chore: Russian translations 2026-02-05 12:13:32 +05:30
Jannat Patel
5a288836e0 chore: Portuguese translations 2026-02-05 12:13:30 +05:30
Jannat Patel
4eab5e2867 chore: Polish translations 2026-02-05 12:13:29 +05:30
Jannat Patel
6082093fb6 chore: Dutch translations 2026-02-05 12:13:27 +05:30
Jannat Patel
66c26c2a2c chore: Italian translations 2026-02-05 12:13:26 +05:30
Jannat Patel
f7eaf3faaa chore: Hungarian translations 2026-02-05 12:13:24 +05:30
Jannat Patel
82a43b4f24 chore: German translations 2026-02-05 12:13:23 +05:30
Jannat Patel
5bd33a1536 chore: Danish translations 2026-02-05 12:13:21 +05:30
Jannat Patel
a063c0735c chore: Czech translations 2026-02-05 12:13:20 +05:30
Jannat Patel
732db8290d chore: Arabic translations 2026-02-05 12:13:19 +05:30
Jannat Patel
2b1d57f2bc chore: Spanish translations 2026-02-05 12:13:17 +05:30
Jannat Patel
c8c051c1de chore: French translations 2026-02-05 12:13:16 +05:30
Jannat Patel
13139bc2de test: course assessment progress 2026-02-05 12:10:24 +05:30
Jannat Patel
9814abf55f fix: dark mode issues of course dashboard 2026-02-04 16:04:28 +05:30
Jannat Patel
249ecb8c4c Merge pull request #2053 from frappe/develop
chore: merge 'develop' into 'main'
2026-02-04 15:24:23 +05:30
Jannat Patel
582540e7f0 feat: student progress on course dashboard 2026-02-03 21:31:31 +05:30
raizasafeel
2f3fa7c295 fix: added regex anchors to embed urls 2026-02-03 16:22:50 +05:30
raizasafeel
3b49aac1b3 refactor: removed unused functions 2026-02-03 16:14:58 +05:30
raizasafeel
dc25b408e6 fix(vimeo): video player is rendered for private videos and unsanitized vimeo links 2026-02-03 14:51:17 +05:30
raizasafeel
c8d9b97ab7 refactor: reuse function 'escapehtml' from utils 2026-02-03 14:01:48 +05:30
Jannat Patel
754d3cf2ca fix: import permissions 2026-02-03 10:56:22 +05:30
Jannat Patel
e4268d0437 fix: allow attaching payment information for batch enrollment 2026-02-03 10:49:48 +05:30
Jannat Patel
8febe21aa8 fix: dont allow contact us email sending to guest users 2026-02-03 10:48:59 +05:30
Jannat Patel
5384b26610 Merge pull request #2042 from raizasafeel/fix/chapter-deletion
test(chapter): added reindexing test on chapter deletion
2026-02-02 19:20:55 +05:30
Jannat Patel
2a4650e5ed Merge pull request #2044 from raizasafeel/fix/quiz-order
fix(batches): order assessments by their index
2026-02-02 19:12:50 +05:30
Jannat Patel
737993c543 Merge pull request #2043 from raizasafeel/fix/multilanguage-filters
fix: filter tabs not working in non-english languages
2026-02-02 18:34:50 +05:30
raizasafeel
58b49e3608 fix(batches): order assessments by their index 2026-02-02 18:28:06 +05:30
Raizaaa
27553464d6 Merge branch 'frappe:develop' into fix/chapter-deletion 2026-02-02 16:28:06 +05:30
raizasafeel
be76268c70 fix(batches): use constant value for filter instead of translated label 2026-02-02 16:22:37 +05:30
raizasafeel
df2f2e6603 fix(courses): use constant value for filter instead of translated label 2026-02-02 16:21:31 +05:30
Jannat Patel
fb1e1ec2e4 Merge pull request #2040 from pateljannat/issues-177
fix: permissions cleanup
2026-02-02 15:55:22 +05:30
Jannat Patel
ac81d1817b fix: batch dashboard chart visibility 2026-02-02 15:43:36 +05:30
Jannat Patel
da33e1d3bd test: course and batch details 2026-02-02 15:11:16 +05:30
Jannat Patel
24a511f48e fix: do nor return details of unpublished courses and batches via api 2026-02-02 14:44:13 +05:30
Jannat Patel
14e669435f fix: permissions cleanup 2026-02-02 13:14:16 +05:30
Jannat Patel
0407f01016 Merge pull request #2038 from vishwajeet-13/fix/issue-date
fix: issue date not coming from backend
2026-02-02 11:06:53 +05:30
vishwajeet-13
2a63f781ac fix: issue date not coming from backend 2026-01-30 17:37:07 +05:30
Aradhya-Tripathi
3e7555264f fix(setup): Add frappe dependency and build utils 2026-01-30 15:12:23 +05:30
Jannat Patel
ab155ae609 Merge pull request #2017 from Anexus5919/fix-instructor-notes-settings-menu
fix: instructor notes block settings menu overlapped by sidebar
2026-01-30 13:02:07 +05:30
Jannat Patel
2c60521894 Merge pull request #2018 from Anexus5919/fix-lesson-form-stale-state
fix: reindex lessons after deletion to prevent stale form state
2026-01-30 13:01:32 +05:30
Jannat Patel
4fd3e2549a fix: batch access conditions 2026-01-30 13:00:49 +05:30
Jannat Patel
6d3f7ef3c1 chore: resolved conflicts 2026-01-30 12:53:57 +05:30
Jannat Patel
10a9a5230e Merge pull request #2001 from frappe/feat/config-fe-base-path
feat: configurable frontend base path
2026-01-30 12:40:43 +05:30
Jannat Patel
bfda88dfd2 chore: resolved conflicts 2026-01-30 12:30:53 +05:30
Jannat Patel
4be34848a4 fix: allow mark as read notification endpoints in auth 2026-01-30 12:26:29 +05:30
Jannat Patel
c51eae5665 fix: show course progress summary if average progress is greater than 0 2026-01-30 11:19:08 +05:30
Jannat Patel
6c06a86af4 fix: reset the value of multiple of only one correct answer in quiz question 2026-01-30 11:08:53 +05:30
Jannat Patel
8be53d9050 Merge pull request #2035 from pateljannat/issues-176
fix: api permissions
2026-01-29 21:54:32 +05:30
Jannat Patel
015f903d68 fix: api permissions 2026-01-29 21:42:20 +05:30
Jannat Patel
dc06ad6b22 fix: removed tags methods 2026-01-29 20:40:35 +05:30
Jannat Patel
5689fbb455 fix: api permissions 2026-01-29 20:32:02 +05:30
Jannat Patel
62631daafb chore: resolved conflicts 2026-01-29 19:55:13 +05:30
Jannat Patel
a9a322c9af Merge pull request #2015 from pateljannat/enroll-students-in-course-from-ui
feat: course admin dashboard
2026-01-29 19:53:27 +05:30
Jannat Patel
933bc58264 fix: api permissions 2026-01-29 19:52:56 +05:30
Jannat Patel
8896a79c09 Merge branch 'develop' into enroll-students-in-course-from-ui 2026-01-29 16:42:26 +05:30
Jannat Patel
3170c066dc Merge pull request #2012 from raizasafeel/fix/chapter-deletion
fix(chapter): recalculate indices on deletion to prevent duplicate index errors
2026-01-29 16:37:44 +05:30
Jannat Patel
7360780022 Merge pull request #1998 from raizasafeel/fix/course-deletion
fix(course): resolve deletion failure for enrolled courses
2026-01-29 16:35:59 +05:30
Jannat Patel
7fe398fc66 test: fixed course ui tests based on new flow 2026-01-29 16:17:34 +05:30
Jannat Patel
5970540a99 feat: lesson completion rate in course dashboard 2026-01-29 15:24:20 +05:30
raizasafeel
a882432702 test: added test for chapter deletion and renumbering 2026-01-29 13:32:37 +05:30
raizasafeel
f8b6dfc981 Merge branch 'develop' into fix/chapter-deletion 2026-01-29 13:27:29 +05:30
Jannat Patel
486e2b4a37 Merge branch 'develop' of https://github.com/frappe/lms into enroll-students-in-course-from-ui 2026-01-29 12:35:58 +05:30
Jannat Patel
d8891b8a7d fix: brand settings 2026-01-29 12:35:44 +05:30
Jannat Patel
98c5318b66 feat: new course form slow 2026-01-29 10:50:28 +05:30
Jannat Patel
a353635bb9 Merge pull request #2028 from raizasafeel/fix/restrict-batch-student-details
fix(batch): restrict student list in batch details API to authorised roles
2026-01-28 21:20:33 +05:30
Jannat Patel
9eeb948f04 Merge pull request #2029 from raizasafeel/fix/quiz-questions-empty-on-back-navigation
fix(quiz): questions list empty on back navigation from test quiz
2026-01-28 21:19:20 +05:30
raizasafeel
383850aeb8 fix(quiz): questions list empty on back navigation from test quiz 2026-01-28 16:30:36 +05:30
raizasafeel
fb0499acc4 fix(Batch): restrict student list in batch details API 2026-01-28 14:41:10 +05:30
Jannat Patel
ff978f5c29 Merge pull request #2022 from raizasafeel/fix/multiselect-last-option-visibility
fix: prevent 'Create New' button from overlapping last dropdown element
2026-01-28 11:33:36 +05:30
Jannat Patel
e8768d5687 Merge pull request #2026 from frappe/develop
chore: merge 'develop' into 'main'
2026-01-28 11:32:28 +05:30
Jannat Patel
fa1b583968 Merge branch 'main' into develop 2026-01-28 11:15:28 +05:30
Jannat Patel
b50d584a5b feat: new course modal 2026-01-28 10:57:58 +05:30
Jannat Patel
5271709094 Merge pull request #2016 from frappe/pot_develop_2026-01-23
chore: update POT file
2026-01-27 14:37:20 +05:30
raizasafeel
b66b603af2 fix(MultiSelect): prevent 'Create New' button from overlapping dropdown option 2026-01-27 14:12:43 +05:30
Adarsh Singh
53c77f9070 fix: reindex lessons after deletion to prevent stale form state
When a lesson was deleted, the remaining lessons' idx values were not resequenced. This caused Add Lesson to navigate to an existing lesson instead of creating a new one, showing stale data in the form.
2026-01-24 17:04:38 +05:30
Jannat Patel
49ed082831 feat: allow moderators to create new payment from settings 2026-01-24 11:31:37 +05:30
Adarsh Singh
05d21cf817 fix: instructor notes block settings menu overlapped by sidebar
PR #1987 fixed the "+" (Add block) menu positioning but missed the
block settings menu (6-dot "Click to tune" icon) which uses a different
parent container (.ce-toolbar__actions instead of .ce-toolbox).

This adds the missing selector to fix both menus.

Related: #1990
2026-01-24 00:25:21 +05:30
frappe-pr-bot
39473f0037 chore: update POT file 2026-01-23 16:05:21 +00:00
Jannat Patel
0e8b232ef1 feat: course admin dashboard 2026-01-23 18:26:09 +05:30
raizasafeel
aeb2724e82 test: use BaseTestUtils in test_auth for consistent test setup 2026-01-23 12:26:03 +05:30
Jannat Patel
b429fb2e47 Merge pull request #1997 from raizasafeel/fix/markdown-paste-render
fix(lesson): ensure markdown text is displayed when pasted
2026-01-23 10:25:25 +05:30
Jannat Patel
14a1c2ac07 Merge pull request #2014 from raizasafeel/fix/lms-import-get-frappe-version
refactor(lms): update get_frappe_version import
2026-01-22 18:25:30 +05:30
raizasafeel
36b0594960 refactor(lms): update get_frappe_version import 2026-01-22 17:42:06 +05:30
raizasafeel
cc3cb7ac8d Merge remote-tracking branch 'upstream/develop' into fix/course-deletion 2026-01-22 16:33:07 +05:30
raizasafeel
8bf896f766 refactor(tests): use BaseTestUtils inheritance for tests 2026-01-22 16:05:48 +05:30
raizasafeel
9e61982dcb refactor(tests): extract shared test utilities into BaseTestUtils 2026-01-22 16:03:01 +05:30
Jannat Patel
59e40d5a83 Merge branch 'develop' into fix/chapter-deletion 2026-01-22 14:00:12 +05:30
Jannat Patel
412bdeb085 Merge pull request #2007 from pateljannat/issues-174
fix: misc issues
2026-01-22 13:59:54 +05:30
Jannat Patel
4ef0bd30d9 test: fixed authentication tests 2026-01-22 13:16:12 +05:30
Jannat Patel
87e1fa8d6c Merge pull request #2013 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-22 12:16:01 +05:30
Jannat Patel
984ea27d59 chore: Serbian (Latin) translations 2026-01-22 01:46:42 +05:30
Jannat Patel
9d2c7a7ce0 chore: Norwegian Bokmal translations 2026-01-22 01:46:41 +05:30
Jannat Patel
3b2768cdcc chore: Bosnian translations 2026-01-22 01:46:39 +05:30
Jannat Patel
9ef5f01a80 chore: Croatian translations 2026-01-22 01:46:37 +05:30
Jannat Patel
fe492d1134 chore: Portuguese, Brazilian translations 2026-01-22 01:46:33 +05:30
Jannat Patel
3a15e132c2 chore: Vietnamese translations 2026-01-22 01:46:32 +05:30
Jannat Patel
2fb20e2db1 chore: Chinese Simplified translations 2026-01-22 01:46:31 +05:30
Jannat Patel
1fd9b4e626 chore: Turkish translations 2026-01-22 01:46:29 +05:30
Jannat Patel
11668235c2 chore: Swedish translations 2026-01-22 01:46:27 +05:30
Jannat Patel
d349af940a chore: Serbian (Cyrillic) translations 2026-01-22 01:46:26 +05:30
Jannat Patel
7eadb6b683 chore: Polish translations 2026-01-22 01:46:22 +05:30
Jannat Patel
904b6c3462 chore: German translations 2026-01-22 01:46:20 +05:30
Jannat Patel
733ab34007 chore: Arabic translations 2026-01-22 01:46:17 +05:30
Jannat Patel
6215817954 chore: Spanish translations 2026-01-22 01:46:15 +05:30
Jannat Patel
1760b01914 chore: French translations 2026-01-22 01:46:14 +05:30
Jannat Patel
d218a4773a chore: Italian translations 2026-01-22 01:46:12 +05:30
Jannat Patel
6fbe32cacd chore: Hungarian translations 2026-01-22 01:46:11 +05:30
Jannat Patel
c10dd9f5b6 chore: Persian translations 2026-01-22 01:46:09 +05:30
Jannat Patel
7eb1237aa0 chore: Russian translations 2026-01-22 01:46:07 +05:30
Jannat Patel
570e8eaf34 chore: Dutch translations 2026-01-22 01:46:06 +05:30
raizasafeel
297ffa5171 fix(chapter): index recalculation on deletion 2026-01-21 14:37:10 +05:30
Jannat Patel
355c752ec1 Merge pull request #2008 from pateljannat/issues-175
fix: misc issues
2026-01-20 22:08:56 +05:30
Jannat Patel
f1031c49a9 fix: allow reset password endpoint 2026-01-20 22:08:39 +05:30
Jannat Patel
043c7902a3 test: updated test for open to work 2026-01-20 22:07:42 +05:30
Jannat Patel
338f46ac17 chore: changed frappe dependency range 2026-01-20 16:57:30 +05:30
Jannat Patel
9987ea1db6 fix: enable pulse only for logged in users 2026-01-20 16:52:42 +05:30
Jannat Patel
d507479bda fix: filters on jobs page 2026-01-20 16:52:20 +05:30
Jannat Patel
afbdb46fe8 fix: open to work patch 2026-01-20 15:47:55 +05:30
Jannat Patel
1308101fe6 fix: parse allowed_endpoints to list if its string 2026-01-20 12:45:28 +05:30
Jannat Patel
c2d6e160d9 fix: renamed open to opportunities to open to work 2026-01-20 12:43:00 +05:30
Jannat Patel
28c88ec519 Merge pull request #2005 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-20 10:58:07 +05:30
Jannat Patel
f77eeafb9e chore: Esperanto translations 2026-01-20 01:43:56 +05:30
Jannat Patel
92b8343db3 chore: Serbian (Latin) translations 2026-01-20 01:43:54 +05:30
Jannat Patel
ac44cdc303 chore: Norwegian Bokmal translations 2026-01-20 01:43:53 +05:30
Jannat Patel
75dcea4c7e chore: Bosnian translations 2026-01-20 01:43:52 +05:30
Jannat Patel
ee03d75b5f chore: Burmese translations 2026-01-20 01:43:50 +05:30
Jannat Patel
31f372629a chore: Croatian translations 2026-01-20 01:43:49 +05:30
Jannat Patel
1b30f476e8 chore: Thai translations 2026-01-20 01:43:47 +05:30
Jannat Patel
e4738e9a50 chore: Tamil translations 2026-01-20 01:43:46 +05:30
Jannat Patel
893f9d34fd chore: Indonesian translations 2026-01-20 01:43:44 +05:30
Jannat Patel
2c17a03d36 chore: Portuguese, Brazilian translations 2026-01-20 01:43:43 +05:30
Jannat Patel
74cbe1bb47 chore: Vietnamese translations 2026-01-20 01:43:41 +05:30
Jannat Patel
88c468068b chore: Chinese Simplified translations 2026-01-20 01:43:40 +05:30
Jannat Patel
6b5e269564 chore: Turkish translations 2026-01-20 01:43:38 +05:30
Jannat Patel
97d9460157 chore: Swedish translations 2026-01-20 01:43:37 +05:30
Jannat Patel
0d614a5919 chore: Serbian (Cyrillic) translations 2026-01-20 01:43:35 +05:30
Jannat Patel
3f6afbf1ff chore: Slovenian translations 2026-01-20 01:43:34 +05:30
Jannat Patel
6d0996ebfd chore: Portuguese translations 2026-01-20 01:43:32 +05:30
Jannat Patel
14e085c826 chore: Polish translations 2026-01-20 01:43:31 +05:30
Jannat Patel
53bf2f1fe8 chore: German translations 2026-01-20 01:43:29 +05:30
Jannat Patel
b06e199432 chore: Danish translations 2026-01-20 01:43:28 +05:30
Jannat Patel
06e1ec38ad chore: Czech translations 2026-01-20 01:43:27 +05:30
Jannat Patel
3063236bfb chore: Arabic translations 2026-01-20 01:43:25 +05:30
Jannat Patel
e3a2eb382d chore: Spanish translations 2026-01-20 01:43:24 +05:30
Jannat Patel
566ef576f5 chore: French translations 2026-01-20 01:43:23 +05:30
Jannat Patel
80ad2da206 chore: Italian translations 2026-01-20 01:43:21 +05:30
Jannat Patel
5d720031b6 chore: Hungarian translations 2026-01-20 01:43:20 +05:30
Jannat Patel
ac719b9e8e chore: Persian translations 2026-01-20 01:43:19 +05:30
Jannat Patel
db9ebb1d86 chore: Russian translations 2026-01-20 01:43:17 +05:30
Jannat Patel
37f6d62101 chore: Dutch translations 2026-01-20 01:43:16 +05:30
Jannat Patel
f7a0fc8b5f build: removed unused inter files 2026-01-19 19:54:06 +05:30
Jannat Patel
0fbe015607 fix: center aligned the load more button on batch student list 2026-01-19 19:49:30 +05:30
Jannat Patel
61909c8498 fix: track cmi.completion_status for SCORM 2004 2026-01-19 19:49:05 +05:30
Jannat Patel
f297f2630b fix: only block endpoints if enabled in site config 2026-01-19 19:03:51 +05:30
Jannat Patel
9425f2aa0e fix: allow custom app endpoints 2026-01-19 18:56:39 +05:30
Jannat Patel
535bb60d69 Merge pull request #2004 from pateljannat/issues-173
fix: allow enabled server script endpoints
2026-01-19 18:33:51 +05:30
Jannat Patel
5175050ed6 fix: allow enabled server script endpoints 2026-01-19 18:33:14 +05:30
Jannat Patel
66486a5a1c fix: do no execute send_notification bg job from batch immediately 2026-01-19 18:17:40 +05:30
Jannat Patel
2d4bf49ab9 Merge pull request #2003 from pateljannat/issues-172
fix: allow social login endpoints
2026-01-19 17:41:10 +05:30
Jannat Patel
50a091a7b0 fix: allow social login endpoints 2026-01-19 17:32:09 +05:30
Jannat Patel
854ebee2e6 Merge branch 'develop' of https://github.com/frappe/lms into issues-172 2026-01-19 17:04:35 +05:30
Jannat Patel
c829ab65d9 fix: payment gateway form 2026-01-19 16:28:16 +05:30
Jannat Patel
1e0e10ca59 Merge branch 'develop' into fix/markdown-paste-render 2026-01-19 15:39:15 +05:30
Jannat Patel
a8cdc76278 Merge branch 'develop' into fix/course-deletion 2026-01-19 15:32:51 +05:30
Jannat Patel
c310460727 Merge pull request #2002 from pateljannat/issues-171
fix: misc issues
2026-01-19 15:31:31 +05:30
Jannat Patel
3e98d962aa test: access to endpoints 2026-01-19 15:06:44 +05:30
Jannat Patel
ad90b89d25 fix: restrict comments in notifications to 3 lines 2026-01-19 12:58:43 +05:30
Jannat Patel
92eac9634a test: fixed evaluator schedule test 2026-01-19 12:46:45 +05:30
Jannat Patel
96015246bd refactor: get certified from certification list will now redirect to certification courses 2026-01-19 12:39:54 +05:30
Jannat Patel
0883aedc1d fix: course and bath category filter issue 2026-01-19 12:38:12 +05:30
Jannat Patel
c97a5e813c fix: only system users can access high level APIs 2026-01-19 11:47:57 +05:30
raizasafeel
2abc243b88 test: refactor course tests and add deletion test 2026-01-19 11:20:33 +05:30
Jannat Patel
62b18e6ffa Merge pull request #2000 from frappe/pot_develop_2026-01-16
chore: update POT file
2026-01-19 10:35:43 +05:30
raizasafeel
36d813b90f feat: add markdown styling for pasted content 2026-01-19 09:53:41 +05:30
Hussain Nagaria
5ce8e8c4ff test: flaky evaluation schedule
* Earlier logic was flaky because the test calculated the last expected date using a 56‑day window, while the production code builds
  schedules for 60 days. Those extra 4 days sometimes include another Monday or Wednesday, so the schedule ends later than the test
  expects.
2026-01-17 23:22:38 +05:30
Hussain Nagaria
3b88892905 refactor: change global variable to function in hooks 2026-01-17 23:14:33 +05:30
Hussain Nagaria
fe1aa3dd40 feat: configurable frontend base path
Co-authored-by: Suraj Shetty <surajshetty3416@users.noreply.github.com>
2026-01-17 23:04:31 +05:30
frappe-pr-bot
ec5e197716 chore: update POT file 2026-01-16 16:04:50 +00:00
Jannat Patel
244b5e445c fix: open course and batch from evaluation event modal 2026-01-16 15:59:16 +05:30
raizasafeel
0f927071c0 fix(course): resolve deletion failure for enrolled courses 2026-01-16 11:37:13 +05:30
Jannat Patel
376de99ef7 Merge pull request #1996 from pateljannat/issues-170
fix: misc issues
2026-01-16 11:34:29 +05:30
Jannat Patel
7a649957dd refactor: list for programming exercises 2026-01-15 19:46:13 +05:30
Jannat Patel
0968c90717 fix: quiz shuffle and limit conditions 2026-01-15 15:09:59 +05:30
Jannat Patel
e22eca9888 Merge pull request #1992 from raizasafeel/improve_batch_performance
feat(batch): add student pagination and optimize dashboard queries
2026-01-15 14:54:48 +05:30
raizasafeel
5890885475 fix(lesson): ensure markdown text is displayed when pasted 2026-01-15 14:49:04 +05:30
Jannat Patel
4a012a99a4 fix: programming exercise test case deletion 2026-01-15 14:47:03 +05:30
raizasafeel
e2c0355821 refactor(batch): simplify dashboard with get_count and conditional rendering 2026-01-15 13:13:40 +05:30
Jannat Patel
bcf27b7150 Merge pull request #1991 from nextchamp-saqib/refactor-lms-telemetry
refactor: telemetry
2026-01-15 10:37:27 +05:30
Jannat Patel
078f18d99c chore: capture quiz creation and certificate creation for analytics 2026-01-15 10:18:09 +05:30
Jannat Patel
19258e263d chore: resolved conflicts 2026-01-15 09:41:48 +05:30
Jannat Patel
7a52a2bf46 Merge pull request #1995 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-15 09:30:49 +05:30
Jannat Patel
a03be5ab4d chore: Serbian (Latin) translations 2026-01-15 01:14:14 +05:30
Jannat Patel
de13c5ddfb chore: Serbian (Cyrillic) translations 2026-01-15 01:14:11 +05:30
Jannat Patel
02564b2e77 chore: Russian translations 2026-01-15 01:14:08 +05:30
Jannat Patel
201e0b96a3 Merge pull request #1993 from pateljannat/issues-169
fix: misc issues
2026-01-14 18:02:33 +05:30
Jannat Patel
e7ccf0a711 fix: sanitize image filename before saving for course and jobs 2026-01-14 17:54:23 +05:30
raizasafeel
c59be28a26 perf(batch): optimise dashboard with query builder 2026-01-14 15:54:54 +05:30
Saqib Ansari
a0ede1dd2a fix: run pre-commit 2026-01-14 14:55:28 +05:30
Saqib Ansari
0aeada4549 refactor: telemetry
* only works with frappe v15.96+
2026-01-14 14:48:05 +05:30
Jannat Patel
6d988eb2b4 chore: changed frappe dependency version 2026-01-14 14:01:21 +05:30
raizasafeel
b58d04c7dc feat(batch): add load more pagination for batch students 2026-01-14 13:56:07 +05:30
Jannat Patel
e2479cd787 Merge pull request #1978 from Omcodes23/develop
fix: Add missing /lms prefix to assignment submission notification links
2026-01-14 12:46:57 +05:30
Jannat Patel
ca30ab8a5e Merge pull request #1987 from pateljannat/issues-168
fix: misc issues
2026-01-14 12:46:39 +05:30
Jannat Patel
da87845a4c chore: changed frappe dependency version 2026-01-14 12:37:49 +05:30
Om vataliya
e7c2ec6965 Merge branch 'develop' into develop 2026-01-14 11:35:38 +05:30
Jannat Patel
a7bcc53e0a fix: dayjs condition for evaluation end date 2026-01-14 10:08:21 +05:30
Jannat Patel
6a5978fed6 fix: instructor notes menu position in lesson form 2026-01-14 09:45:44 +05:30
Jannat Patel
8ff339b7ed fix: misc issues 2026-01-14 09:24:24 +05:30
Jannat Patel
4ace8b2ec0 Merge pull request #1986 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-14 09:01:18 +05:30
Jannat Patel
40c917f255 chore: Esperanto translations 2026-01-14 01:14:55 +05:30
Jannat Patel
bf024af8aa chore: Serbian (Latin) translations 2026-01-14 01:14:53 +05:30
Jannat Patel
675caa380f chore: Norwegian Bokmal translations 2026-01-14 01:14:52 +05:30
Jannat Patel
ad092a71d5 chore: Bosnian translations 2026-01-14 01:14:51 +05:30
Jannat Patel
971fe8fe64 chore: Burmese translations 2026-01-14 01:14:49 +05:30
Jannat Patel
fa63a1d5e5 chore: Croatian translations 2026-01-14 01:14:47 +05:30
Jannat Patel
ece642796a chore: Thai translations 2026-01-14 01:14:46 +05:30
Jannat Patel
ce8f1a5b77 chore: Tamil translations 2026-01-14 01:14:44 +05:30
Jannat Patel
973630059f chore: Indonesian translations 2026-01-14 01:14:43 +05:30
Jannat Patel
0f7e9d2d95 chore: Portuguese, Brazilian translations 2026-01-14 01:14:41 +05:30
Jannat Patel
a7f494f4d8 chore: Vietnamese translations 2026-01-14 01:14:39 +05:30
Jannat Patel
eb37bd1106 chore: Chinese Simplified translations 2026-01-14 01:14:38 +05:30
Jannat Patel
02c1f4c19e chore: Turkish translations 2026-01-14 01:14:36 +05:30
Jannat Patel
00040061f6 chore: Swedish translations 2026-01-14 01:14:35 +05:30
Jannat Patel
cf6292c2c6 chore: Serbian (Cyrillic) translations 2026-01-14 01:14:33 +05:30
Jannat Patel
0cf9984b6b chore: Slovenian translations 2026-01-14 01:14:32 +05:30
Jannat Patel
e793afd063 chore: Portuguese translations 2026-01-14 01:14:30 +05:30
Jannat Patel
6ce655c5b6 chore: Polish translations 2026-01-14 01:14:29 +05:30
Jannat Patel
dd7eb9a9c8 chore: German translations 2026-01-14 01:14:27 +05:30
Jannat Patel
bd8baa6671 chore: Danish translations 2026-01-14 01:14:25 +05:30
Jannat Patel
d387c7b493 chore: Czech translations 2026-01-14 01:14:24 +05:30
Jannat Patel
c7a991e1b0 chore: Arabic translations 2026-01-14 01:14:22 +05:30
Jannat Patel
f7e8707220 chore: Spanish translations 2026-01-14 01:14:21 +05:30
Jannat Patel
27cbc265e2 chore: French translations 2026-01-14 01:14:19 +05:30
Jannat Patel
949ea333eb chore: Italian translations 2026-01-14 01:14:18 +05:30
Jannat Patel
d32a80ef85 chore: Hungarian translations 2026-01-14 01:14:16 +05:30
Jannat Patel
d0ee3fe2d0 chore: Persian translations 2026-01-14 01:14:14 +05:30
Jannat Patel
d4e3676032 chore: Russian translations 2026-01-14 01:14:13 +05:30
Jannat Patel
0265c9dc4c chore: Dutch translations 2026-01-14 01:14:10 +05:30
Jannat Patel
4c81eef80d Merge pull request #1971 from pateljannat/notifications-feed
Notifications feed
2026-01-13 20:38:10 +05:30
Jannat Patel
57c7a8d85c chore: fixed translatable string 2026-01-13 19:16:36 +05:30
Jannat Patel
775d5ecf3a chore: formatting of BrandSettings 2026-01-13 19:13:12 +05:30
Jannat Patel
60fcf0472e fix: mark notification as read when link is visited 2026-01-13 19:07:40 +05:30
Jannat Patel
a09599ec8a feat: notification sent checkbox to prevent resending the notification 2026-01-13 19:06:30 +05:30
Om vataliya
fb5d79fc77 Merge branch 'develop' into develop 2026-01-13 16:55:36 +05:30
Jannat Patel
8ad5288226 chore: resolved merge conflicts 2026-01-13 12:18:30 +05:30
Jannat Patel
65b18641b5 Merge branch 'develop' of https://github.com/frappe/lms into develop 2026-01-13 11:22:40 +05:30
Jannat Patel
78494b9963 chore: added frappe dependency range to pyproject 2026-01-13 11:22:23 +05:30
Jannat Patel
1d83402163 Merge pull request #1984 from frappe/pot_develop_2026-01-13
chore: update POT file
2026-01-13 11:14:47 +05:30
frappe-pr-bot
64bdd85b03 chore: update POT file 2026-01-13 05:34:56 +00:00
Jannat Patel
0f1b6f3eeb chore: upgraded node version for pot file runner 2026-01-13 11:02:16 +05:30
Jannat Patel
566711df22 Merge pull request #1983 from pateljannat/issues-167
fix: certification filters
2026-01-13 10:44:24 +05:30
Jannat Patel
c9c6aef466 fix: certified participants query 2026-01-13 10:36:17 +05:30
Jannat Patel
ad650c73c1 fix: changed order by query for certified participants listing 2026-01-13 10:27:08 +05:30
Jannat Patel
af5bc2b071 chore: upgraded node version for linter and release github actions 2026-01-13 10:26:13 +05:30
Jannat Patel
b5fcfb62de fix: certification filters 2026-01-13 10:18:06 +05:30
Om vataliya
7dccef6b10 fix: Add missing /lms prefix to assignment submission notification links
Fixes #1969

- Added /lms prefix to notification link in lms_assignment_submission.py
- Added /lms prefix to assessment URL in utils.py
- Ensures consistent routing with other notification links (courses, billing, etc.)
- Resolves 404 errors when users click 'View' on assignment submission notifications
2026-01-09 23:45:29 +05:30
Jannat Patel
e1d343528d Merge pull request #1972 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-09 15:47:05 +05:30
Jannat Patel
6eeb688f06 chore: Hungarian translations 2026-01-07 20:03:15 +05:30
Jannat Patel
86e3794d00 Merge pull request #1970 from frappe/develop
chore: merge 'develop' into 'main'
2026-01-07 14:29:49 +05:30
Jannat Patel
45e98b9ddc fix: notification on quiz update and mention 2026-01-07 14:28:56 +05:30
Jannat Patel
aad875f72c Merge pull request #1968 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-07 10:59:45 +05:30
Jannat Patel
1698bf0bca chore: Hungarian translations 2026-01-06 20:01:12 +05:30
Jannat Patel
ea94813d94 feat: system notification for new courses published 2026-01-06 12:36:15 +05:30
Jannat Patel
142b893f2c Merge pull request #1966 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-06 10:26:01 +05:30
Jannat Patel
f88dd84ed3 ci: upgraded python version for pot file generation 2026-01-05 19:55:32 +05:30
Jannat Patel
50660f6720 chore: Persian translations 2026-01-05 19:12:35 +05:30
Jannat Patel
958128060c chore: Dutch translations 2026-01-05 19:12:33 +05:30
Jannat Patel
b2b1d2bb00 feat: email notifications for published courses 2026-01-05 18:18:56 +05:30
Jannat Patel
a3069bd760 Merge pull request #1965 from frappe/l10n_develop2
chore: sync translations from crowdin
2026-01-05 12:52:02 +05:30
Jannat Patel
f39ee39452 chore: Russian translations 2026-01-04 18:25:34 +05:30
Jannat Patel
a43e90e2d0 Merge pull request #1963 from pateljannat/issues-166
fix: payment validation during course enrollment
2026-01-02 16:54:29 +05:30
Jannat Patel
2aa3765ed3 Merge pull request #1962 from rehanrehman389/multiselect
fix: show options on focus and filter selected values
2026-01-02 14:58:17 +05:30
Jannat Patel
45dae0b9d3 fix: payment validation during course enrollment 2026-01-02 14:47:28 +05:30
Jannat Patel
b1789cdcba fix: payment validation during course enrollment 2026-01-02 14:42:42 +05:30
Jannat Patel
e21f0e3a7f refactor(ui): settings 2026-01-02 14:41:17 +05:30
rehanrehman389
ea9975db2c fix: fix build failure 2026-01-02 12:33:09 +05:30
rehanrehman389
b1c8e01bf5 fix: show options on focus and filter selected values 2026-01-02 12:18:03 +05:30
Jannat Patel
1d19389fc0 Merge pull request #1961 from pateljannat/issues-165
fix: misc issues
2026-01-01 14:51:58 +05:30
Jannat Patel
dbd3e17b26 Merge pull request #1912 from royalpinto007/fix/quiz-enter
fix(quizzes): enable enter key to submit quiz creation
2026-01-01 14:28:42 +05:30
Jannat Patel
b17c7ca2db fix: statistics chart based on frappe version 2026-01-01 14:26:34 +05:30
Jannat Patel
e17af04c9a Merge branch 'develop' into fix/quiz-enter 2026-01-01 13:16:34 +05:30
Jannat Patel
638a9abf88 fix: test case table issue in programming exercise form 2026-01-01 13:09:35 +05:30
Jannat Patel
89f9cbd30d Merge pull request #1959 from pateljannat/full-text-search-improvements
feat: search by instructor name from command palette
2026-01-01 12:30:56 +05:30
Jannat Patel
204fb669c0 test: fixed evaluator schedule test 2026-01-01 12:23:31 +05:30
Jannat Patel
7a24a83d9e fix: improved search result sorting 2026-01-01 12:06:15 +05:30
Jannat Patel
edc6007fb6 feat: search by instructor name from command palette 2025-12-31 20:03:44 +05:30
Jannat Patel
27a36540d4 Merge pull request #1957 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-31 12:19:21 +05:30
Frappe PR Bot
6dd1274150 chore(release): Bumped to Version 2.44.0 2025-12-31 06:37:47 +00:00
Jannat Patel
6a80c2ab38 Merge pull request #1952 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-30 10:47:34 +05:30
Jannat Patel
8633444b91 Merge pull request #1953 from pateljannat/open-to-hiring
feat: open to hiring
2025-12-30 10:46:57 +05:30
Jannat Patel
cb014a9507 test: certified participants with opportunities and hiring filters 2025-12-30 10:40:26 +05:30
Jannat Patel
c241abb820 test: fixed evaluator schedule test 2025-12-29 19:37:02 +05:30
Jannat Patel
a497a2d838 fix: mobile layout 2025-12-29 19:14:22 +05:30
Jannat Patel
5e78848d38 chore: Persian translations 2025-12-29 16:39:59 +05:30
Jannat Patel
adf897cc08 Merge pull request #1947 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-29 16:03:14 +05:30
Jannat Patel
5053b4e45f test: certified participants data 2025-12-29 16:02:38 +05:30
Jannat Patel
3151854bfd feat: open to hiring 2025-12-29 13:24:04 +05:30
Jannat Patel
db5868f69a refactor(ui): no permission page 2025-12-29 13:21:13 +05:30
Jannat Patel
9b6f7635bc chore: Persian translations 2025-12-29 06:49:37 +05:30
Jannat Patel
55ff59095c chore: Persian translations 2025-12-28 06:29:21 +05:30
Jannat Patel
3dce1c0930 Merge pull request #1945 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-26 10:45:27 +05:30
Jannat Patel
fd33aa8b70 Merge pull request #1943 from rehanrehman389/python-3.14
build: support Python 3.14
2025-12-26 10:45:06 +05:30
Jannat Patel
05170da762 chore: Serbian (Latin) translations 2025-12-26 05:02:40 +05:30
Jannat Patel
64f0c693d2 chore: Serbian (Cyrillic) translations 2025-12-26 05:02:38 +05:30
Jannat Patel
aac1e2d01f chore: Portuguese, Brazilian translations 2025-12-25 04:55:13 +05:30
Jannat Patel
5472c4d387 chore: Chinese Simplified translations 2025-12-25 04:55:11 +05:30
Jannat Patel
39a31a0baf chore: Turkish translations 2025-12-25 04:55:10 +05:30
Jannat Patel
cffc740ed3 chore: Russian translations 2025-12-25 04:55:05 +05:30
Rehan Ansari
114f3aae6d build(ci): use python 3.14 and node 24 2025-12-24 19:22:23 +05:30
Rehan Ansari
fea7f8f9ae build(deps): bump 2025-12-24 19:21:54 +05:30
Jannat Patel
830f513a06 Merge pull request #1939 from pateljannat/consent-on-billing-page
feat: consent on billing page
2025-12-24 16:50:24 +05:30
Jannat Patel
e76ff45241 Merge pull request #1942 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-24 15:57:13 +05:30
Jannat Patel
66f19d06b0 chore: resolved conflicts 2025-12-24 15:54:55 +05:30
Jannat Patel
987b655976 fix: skip self enrollment check if paid batch 2025-12-24 11:18:54 +05:30
Jannat Patel
8619712d20 Merge pull request #1940 from rehanrehman389/dark-mode-fix
fix: dark mode visibility
2025-12-24 09:09:02 +05:30
Jannat Patel
bc6ca205d5 Merge pull request #1941 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-24 09:06:27 +05:30
Jannat Patel
48d17e88d9 chore: Esperanto translations 2025-12-24 04:55:52 +05:30
Jannat Patel
1e9f1b7661 chore: Serbian (Latin) translations 2025-12-24 04:55:50 +05:30
Jannat Patel
faa6fbf68e chore: Norwegian Bokmal translations 2025-12-24 04:55:49 +05:30
Jannat Patel
789b1016fb chore: Bosnian translations 2025-12-24 04:55:48 +05:30
Jannat Patel
8fb491ac18 chore: Burmese translations 2025-12-24 04:55:46 +05:30
Jannat Patel
3eea872137 chore: Croatian translations 2025-12-24 04:55:45 +05:30
Jannat Patel
e4d98019e7 chore: Thai translations 2025-12-24 04:55:44 +05:30
Jannat Patel
7847f681e9 chore: Tamil translations 2025-12-24 04:55:42 +05:30
Jannat Patel
26d7971d99 chore: Persian translations 2025-12-24 04:55:41 +05:30
Jannat Patel
e25ef7e07b chore: Indonesian translations 2025-12-24 04:55:40 +05:30
Jannat Patel
85fafe7d56 chore: Portuguese, Brazilian translations 2025-12-24 04:55:38 +05:30
Jannat Patel
fdced5c204 chore: Vietnamese translations 2025-12-24 04:55:37 +05:30
Jannat Patel
b21fe69123 chore: Chinese Simplified translations 2025-12-24 04:55:36 +05:30
Jannat Patel
4572d03470 chore: Turkish translations 2025-12-24 04:55:35 +05:30
Jannat Patel
d348fd9f99 chore: Swedish translations 2025-12-24 04:55:33 +05:30
Jannat Patel
4b6e91e81f chore: Serbian (Cyrillic) translations 2025-12-24 04:55:32 +05:30
Jannat Patel
9126e7cd27 chore: Slovenian translations 2025-12-24 04:55:31 +05:30
Jannat Patel
713ee5287f chore: Russian translations 2025-12-24 04:55:29 +05:30
Jannat Patel
79109f1265 chore: Portuguese translations 2025-12-24 04:55:28 +05:30
Jannat Patel
20f16849bf chore: Polish translations 2025-12-24 04:55:27 +05:30
Jannat Patel
99968f5961 chore: Dutch translations 2025-12-24 04:55:25 +05:30
Jannat Patel
0741fbf583 chore: Italian translations 2025-12-24 04:55:24 +05:30
Jannat Patel
784ed37de0 chore: Hungarian translations 2025-12-24 04:55:23 +05:30
Jannat Patel
1c29b4966c chore: German translations 2025-12-24 04:55:21 +05:30
Jannat Patel
a06ea92c8e chore: Danish translations 2025-12-24 04:55:20 +05:30
Jannat Patel
60a47889d2 chore: Czech translations 2025-12-24 04:55:18 +05:30
Jannat Patel
d758039b2c chore: Arabic translations 2025-12-24 04:55:17 +05:30
Jannat Patel
4b507f0706 chore: Spanish translations 2025-12-24 04:55:16 +05:30
Jannat Patel
5986838056 chore: French translations 2025-12-24 04:55:14 +05:30
Rehan Ansari
e02c99bd16 fix: dark theme styling for programs 2025-12-23 22:35:03 +05:30
Rehan Ansari
a3f580a9fb fix: search page dark theme styling 2025-12-23 21:54:07 +05:30
Jannat Patel
43fb0f92df fix: hide consent warning when consent has been provided 2025-12-23 15:05:28 +05:30
Jannat Patel
dd4fbfa8a2 feat: billing consent ui 2025-12-23 15:01:14 +05:30
Jannat Patel
cf2e57ec40 Merge branch 'develop' of https://github.com/frappe/lms into consent-on-billing-page 2025-12-23 13:09:53 +05:30
Jannat Patel
daf2d28f3a Merge pull request #1938 from pateljannat/enrollment-from-batch-main
fix: batch enrollment conditions
2025-12-23 13:05:31 +05:30
Jannat Patel
d5e48f9502 fix: batch enrollment conditions 2025-12-23 12:56:17 +05:30
Jannat Patel
3318a1c599 fix: certification list border style 2025-12-23 12:44:36 +05:30
Jannat Patel
3fa2320fe3 Merge pull request #1937 from pateljannat/enrollment-from-batch
fix: batch enrollment conditions
2025-12-23 12:13:11 +05:30
Jannat Patel
c0c8fb5bf3 fix: skip cancelled slots when checking if slot is already booked 2025-12-23 12:05:43 +05:30
Jannat Patel
acde5ad1d1 fix: batch enrollment conditions 2025-12-23 11:42:45 +05:30
Jannat Patel
0a9b18d04d Merge pull request #1928 from pateljannat/issues-164
fix: misc issues
2025-12-23 11:08:06 +05:30
Jannat Patel
3013372711 fix: don't fetch application count when user is not logged in 2025-12-23 10:58:12 +05:30
Jannat Patel
76979e292d Merge branch 'develop' of https://github.com/frappe/lms into issues-164 2025-12-23 10:40:03 +05:30
Jannat Patel
115d52776f Merge pull request #1936 from rehanrehman389/profile
fix: profile save after image removal
2025-12-23 10:39:36 +05:30
Jannat Patel
f1b392ac9b Merge pull request #1935 from rehanrehman389/guest-issue
fix: job details for guest user
2025-12-23 10:38:55 +05:30
Jannat Patel
00198464e9 Merge pull request #1934 from rehanrehman389/fix/jobs
fix: skip closed jobs API call for guest users
2025-12-23 10:37:49 +05:30
Jannat Patel
1b84f00673 Merge pull request #1927 from frappe/pot_develop_2025-12-19
chore: update POT file
2025-12-23 10:36:03 +05:30
Rehan Ansari
3d9850dc73 fix: profile save after image removal 2025-12-22 23:26:41 +05:30
Rehan Ansari
dfcf295493 revert: job details api 2025-12-22 23:00:53 +05:30
Rehan Ansari
b987bf7f20 revert: job details changes from PR #1890 2025-12-22 22:49:22 +05:30
Rehan Ansari
e1d160a898 fix: skip closed jobs API call for guest users 2025-12-22 21:54:11 +05:30
Jannat Patel
a3fb63cd08 fix: default livecode url 2025-12-22 11:45:52 +05:30
Jannat Patel
1faa697b6c fix: enrollment eligibility conditions 2025-12-22 11:45:36 +05:30
Jannat Patel
01094cd10a fix: enrollment eligibility conditions 2025-12-22 10:55:36 +05:30
Jannat Patel
4141022431 feat: billing consent 2025-12-22 10:50:45 +05:30
Jannat Patel
69d0efbfa7 Merge pull request #1926 from pateljannat/filter-assignment-by-course
feat: filter assignments by course in lesson
2025-12-19 22:54:29 +05:30
frappe-pr-bot
66cc7d392e chore: update POT file 2025-12-19 16:05:21 +00:00
Jannat Patel
8048cb47c5 feat: filter assignments by course in lesson 2025-12-19 17:42:33 +05:30
Jannat Patel
09c668f7ed fix: description on course details 2025-12-19 16:21:22 +05:30
Jannat Patel
984a63c46a fix: description on course details 2025-12-19 16:20:53 +05:30
Jannat Patel
80c978d265 Merge pull request #1924 from pateljannat/issues-163
fix: certification caching issue
2025-12-19 16:12:47 +05:30
Jannat Patel
cde6828c1f fix: certification caching issue 2025-12-19 15:58:46 +05:30
Jannat Patel
51c1b816a1 fix: increase toast duration when evaluation scheduling fails 2025-12-19 14:36:00 +05:30
Jannat Patel
356ce7478c Merge pull request #1923 from pateljannat/improved-evaluation-scheduling
feat: Improved evaluation scheduling
2025-12-19 12:47:49 +05:30
Jannat Patel
2210ef7af7 test: evaluation schedule unavailability 2025-12-19 12:39:39 +05:30
Jannat Patel
0884e1315b test: evaluation schedule 2025-12-19 12:14:10 +05:30
Jannat Patel
58b26b6e32 fix: increased schedule range to 60 days 2025-12-19 10:42:45 +05:30
Jannat Patel
2631681c1d fix: enrollment in restricted courses by admin 2025-12-18 18:01:08 +05:30
Jannat Patel
82d6284b06 Merge pull request #1922 from pateljannat/issues-162
fix: enrollment in restricted courses by admin
2025-12-18 17:56:44 +05:30
Jannat Patel
46d13d65c1 fix: enrollment in restricted courses by admin 2025-12-18 14:59:37 +05:30
Jannat Patel
9da6cff8a5 feat: see all upcoming slots when scheduling evaluation 2025-12-18 14:40:41 +05:30
Jannat Patel
dc724831c3 fix: read permission for certificates 2025-12-17 16:50:51 +05:30
Jannat Patel
776730447a fix: removed vue jsx 2025-12-17 15:30:16 +05:30
Jannat Patel
82c6fdf475 fix: removed vue jsx 2025-12-17 14:58:38 +05:30
Jannat Patel
ca2b175e1c fix: assignment permission to course creator and evaluator 2025-12-17 14:47:47 +05:30
Jannat Patel
ec7e250f96 Merge pull request #1919 from pateljannat/open-to-work
feat: open to opportunities
2025-12-17 11:46:35 +05:30
Jannat Patel
126570fcca fix: save button of profile edit modal 2025-12-17 11:23:10 +05:30
Jannat Patel
a7bbb7f150 Merge pull request #1921 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-17 11:01:30 +05:30
Frappe PR Bot
4345ff18bd chore(release): Bumped to Version 2.43.0 2025-12-17 05:20:08 +00:00
Jannat Patel
26409b0336 Merge pull request #1907 from KerollesFathy/fix-category-on-certified-members
fix: Category dropdown options not visible in the certification list page
2025-12-17 10:49:11 +05:30
Jannat Patel
7653b64353 Merge pull request #1920 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-17 10:45:01 +05:30
Jannat Patel
32dd5832b8 chore: Swedish translations 2025-12-17 02:53:41 +05:30
Jannat Patel
59072c3580 chore: Slovenian translations 2025-12-17 02:53:39 +05:30
Jannat Patel
d121cd3526 chore: Hungarian translations 2025-12-17 02:53:28 +05:30
KerollesFathy
cfbac5f2c4 fix: use update:modelvalue instead of using watch 2025-12-16 21:05:04 +00:00
Jannat Patel
371c72b96c Merge branch 'develop' of https://github.com/frappe/lms into open-to-work 2025-12-16 20:37:38 +05:30
Jannat Patel
55a02004bd fix: updated course and batch category filters 2025-12-16 20:36:53 +05:30
Jannat Patel
b3119f5295 feat: open to opportunities 2025-12-16 20:27:00 +05:30
Jannat Patel
f57b64531c fix: misc home page issues 2025-12-16 14:33:35 +05:30
Jannat Patel
94a3afdc9b Merge pull request #1918 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-16 11:09:00 +05:30
Jannat Patel
b65102e6c8 fix: course card gradient color 2025-12-16 11:06:03 +05:30
Jannat Patel
0f1b48b3e3 chore: Esperanto translations 2025-12-16 02:50:54 +05:30
Jannat Patel
0a62da02fe chore: Serbian (Latin) translations 2025-12-16 02:50:52 +05:30
Jannat Patel
ae54e39b69 chore: Norwegian Bokmal translations 2025-12-16 02:50:50 +05:30
Jannat Patel
ea565d7334 chore: Bosnian translations 2025-12-16 02:50:49 +05:30
Jannat Patel
d11f625ecb chore: Burmese translations 2025-12-16 02:50:48 +05:30
Jannat Patel
ab6761a6ce chore: Croatian translations 2025-12-16 02:50:46 +05:30
Jannat Patel
081bc0eaa6 chore: Thai translations 2025-12-16 02:50:45 +05:30
Jannat Patel
30748b7287 chore: Tamil translations 2025-12-16 02:50:43 +05:30
Jannat Patel
ad928eb2a6 chore: Persian translations 2025-12-16 02:50:42 +05:30
Jannat Patel
bdb281dfa3 chore: Indonesian translations 2025-12-16 02:50:41 +05:30
Jannat Patel
8d26800a5d chore: Portuguese, Brazilian translations 2025-12-16 02:50:39 +05:30
Jannat Patel
f0d85b391c chore: Vietnamese translations 2025-12-16 02:50:37 +05:30
Jannat Patel
ac205b6944 chore: Chinese Simplified translations 2025-12-16 02:50:36 +05:30
Jannat Patel
c6979a2f61 chore: Turkish translations 2025-12-16 02:50:35 +05:30
Jannat Patel
0bbf4a9925 chore: Swedish translations 2025-12-16 02:50:33 +05:30
Jannat Patel
16b047bb73 chore: Serbian (Cyrillic) translations 2025-12-16 02:50:32 +05:30
Jannat Patel
f674fdcc0d chore: Slovenian translations 2025-12-16 02:50:30 +05:30
Jannat Patel
05b40ae47e chore: Russian translations 2025-12-16 02:50:29 +05:30
Jannat Patel
ce5413f622 chore: Portuguese translations 2025-12-16 02:50:28 +05:30
Jannat Patel
fbf4971f52 chore: Polish translations 2025-12-16 02:50:26 +05:30
Jannat Patel
a2e2de2ec3 chore: Dutch translations 2025-12-16 02:50:25 +05:30
Jannat Patel
9937851146 chore: Italian translations 2025-12-16 02:50:23 +05:30
Jannat Patel
a0bdb84d3f chore: German translations 2025-12-16 02:50:22 +05:30
Jannat Patel
21fca61fab chore: Danish translations 2025-12-16 02:50:21 +05:30
Jannat Patel
1157e6a007 chore: Czech translations 2025-12-16 02:50:19 +05:30
Jannat Patel
6d9db8ef46 chore: Arabic translations 2025-12-16 02:50:18 +05:30
Jannat Patel
8c23510622 chore: Spanish translations 2025-12-16 02:50:16 +05:30
Jannat Patel
660945b8fa chore: French translations 2025-12-16 02:50:15 +05:30
Jannat Patel
6550e1c926 chore: Hungarian translations 2025-12-16 02:50:13 +05:30
KerollesFathy
49bc5750a1 fix: prevent duplicate certification categories 2025-12-15 18:30:01 +00:00
Jannat Patel
163e4b8b1e fix: course card gradient color 2025-12-15 16:32:42 +05:30
Jannat Patel
52fb5e2ad8 Merge pull request #1915 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-15 16:02:38 +05:30
Jannat Patel
2596b85eb2 Merge pull request #1913 from rehanrehman389/cleanup-cohort
chore: Remove deprecated doctype and related links
2025-12-15 15:44:24 +05:30
Jannat Patel
1210a6aa87 chore: beautifulsoup dependency management 2025-12-15 15:21:44 +05:30
Jannat Patel
d3a27e8bc9 fix: enrollment eligibility validation 2025-12-15 15:21:22 +05:30
Jannat Patel
6cabb4eed7 chore: resolved conflicts 2025-12-15 15:18:08 +05:30
Jannat Patel
38e6320d8f Merge pull request #1890 from pateljannat/tests-1
test: utils
2025-12-15 15:05:39 +05:30
Jannat Patel
8d41a3d688 test: certificate and rating test 2025-12-15 14:51:37 +05:30
Jannat Patel
7bf6311a90 Merge branch 'develop' of https://github.com/frappe/lms into tests-1 2025-12-15 12:13:19 +05:30
Jannat Patel
41660a1dfe Merge pull request #1895 from pateljannat/full-text-search
feat: Full Text Search
2025-12-15 12:07:04 +05:30
Jannat Patel
46b467847e Merge pull request #1908 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-15 11:22:02 +05:30
Jannat Patel
810635d964 Merge pull request #1906 from frappe/pot_develop_2025-12-12
chore: update POT file
2025-12-15 11:21:48 +05:30
Rehan Ansari
b0a96641ef chore: Remove deprecated doctype and related links 2025-12-14 21:16:02 +05:30
royalpinto007
f2846da4ad fix(quizzes): enable enter key to submit quiz creation
Signed-off-by: royalpinto007 <royalpinto007@gmail.com>
2025-12-14 15:28:29 +05:30
Jannat Patel
e150225226 chore: Hungarian translations 2025-12-14 02:05:58 +05:30
KerollesFathy
4580ab0181 fix: add watcher for currentCategory to update participants 2025-12-13 15:18:37 +00:00
KerollesFathy
f8c10d1807 fix: update certification categories to include label and value 2025-12-13 14:22:08 +00:00
frappe-pr-bot
f783c6a62f chore: update POT file 2025-12-12 16:04:35 +00:00
Jannat Patel
1bc610bd76 feat: search jobs from command palette 2025-12-12 19:14:25 +05:30
Jannat Patel
f49bb98b92 fix: sidebar improvements 2025-12-12 16:25:45 +05:30
Jannat Patel
819318de37 feat: broke down sidebar into categories 2025-12-12 12:30:16 +05:30
Jannat Patel
3ebff2143a Merge pull request #1903 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-11 17:28:55 +05:30
Frappe PR Bot
80deed2be7 chore(release): Bumped to Version 2.42.0 2025-12-11 11:48:42 +00:00
Jannat Patel
8de3996d36 Merge pull request #1866 from muhamiyan/issues-1
fix: open dialog directly to create quiz and assignment
2025-12-11 17:15:47 +05:30
Jannat Patel
b56fd01f39 Merge pull request #1899 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-11 16:36:05 +05:30
Jannat Patel
820ea7e2a4 feat: search page functionality 2025-12-11 14:56:28 +05:30
Jannat Patel
3de5fb0622 Merge branch 'develop' of https://github.com/frappe/lms into full-text-search 2025-12-11 11:45:36 +05:30
Jannat Patel
a5f112ff16 fix: remove course filters from certificate evaluation form 2025-12-11 11:33:29 +05:30
Jannat Patel
32b3fceb3b Merge pull request #1900 from pateljannat/issues-161
fix: exclude cancelled slots when fetching available slots
2025-12-11 11:27:58 +05:30
Jannat Patel
1d7c88674d fix: exclude cancelled slots when fetching available slots 2025-12-11 11:25:59 +05:30
Jannat Patel
ce158692d4 Merge pull request #1898 from pateljannat/issues-160
fix: misc issues
2025-12-11 10:39:02 +05:30
Jannat Patel
9845498f76 chore: Croatian translations 2025-12-11 00:59:38 +05:30
Jannat Patel
7b8250056b chore: Serbian (Cyrillic) translations 2025-12-11 00:59:37 +05:30
Jannat Patel
80a217e646 feat: configuration to disable pwa 2025-12-10 18:05:50 +05:30
Jannat Patel
0877e32e1b fix: sanitize html of all description fields 2025-12-10 17:25:56 +05:30
Jannat Patel
316e739dd6 Merge pull request #1897 from pateljannat/issues-159
fix: misc issues
2025-12-10 16:41:22 +05:30
Jannat Patel
43efebe3a7 fix: removed filter of an old field from LMS Certificate Evaluation 2025-12-10 16:14:27 +05:30
Jannat Patel
ca849da815 fix: validate url starts with http or https for job form 2025-12-10 14:51:16 +05:30
Jannat Patel
9470cc192c fix: batch validations 2025-12-10 14:25:10 +05:30
Jannat Patel
631008832c fix: no need to check if not in test before sending certificate email 2025-12-10 14:11:08 +05:30
Jannat Patel
7fc066679d fix: validate url starts with http or https for job form 2025-12-10 14:06:17 +05:30
Jannat Patel
73ee1b2f09 Merge pull request #1893 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-10 11:19:02 +05:30
Jannat Patel
4c98282335 feat: search page 2025-12-10 11:17:49 +05:30
Jannat Patel
7e3c5beaea fix: filter search records based on roles 2025-12-10 11:17:21 +05:30
Jannat Patel
59d27e06a0 chore: Serbian (Latin) translations 2025-12-10 00:25:38 +05:30
Jannat Patel
2caddafd0b chore: Russian translations 2025-12-10 00:25:22 +05:30
Jannat Patel
cbfc10c08b chore: Hungarian translations 2025-12-10 00:25:17 +05:30
muhamiyan
ae8ffd4cbd fix: linter issues 2025-12-09 19:41:12 +07:00
muhamiyan
e768d5d55c fix: reset QuizForm.vue 2025-12-09 19:40:51 +07:00
Jannat Patel
829245f373 Merge pull request #1891 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-09 11:29:29 +05:30
Jannat Patel
c901a15969 test: utils 2025-12-09 11:24:35 +05:30
muhamiyan
017798ce89 fix: url with query string 2025-12-09 12:07:58 +07:00
muhamiyan
d16874da7c fix: open dialog directly to create assignment 2025-12-09 12:01:10 +07:00
muhamiyan
d8c9204b61 fix: open dialog directly to create quiz 2025-12-09 11:19:51 +07:00
muhamiyan
1ab51b423f Revert "fix: remove redirect to create new quiz and new assignment"
This reverts commit fc82ec8070.
2025-12-09 09:54:16 +07:00
Jannat Patel
ce110e8758 chore: Esperanto translations 2025-12-09 00:28:23 +05:30
Jannat Patel
e257a3f757 chore: French translations 2025-12-09 00:28:21 +05:30
Jannat Patel
49a6305dec chore: Serbian (Latin) translations 2025-12-09 00:28:20 +05:30
Jannat Patel
1b30df0c41 chore: Norwegian Bokmal translations 2025-12-09 00:28:19 +05:30
Jannat Patel
edb2369967 chore: Bosnian translations 2025-12-09 00:28:17 +05:30
Jannat Patel
4f93908ab1 chore: Burmese translations 2025-12-09 00:28:16 +05:30
Jannat Patel
dc95c63c62 chore: Croatian translations 2025-12-09 00:28:14 +05:30
Jannat Patel
28a0cb5b12 chore: Thai translations 2025-12-09 00:28:13 +05:30
Jannat Patel
a4cbcc2f32 chore: Tamil translations 2025-12-09 00:28:12 +05:30
Jannat Patel
0add20e637 chore: Persian translations 2025-12-09 00:28:10 +05:30
Jannat Patel
50a8550a0c chore: Indonesian translations 2025-12-09 00:28:09 +05:30
Jannat Patel
dbd6ac10e9 chore: Portuguese, Brazilian translations 2025-12-09 00:28:07 +05:30
Jannat Patel
1252234eed chore: Vietnamese translations 2025-12-09 00:28:06 +05:30
Jannat Patel
5ee7a6efb6 chore: Chinese Simplified translations 2025-12-09 00:28:04 +05:30
Jannat Patel
a4bfe3752b chore: Turkish translations 2025-12-09 00:28:03 +05:30
Jannat Patel
dd034ec7ae chore: Swedish translations 2025-12-09 00:28:02 +05:30
Jannat Patel
c93b189ede chore: Serbian (Cyrillic) translations 2025-12-09 00:28:00 +05:30
Jannat Patel
2819531d57 chore: Slovenian translations 2025-12-09 00:27:59 +05:30
Jannat Patel
dc1ce8e55e chore: Russian translations 2025-12-09 00:27:57 +05:30
Jannat Patel
2e0266a265 chore: Portuguese translations 2025-12-09 00:27:56 +05:30
Jannat Patel
bcd7aca2ff chore: Polish translations 2025-12-09 00:27:55 +05:30
Jannat Patel
8c66558b63 chore: Dutch translations 2025-12-09 00:27:53 +05:30
Jannat Patel
a263bcd8c3 chore: Italian translations 2025-12-09 00:27:52 +05:30
Jannat Patel
97a873c6b0 chore: Hungarian translations 2025-12-09 00:27:50 +05:30
Jannat Patel
fc43259dcb chore: German translations 2025-12-09 00:27:49 +05:30
Jannat Patel
73116446e2 chore: Danish translations 2025-12-09 00:27:48 +05:30
Jannat Patel
f0d3439071 chore: Czech translations 2025-12-09 00:27:46 +05:30
Jannat Patel
31a1aa7cac chore: Arabic translations 2025-12-09 00:27:45 +05:30
Jannat Patel
34cd751114 chore: Spanish translations 2025-12-09 00:27:43 +05:30
Jannat Patel
196d4a835f test: utils 2025-12-08 21:10:13 +05:30
Jannat Patel
d82517f402 test: utils 2025-12-08 20:22:55 +05:30
Jannat Patel
bcda74a455 Merge pull request #1889 from pateljannat/code-coverage
chore: configured codecov for CI
2025-12-08 15:59:53 +05:30
Jannat Patel
1f65439efd chore: upload coverage data codecov 2025-12-08 15:52:41 +05:30
Jannat Patel
f95b62a6a6 chore: configured codecov for CI 2025-12-08 15:23:18 +05:30
Jannat Patel
0dc0794add Merge pull request #1888 from pateljannat/version-16-changes
chore: removed cohort related doctypes
2025-12-08 14:54:31 +05:30
Jannat Patel
552b5845ea chore: fixed merge conflicts 2025-12-08 14:54:14 +05:30
Jannat Patel
b05739257d chore: removed cohort related doctypes 2025-12-08 14:47:02 +05:30
Jannat Patel
61215cf0ad Merge pull request #1887 from pateljannat/issues-158
fix: charts and dashboards as per version 16
2025-12-08 13:43:16 +05:30
Jannat Patel
bc84e46e09 fix: charts and dashboards as per version 16 2025-12-08 13:23:42 +05:30
Jannat Patel
645581e202 Merge pull request #1886 from pateljannat/issues-157
fix: misc issues
2025-12-08 12:46:51 +05:30
Jannat Patel
4dddb9f2e1 fix: exclude frappe-ui from optimiseDeps only on local 2025-12-08 12:37:04 +05:30
Jannat Patel
6e93f952ab refactor: markdown editor 2025-12-08 12:33:38 +05:30
Jannat Patel
93ffcdb8f9 Merge branch 'develop' of https://github.com/frappe/lms into issues-157 2025-12-08 10:37:45 +05:30
Jannat Patel
040d74c20a fix: use frappe-ui theme colors and fixed tailwind config 2025-12-08 10:37:31 +05:30
Jannat Patel
5825bcf9b3 fix: use frappe-ui theme colors and fixed tailwind config 2025-12-08 10:37:08 +05:30
Jannat Patel
114d183524 Merge pull request #1884 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-08 10:35:55 +05:30
Jannat Patel
de356c4e64 Merge pull request #1883 from frappe/pot_develop_2025-12-05
chore: update POT file
2025-12-08 10:35:42 +05:30
Jannat Patel
fedd5e6a14 chore: Hungarian translations 2025-12-05 22:58:03 +05:30
frappe-pr-bot
29217ab2bb chore: update POT file 2025-12-05 16:04:48 +00:00
Jannat Patel
95f37f7120 Merge pull request #1882 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-05 12:16:51 +05:30
Frappe PR Bot
87a7b93334 chore(release): Bumped to Version 2.41.0 2025-12-05 06:34:16 +00:00
Jannat Patel
0d430ad86c Merge pull request #1881 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-05 12:02:53 +05:30
Jannat Patel
308a108e60 Merge pull request #1880 from pateljannat/issues-155
fix: misc permission issues
2025-12-05 12:02:32 +05:30
Jannat Patel
17401a330d fix: settings issue 2025-12-05 11:34:49 +05:30
Jannat Patel
f9d7463710 test: removed tests for old batch and exercises 2025-12-05 11:03:38 +05:30
Jannat Patel
097c53c7b1 chore: Bosnian translations 2025-12-04 22:37:08 +05:30
Jannat Patel
9ae2e5babb chore: Croatian translations 2025-12-04 22:37:05 +05:30
Jannat Patel
1f29f7a282 chore: Slovenian translations 2025-12-04 22:36:52 +05:30
Jannat Patel
1d1fcd5f6d chore: Russian translations 2025-12-04 22:36:51 +05:30
Jannat Patel
b6f8f87923 chore: Hungarian translations 2025-12-04 22:36:45 +05:30
Jannat Patel
466b248c30 fix: validate course details before enrolling 2025-12-04 17:48:39 +05:30
Jannat Patel
7c6747aeb0 test: enroll user in course before creating certificate 2025-12-04 17:35:08 +05:30
Jannat Patel
836a6d1203 fix: misc permission issues 2025-12-04 17:15:14 +05:30
Jannat Patel
a3bd9d2706 Merge pull request #1879 from pateljannat/issues-156
fix: use dayjs from frappe-ui in evaluation modal
2025-12-04 11:24:49 +05:30
Jannat Patel
2cdd45da96 fix: use dayjs from frappe-ui in evaluation modal 2025-12-04 11:06:34 +05:30
Jannat Patel
0367c1db72 fix: validate course and batch before a reply is added 2025-12-04 09:25:07 +05:30
Jannat Patel
5f17802ab8 fix: only moderators should be allowed to delete sidebar pages 2025-12-04 09:24:27 +05:30
Jannat Patel
b3e90c7f2f fix: validate before enrolling in batch 2025-12-03 15:44:19 +05:30
Jannat Patel
6c2978306c Merge branch 'develop' of https://github.com/frappe/lms into develop 2025-12-03 14:18:20 +05:30
Jannat Patel
b951e1567c chore: upgraded dependencies 2025-12-03 14:17:49 +05:30
Jannat Patel
97cdb57406 Merge pull request #1878 from pateljannat/issues-154
fix: increased the rate limit
2025-12-03 10:56:05 +05:30
Jannat Patel
651300b043 Merge pull request #1877 from frappe/develop
chore: merge 'develop' into 'main'
2025-12-03 10:48:37 +05:30
Jannat Patel
b537e2789d fix: increased the rate limit 2025-12-03 10:43:19 +05:30
Jannat Patel
3cd766bc74 Merge pull request #1876 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-03 10:38:46 +05:30
Jannat Patel
30ee06c0ab Merge pull request #1874 from pateljannat/closed-job-applications
feat: Closed job applications
2025-12-03 10:38:34 +05:30
Jannat Patel
74a1f1dc77 chore: Bosnian translations 2025-12-02 21:42:12 +05:30
Jannat Patel
7a6f6d2c7c chore: Croatian translations 2025-12-02 21:42:11 +05:30
Jannat Patel
de8868ec68 chore: Persian translations 2025-12-02 21:42:09 +05:30
Jannat Patel
7105d6271f fix: allow moderators to edit all jobs 2025-12-02 18:34:46 +05:30
Jannat Patel
b04a3de201 feat: closed jobs 2025-12-02 17:59:04 +05:30
Jannat Patel
0107032ee3 Merge pull request #1873 from pateljannat/issues-153
fix: misc issues
2025-12-02 16:41:05 +05:30
Jannat Patel
34e07b0083 fix: fetch correct application count 2025-12-02 16:31:54 +05:30
Jannat Patel
6c16516e89 fix: evaluators can only edit batches where they are instructors 2025-12-02 16:14:17 +05:30
Jannat Patel
7bc6dff6ea fix: misc issues 2025-12-02 15:41:42 +05:30
Jannat Patel
1c0be8a2ec Merge pull request #1870 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-02 11:37:50 +05:30
Jannat Patel
4a9f1197fb Merge pull request #1861 from pateljannat/data-import-fixes
fix: data import issues
2025-12-02 11:34:45 +05:30
Jannat Patel
ad4e7496cb chore: Slovenian translations 2025-12-01 21:28:42 +05:30
Jannat Patel
d321b81a64 chore: Esperanto translations 2025-12-01 21:28:40 +05:30
Jannat Patel
3691e3f240 chore: Serbian (Latin) translations 2025-12-01 21:28:38 +05:30
Jannat Patel
62e2fe56d0 chore: Norwegian Bokmal translations 2025-12-01 21:28:37 +05:30
Jannat Patel
4a6a98c533 chore: Bosnian translations 2025-12-01 21:28:35 +05:30
Jannat Patel
4e5d44c464 chore: Burmese translations 2025-12-01 21:28:34 +05:30
Jannat Patel
b2e8fbd84d chore: Croatian translations 2025-12-01 21:28:32 +05:30
Jannat Patel
41dde09a6a chore: Thai translations 2025-12-01 21:28:30 +05:30
Jannat Patel
7bbfb85b0e chore: Tamil translations 2025-12-01 21:28:29 +05:30
Jannat Patel
c99d05cf6c chore: Persian translations 2025-12-01 21:28:27 +05:30
Jannat Patel
f856aaaacd chore: Indonesian translations 2025-12-01 21:28:26 +05:30
Jannat Patel
d029d4e371 chore: Portuguese, Brazilian translations 2025-12-01 21:28:24 +05:30
Jannat Patel
04f3744624 chore: Vietnamese translations 2025-12-01 21:28:23 +05:30
Jannat Patel
4e1d00afff chore: Chinese Simplified translations 2025-12-01 21:28:21 +05:30
Jannat Patel
ccfa281490 chore: Turkish translations 2025-12-01 21:28:19 +05:30
Jannat Patel
8979b5ef0c chore: Swedish translations 2025-12-01 21:28:17 +05:30
Jannat Patel
765bd8bfab chore: Serbian (Cyrillic) translations 2025-12-01 21:28:16 +05:30
Jannat Patel
4e7f371c49 chore: Russian translations 2025-12-01 21:28:14 +05:30
Jannat Patel
5ca5624f99 chore: Portuguese translations 2025-12-01 21:28:13 +05:30
Jannat Patel
6e5066022b chore: Polish translations 2025-12-01 21:28:11 +05:30
Jannat Patel
b16d8cbd6d chore: Dutch translations 2025-12-01 21:28:10 +05:30
Jannat Patel
030b3095a9 chore: Italian translations 2025-12-01 21:28:08 +05:30
Jannat Patel
d9febdaf82 chore: Hungarian translations 2025-12-01 21:28:06 +05:30
Jannat Patel
3143fc8b2a chore: German translations 2025-12-01 21:28:05 +05:30
Jannat Patel
d8396d13b4 chore: Danish translations 2025-12-01 21:28:03 +05:30
Jannat Patel
6a474b25ef chore: Czech translations 2025-12-01 21:28:02 +05:30
Jannat Patel
c8494f3246 chore: Arabic translations 2025-12-01 21:28:00 +05:30
Jannat Patel
1d66f9695a chore: Spanish translations 2025-12-01 21:27:59 +05:30
Jannat Patel
053a9cd3a9 chore: French translations 2025-12-01 21:27:57 +05:30
Jannat Patel
b7546fd2f4 chore: fixed linters 2025-12-01 17:54:47 +05:30
Jannat Patel
61fc0a9ce7 chore: upgraded frappe-ui 2025-12-01 17:54:15 +05:30
Jannat Patel
251abad821 Merge pull request #1865 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-12-01 12:12:02 +05:30
Jannat Patel
f581e7894d Merge pull request #1867 from frappe/pot_develop_2025-11-28
chore: update POT file
2025-12-01 12:11:49 +05:30
Jannat Patel
c16b81bfa2 chore: Slovenian translations 2025-11-30 20:15:46 +05:30
Jannat Patel
4fcb19010c chore: Persian translations 2025-11-30 20:15:39 +05:30
Jannat Patel
7f1e2a18ea chore: Slovenian translations 2025-11-29 20:09:06 +05:30
Jannat Patel
7f4bb9e05d chore: Persian translations 2025-11-29 20:08:59 +05:30
frappe-pr-bot
58d9dd3c26 chore: update POT file 2025-11-28 16:04:30 +00:00
Jannat Patel
609ed3cb09 chore: Slovenian translations 2025-11-28 20:12:18 +05:30
Jannat Patel
67dd8ac151 Merge pull request #1864 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-28 16:19:37 +05:30
muhamiyan
fc82ec8070 fix: remove redirect to create new quiz and new assignment 2025-11-28 17:21:29 +07:00
Jannat Patel
f218230ad7 chore: Slovenian translations 2025-11-27 19:55:31 +05:30
Jannat Patel
f8380226ee chore: Croatian translations 2025-11-27 19:55:29 +05:30
Jannat Patel
b087faeb90 fix: batch instructors selection issue 2025-11-27 16:19:17 +05:30
Jannat Patel
a333b0b754 chore: fixed conflicts 2025-11-27 16:15:58 +05:30
Jannat Patel
2614fbc94c chore: upgraded frappe-ui to latest version 2025-11-27 14:34:08 +05:30
Jannat Patel
66c70dd233 Merge pull request #1860 from pateljannat/issues-152
fix: round the amount after exchange rate calculation
2025-11-27 12:45:03 +05:30
Jannat Patel
8d1c0a7bd1 Merge pull request #1862 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-27 12:42:18 +05:30
Jannat Patel
eae74dacae fix: redirect to courses if user is not moderator on data import 2025-11-27 12:41:39 +05:30
Jannat Patel
ca0fed9f17 chore: Slovenian translations 2025-11-26 19:26:44 +05:30
Jannat Patel
cfc5d94711 chore: upgraded frappe-ui to latest version 2025-11-26 16:06:03 +05:30
Jannat Patel
924a11e4f4 chore: frappe-ui tree shaking changes 2025-11-26 15:06:21 +05:30
Jannat Patel
3be3124951 fix: profile update form 2025-11-26 15:05:55 +05:30
Jannat Patel
c846e36032 fix: data import issues 2025-11-26 14:14:09 +05:30
Jannat Patel
cf9e5f861b Merge pull request #1858 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-26 11:32:53 +05:30
Jannat Patel
219139e45b fix: round the amount after exchange rate calculation 2025-11-26 11:21:18 +05:30
Jannat Patel
eab43a66cf feat: improved search results in command palette 2025-11-25 19:44:31 +05:30
Jannat Patel
faa18d6a88 chore: Slovenian translations 2025-11-25 19:10:19 +05:30
Jannat Patel
470e446ae6 chore: Persian translations 2025-11-25 19:10:08 +05:30
Jannat Patel
af3f1a5fc3 chore: Turkish translations 2025-11-25 19:10:02 +05:30
Jannat Patel
3b925246ee chore: German translations 2025-11-25 19:09:51 +05:30
Jannat Patel
e2d7b409bd chore: Arabic translations 2025-11-25 19:09:47 +05:30
Jannat Patel
731e242974 chore: Spanish translations 2025-11-25 19:09:46 +05:30
Jannat Patel
9614f8eb9d chore: French translations 2025-11-25 19:09:44 +05:30
Jannat Patel
a98e8025c4 Merge pull request #1854 from rehanrehman389/fix-zoom
fix: data persistence issue
2025-11-25 10:42:10 +05:30
Jannat Patel
397b7ee032 Merge pull request #1807 from rehanrehman389/program-fix
Misc Programs Issues
2025-11-25 10:40:59 +05:30
Jannat Patel
22b041d252 Merge pull request #1852 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-25 10:40:30 +05:30
Jannat Patel
5f20d8ad63 chore: Slovenian translations 2025-11-24 19:11:54 +05:30
Jannat Patel
d3879a9d11 Merge pull request #1853 from frappe/pot_develop_2025-11-21
chore: update POT file
2025-11-24 10:01:27 +05:30
Jannat Patel
0d0b6dcd36 Merge pull request #1785 from pateljannat/data-import
feat: data import
2025-11-24 10:01:10 +05:30
Jannat Patel
588f796069 chore: Slovenian translations 2025-11-23 18:37:06 +05:30
Jannat Patel
440c51f1ca chore: Swedish translations 2025-11-23 18:36:52 +05:30
Rehan Ansari
b99b2bd123 refactor: simplify zoom account modal form handling 2025-11-22 23:29:35 +05:30
Jannat Patel
e502ff3491 chore: Slovenian translations 2025-11-22 18:07:54 +05:30
Jannat Patel
1ae0f87f9c chore: Serbian (Latin) translations 2025-11-22 18:07:53 +05:30
Jannat Patel
5a3e72eaaf chore: Croatian translations 2025-11-22 18:07:52 +05:30
Jannat Patel
41c8384ef5 chore: Serbian (Cyrillic) translations 2025-11-22 18:07:50 +05:30
frappe-pr-bot
12a5641cb4 chore: update POT file 2025-11-21 16:05:05 +00:00
Jannat Patel
e8cd305171 chore: upgraded frappe-ui to latest version 2025-11-21 20:22:32 +05:30
Jannat Patel
880b799df5 chore: upgraded frappe-ui to latest data import commit 2025-11-21 20:00:20 +05:30
Jannat Patel
4c6dc25589 chore: Slovenian translations 2025-11-21 18:01:52 +05:30
Jannat Patel
10a301eebc chore: Persian translations 2025-11-21 18:01:44 +05:30
Jannat Patel
e6e50c96e4 chore: Portuguese, Brazilian translations 2025-11-21 18:01:41 +05:30
Jannat Patel
fab2ee8420 chore: Polish translations 2025-11-21 18:01:33 +05:30
Jannat Patel
58eb3ccacb chore: Spanish translations 2025-11-21 18:01:25 +05:30
Jannat Patel
aacd5ab7a1 chore: upgraded frappe-ui to latest data import commit 2025-11-21 17:40:18 +05:30
Jannat Patel
38b0b9ceb1 Merge branch 'develop' of https://github.com/frappe/lms into data-import 2025-11-21 17:13:27 +05:30
Jannat Patel
9fb2a169e8 chore: downgraded vite-pwa-plugin 2025-11-21 16:46:31 +05:30
Jannat Patel
5977be4a14 chore: upgraded frappe-ui to latest data import commit 2025-11-21 15:53:04 +05:30
Jannat Patel
5b5c53bebc chore: added runtime caching for PWA 2025-11-21 15:02:42 +05:30
Jannat Patel
9b38e62eaf chore: upgraded node version in CI 2025-11-21 14:49:39 +05:30
Jannat Patel
7a490b19bd chore: downgraded vite-pwa-plugin 2025-11-21 14:29:56 +05:30
Jannat Patel
4d34e9e702 Merge pull request #1773 from HUMENTH/patch-2
fix: beautifulsoap4 version upgrade to match with Frappe Framework
2025-11-21 11:32:28 +05:30
Jannat Patel
6fbd504e39 Merge branch 'develop' into patch-2 2025-11-21 10:48:55 +05:30
Jannat Patel
b0b79f1d19 test: click on dropdown option to open course or batch form 2025-11-20 18:30:52 +05:30
Jannat Patel
3d7a3ecfc5 refactor: new data import flow 2025-11-20 18:02:02 +05:30
Jannat Patel
9b7d763d52 refactor: new data import flow 2025-11-20 18:01:36 +05:30
Jannat Patel
aae8624269 Merge pull request #1851 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-19 21:44:59 +05:30
Jannat Patel
7fd16915c0 chore: Persian translations 2025-11-19 18:02:16 +05:30
Jannat Patel
eacb9fe356 Merge pull request #1846 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-18 19:43:28 +05:30
Jannat Patel
a7305b679e Merge branch 'develop' into l10n_develop2 2025-11-18 19:09:55 +05:30
Jannat Patel
d48405d440 chore: Esperanto translations 2025-11-18 18:04:28 +05:30
Jannat Patel
35157c0b58 chore: Serbian (Latin) translations 2025-11-18 18:04:27 +05:30
Jannat Patel
ee7aacd776 chore: Norwegian Bokmal translations 2025-11-18 18:04:25 +05:30
Jannat Patel
2c08b94c3a chore: Bosnian translations 2025-11-18 18:04:23 +05:30
Jannat Patel
d34cfbc327 chore: Burmese translations 2025-11-18 18:04:22 +05:30
Jannat Patel
d9933e6933 chore: Croatian translations 2025-11-18 18:04:20 +05:30
Jannat Patel
7dbb215a18 chore: Thai translations 2025-11-18 18:04:19 +05:30
Jannat Patel
132f8be21c chore: Tamil translations 2025-11-18 18:04:17 +05:30
Jannat Patel
5fac2198cd chore: Persian translations 2025-11-18 18:04:15 +05:30
Jannat Patel
f94b4d1205 chore: Indonesian translations 2025-11-18 18:04:14 +05:30
Jannat Patel
fc75f92e89 chore: Portuguese, Brazilian translations 2025-11-18 18:04:13 +05:30
Jannat Patel
998049872d chore: Vietnamese translations 2025-11-18 18:04:11 +05:30
Jannat Patel
f9ed0bab5e chore: Chinese Simplified translations 2025-11-18 18:04:10 +05:30
Jannat Patel
b87497411b chore: Turkish translations 2025-11-18 18:04:08 +05:30
Jannat Patel
b6be206630 chore: Swedish translations 2025-11-18 18:04:07 +05:30
Jannat Patel
f7a4350fe8 chore: Serbian (Cyrillic) translations 2025-11-18 18:04:05 +05:30
Jannat Patel
802a104a49 chore: Russian translations 2025-11-18 18:04:03 +05:30
Jannat Patel
35aea2dd77 chore: Portuguese translations 2025-11-18 18:04:02 +05:30
Jannat Patel
6ef7dd75e9 chore: Polish translations 2025-11-18 18:04:00 +05:30
Jannat Patel
115ccdb26a chore: Dutch translations 2025-11-18 18:03:59 +05:30
Jannat Patel
6f888bcf4a chore: Italian translations 2025-11-18 18:03:58 +05:30
Jannat Patel
d55de747f5 chore: Hungarian translations 2025-11-18 18:03:56 +05:30
Jannat Patel
ba45c57cc6 chore: German translations 2025-11-18 18:03:54 +05:30
Jannat Patel
8632f81237 chore: Danish translations 2025-11-18 18:03:53 +05:30
Jannat Patel
f7c2ec7fa6 chore: Czech translations 2025-11-18 18:03:51 +05:30
Jannat Patel
6617c1ef54 chore: Arabic translations 2025-11-18 18:03:49 +05:30
Jannat Patel
229c537731 chore: Spanish translations 2025-11-18 18:03:48 +05:30
Jannat Patel
d0060d828f chore: French translations 2025-11-18 18:03:46 +05:30
Jannat Patel
98f9778464 Merge pull request #1845 from frappe/pot_develop_2025-11-14
chore: update POT file
2025-11-17 20:01:25 +05:30
Jannat Patel
1f6a0194f7 chore: resolved conflicts 2025-11-17 10:12:26 +05:30
Jannat Patel
c7915e2c3d feat: launch command palette 2025-11-17 10:10:26 +05:30
Jannat Patel
41f7979eb4 chore: Norwegian Bokmal translations 2025-11-16 17:56:52 +05:30
Jannat Patel
343ed8d22f chore: Bosnian translations 2025-11-16 17:56:51 +05:30
Jannat Patel
1c038d3334 chore: Persian translations 2025-11-16 17:56:46 +05:30
Jannat Patel
1c3cdec563 chore: Indonesian translations 2025-11-16 17:56:44 +05:30
Jannat Patel
6c37f0f4fe chore: Turkish translations 2025-11-16 17:56:40 +05:30
Jannat Patel
f9967bff2e chore: Swedish translations 2025-11-16 17:56:39 +05:30
Jannat Patel
96a9e34487 chore: Italian translations 2025-11-16 17:56:34 +05:30
Jannat Patel
2eb1574131 chore: German translations 2025-11-16 17:56:32 +05:30
Jannat Patel
583871912b chore: Danish translations 2025-11-16 17:56:30 +05:30
Jannat Patel
f4483d7973 chore: Arabic translations 2025-11-16 17:56:28 +05:30
Jannat Patel
21f49690bd chore: Spanish translations 2025-11-16 17:56:27 +05:30
Jannat Patel
18ebac2130 chore: French translations 2025-11-16 17:56:25 +05:30
frappe-pr-bot
1deee7e396 chore: update POT file 2025-11-14 16:05:00 +00:00
Jannat Patel
0a72f0a9a9 Merge pull request #1844 from pateljannat/issues-150
fix: misc issues
2025-11-14 18:14:00 +05:30
Jannat Patel
1932338660 test: open students tab before adding to batch 2025-11-14 18:06:23 +05:30
Jannat Patel
a481bcd974 fix: test case table ux 2025-11-14 17:46:31 +05:30
Jannat Patel
d86fd0f6f6 fix: misc issues 2025-11-14 12:48:46 +05:30
Jannat Patel
bca70e0842 Merge pull request #1843 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-13 19:15:14 +05:30
Jannat Patel
cf659f93d8 Merge branch 'develop' into l10n_develop2 2025-11-13 17:42:48 +05:30
Jannat Patel
8bfc2a5297 Merge pull request #1777 from JoeBrar/feature/coupons
feat: coupon code discount
2025-11-13 17:42:34 +05:30
Jannat Patel
783f0ed750 chore: German translations 2025-11-13 17:11:59 +05:30
Jannat Patel
90e4097fa3 fix: update redemption count when payment received 2025-11-13 14:50:55 +05:30
Jannat Patel
2aac558d4a fix: don't escape batch details field 2025-11-13 14:22:50 +05:30
Jannat Patel
9d3714eb90 Merge pull request #1841 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-13 11:47:06 +05:30
Jannat Patel
46f5808fdb refactor: transactions changes based on coupon code 2025-11-13 11:46:35 +05:30
Jannat Patel
8dbc85d03d chore: Bosnian translations 2025-11-12 17:15:50 +05:30
Jannat Patel
f4be59f958 chore: Croatian translations 2025-11-12 17:15:48 +05:30
Jannat Patel
4d38f0637c chore: Persian translations 2025-11-12 17:15:46 +05:30
Jannat Patel
feb7758830 chore: Swedish translations 2025-11-12 17:15:45 +05:30
Jannat Patel
915be8dbdc Merge pull request #1835 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-12 10:51:02 +05:30
Jannat Patel
3a6d0998c4 chore: Esperanto translations 2025-11-11 17:19:40 +05:30
Jannat Patel
048bc7e421 chore: Serbian (Latin) translations 2025-11-11 17:19:39 +05:30
Jannat Patel
3b0643da47 chore: Norwegian Bokmal translations 2025-11-11 17:19:36 +05:30
Jannat Patel
f873b396b6 chore: Bosnian translations 2025-11-11 17:19:35 +05:30
Jannat Patel
5ec67115ba chore: Burmese translations 2025-11-11 17:19:33 +05:30
Jannat Patel
2a21714ed1 chore: Croatian translations 2025-11-11 17:19:31 +05:30
Jannat Patel
1913529bf0 chore: Thai translations 2025-11-11 17:19:30 +05:30
Jannat Patel
419f47d36e chore: Tamil translations 2025-11-11 17:19:28 +05:30
Jannat Patel
fba2b1ea9b chore: Persian translations 2025-11-11 17:19:27 +05:30
Jannat Patel
9f53e2c8da chore: Indonesian translations 2025-11-11 17:19:25 +05:30
Jannat Patel
13f2a79ebb chore: Portuguese, Brazilian translations 2025-11-11 17:19:24 +05:30
Jannat Patel
800f624d1d chore: Vietnamese translations 2025-11-11 17:19:22 +05:30
Jannat Patel
91e7b18506 chore: Chinese Simplified translations 2025-11-11 17:19:21 +05:30
Jannat Patel
cc3a46b1ff chore: Turkish translations 2025-11-11 17:19:19 +05:30
Jannat Patel
a4aed7b61f chore: Swedish translations 2025-11-11 17:19:17 +05:30
Jannat Patel
1aee97c64f chore: Serbian (Cyrillic) translations 2025-11-11 17:19:16 +05:30
Jannat Patel
d33980a8c6 chore: Russian translations 2025-11-11 17:19:14 +05:30
Jannat Patel
c589def0de chore: Portuguese translations 2025-11-11 17:19:12 +05:30
Jannat Patel
c70ef1e2e5 chore: Polish translations 2025-11-11 17:19:11 +05:30
Jannat Patel
60b6a73bd7 chore: Dutch translations 2025-11-11 17:19:09 +05:30
Jannat Patel
b755f3b29b chore: Italian translations 2025-11-11 17:19:07 +05:30
Jannat Patel
be4eeeb9d7 chore: Hungarian translations 2025-11-11 17:19:06 +05:30
Jannat Patel
31beae63fd chore: German translations 2025-11-11 17:19:04 +05:30
Jannat Patel
16a530fb50 chore: Danish translations 2025-11-11 17:19:03 +05:30
Jannat Patel
b8c5b7f479 chore: Czech translations 2025-11-11 17:19:01 +05:30
Jannat Patel
f64475b793 chore: Arabic translations 2025-11-11 17:18:59 +05:30
Jannat Patel
8e6de04e23 chore: Spanish translations 2025-11-11 17:18:58 +05:30
Jannat Patel
25f6440b1b chore: French translations 2025-11-11 17:18:56 +05:30
Jannat Patel
c1bdfe33f0 Merge branch 'develop' of https://github.com/frappe/lms into feature/coupons 2025-11-11 16:28:07 +05:30
Jannat Patel
d7de538345 Merge pull request #1834 from pateljannat/issues-149
fix: clear user cache after updating roles
2025-11-11 15:42:16 +05:30
Jannat Patel
d2950bc0b5 fix: clear user cache after updating roles 2025-11-11 15:32:44 +05:30
Jannat Patel
6333f58f56 Merge pull request #1831 from pateljannat/issues-148
fix: misc
2025-11-11 14:30:50 +05:30
Jannat Patel
bc187aabfe test: open settings before creating evaluator in batch test 2025-11-11 13:17:10 +05:30
Jannat Patel
f825887181 fix: replaced notifications Link with a tag 2025-11-11 13:01:51 +05:30
Jannat Patel
2bdc35055c Merge pull request #1815 from rehanrehman389/link-fix
fix: ensure options reload after updates
2025-11-11 13:00:26 +05:30
Jannat Patel
b6602f9e4b feat: session refresh 2025-11-11 12:55:51 +05:30
Jannat Patel
ab366837a2 fix: escape HTML in forms 2025-11-11 12:17:26 +05:30
Jannat Patel
c0a7a9b753 Merge branch 'develop' of https://github.com/frappe/lms into data-import 2025-11-10 15:35:42 +05:30
Jannat Patel
c951732eb4 fix: profile edit issue 2025-11-10 15:11:13 +05:30
Jannat Patel
1b638d118d chore: upgraded frappe-ui to latest data import commit 2025-11-10 15:08:07 +05:30
Jannat Patel
1173ac6504 Merge pull request #1830 from pateljannat/issues-147
fix: misc issues
2025-11-10 13:42:13 +05:30
Jannat Patel
9447903d5b fix: misc issues 2025-11-10 13:19:54 +05:30
Jannat Patel
fff9769791 fix: profile slot and schedule tab visibility 2025-11-10 12:09:51 +05:30
Jannat Patel
d5c5faf0ca Merge pull request #1829 from pateljannat/issues-146
fix: evaluation issues
2025-11-10 11:34:18 +05:30
Jannat Patel
85d793ee64 fix: evaluation issues 2025-11-10 11:24:43 +05:30
Jannat Patel
cf9c9fb5d3 chore: upgraded frappe-ui to latest data import commit 2025-11-10 10:39:43 +05:30
Jannat Patel
b7144727e9 Merge pull request #1828 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-10 09:12:29 +05:30
Jannat Patel
60cd84972c Merge pull request #1827 from frappe/pot_develop_2025-11-07
chore: update POT file
2025-11-10 09:12:16 +05:30
Jannat Patel
5854d2514f fix: reload import list after import completes 2025-11-09 15:16:32 +05:30
Jannat Patel
226893a8b2 chore: upgraded frappe-ui to latest data import commit 2025-11-09 14:57:27 +05:30
Jannat Patel
b182d5ea16 fix: replaced Link component with a tag in notifications 2025-11-09 12:12:35 +05:30
Jannat Patel
6bc28eafbf chore: upgraded frappe-ui to latest data import commit 2025-11-08 19:28:54 +05:30
Jannat Patel
1983190da3 chore: Indonesian translations 2025-11-08 14:05:12 +05:30
frappe-pr-bot
656c0cf012 chore: update POT file 2025-11-07 16:04:28 +00:00
Jannat Patel
60ddc1f8b2 chore: merged conflicts 2025-11-07 15:45:21 +05:30
Jannat Patel
c3604ab74a chore: changed frappe-ui version to data-import commit 2025-11-07 15:26:25 +05:30
Jannat Patel
eac0428dc6 Merge pull request #1822 from frappe/develop
chore: merge 'develop' into 'main'
2025-11-07 12:41:36 +05:30
Frappe PR Bot
53cd427a75 chore(release): Bumped to Version 2.40.0 2025-11-07 06:34:46 +00:00
Jannat Patel
96cf6ddbf9 Merge pull request #1791 from rehanrehman389/job-application
feat: show job applications on frontend
2025-11-07 12:02:02 +05:30
Jannat Patel
66c2ec013c Merge pull request #1814 from rehanrehman389/fix-resume-url
fix: use file_url instead of file_name
2025-11-07 12:00:54 +05:30
Rehan Ansari
e3d5bf0220 refactor: add dropdown menu for job application actions 2025-11-06 23:42:36 +05:30
Jannat Patel
3281358282 fix: added horizontal scroll to settings modal 2025-11-06 16:45:31 +05:30
Jannat Patel
18e499e6de fix: coupon code application on billing page 2025-11-04 15:12:17 +05:30
Rehan Ansari
514d52f895 fix: improve job applications UI 2025-11-03 09:49:01 +05:30
Rehan Ansari
8719fa6696 feat: refactor job applications UI with ListView 2025-11-03 01:49:59 +05:30
Rehan Ansari
9c38444c4b fix: make resumes private, linked and shared with job owner 2025-11-01 19:08:36 +05:30
Rehan Ansari
a5f9adc875 fix: patch old resumes to be private, link to job application, and share with job owner 2025-11-01 14:48:45 +05:30
Rehan Ansari
74de43c3d6 fix: ensure options reload after updates 2025-10-31 00:10:16 +05:30
Rehan Ansari
9f81bf695c fix: use file_url instead of file_name 2025-10-30 01:03:01 +05:30
Jannat Patel
3caf743f29 Merge pull request #1811 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-29 21:02:30 +05:30
Jannat Patel
ad28218893 refactor: coupon list and form 2025-10-28 18:05:31 +05:30
Jannat Patel
760811f172 Merge branch 'develop' of https://github.com/frappe/lms into feature/coupons 2025-10-27 12:39:10 +05:30
Rehan Ansari
68e7684da3 fix: avoid backend updates without save action 2025-10-25 14:42:22 +05:30
Rehan Ansari
ca2cc7bbda fix: improve delete button UX and add confirmation dialog 2025-10-25 14:09:31 +05:30
Rehan Ansari
741cc4ccc7 fix: allow adding courses/members to new programs 2025-10-25 13:03:53 +05:30
Rehan Ansari
0c1f1fada4 fix: incorrect error msg 2025-10-25 10:09:31 +05:30
Himanshu Shivhare
5a91c73a91 Merge branch 'develop' into patch-2 2025-10-22 17:38:05 +05:30
Rehan Ansari
9da1bfeea1 fix: set job owner as reply-to for job emails 2025-10-20 13:21:52 +05:30
Jannat Patel
d4b603a4dd Merge pull request #1792 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-20 12:08:28 +05:30
Rehan Ansari
a1a302f222 feat: show job applications on frontend 2025-10-20 11:17:02 +05:30
Jannat Patel
7d2b98f674 Merge pull request #1782 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-15 12:21:17 +05:30
Jannat Patel
2f5b0a3bf8 feat: data import 2025-10-14 19:21:35 +05:30
Joedeep Singh
1efd5ebad5 Merge branch 'develop' into feature/coupons 2025-10-13 21:37:44 +05:30
Joedeep Singh
acb5e5e1c9 chore: removed unnecessary comments and log statements 2025-10-13 15:35:56 +00:00
Joedeep Singh
0f24fd6edc feat(coupon-details): prevent duplicate course/batch selections and clear name on type change 2025-10-13 14:54:03 +00:00
Joedeep Singh
99c448e0e5 chore: applied pre-commit hooks 2025-10-13 14:06:37 +00:00
Joedeep Singh
cabb499a43 fix: correctly show coupon code in transactions 2025-10-13 13:47:02 +00:00
Joedeep Singh
6933105261 feat: implement coupon code manage
ment with billing and transaction integration; bug fixes
2025-10-13 13:03:43 +00:00
Joedeep Singh
bf36890bd3 feat: added coupon code functionality 2025-10-12 17:07:42 +00:00
Himanshu Shivhare
05b6b97b2a fix: beautifulsoap4 version upgrade to match with Frappe Framework 2025-10-12 17:00:34 +05:30
Jannat Patel
6a8aca39a0 Merge pull request #1764 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-10 16:45:01 +05:30
Jannat Patel
7434a324fb Merge pull request #1754 from frappe/develop
chore: merge 'develop' into 'main'
2025-10-01 13:08:16 +05:30
Jannat Patel
fd934e1e82 Merge pull request #1735 from frappe/develop
chore: merge 'develop' into 'main'
2025-09-24 13:02:43 +05:30
Jannat Patel
dd94d12e3a Merge pull request #1723 from frappe/develop
chore: merge 'develop' into 'main'
2025-09-19 10:51:28 +05:30
Jannat Patel
fe85dd867d Merge branch 'develop' of https://github.com/frappe/lms 2025-09-03 15:44:43 +05:30
Jannat Patel
12f2047910 Merge pull request #1709 from frappe/develop
chore: merge 'develop' into 'main'
2025-09-03 12:30:33 +05:30
Jannat Patel
cb6931bd88 Merge pull request #1699 from frappe/develop
chore: merge 'develop' into 'main'
2025-08-29 17:42:38 +05:30
Jannat Patel
c76f9141fc Merge pull request #1685 from frappe/develop
chore: merge 'develop' into 'main'
2025-08-13 10:47:33 +05:30
Jannat Patel
ddf70ce3d4 Merge pull request #1662 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-30 11:57:59 +05:30
Jannat Patel
7beb82e804 Merge pull request #1654 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-23 11:34:46 +05:30
Jannat Patel
8d9b8951bf Merge pull request #1652 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-23 11:10:44 +05:30
Jannat Patel
b20d045c8e Merge pull request #1650 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-22 19:00:42 +05:30
Jannat Patel
736ce8eb3f Merge pull request #1636 from frappe/develop
chore: merge 'develop' into 'main'
2025-07-14 12:06:42 +05:30
Jannat Patel
3409049559 Merge pull request #1576 from frappe/develop
chore: merge 'develop' into 'main'
2025-06-16 17:17:02 +05:30
Jannat Patel
5212122946 Merge pull request #1570 from frappe/develop
chore: merge 'develop' into 'main'
2025-06-10 15:51:20 +05:30
431 changed files with 65872 additions and 47161 deletions

View File

@@ -3,10 +3,15 @@ on:
push:
branches:
- main
- develop
- main-hotfix
pull_request: {}
jobs:
tests:
name: Server Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
services:
redis-cache:
image: redis:alpine
@@ -30,13 +35,13 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.14'
- name: setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v6
with:
node-version: '18'
node-version: '24'
check-latest: true
- name: setup cache for bench
uses: actions/cache@v4
@@ -69,6 +74,9 @@ jobs:
- name: setup requirements
working-directory: /home/runner/frappe-bench
run: bench setup requirements --dev
- name: block endpoints
working-directory: /home/runner/frappe-bench
run: bench --site frappe.local set-config block_endpoints 1
- name: allow tests
working-directory: /home/runner/frappe-bench
run: bench --site frappe.local set-config allow_tests true
@@ -77,4 +85,27 @@ jobs:
run: bench --site frappe.local build
- name: run tests
working-directory: /home/runner/frappe-bench
run: bench --site frappe.local run-tests --app lms
run: bench --site frappe.local run-tests --app lms --coverage
- name: Upload coverage data
uses: actions/upload-artifact@v4
with:
path: /home/runner/frappe-bench/sites/coverage.xml
coverage:
name: Coverage Wrap Up
needs: tests
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Upload coverage data
uses: codecov/codecov-action@v5
with:
name: Server
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
verbose: true

View File

@@ -22,9 +22,14 @@ jobs:
ref: ${{ matrix.branch }}
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
python-version: "3.14"
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Run script to update POT file
run: |

View File

@@ -16,9 +16,9 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 200
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 20
node-version: 24
check-latest: true
- name: Check commit titles
@@ -35,9 +35,9 @@ jobs:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.14'
- name: Cache pip
uses: actions/cache@v4

View File

@@ -18,9 +18,9 @@ jobs:
owner: frappe
repo: lms
title: |-
"chore: merge 'develop' into 'main'"
"chore: merge 'main-hotfix' into 'main'"
body: "Automated weekly release"
base: main
head: develop
head: main-hotfix
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}

View File

@@ -15,9 +15,9 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 24
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save

View File

@@ -4,7 +4,10 @@ on:
pull_request:
workflow_dispatch:
push:
branches: [ main ]
branches:
- main
- develop
- main-hotfix
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
@@ -36,9 +39,9 @@ jobs:
uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.11'
python-version: '3.14'
- name: Check for valid Python & Merge Conflicts
run: |
@@ -48,9 +51,9 @@ jobs:
exit 1
fi
- uses: actions/setup-node@v3
- uses: actions/setup-node@v6
with:
node-version: 18
node-version: 24
check-latest: true
- name: Add to Hosts

4
.gitignore vendored
View File

@@ -12,4 +12,6 @@ node_modules
package-lock.json
lms/public/frontend
lms/www/lms.html
frappe-ui
lms/www/_lms.html
frappe-ui
frappe-semgrep-rules

30
.mergify.yml Normal file
View File

@@ -0,0 +1,30 @@
pull_request_rules:
- name: backport to develop
conditions:
- label="backport develop"
actions:
backport:
branches:
- develop
assignees:
- "{{ author }}"
- name: backport to main-hotfix
conditions:
- label="backport main-hotfix"
actions:
backport:
branches:
- main-hotfix
assignees:
- "{{ author }}"
- name: backport to main
conditions:
- label="backport main"
actions:
backport:
branches:
- main
assignees:
- "{{ author }}"

View File

@@ -1,5 +1,5 @@
{
"branches": ["develop"],
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular"

2
codecov.yml Normal file
View File

@@ -0,0 +1,2 @@
ignore:
- "**/test_helper.py"

View File

@@ -27,6 +27,10 @@ describe("Batch Creation", () => {
cy.get("input[placeholder='Jane']").type(randomName);
cy.get("button").contains("Add").click();
// Open Settings
cy.get("span").contains("Learning").click();
cy.get("span").contains("Settings").click();
// Add evaluator
cy.get("[data-dismissable-layer]")
.find("span")
@@ -48,26 +52,23 @@ describe("Batch Creation", () => {
// Create a batch
cy.get("button").contains("Create").click();
cy.get("span").contains("New Batch").click();
cy.wait(500);
cy.url().should("include", "/batches/new/edit");
cy.get("label").contains("Title").type("Test Batch");
cy.get("label").contains("Start Date").type("2030-10-01");
cy.get("label").contains("End Date").type("2030-10-31");
cy.get("label").contains("Start Time").type("10:00");
cy.get("label").contains("End Time").type("11:00");
cy.get("label").contains("Timezone").type("IST");
cy.get("label").contains("Seat Count").type("10");
cy.get("label").contains("Published").click();
cy.get("label")
.contains("Short Description")
.contains("Description")
.type("Test Batch Short Description to test the UI");
cy.get("div[contenteditable=true").invoke(
"text",
"Test Batch Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
);
/* Instructor */
cy.get("label")
.contains("Instructors")
@@ -85,13 +86,14 @@ describe("Batch Creation", () => {
cy.get("[id^=headlessui-combobox-option-").first().click();
});
});
cy.button("Save").click();
cy.get("label").contains("Published").click();
cy.button("Save").click();
cy.wait(1000);
let batchName;
cy.url().then((url) => {
console.log(url);
batchName = url.split("/").pop();
batchName = url.split("/").pop().split("#")[0];
cy.wrap(batchName).as("batchName");
});
cy.wait(500);
@@ -110,7 +112,7 @@ describe("Batch Creation", () => {
.click();
cy.get("@batchName").then((batchName) => {
cy.get(`a[href='/lms/batches/details/${batchName}'`).within(() => {
cy.get(`a[href='/lms/batches/${batchName}'`).within(() => {
cy.get("div").contains("Test Batch").should("be.visible");
cy.get("div")
.contains("Test Batch Short Description to test the UI")
@@ -123,14 +125,11 @@ describe("Batch Creation", () => {
.should("be.visible");
cy.get("span").contains("IST").should("be.visible");
cy.get("a").contains("Evaluator").should("be.visible");
cy.get("div")
.contains("10")
.should("be.visible")
.get("span")
.contains("Seats Left")
.should("be.visible");
cy.contains("div:visible", "10 Seats Left").should(
"be.visible"
);
});
cy.get(`a[href='/lms/batches/details/${batchName}'`).click();
cy.get(`a[href='/lms/batches/${batchName}'`).click();
});
cy.get("div").contains("Test Batch").should("be.visible");
@@ -152,17 +151,22 @@ describe("Batch Creation", () => {
"Test Batch Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
)
.should("be.visible");
cy.get("button:visible").contains("Manage Batch").click();
cy.get("button:visible").contains("Dashboard").click();
/* Add student to batch */
cy.get("button").contains("Add").click();
cy.get('div[role="dialog"]').first().find("button").eq(1).click();
cy.get("input[id^='headlessui-combobox-input-v-']").type(randomEmail);
cy.get("button").contains("Enroll").click();
cy.get('div[role="dialog"]')
.first()
.find("div[label='Student']")
.find("div")
.first()
.click();
cy.get("input[placeholder='Search']").type(randomEmail);
cy.get("div").contains(randomEmail).click();
cy.get("button").contains("Submit").click();
// Verify Seat Count
cy.get("span").contains("Details").click();
cy.get("button:visible").contains("Overview").click();
cy.contains("div:visible", "9 Seats Left").should("be.visible");
});
});

View File

@@ -9,8 +9,8 @@ describe("Course Creation", () => {
// Create a course
cy.get("button").contains("Create").click();
cy.get("span").contains("New Course").click();
cy.wait(500);
cy.url().should("include", "/courses/new/edit");
cy.get("label").contains("Title").type("Test Course");
cy.get("label")
@@ -34,27 +34,13 @@ describe("Course Creation", () => {
});
});
cy.get("label")
.contains("Preview Video")
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
cy.get("label")
.contains("Category")
.parent()
.within(() => {
cy.get("button").click();
});
cy.get("[id^=headlessui-combobox-option-")
.should("be.visible")
.first()
.click();
/* Instructor */
cy.get("label")
.contains("Instructors")
.parent()
.within(() => {
cy.get("input").click().type("frappe");
cy.wait(500);
cy.get("input")
.invoke("attr", "aria-controls")
.as("instructor_list_id");
@@ -67,13 +53,29 @@ describe("Course Creation", () => {
});
});
cy.button("Save").last().click();
// Edit Course Details
cy.wait(500);
cy.get("label")
.contains("Preview Video")
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
cy.get("label")
.contains("Category")
.parent()
.within(() => {
cy.get("button").click();
});
cy.get("div").contains("Business").click();
cy.get("label").contains("Published").click();
cy.get("label").contains("Published On").type("2021-01-01");
cy.button("Save").click();
// Add Chapter
cy.wait(1000);
cy.button("Add Chapter").click();
cy.button("Add").click();
cy.wait(1000);
cy.get("[data-dismissable-layer]")

View File

@@ -6,5 +6,7 @@
// biome-ignore lint: disable
export {}
declare global {
const LucideGithub: typeof import('~icons/lucide/github').default
const LucideLinkedin: typeof import('~icons/lucide/linkedin').default
const LucideTwitter: typeof import('~icons/lucide/twitter').default
}

View File

@@ -8,14 +8,10 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
Annoucements: typeof import('./src/components/Annoucements.vue')['default']
AnnouncementModal: typeof import('./src/components/Modals/AnnouncementModal.vue')['default']
AppHeader: typeof import('./src/components/AppHeader.vue')['default']
Apps: typeof import('./src/components/Apps.vue')['default']
AppSidebar: typeof import('./src/components/AppSidebar.vue')['default']
Apps: typeof import('./src/components/Sidebar/Apps.vue')['default']
AppSidebar: typeof import('./src/components/Sidebar/AppSidebar.vue')['default']
AssessmentModal: typeof import('./src/components/Modals/AssessmentModal.vue')['default']
AssessmentPlugin: typeof import('./src/components/AssessmentPlugin.vue')['default']
Assessments: typeof import('./src/components/Assessments.vue')['default']
Assignment: typeof import('./src/components/Assignment.vue')['default']
AssignmentForm: typeof import('./src/components/Modals/AssignmentForm.vue')['default']
AudioBlock: typeof import('./src/components/AudioBlock.vue')['default']
@@ -24,16 +20,8 @@ declare module 'vue' {
BadgeAssignments: typeof import('./src/components/Settings/BadgeAssignments.vue')['default']
BadgeForm: typeof import('./src/components/Settings/BadgeForm.vue')['default']
Badges: typeof import('./src/components/Settings/Badges.vue')['default']
BatchCard: typeof import('./src/components/BatchCard.vue')['default']
BatchCourseModal: typeof import('./src/components/Modals/BatchCourseModal.vue')['default']
BatchCourses: typeof import('./src/components/BatchCourses.vue')['default']
BatchDashboard: typeof import('./src/components/BatchDashboard.vue')['default']
BatchFeedback: typeof import('./src/components/BatchFeedback.vue')['default']
BatchOverlay: typeof import('./src/components/BatchOverlay.vue')['default']
BatchStudentProgress: typeof import('./src/components/Modals/BatchStudentProgress.vue')['default']
BatchStudents: typeof import('./src/components/BatchStudents.vue')['default']
BrandSettings: typeof import('./src/components/Settings/BrandSettings.vue')['default']
BulkCertificates: typeof import('./src/components/Modals/BulkCertificates.vue')['default']
Categories: typeof import('./src/components/Settings/Categories.vue')['default']
CertificationLinks: typeof import('./src/components/CertificationLinks.vue')['default']
ChapterModal: typeof import('./src/components/Modals/ChapterModal.vue')['default']
@@ -42,12 +30,18 @@ declare module 'vue' {
CodeEditor: typeof import('./src/components/Controls/CodeEditor.vue')['default']
CollapseSidebar: typeof import('./src/components/Icons/CollapseSidebar.vue')['default']
ColorSwatches: typeof import('./src/components/Controls/ColorSwatches.vue')['default']
CommandPalette: typeof import('./src/components/CommandPalette/CommandPalette.vue')['default']
CommandPaletteGroup: typeof import('./src/components/CommandPalette/CommandPaletteGroup.vue')['default']
Configuration: typeof import('./src/components/Sidebar/Configuration.vue')['default']
ContactUsEmail: typeof import('./src/components/ContactUsEmail.vue')['default']
CouponDetails: typeof import('./src/components/Settings/Coupons/CouponDetails.vue')['default']
CouponItems: typeof import('./src/components/Settings/Coupons/CouponItems.vue')['default']
CouponList: typeof import('./src/components/Settings/Coupons/CouponList.vue')['default']
Coupons: typeof import('./src/components/Settings/Coupons/Coupons.vue')['default']
CourseCard: typeof import('./src/components/CourseCard.vue')['default']
CourseCardOverlay: typeof import('./src/components/CourseCardOverlay.vue')['default']
CourseInstructors: typeof import('./src/components/CourseInstructors.vue')['default']
CourseOutline: typeof import('./src/components/CourseOutline.vue')['default']
CourseProgressSummary: typeof import('./src/components/Modals/CourseProgressSummary.vue')['default']
CourseReviews: typeof import('./src/components/CourseReviews.vue')['default']
CreateOutline: typeof import('./src/components/CreateOutline.vue')['default']
DateRange: typeof import('./src/components/Common/DateRange.vue')['default']
@@ -73,11 +67,9 @@ declare module 'vue' {
InviteIcon: typeof import('./src/components/Icons/InviteIcon.vue')['default']
JobApplicationModal: typeof import('./src/components/Modals/JobApplicationModal.vue')['default']
JobCard: typeof import('./src/components/JobCard.vue')['default']
LayoutHeader: typeof import('./src/components/LayoutHeader.vue')['default']
LessonContent: typeof import('./src/components/LessonContent.vue')['default']
LessonHelp: typeof import('./src/components/LessonHelp.vue')['default']
Link: typeof import('./src/components/Controls/Link.vue')['default']
LiveClass: typeof import('./src/components/LiveClass.vue')['default']
LiveClassAttendance: typeof import('./src/components/Modals/LiveClassAttendance.vue')['default']
LiveClassModal: typeof import('./src/components/Modals/LiveClassModal.vue')['default']
LMSLogo: typeof import('./src/components/Icons/LMSLogo.vue')['default']
@@ -88,6 +80,7 @@ declare module 'vue' {
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
Notes: typeof import('./src/components/Notes/Notes.vue')['default']
NotPermitted: typeof import('./src/components/NotPermitted.vue')['default']
NumberChartGraph: typeof import('./src/components/NumberChartGraph.vue')['default']
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
PaymentGatewayDetails: typeof import('./src/components/Settings/PaymentGatewayDetails.vue')['default']
PaymentGateways: typeof import('./src/components/Settings/PaymentGateways.vue')['default']
@@ -105,18 +98,19 @@ declare module 'vue' {
SettingDetails: typeof import('./src/components/Settings/SettingDetails.vue')['default']
SettingFields: typeof import('./src/components/Settings/SettingFields.vue')['default']
Settings: typeof import('./src/components/Settings/Settings.vue')['default']
SidebarLink: typeof import('./src/components/SidebarLink.vue')['default']
SidebarLink: typeof import('./src/components/Sidebar/SidebarLink.vue')['default']
StudentHeatmap: typeof import('./src/components/StudentHeatmap.vue')['default']
StudentModal: typeof import('./src/components/Modals/StudentModal.vue')['default']
Tags: typeof import('./src/components/Tags.vue')['default']
TransactionDetails: typeof import('./src/components/Settings/TransactionDetails.vue')['default']
Transactions: typeof import('./src/components/Settings/Transactions.vue')['default']
TransactionDetails: typeof import('./src/components/Settings/Transactions/TransactionDetails.vue')['default']
TransactionList: typeof import('./src/components/Settings/Transactions/TransactionList.vue')['default']
Transactions: typeof import('./src/components/Settings/Transactions/Transactions.vue')['default']
UnsplashImageBrowser: typeof import('./src/components/UnsplashImageBrowser.vue')['default']
UpcomingEvaluations: typeof import('./src/components/UpcomingEvaluations.vue')['default']
Uploader: typeof import('./src/components/Controls/Uploader.vue')['default']
UploadPlugin: typeof import('./src/components/UploadPlugin.vue')['default']
UserAvatar: typeof import('./src/components/UserAvatar.vue')['default']
UserDropdown: typeof import('./src/components/UserDropdown.vue')['default']
UserDropdown: typeof import('./src/components/Sidebar/UserDropdown.vue')['default']
VideoBlock: typeof import('./src/components/VideoBlock.vue')['default']
VideoStatistics: typeof import('./src/components/Modals/VideoStatistics.vue')['default']
ZoomAccountModal: typeof import('./src/components/Modals/ZoomAccountModal.vue')['default']

View File

@@ -6,55 +6,57 @@
"scripts": {
"dev": "vite",
"serve": "vite preview",
"build": "vite build --base=/assets/lms/frontend/ && yarn copy-html-entry",
"copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/lms.html"
"build": "vite build --base=/assets/lms/frontend/ && yarn copy-html-entry && yarn copy-colors-json",
"copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/_lms.html",
"copy-colors-json": "cp node_modules/frappe-ui/tailwind/colors.json src/utils/frappe-ui-colors.json"
},
"dependencies": {
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-python": "^6.2.1",
"@editorjs/checklist": "^1.6.0",
"@editorjs/code": "^2.9.0",
"@editorjs/editorjs": "^2.29.0",
"@editorjs/embed": "^2.7.0",
"@editorjs/header": "^2.8.1",
"@editorjs/inline-code": "^1.5.0",
"@editorjs/nested-list": "^1.4.2",
"@editorjs/paragraph": "^2.11.3",
"@editorjs/simple-image": "^1.6.0",
"@editorjs/table": "^2.4.2",
"@vueuse/router": "^12.7.0",
"ace-builds": "^1.36.2",
"apexcharts": "^4.3.0",
"chart.js": "^4.4.1",
"codemirror": "^6.0.1",
"dayjs": "^1.11.6",
"dompurify": "^3.2.6",
"feather-icons": "^4.28.0",
"frappe-ui": "^0.1.201",
"highlight.js": "^11.11.1",
"lucide-vue-next": "^0.383.0",
"markdown-it": "^14.0.0",
"pinia": "^2.0.33",
"plyr": "^3.7.8",
"socket.io-client": "^4.7.2",
"tailwindcss": "3.4.15",
"thememirror": "^2.0.1",
"typescript": "^5.7.2",
"vue": "^3.4.23",
"vue-chartjs": "^5.3.0",
"vue-codemirror": "^6.1.1",
"vue-draggable-next": "^2.2.1",
"vue-router": "^4.0.12",
"vue3-apexcharts": "^1.8.0",
"@codemirror/lang-html": "6.4.9",
"@codemirror/lang-javascript": "6.2.4",
"@codemirror/lang-json": "6.0.1",
"@codemirror/lang-python": "6.2.1",
"@editorjs/checklist": "1.6.0",
"@editorjs/code": "2.9.0",
"@editorjs/editorjs": "2.29.0",
"@editorjs/embed": "2.7.0",
"@editorjs/header": "2.8.1",
"@editorjs/inline-code": "1.5.0",
"@editorjs/nested-list": "1.4.2",
"@editorjs/paragraph": "2.11.3",
"@editorjs/simple-image": "1.6.0",
"@editorjs/table": "2.4.2",
"@vueuse/core": "^14.1.0",
"ace-builds": "1.36.2",
"apexcharts": "4.3.0",
"chart.js": "4.4.1",
"codemirror": "6.0.1",
"dayjs": "1.11.10",
"dompurify": "3.2.6",
"feather-icons": "4.28.0",
"frappe-ui": "^0.1.261",
"highlight.js": "11.11.1",
"lucide-vue-next": "0.383.0",
"markdown-it": "14.0.0",
"pinia": "2.0.33",
"plyr": "3.7.8",
"socket.io-client": "4.7.2",
"thememirror": "2.0.1",
"typescript": "5.7.2",
"vue": "^3.5.27",
"vue-chartjs": "5.3.0",
"vue-codemirror": "6.1.1",
"vue-draggable-next": "2.2.1",
"vue-router": "^4.6.4",
"vue3-apexcharts": "1.8.0",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.3",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.5",
"vite": "^5.0.11",
"vite-plugin-pwa": "^1.0.2"
"@vitejs/plugin-vue": "5.0.3",
"autoprefixer": "10.4.2",
"postcss": "8.4.5",
"tailwindcss": "^3.4.15",
"unplugin-auto-import": "^20.3.0",
"vite": "5.0.11",
"vite-plugin-pwa": "^1.2.0"
}
}

View File

@@ -3,18 +3,17 @@
<Layout class="isolate text-base">
<router-view />
</Layout>
<!--<InstallPrompt v-if="isMobile" />-->
<InstallPrompt v-if="isMobile && !settings.data?.disable_pwa" />
<Dialogs />
</FrappeUIProvider>
</template>
<script setup>
import { FrappeUIProvider } from 'frappe-ui'
import { Dialogs } from '@/utils/dialogs'
import { computed, onUnmounted, ref, watch } from 'vue'
import { computed, onUnmounted, ref } from 'vue'
import { useScreenSize } from './utils/composables'
import { usersStore } from '@/stores/user'
import { useSettings } from '@/stores/settings'
import { useRouter } from 'vue-router'
import { posthogSettings } from '@/telemetry'
import DesktopLayout from './components/DesktopLayout.vue'
import MobileLayout from './components/MobileLayout.vue'
import NoSidebarLayout from './components/NoSidebarLayout.vue'
@@ -23,7 +22,7 @@ import InstallPrompt from './components/InstallPrompt.vue'
const { isMobile } = useScreenSize()
const router = useRouter()
const noSidebar = ref(false)
const { userResource } = usersStore()
const { settings } = useSettings()
router.beforeEach((to, from, next) => {
if (to.query.fromLesson || to.path === '/persona') {
@@ -47,10 +46,4 @@ const Layout = computed(() => {
onUnmounted(() => {
noSidebar.value = false
})
watch(userResource, () => {
if (userResource.data) {
posthogSettings.reload()
}
})
</script>

View File

@@ -1,152 +0,0 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url("Inter-Thin.woff2?v=3.12") format("woff2"),
url("Inter-Thin.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url("Inter-ThinItalic.woff2?v=3.12") format("woff2"),
url("Inter-ThinItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url("Inter-ExtraLight.woff2?v=3.12") format("woff2"),
url("Inter-ExtraLight.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url("Inter-ExtraLightItalic.woff2?v=3.12") format("woff2"),
url("Inter-ExtraLightItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("Inter-Light.woff2?v=3.12") format("woff2"),
url("Inter-Light.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url("Inter-LightItalic.woff2?v=3.12") format("woff2"),
url("Inter-LightItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("Inter-Regular.woff2?v=3.12") format("woff2"),
url("Inter-Regular.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url("Inter-Italic.woff2?v=3.12") format("woff2"),
url("Inter-Italic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("Inter-Medium.woff2?v=3.12") format("woff2"),
url("Inter-Medium.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url("Inter-MediumItalic.woff2?v=3.12") format("woff2"),
url("Inter-MediumItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("Inter-SemiBold.woff2?v=3.12") format("woff2"),
url("Inter-SemiBold.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url("Inter-SemiBoldItalic.woff2?v=3.12") format("woff2"),
url("Inter-SemiBoldItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("Inter-Bold.woff2?v=3.12") format("woff2"),
url("Inter-Bold.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url("Inter-BoldItalic.woff2?v=3.12") format("woff2"),
url("Inter-BoldItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url("Inter-ExtraBold.woff2?v=3.12") format("woff2"),
url("Inter-ExtraBold.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url("Inter-ExtraBoldItalic.woff2?v=3.12") format("woff2"),
url("Inter-ExtraBoldItalic.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url("Inter-Black.woff2?v=3.12") format("woff2"),
url("Inter-Black.woff?v=3.12") format("woff");
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url("Inter-BlackItalic.woff2?v=3.12") format("woff2"),
url("Inter-BlackItalic.woff?v=3.12") format("woff");
}

View File

@@ -1,53 +0,0 @@
<template>
<div v-if="communications.data?.length">
<div v-for="comm in communications.data">
<div class="mb-8">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center">
<Avatar :label="comm.sender_full_name" size="lg" />
<div class="ml-2 text-ink-gray-7">
{{ comm.sender_full_name }}
</div>
</div>
<div class="text-sm">
{{ timeAgo(comm.communication_date) }}
</div>
</div>
<div
class="prose prose-sm bg-surface-menu-bar !min-w-full px-4 py-2 rounded-md"
v-html="comm.content"
></div>
</div>
</div>
</div>
<div v-else class="text-sm italic text-ink-gray-5">
{{ __('No announcements') }}
</div>
</template>
<script setup>
import { createResource, Avatar } from 'frappe-ui'
import { timeAgo } from '@/utils'
const props = defineProps({
batch: {
type: String,
required: true,
},
})
const communications = createResource({
url: 'lms.lms.api.get_announcements',
makeParams(value) {
return {
batch: props.batch,
}
},
auto: true,
cache: ['announcement', props.batch],
})
</script>
<style>
.prose-sm p {
margin: 0 0 0.5rem;
}
</style>

View File

@@ -26,28 +26,52 @@
v-model="quiz"
doctype="LMS Quiz"
:label="__('Select a quiz')"
placeholder=" "
:onCreate="(value, close) => redirectToForm()"
/>
<Link
v-else
v-model="assignment"
doctype="LMS Assignment"
:label="__('Select an assignment')"
:onCreate="(value, close) => redirectToForm()"
/>
<div v-else class="space-y-4">
<Link
v-if="filterAssignmentsByCourse"
v-model="assignment"
doctype="LMS Assignment"
:filters="{
course: route.params.courseName,
}"
placeholder=" "
:label="__('Select an Assignment')"
:onCreate="(value, close) => redirectToForm()"
/>
<Link
v-else
v-model="assignment"
doctype="LMS Assignment"
placeholder=" "
:label="__('Select an Assignment')"
:onCreate="(value, close) => redirectToForm()"
/>
<FormControl
type="checkbox"
:label="__('Filter assignments by course')"
v-model="filterAssignmentsByCourse"
/>
</div>
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog } from 'frappe-ui'
import { onMounted, ref, nextTick } from 'vue'
import { Dialog, FormControl } from 'frappe-ui'
import { nextTick, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { getLmsRoute } from '@/utils/basePath'
import Link from '@/components/Controls/Link.vue'
const show = ref(false)
const quiz = ref(null)
const assignment = ref(null)
const filterAssignmentsByCourse = ref(false)
const route = useRoute()
const props = defineProps({
type: {
@@ -71,7 +95,10 @@ const addAssessment = () => {
}
const redirectToForm = () => {
if (props.type == 'quiz') window.open('/lms/quizzes/new', '_blank')
else window.open('/lms/assignments/new', '_blank')
if (props.type == 'quiz') {
window.open(getLmsRoute('quizzes?new=true'), '_blank')
} else {
window.open(getLmsRoute('assignments?new=true'), '_blank')
}
}
</script>

View File

@@ -16,8 +16,8 @@
{{ __('Submission by') }} {{ submissionResource.doc?.member_name }}
</div>
</div>
<div class="text-sm text-ink-gray-7 font-medium mb-2">
{{ __('Question') }}:
<div class="text-ink-gray-9 font-semibold mb-5">
{{ __('Assignment Question') }}
</div>
<div
v-html="assignment.data.question"
@@ -25,9 +25,9 @@
></div>
</div>
<div class="flex flex-col">
<div class="p-5">
<div class="flex items-center justify-between mb-4">
<div class="flex flex-col overflow-y-auto">
<div class="p-5 space-y-5">
<div class="flex items-center justify-between">
<div class="font-semibold text-ink-gray-9">
{{ __('Submission') }}
</div>
@@ -42,7 +42,11 @@
>
{{ submissionResource.doc?.status }}
</Badge>
<Button variant="solid" @click="submitAssignment()">
<Button
v-if="canModifyAssignment"
variant="solid"
@click="submitAssignment()"
>
{{ __('Save') }}
</Button>
</div>
@@ -53,7 +57,7 @@
!['Pass', 'Fail'].includes(submissionResource.doc?.status) &&
submissionResource.doc?.owner == user.data?.name
"
class="bg-surface-blue-2 text-ink-blue-2 p-3 rounded-md leading-5 text-sm mb-4"
class="bg-surface-blue-2 text-ink-blue-2 p-3 rounded-md leading-5 text-sm"
>
{{ __("You've successfully submitted the assignment.") }}
{{
@@ -63,17 +67,24 @@
}}
{{ __('Feel free to make edits to your submission if needed.') }}
</div>
<div v-if="showUploader()">
<div class="text-xs text-ink-gray-5 mt-1 mb-2">
{{ __('Add your assignment as {0}').format(assignment.data.type) }}
<div v-if="showUploader()" class="border rounded-lg p-3">
<div class="font-semibold mb-2">
{{ __('Upload Assignment') }}
</div>
<div class="text-ink-gray-5 text-sm mt-1 mb-4">
{{
__('You can only upload {0} files').format(assignment.data.type)
}}
</div>
<FileUploader
v-if="!submissionFile"
v-if="!attachment"
:fileTypes="getType()"
:uploadArgs="{
private: true,
}"
:validateFile="validateFile"
:validateFile="
(file) => validateFile(file, assignment.data.type.toLowerCase())
"
@success="(file) => saveSubmission(file)"
>
<template #default="{ uploading, progress, openFileSelector }">
@@ -87,21 +98,20 @@
</template>
</FileUploader>
<div v-else>
<div class="flex text-ink-gray-7">
<div class="border self-start rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5" />
</div>
<div class="flex items-center text-ink-gray-7">
<a
:href="submissionFile.file_url"
:href="attachment"
target="_blank"
class="flex flex-col cursor-pointer !no-underline"
class="cursor-pointer !no-underline text-sm leading-5"
>
<span class="text-sm leading-5">
{{ submissionFile.file_name }}
</span>
<span class="text-sm text-ink-gray-5 mt-1">
{{ getFileSize(submissionFile.file_size) }}
</span>
<div class="flex items-center">
<div class="border rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5" />
</div>
<span>
{{ attachment.split('/').pop() }}
</span>
</div>
</a>
<X
v-if="canModifyAssignment"
@@ -130,10 +140,11 @@
@change="(val) => (answer = val)"
:editable="true"
:fixedMenu="true"
:readonly="!canModifyAssignment"
:uploadArgs="{
private: true,
}"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
@@ -142,13 +153,13 @@
user.data?.name == submissionResource.doc?.owner &&
submissionResource.doc?.comments
"
class="mt-8 p-3 bg-surface-blue-2 rounded-md"
class="mt-8 p-3 border rounded-lg bg-surface-gray-2"
>
<div class="text-sm text-ink-gray-5 font-medium mb-2">
{{ __('Comments by Evaluator') }}:
<div class="text-ink-gray-5 mb-4">
{{ __('Comments by Evaluator') }}
</div>
<div
class="leading-5 text-ink-gray-9"
class="leading-6 text-ink-gray-9"
v-html="submissionResource.doc.comments"
></div>
</div>
@@ -179,7 +190,10 @@
"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
:uploadArgs="{
private: true,
}"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
</div>
@@ -201,11 +215,11 @@ import {
} from 'frappe-ui'
import { computed, inject, onMounted, onBeforeUnmount, ref, watch } from 'vue'
import { FileText, X } from 'lucide-vue-next'
import { getFileSize } from '@/utils'
import { useRouter } from 'vue-router'
import { validateFile } from '@/utils'
const submissionFile = ref(null)
const answer = ref(null)
const attachment = ref(null)
const comments = ref(null)
const router = useRouter()
const user = inject('$user')
@@ -255,129 +269,98 @@ const assignment = createResource({
},
})
const newSubmission = createResource({
url: 'frappe.client.insert',
makeParams(values) {
let doc = {
doctype: 'LMS Assignment Submission',
assignment: props.assignmentID,
member: user.data?.name,
}
if (showUploader()) {
doc.assignment_attachment = submissionFile.value.file_url
} else {
doc.answer = answer.value
}
return {
doc: doc,
}
},
})
const imageResource = createResource({
url: 'lms.lms.api.get_file_info',
makeParams(values) {
return {
file_url: values.image,
}
},
auto: false,
onSuccess(data) {
submissionFile.value = data
},
})
const submissionResource = createDocumentResource({
doctype: 'LMS Assignment Submission',
name: props.submissionName,
auto: false,
onError(err) {
toast.error(err.messages?.[0] || err)
},
auto: false,
cache: [user.data?.name, props.assignmentID],
})
watch(submissionResource, () => {
if (submissionResource.doc) {
if (submissionResource.doc.assignment_attachment) {
imageResource.reload({
image: submissionResource.doc.assignment_attachment,
})
}
if (submissionResource.doc.answer) {
answer.value = submissionResource.doc.answer
}
if (submissionResource.doc.comments) {
comments.value = submissionResource.doc.comments
}
if (submissionResource.isDirty) {
isDirty.value = true
} else if (showUploader() && !submissionFile.value) {
isDirty.value = true
} else if (!showUploader() && !answer.value) {
isDirty.value = true
} else {
isDirty.value = false
}
if (!submissionResource.doc) return
console.log(submissionResource.doc)
if (submissionResource.doc.answer) {
answer.value = submissionResource.doc.answer
}
})
watch(submissionFile, () => {
if (props.submissionName == 'new' && submissionFile.value) {
isDirty.value = true
if (submissionResource.doc.assignment_attachment) {
attachment.value = submissionResource.doc.assignment_attachment
}
if (submissionResource.doc.comments) {
comments.value = submissionResource.doc.comments
}
})
const submitAssignment = () => {
if (props.submissionName != 'new') {
let evaluator =
submissionResource.doc && submissionResource.doc.owner != user.data?.name
? user.data?.name
: null
submissionResource.setValue.submit(
{
...submissionResource.doc,
assignment_attachment: submissionFile.value?.file_url,
evaluator: evaluator,
comments: comments.value,
answer: answer.value,
},
{
onSuccess(data) {
toast.success(__('Changes saved successfully'))
},
}
)
updateSubmission()
} else {
addNewSubmission()
}
}
const addNewSubmission = () => {
newSubmission.submit(
{},
let doc = {
doctype: 'LMS Assignment Submission',
assignment: props.assignmentID,
member: user.data?.name,
}
if (!showUploader()) {
doc.answer = answer.value
} else {
doc.assignment_attachment = attachment.value
}
call('frappe.client.insert', {
doc: doc,
})
.then((data) => {
toast.success(__('Assignment submitted successfully'))
if (router.currentRoute.value.name == 'AssignmentSubmission') {
router.push({
name: 'AssignmentSubmission',
params: {
assignmentID: props.assignmentID,
submissionName: data.name,
},
query: { fromLesson: router.currentRoute.value.query.fromLesson },
})
} else {
markLessonProgress()
router.go()
}
isDirty.value = false
submissionResource.name = data.name
submissionResource.reload()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const updateSubmission = () => {
let evaluator =
submissionResource.doc && submissionResource.doc.owner != user.data?.name
? user.data?.name
: null
submissionResource.setValue.submit(
{
...submissionResource.doc,
evaluator: evaluator,
comments: comments.value,
answer: answer.value,
assignment_attachment: attachment.value,
},
{
onSuccess(data) {
toast.success(__('Assignment submitted successfully'))
if (router.currentRoute.value.name == 'AssignmentSubmission') {
router.push({
name: 'AssignmentSubmission',
params: {
assignmentID: props.assignmentID,
submissionName: data.name,
},
query: { fromLesson: router.currentRoute.value.query.fromLesson },
})
} else {
markLessonProgress()
router.go()
}
submissionResource.name = data.name
submissionResource.reload()
isDirty.value = false
toast.success(__('Changes saved successfully'))
},
onError(err) {
toast.error(err.messages?.[0] || err)
console.error(err)
},
}
)
@@ -385,7 +368,7 @@ const addNewSubmission = () => {
const saveSubmission = (file) => {
isDirty.value = true
submissionFile.value = file
attachment.value = file.file_url
}
const markLessonProgress = () => {
@@ -419,24 +402,9 @@ const getType = () => {
}
}
const validateFile = (file) => {
let type = assignment.data?.type
let extension = file.name.split('.').pop().toLowerCase()
if (type == 'Image' && !['jpg', 'jpeg', 'png'].includes(extension)) {
return 'Only image file is allowed.'
} else if (
type == 'Document' &&
!['doc', 'docx', 'xml'].includes(extension)
) {
return 'Only document file is allowed.'
} else if (type == 'PDF' && !['pdf'].includes(extension)) {
return 'Only PDF file is allowed.'
}
}
const removeSubmission = () => {
isDirty.value = true
submissionFile.value = null
submissionResource.doc.assignment_attachment = ''
}
const canGradeSubmission = computed(() => {

View File

@@ -1,26 +0,0 @@
<template>
<div class="space-y-10">
<UpcomingEvaluations
:batch="batch.data.name"
:endDate="batch.data.evaluation_end_date"
:courses="batch.data.courses"
/>
<Assessments :batch="batch.data.name" />
<!-- <StudentHeatmap /> -->
</div>
</template>
<script setup>
import UpcomingEvaluations from '@/components/UpcomingEvaluations.vue'
import Assessments from '@/components/Assessments.vue'
const props = defineProps({
batch: {
type: Object,
default: null,
},
isStudent: {
type: Boolean,
default: false,
},
})
</script>

View File

@@ -1,354 +0,0 @@
<template>
<div v-if="batch.data" class="">
<div class="w-full flex items-center justify-between pb-4">
<div class="font-medium text-ink-gray-7">
{{ __('Statistics') }}
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-5 mb-8">
<NumberChart
class="border rounded-md"
:config="{ title: __('Students'), value: students.data?.length || 0 }"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Certified'),
value: certificationCount.data || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Courses'),
value: batch.data.courses?.length || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{ title: __('Assessments'), value: assessmentCount || 0 }"
/>
</div>
<AxisChart
v-if="showProgressChart"
:config="{
data: chartData,
title: __('Batch Summary'),
subtitle: __('Progress of students in courses and assessments'),
xAxis: {
key: 'task',
title: 'Tasks',
type: 'category',
},
yAxis: {
title: __('Number of Students'),
echartOptions: {
minInterval: 1,
},
},
swapXY: true,
series: [
{
name: 'value',
type: 'bar',
},
],
}"
/>
</div>
<div>
<div class="flex items-center justify-between mb-4">
<div class="text-ink-gray-7 font-medium">
{{ __('Students') }}
</div>
<Button v-if="!readOnlyMode" @click="openStudentModal()">
<template #prefix>
<Plus class="h-4 w-4" />
</template>
{{ __('Add') }}
</Button>
</div>
<div v-if="students.data?.length">
<ListView
:columns="getStudentColumns()"
:rows="students.data"
row-key="name"
:options="{
showTooltip: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
>
<ListHeaderItem
:item="item"
v-for="item in getStudentColumns()"
:title="item.label"
>
<template #prefix="{ item }">
<FeatherIcon
v-if="item.icon"
:name="item.icon"
class="h-4 w-4 stroke-1.5"
/>
</template>
</ListHeaderItem>
</ListHeader>
<ListRows>
<ListRow
:row="row"
v-for="row in students.data"
class="group cursor-pointer"
@click="openStudentProgressModal(row)"
>
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
class="text-sm"
>
<template #prefix>
<div v-if="column.key == 'full_name'">
<Avatar
class="flex items-center"
:image="row['user_image']"
:label="item"
size="sm"
/>
</div>
</template>
<div
v-if="column.key == 'progress'"
class="flex items-center space-x-4 w-full"
>
<ProgressBar :progress="row[column.key]" size="sm" />
<div class="text-xs">{{ row[column.key] }}%</div>
</div>
<div v-else>
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
<ListSelectBanner>
<template #actions="{ unselectAll, selections }">
<div class="flex gap-2">
<Button
variant="ghost"
@click="removeStudents(selections, unselectAll)"
>
<Trash2 class="h-4 w-4 stroke-1.5" />
</Button>
</div>
</template>
</ListSelectBanner>
</ListView>
</div>
<div v-else class="text-sm italic text-ink-gray-5">
{{ __('There are no students in this batch.') }}
</div>
</div>
<StudentModal
:batch="props.batch.data.name"
v-model="showStudentModal"
v-model:reloadStudents="students"
v-model:batchModal="props.batch"
/>
<BatchStudentProgress
:student="selectedStudent"
v-model="showStudentProgressModal"
/>
</template>
<script setup>
import {
Avatar,
AxisChart,
Button,
createResource,
FeatherIcon,
ListHeader,
ListHeaderItem,
ListSelectBanner,
ListRow,
ListRows,
ListView,
ListRowItem,
NumberChart,
toast,
} from 'frappe-ui'
import {
BookOpen,
GraduationCap,
Plus,
ShieldCheck,
Trash2,
User,
} from 'lucide-vue-next'
import { ref, watch } from 'vue'
import StudentModal from '@/components/Modals/StudentModal.vue'
import ProgressBar from '@/components/ProgressBar.vue'
import BatchStudentProgress from '@/components/Modals/BatchStudentProgress.vue'
import ApexChart from 'vue3-apexcharts'
import { theme } from '@/utils/theme'
const showStudentModal = ref(false)
const showStudentProgressModal = ref(false)
const selectedStudent = ref(null)
const chartData = ref(null)
const showProgressChart = ref(false)
const assessmentCount = ref(0)
const readOnlyMode = window.read_only_mode
const props = defineProps({
batch: {
type: Object,
default: null,
},
})
const students = createResource({
url: 'lms.lms.utils.get_batch_students',
params: {
batch: props.batch?.data?.name,
},
auto: true,
onSuccess(data) {
chartData.value = getChartData()
showProgressChart.value =
data.length &&
(props.batch?.data?.courses?.length || assessmentCount.value)
},
})
const getStudentColumns = () => {
let columns = [
{
label: 'Full Name',
key: 'full_name',
width: '20rem',
icon: 'user',
},
{
label: 'Progress',
key: 'progress',
width: '15rem',
icon: 'activity',
},
{
label: 'Last Active',
key: 'last_active',
width: '10rem',
align: 'center',
icon: 'clock',
},
]
return columns
}
const openStudentModal = () => {
showStudentModal.value = true
}
const openStudentProgressModal = (row) => {
showStudentProgressModal.value = true
selectedStudent.value = row
}
const deleteStudents = createResource({
url: 'lms.lms.api.delete_documents',
makeParams(values) {
return {
doctype: 'LMS Batch Enrollment',
documents: values.students,
}
},
})
const removeStudents = (selections, unselectAll) => {
deleteStudents.submit(
{
students: Array.from(selections),
},
{
onSuccess(data) {
students.reload()
props.batch.reload()
toast.success(__('Students deleted successfully'))
unselectAll()
},
}
)
}
const getChartData = () => {
let tasks = []
let data = []
students.data.forEach((row) => {
tasks = countAssessments(row, tasks)
tasks = countCourses(row, tasks)
})
tasks.forEach((task) => {
data.push({
task: task.label,
value: task.value,
})
})
return data
}
const countAssessments = (row, tasks) => {
Object.keys(row.assessments).forEach((assessment) => {
if (row.assessments[assessment].result === 'Pass') {
tasks.filter((task) => task.label === assessment).length
? tasks.filter((task) => task.label === assessment)[0].value++
: tasks.push({
value: 1,
label: assessment,
})
}
})
return tasks
}
const countCourses = (row, tasks) => {
Object.keys(row.courses).forEach((course) => {
if (row.courses[course] === 100) {
tasks.filter((task) => task.label === course).length
? tasks.filter((task) => task.label === course)[0].value++
: tasks.push({
value: 1,
label: course,
})
}
})
return tasks
}
watch(students, () => {
if (students.data?.length) {
assessmentCount.value = Object.keys(students.data?.[0].assessments).length
}
})
const certificationCount = createResource({
url: 'frappe.client.get_count',
params: {
doctype: 'LMS Certificate',
filters: {
batch_name: props.batch?.data?.name,
},
},
auto: true,
})
</script>

View File

@@ -68,11 +68,12 @@ const props = defineProps({
const certification = createResource({
url: 'lms.lms.api.get_certification_details',
params: {
course: props.courseName,
makeParams(values) {
return {
course: props.courseName,
}
},
auto: user.data ? true : false,
cache: ['certificationData', user.data?.name],
})
const downloadCertificate = () => {

View File

@@ -0,0 +1,272 @@
<template>
<Dialog v-model="show" :options="{ size: '2xl' }">
<template #body>
<div class="text-base">
<div class="flex items-center space-x-2 pl-4.5 border-b">
<Search class="size-4 text-ink-gray-4" />
<input
ref="inputRef"
type="text"
placeholder="Search"
class="w-full border-none bg-transparent py-3 !pl-2 pr-4.5 text-base text-ink-gray-7 placeholder-ink-gray-4 focus:ring-0"
@input="onInput"
v-model="query"
autocomplete="off"
/>
</div>
<div class="max-h-96 overflow-auto mb-2">
<div v-if="query.length" class="mt-5 space-y-5">
<CommandPaletteGroup
:list="searchResults"
@navigateTo="navigateTo"
/>
</div>
<div v-else class="mt-5 space-y-5">
<CommandPaletteGroup
:list="jumpToOptions"
@navigateTo="navigateTo"
/>
</div>
</div>
<div
class="flex items-center space-x-5 w-full border-t py-2 text-sm text-ink-gray-7 px-4.5"
>
<div class="flex items-center space-x-2">
<MoveUp
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
/>
<MoveDown
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
/>
<span>
{{ __('to navigate') }}
</span>
</div>
<div class="flex items-center space-x-2">
<CornerDownLeft
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
/>
<span>
{{ __('to select') }}
</span>
</div>
<div class="flex items-center space-x-2">
<span class="bg-surface-gray-2 p-1 rounded-sm"> esc </span>
<span>
{{ __('to close') }}
</span>
</div>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { createResource, debounce, Dialog } from 'frappe-ui'
import { nextTick, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import {
BookOpen,
Briefcase,
CornerDownLeft,
FileSearch,
MoveUp,
MoveDown,
Search,
Users,
} from 'lucide-vue-next'
import CommandPaletteGroup from './CommandPaletteGroup.vue'
const show = defineModel<boolean>({ required: true, default: false })
const router = useRouter()
const query = ref<string>('')
const searchResults = ref<Array<any>>([])
const search = createResource({
url: 'lms.command_palette.search_sqlite',
makeParams: () => ({
query: query.value,
}),
onSuccess() {
generateSearchResults()
},
})
const debouncedSearch = debounce(() => {
if (query.value.length > 2) {
search.reload()
}
}, 500)
const onInput = () => {
debouncedSearch()
}
const generateSearchResults = () => {
search.data?.forEach((type: any) => {
let result: { title: string; items: any[] } = { title: '', items: [] }
result.title = type.title
type.items.forEach((item: any) => {
let paramName = item.doctype === 'LMS Course' ? 'courseName' : 'batchName'
item.route = {
name: item.doctype === 'LMS Course' ? 'CourseDetail' : 'BatchDetail',
params: {
[paramName]: item.name,
},
}
item.isActive = false
})
result.items = type.items
searchResults.value.push(result)
})
}
const appendSearchPage = () => {
let searchPage: { title: string; items: Array<any> } = {
title: '',
items: [],
}
searchPage.title = __('Jump to')
searchPage.items = [
{
title: __('Search for ') + `"${query.value}"`,
route: {
name: 'Search',
query: {
q: query.value,
},
},
icon: FileSearch,
isActive: true,
},
]
searchResults.value = [searchPage]
}
watch(
query,
() => {
appendSearchPage()
},
{ immediate: true }
)
watch(show, () => {
if (!show.value) {
query.value = ''
searchResults.value = []
}
})
onMounted(() => {
addKeyboardShortcuts()
})
const addKeyboardShortcuts = () => {
window.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'ArrowUp' && show.value) {
e.preventDefault()
shortcutForArrowKey(-1)
} else if (e.key === 'ArrowDown' && show.value) {
shortcutForArrowKey(1)
} else if (e.key === 'Enter' && show.value) {
shortcutForEnter()
} else if (e.key === 'Escape' && show.value) {
show.value = false
}
})
}
const shortcutForArrowKey = (direction: number) => {
let currentList = query.value.length
? searchResults.value
: jumpToOptions.value
let allItems = currentList.flatMap((result: any) => result.items)
let indexOfActive = allItems.findIndex((option: any) => option.isActive)
let newIndex = indexOfActive + direction
if (newIndex < 0) newIndex = allItems.length - 1
if (newIndex >= allItems.length) newIndex = 0
allItems[indexOfActive].isActive = false
allItems[newIndex].isActive = true
nextTick(scrollActiveItemIntoView)
}
const scrollActiveItemIntoView = () => {
const activeItem = document.querySelector(
'.hover\\:bg-surface-gray-2.bg-surface-gray-2'
) as HTMLElement
if (activeItem) {
activeItem.scrollIntoView({ block: 'nearest' })
}
}
const shortcutForEnter = () => {
let currentList = query.value.length
? searchResults.value
: jumpToOptions.value
let allItems = currentList.flatMap((result: any) => result.items)
let activeOption = allItems.find((option) => option.isActive)
if (activeOption) {
navigateTo(activeOption.route)
}
}
const navigateTo = (route: {
name: string
params?: Record<string, any>
query?: Record<string, any>
}) => {
show.value = false
query.value = ''
router.replace({ name: route.name, params: route.params, query: route.query })
}
const jumpToOptions = ref([
{
title: __('Jump to'),
items: [
{
title: 'Advanced Search',
icon: Search,
route: {
name: 'Search',
},
isActive: true,
},
{
title: 'Courses',
icon: BookOpen,
route: {
name: 'Courses',
},
isActive: false,
},
{
title: 'Batches',
icon: Users,
route: {
name: 'Batches',
},
isActive: false,
},
{
title: 'Jobs',
icon: Briefcase,
route: {
name: 'Jobs',
},
isActive: false,
},
],
},
])
</script>
<style>
mark {
background-color: theme('colors.amber.100');
font-weight: 500;
}
</style>

View File

@@ -0,0 +1,45 @@
<template>
<div v-for="result in list" class="px-2.5 space-y-2">
<div class="text-ink-gray-5 px-2">
{{ result.title }}
</div>
<div class="">
<div
v-for="item in result.items"
class="flex items-center justify-between p-2 rounded hover:bg-surface-gray-2 cursor-pointer"
:class="{ 'bg-surface-gray-2': item.isActive }"
@click="emit('navigateTo', item.route)"
>
<div class="flex items-center space-x-3">
<component
v-if="item.icon"
:is="item.icon"
class="size-4 stroke-1.5 text-ink-gray-6"
/>
<div v-html="item.title"></div>
</div>
<div v-if="item.modified" class="text-ink-gray-5">
{{ dayjs.unix(item.modified).fromNow(true) }}
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { inject } from 'vue'
const dayjs = inject<any>('$dayjs')
const emit = defineEmits(['navigateTo'])
const props = defineProps<{
list: Array<{
title: string
items: Array<{
title: string
icon?: any
isActive?: boolean
modified?: string
}>
}>
}>()
</script>

View File

@@ -48,7 +48,7 @@ const settingsStore = useSettings()
const sendMail = (close: Function) => {
call('frappe.core.doctype.communication.email.make', {
recipients: settingsStore.contactUsEmail?.data,
recipients: settingsStore.settings?.data?.contact_us_email,
subject: subject.value,
content: message.value,
send_email: true,

View File

@@ -16,13 +16,18 @@
<button
class="flex w-full items-center justify-between focus:outline-none"
:class="inputClasses"
@click="() => togglePopover()"
@click="
() => {
showOptions = !showOptions
togglePopover()
}
"
:disabled="attrs.readonly"
>
<div class="flex items-center">
<div class="flex items-center w-[90%]">
<slot name="prefix" />
<span
class="overflow-hidden text-ellipsis whitespace-nowrap text-base leading-5"
class="block truncate text-base leading-5"
v-if="selectedValue"
>
{{ displayValue(selectedValue) }}
@@ -99,18 +104,17 @@
name="item-label"
v-bind="{ active, selected, option }"
>
<div class="flex flex-col space-y-1 text-ink-gray-8">
<div>
{{ option.label }}
<div class="flex flex-col gap-1 p-1">
<div class="text-base font-medium text-ink-gray-8">
{{
option.value == option.label && option.description
? option.description
: option.label
}}
</div>
<div class="text-sm text-ink-gray-5">
{{ option.value }}
</div>
<div
v-if="
option.description &&
option.description != option.label
"
class="text-xs text-ink-gray-7"
v-html="option.description"
></div>
</div>
</slot>
</li>
@@ -120,7 +124,7 @@
v-if="groups.length == 0"
class="mt-1.5 rounded-md px-2.5 py-1.5 text-base text-ink-gray-5"
>
No results found
{{ __('No results found') }}
</li>
</ComboboxOptions>
<div v-if="slots.footer" class="border-t p-1.5 pb-0.5">
@@ -284,7 +288,7 @@ const inputClasses = computed(() => {
let variant = props.disabled ? 'disabled' : props.variant
let variantClasses = {
subtle:
'border border-gray-100 bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
'border border-outline-gray-modals bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
outline:
'border border-outline-gray-2 bg-surface-white placeholder-ink-gray-4 hover:border-outline-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
disabled: [

View File

@@ -3,59 +3,67 @@
<div class="text-xs text-ink-gray-5 mb-2">
{{ label }}
</div>
<div class="overflow-x-auto border rounded-md">
<div
class="grid items-center space-x-4 p-2 border-b"
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
>
<div class="overflow-visible border border-outline-gray-modals rounded-md">
<div class="overflow-x-auto">
<div
v-for="(column, index) in columns"
:key="index"
class="text-sm text-ink-gray-5"
class="grid items-center space-x-4 p-2 border-b border-outline-gray-modals"
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
>
{{ column }}
</div>
<div></div>
</div>
<div
v-for="(row, rowIndex) in rows"
:key="rowIndex"
class="grid items-center space-x-4 p-2"
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
>
<template v-for="key in Object.keys(row)" :key="key">
<input
v-if="showKey(key)"
v-model="row[key]"
class="py-1.5 px-2 border-none focus:ring-0 focus:border focus:border-gray-300 focus:bg-surface-gray-2 rounded-sm text-sm focus:outline-none"
/>
</template>
<div class="relative" ref="menuRef">
<Button
variant="ghost"
@click="(event: MouseEvent) => toggleMenu(rowIndex, event)"
>
<template #icon>
<Ellipsis
class="size-4 text-ink-gray-7 stroke-1.5 cursor-pointer"
/>
</template>
</Button>
<div
v-if="menuOpenIndex === rowIndex"
class="absolute right-[30px] top-5 mt-1 w-32 bg-surface-white border border-outline-gray-1 rounded-md shadow-sm"
v-for="(column, index) in columns"
:key="index"
class="text-sm text-ink-gray-5"
>
<button
@click="deleteRow(rowIndex)"
class="flex items-center space-x-2 w-full text-left px-3 py-2 text-sm text-ink-red-3"
{{ column }}
</div>
<div></div>
</div>
<div
v-for="(row, rowIndex) in rows"
:key="rowIndex"
class="grid items-center space-x-4 p-2"
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
>
<template v-for="key in Object.keys(row)" :key="key">
<input
v-if="showKey(key)"
v-model="row[key]"
class="py-1.5 px-2 w-full border-none bg-transparent text-ink-gray-8 focus:ring-0 focus:border focus:border-outline-gray-3 focus:bg-surface-gray-2 rounded-md text-sm focus:outline-none"
/>
</template>
<div class="relative">
<Button
variant="ghost"
@click="(event: MouseEvent) => toggleMenu(rowIndex, event)"
>
<Trash2 class="size-4 stroke-1.5" />
<span>
{{ __('Delete') }}
</span>
</button>
<template #icon>
<Ellipsis
class="size-4 text-ink-gray-7 stroke-1.5 cursor-pointer"
/>
</template>
</Button>
<div
v-if="menuOpenIndex === rowIndex"
ref="menuRef"
class="absolute right-0 w-32 z-50 bg-surface-modal border border-outline-gray-modals rounded-md shadow-sm"
:class="
rowIndex == (rows?.length ?? 0) - 1
? 'bottom-full mb-1'
: 'top-full mt-1'
"
>
<button
@click="deleteRow(rowIndex)"
class="flex items-center space-x-2 w-full text-left px-3 py-2 text-sm text-ink-red-3"
>
<Trash2 class="size-4 stroke-1.5" />
<span>
{{ __('Delete') }}
</span>
</button>
</div>
</div>
</div>
</div>
@@ -73,17 +81,19 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { nextTick, ref, watch } from 'vue'
import { Button } from 'frappe-ui'
import { Ellipsis, Plus, Trash2 } from 'lucide-vue-next'
import { onClickOutside } from '@vueuse/core'
const rows = defineModel<Cell[][]>()
const rows = defineModel<Record<string, string>[]>()
const menuRef = ref(null)
const menuOpenIndex = ref<number | null>(null)
const menuTopPosition = ref<string>('')
const menuLeftPosition = ref('0px')
const emit = defineEmits<{
(e: 'update:modelValue', value: Cell[][]): void
(e: 'update:modelValue', value: Record<string, string>[]): void
}>()
type Cell = {
@@ -93,19 +103,19 @@ type Cell = {
const props = withDefaults(
defineProps<{
modelValue?: Cell[][]
modelValue?: Record<string, string>[]
columns?: string[]
label?: string
}>(),
{
columns: [],
columns: () => [] as string[],
}
)
const columns = ref(props.columns)
watch(rows, () => {
if (rows.value?.length < 1) {
if (rows.value && rows.value.length < 1) {
addRow()
}
})
@@ -119,12 +129,25 @@ const addRow = () => {
newRow[column.toLowerCase().split(' ').join('_')] = ''
})
rows.value.push(newRow)
focusNewRowInput()
emit('update:modelValue', rows.value)
}
const focusNewRowInput = () => {
nextTick(() => {
const rowElements = document.querySelectorAll('.overflow-x-auto .grid')[
rows.value!.length
]
const firstInput = rowElements.querySelector('input')
if (firstInput) {
;(firstInput as HTMLInputElement).focus()
}
})
}
const deleteRow = (index: number) => {
rows.value.splice(index, 1)
emit('update:modelValue', rows.value)
rows.value?.splice(index, 1)
emit('update:modelValue', rows.value ?? [])
}
const getGridTemplateColumns = () => {
@@ -133,7 +156,6 @@ const getGridTemplateColumns = () => {
const toggleMenu = (index: number, event: MouseEvent) => {
menuOpenIndex.value = menuOpenIndex.value === index ? null : index
menuTopPosition.value = `${event.clientY + 10}px`
}
onClickOutside(menuRef, () => {

View File

@@ -107,7 +107,7 @@ async function setLanguageExtension() {
if (!languageImport) return
const module = await languageImport()
languageExtension.value = (module as any)[props.language]()
languageExtension.value = (module as any)[props.language]?.()
if (props.completions) {
const languageData = (module as any)[`${props.language}Language`]

View File

@@ -21,8 +21,10 @@
:style="
modelValue
? {
backgroundColor:
theme.backgroundColor[modelValue.toLowerCase()][400],
backgroundColor: getColor(
modelValue.toLowerCase(),
400
),
}
: {}
"
@@ -55,8 +57,7 @@
:key="color"
class="size-5 rounded-full cursor-pointer"
:style="{
backgroundColor:
theme.backgroundColor[color.toLowerCase()][400],
backgroundColor: getColor(color.toLowerCase(), 400),
}"
@click="
(e) => {
@@ -79,7 +80,7 @@
import { Button, FormControl, Popover } from 'frappe-ui'
import { computed } from 'vue'
import { Palette, X } from 'lucide-vue-next'
import { theme } from '@/utils/theme'
import { getColor } from '@/utils'
const emit = defineEmits(['update:modelValue', 'change'])

View File

@@ -20,7 +20,7 @@
class="w-4 h-4 text-ink-gray-7 stroke-1.5"
:is="icons.Folder"
/>
<span v-if="selectedIcon">
<span v-if="selectedIcon" class="text-ink-gray-7">
{{ selectedIcon }}
</span>
<span v-else class="text-ink-gray-5">

View File

@@ -11,7 +11,6 @@
:size="attrs.size || 'sm'"
:variant="attrs.variant"
:placeholder="attrs.placeholder"
:filterable="false"
:readonly="attrs.readonly"
>
<template #target="{ open, togglePopover }">
@@ -67,6 +66,7 @@ import { watchDebounced } from '@vueuse/core'
import { createResource, Button } from 'frappe-ui'
import { Plus, X } from 'lucide-vue-next'
import { useAttrs, computed, ref } from 'vue'
import { useSettings } from '@/stores/settings'
const props = defineProps({
doctype: {
@@ -96,13 +96,14 @@ const value = computed({
set: (val) => {
return (
val?.value &&
emit(valuePropPassed.value ? 'change' : 'update:modelValue', val?.value)
emit(valuePropPassed.value ? 'change' : 'update:modelValue', val.value)
)
},
})
const autocomplete = ref(null)
const text = ref('')
const settingsStore = useSettings()
watchDebounced(
() => autocomplete.value?.query,
@@ -121,6 +122,16 @@ watchDebounced(
{ debounce: 300, immediate: true }
)
watchDebounced(
() => settingsStore.isSettingsOpen,
(isOpen, wasOpen) => {
if (wasOpen && !isOpen) {
reload('')
}
},
{ debounce: 200 }
)
const options = createResource({
url: 'frappe.desk.search.search_link',
cache: [props.doctype, text.value],

View File

@@ -1,160 +1,145 @@
<template>
<div>
<label class="block mb-1" :class="labelClasses" v-if="label">
<label v-if="label" class="block mb-1" :class="labelClasses">
{{ label }}
<span class="text-ink-red-3" v-if="required">*</span>
<span v-if="required" class="text-ink-red-3">*</span>
</label>
<div class="w-full">
<Combobox v-model="selectedValue" nullable>
<Popover class="w-full" v-model:show="showOptions">
<template #target="{ togglePopover }">
<ComboboxInput
ref="search"
class="search-input form-input w-full focus-visible:!ring-0"
type="text"
:value="query"
@change="
(e) => {
query = e.target.value
showOptions = true
}
"
autocomplete="off"
@focus="() => togglePopover()"
@keydown.delete.capture.stop="removeLastValue"
/>
</template>
<template #body="{ isOpen, close }">
<div v-show="isOpen">
<div
class="mt-1 rounded-lg bg-surface-white py-1 text-base border-2"
<Combobox v-model="selectedValue" nullable v-slot="{ open }">
<div class="relative w-full">
<ComboboxInput
ref="search"
class="form-input w-full focus-visible:!ring-0"
type="text"
@change="
(e) => {
query = e.target.value
}
"
autocomplete="off"
@focus="onFocus"
/>
<ComboboxButton ref="trigger" class="hidden" />
<ComboboxOptions
v-show="open"
static
class="absolute z-20 mt-1 w-full rounded-lg bg-surface-modal border-2 border-outline-gray-modals max-h-[13rem] flex flex-col"
>
<div
class="flex-1 my-1 overflow-y-auto px-1.5"
:class="options.length ? 'min-h-[6rem]' : 'min-h-[3.8rem]'"
>
<template v-if="options.length">
<ComboboxOption
v-for="option in options"
:key="option.value"
:value="option"
v-slot="{ active }"
>
<ComboboxOptions
class="my-1 min-h-[6rem] max-h-[12rem] overflow-y-auto px-1.5"
static
<li
:class="[
'flex cursor-pointer items-center rounded px-2 py-1 text-base',
{ 'bg-surface-gray-2': active },
]"
>
<ComboboxOption
v-for="option in options"
:key="option.value"
:value="option"
v-slot="{ active }"
>
<li
:class="[
'flex cursor-pointer items-center rounded px-2 py-1 text-base',
{ 'bg-surface-gray-2': active },
]"
>
<div class="flex flex-col gap-1 p-1">
<div class="text-base font-medium text-ink-gray-8">
{{ option.description }}
</div>
<div class="text-sm text-ink-gray-5">
{{ option.value }}
</div>
</div>
</li>
</ComboboxOption>
<div class="h-10"></div>
<div
v-if="attrs.onCreate"
class="absolute bottom-2 left-1 w-[99%] pt-2 bg-white border-t"
>
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
@click="attrs.onCreate(close)"
>
<template #prefix>
<Plus class="h-4 w-4 stroke-1.5" />
</template>
</Button>
<div class="flex flex-col gap-1 p-1">
<div class="text-base font-medium text-ink-gray-8">
{{
option.value === option.label
? option.description
: option.label
}}
</div>
<div class="text-sm text-ink-gray-5">
{{ option.value }}
</div>
</div>
</ComboboxOptions>
</div>
</li>
</ComboboxOption>
</template>
<div v-else class="text-ink-gray-7 px-4 py-2">
{{ __('No results found') }}
</div>
</template>
</Popover>
</Combobox>
</div>
<div v-if="values.length" class="grid grid-cols-2 gap-2 mt-1">
</div>
<div
v-if="attrs.onCreate"
class="p-1 bg-surface-white border-t rounded-b-lg"
>
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
@click="attrs.onCreate()"
>
<template #prefix>
<Plus class="h-4 w-4 stroke-1.5" />
</template>
</Button>
</div>
</ComboboxOptions>
</div>
</Combobox>
<!-- Selected values -->
<div v-if="values?.length" class="grid grid-cols-2 gap-2 mt-1">
<div
v-for="value in values"
class="flex items-center justify-between break-all bg-surface-gray-2 text-ink-gray-7 word-wrap p-2 rounded-md mr-2"
:key="value"
class="flex items-center justify-between break-all bg-surface-gray-2 text-ink-gray-7 p-2 rounded-md"
>
<span class="break-all">
{{ value }}
</span>
<span>{{ value }}</span>
<X
class="size-4 stroke-1.5 cursor-pointer"
@click="removeValue(value)"
/>
</div>
</div>
<!-- <ErrorMessage class="mt-2 pl-2" v-if="error" :message="error" /> -->
</div>
</template>
<script setup>
import {
Combobox,
ComboboxButton,
ComboboxInput,
ComboboxOptions,
ComboboxOption,
} from '@headlessui/vue'
import { createResource, Popover, Button } from 'frappe-ui'
import { ref, computed, nextTick, useAttrs } from 'vue'
import { createResource, Button } from 'frappe-ui'
import { ref, computed, useAttrs, watch } from 'vue'
import { watchDebounced } from '@vueuse/core'
import { X, Plus } from 'lucide-vue-next'
const props = defineProps({
label: {
type: String,
},
size: {
type: String,
default: 'sm',
},
doctype: {
type: String,
required: true,
},
filters: {
type: Object,
default: () => ({}),
},
validate: {
type: Function,
default: null,
},
label: String,
size: { type: String, default: 'sm' },
doctype: { type: String, required: true },
filters: { type: Object, default: () => ({}) },
validate: Function,
errorMessage: {
type: Function,
default: (value) => `${value} is an Invalid value`,
},
required: {
type: Boolean,
},
required: Boolean,
})
const values = defineModel()
const attrs = useAttrs()
const emails = ref([])
const search = ref(null)
const error = ref(null)
const trigger = ref(null)
const query = ref('')
const text = ref('')
const showOptions = ref(false)
const selectedValue = ref(null)
const error = ref(null)
const selectedValue = computed({
get: () => query.value || '',
set: (val) => {
query.value = ''
if (val) {
showOptions.value = false
}
val?.value && addValue(val.value)
},
const emit = defineEmits(['update:modelValue'])
watch(selectedValue, (val) => {
if (!val?.value) return
query.value = ''
addValue(val.value)
selectedValue.value = null
emit('update:modelValue', values.value)
})
watchDebounced(
@@ -171,7 +156,6 @@ watchDebounced(
const filterOptions = createResource({
url: 'frappe.desk.search.search_link',
method: 'POST',
cache: [text.value, props.doctype],
auto: true,
params: {
txt: text.value,
@@ -180,7 +164,8 @@ const filterOptions = createResource({
})
const options = computed(() => {
return filterOptions.data || []
const allOptions = filterOptions.data || []
return allOptions.filter((option) => !values.value?.includes(option.value))
})
function reload(val) {
@@ -193,70 +178,46 @@ function reload(val) {
filterOptions.reload()
}
const addValue = (value) => {
function onFocus() {
if (!filterOptions.data?.length) {
reload('')
}
trigger.value?.$el.click()
}
function addValue(value) {
error.value = null
if (value) {
const splitValues = value.split(',')
splitValues.forEach((value) => {
value = value.trim()
if (value) {
// check if value is not already in the values array
if (!values.value?.includes(value)) {
// check if value is valid
if (value && props.validate && !props.validate(value)) {
error.value = props.errorMessage(value)
return
}
// add value to values array
if (!values.value) {
values.value = [value]
} else {
values.value.push(value)
}
value = value.replace(value, '')
}
}
})
!error.value && (value = '')
if (!value) return
const splitValues = value.split(',')
splitValues.forEach((val) => {
val = val.trim()
if (!val) return
if (values.value?.includes(val)) return
if (props.validate && !props.validate(val)) {
error.value = props.errorMessage(val)
return
}
if (!values.value) values.value = [val]
else values.value.push(val)
})
}
function removeValue(value) {
let indexToRemove = values.value.indexOf(value)
if (indexToRemove > -1) {
values.value.splice(indexToRemove, 1)
}
emit('update:modelValue', values.value)
}
const removeValue = (value) => {
values.value = values.value.filter((v) => v !== value)
}
const removeLastValue = () => {
if (query.value) return
let emailRef = emails.value[emails.value.length - 1]?.$el
if (document.activeElement === emailRef) {
values.value.pop()
nextTick(() => {
if (values.value.length) {
emailRef = emails.value[emails.value.length - 1].$el
emailRef?.focus()
} else {
setFocus()
}
})
} else {
emailRef?.focus()
}
}
function setFocus() {
search.value.$el.focus()
}
defineExpose({ setFocus })
const labelClasses = computed(() => {
return [
{
sm: 'text-xs',
md: 'text-base',
}[props.size || 'sm'],
'text-ink-gray-5',
]
})
const labelClasses = computed(() => [
{ sm: 'text-xs', md: 'text-base' }[props.size || 'sm'],
'text-ink-gray-5',
])
</script>

View File

@@ -2,18 +2,21 @@
<div class="mb-4">
<div v-if="label" class="text-xs text-ink-gray-5 mb-2">
{{ __(label) }}
<span class="text-ink-red-3">*</span>
<span v-if="required" class="text-ink-red-3">*</span>
</div>
<FileUploader
v-if="!modelValue"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file: File) => saveImage(file)"
:fileTypes="[fileType]"
:validateFile="(file: File) => validateFile(file, true, type)"
@success="(file: File) => saveFile(file)"
>
<template v-slot="{ file, progress, uploading, openFileSelector }">
<div class="flex items-center">
<div class="border rounded-md w-fit py-7 px-20">
<Image class="size-5 stroke-1 text-ink-gray-7" />
<component
:is="props.type === 'image' ? Image : Video"
class="size-5 stroke-1 text-ink-gray-7"
/>
</div>
<div class="ml-4">
<Button @click="openFileSelector">
@@ -28,7 +31,20 @@
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img :src="modelValue" class="border rounded-md w-44 h-auto" />
<img
v-if="type == 'image'"
:src="modelValue"
:class="[
'border object-cover',
shape === 'circle'
? 'w-20 h-20 rounded-full'
: 'w-44 h-auto min-h-20 max-h-32 rounded-md',
]"
/>
<video v-else controls class="border rounded-md w-44 h-auto">
<source :src="modelValue" />
{{ __('Your browser does not support the video tag.') }}
</video>
<div class="ml-4">
<Button @click="removeImage()">
{{ __('Remove') }}
@@ -47,7 +63,8 @@
<script setup lang="ts">
import { validateFile } from '@/utils'
import { Button, FileUploader } from 'frappe-ui'
import { Image } from 'lucide-vue-next'
import { Image, Video } from 'lucide-vue-next'
import { computed } from 'vue'
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
@@ -55,18 +72,28 @@ const emit = defineEmits<{
const props = withDefaults(
defineProps<{
modelValue: string
modelValue: string | null
label?: string
description?: string
type?: 'image' | 'video'
required?: boolean
shape?: 'square' | 'circle'
}>(),
{
modelValue: '',
label: '',
description: '',
type: 'image',
required: true,
shape: 'square',
}
)
const saveImage = (file: any) => {
const fileType = computed(() => {
return props.type === 'image' ? 'image/*' : 'video/*'
})
const saveFile = (file: any) => {
emit('update:modelValue', file.file_url)
}

View File

@@ -136,11 +136,11 @@
import { Award, BookOpen, GraduationCap, Star, Users } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
import { Tooltip } from 'frappe-ui'
import { theme } from '@/utils/theme'
import { formatAmount } from '@/utils'
import CourseInstructors from '@/components/CourseInstructors.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import ProgressBar from '@/components/ProgressBar.vue'
import colors from '@/utils/frappe-ui-colors.json'
const { user } = sessionStore()
@@ -152,19 +152,10 @@ const props = defineProps({
})
const getGradientColor = () => {
let theme = localStorage.getItem('theme') == 'dark' ? 'darkMode' : 'lightMode'
let color = props.course.card_gradient?.toLowerCase() || 'blue'
let colorMap = theme.backgroundColor[color]
let colorMap = colors[theme][color]
return `linear-gradient(to top right, black, ${colorMap[400]})`
/* return `bg-gradient-to-br from-${color}-100 via-${color}-200 to-${color}-400` */
/* return `linear-gradient(to bottom right, ${colorMap[100]}, ${colorMap[400]})` */
/* return `radial-gradient(ellipse at 80% 20%, black 20%, ${colorMap[500]} 100%)` */
/* return `radial-gradient(ellipse at 30% 70%, black 50%, ${colorMap[500]} 100%)` */
/* return `radial-gradient(ellipse at 80% 20%, ${colorMap[100]} 0%, ${colorMap[300]} 50%, ${colorMap[500]} 100%)` */
/* return `conic-gradient(from 180deg at 50% 50%, ${colorMap[100]} 0%, ${colorMap[200]} 50%, ${colorMap[400]} 100%)` */
/* return `linear-gradient(135deg, ${colorMap[100]}, ${colorMap[300]}), linear-gradient(120deg, rgba(255,255,255,0.4) 0%, transparent 60%) ` */
/* return `radial-gradient(circle at 20% 30%, ${colorMap[100]} 0%, transparent 40%),
radial-gradient(circle at 80% 40%, ${colorMap[200]} 0%, transparent 50%),
linear-gradient(135deg, ${colorMap[300]} 0%, ${colorMap[400]} 100%);` */
}
</script>
<style>

View File

@@ -37,7 +37,7 @@
<CertificationLinks :courseName="course.data.name" class="w-full" />
</div>
<router-link
v-else-if="course.data.paid_course"
v-else-if="course.data.paid_course && !isAdmin"
:to="{
name: 'Billing',
params: {
@@ -56,14 +56,15 @@
</Button>
</router-link>
<Badge
v-else-if="course.data.disable_self_learning"
v-else-if="course.data.disable_self_learning && !isAdmin"
theme="blue"
size="lg"
class="mb-4"
>
{{ __('Contact the Administrator to enroll for this course.') }}
{{ __('Contact the Administrator to enroll for this course') }}
</Badge>
<Button
v-else-if="!user.data?.is_moderator && !is_instructor()"
v-else-if="!isAdmin"
@click="enrollStudent()"
variant="solid"
class="w-full"
@@ -88,35 +89,6 @@
</template>
{{ __('Get Certificate') }}
</Button>
<Button
v-if="user.data?.is_moderator || is_instructor()"
class="w-full mt-2"
size="md"
@click="showProgressSummary"
>
<template #prefix>
<TrendingUp class="size-4 stroke-1.5" />
{{ __('Progress Summary') }}
</template>
</Button>
<router-link
v-if="user?.data?.is_moderator || is_instructor()"
:to="{
name: 'CourseForm',
params: {
courseName: course.data.name,
},
}"
>
<Button variant="subtle" class="w-full mt-2" size="md">
<template #prefix>
<Pencil class="size-4 stroke-1.5" />
</template>
<span>
{{ __('Edit') }}
</span>
</Button>
</router-link>
</div>
<div class="space-y-4">
<div
@@ -168,12 +140,6 @@
</div>
</div>
</div>
<CourseProgressSummary
v-if="user.data?.is_moderator || is_instructor()"
v-model="showProgressModal"
:courseName="course.data.name"
:enrollments="course.data.enrollments"
/>
</template>
<script setup>
import {
@@ -189,15 +155,14 @@ import {
import { computed, inject, ref } from 'vue'
import { Badge, Button, call, createResource, toast } from 'frappe-ui'
import { formatAmount } from '@/utils/'
import { capture } from '@/telemetry'
import { useRouter } from 'vue-router'
import CertificationLinks from '@/components/CertificationLinks.vue'
import CourseProgressSummary from '@/components/Modals/CourseProgressSummary.vue'
import { useTelemetry } from 'frappe-ui/frappe'
const router = useRouter()
const user = inject('$user')
const showProgressModal = ref(false)
const readOnlyMode = window.read_only_mode
const { capture } = useTelemetry()
const props = defineProps({
course: {
@@ -215,13 +180,17 @@ const video_link = computed(() => {
function enrollStudent() {
if (!user.data) {
toast.success(__('You need to login first to enroll for this course'))
toast.warning(__('You need to login first to enroll for this course'))
setTimeout(() => {
window.location.href = `/login?redirect-to=${window.location.pathname}`
}, 500)
} else {
call('lms.lms.doctype.lms_enrollment.lms_enrollment.create_membership', {
course: props.course.data.name,
call('frappe.client.insert', {
doc: {
doctype: 'LMS Enrollment',
course: props.course.data.name,
member: user.data.name,
},
})
.then(() => {
capture('enrolled_in_course', {
@@ -290,7 +259,7 @@ const fetchCertificate = () => {
})
}
const showProgressSummary = () => {
showProgressModal.value = true
}
const isAdmin = computed(() => {
return user.data?.is_moderator || is_instructor()
})
</script>

View File

@@ -15,7 +15,10 @@
{{ __(title) }}
</div>
<Button size="sm" v-if="allowEdit" @click="openChapterModal()">
{{ __('Add Chapter') }}
<template #prefix>
<Plus class="size-4 stroke-1.5" />
</template>
{{ __('Add') }}
</Button>
</div>
<div
@@ -95,8 +98,8 @@
name: allowEdit ? 'LessonForm' : 'Lesson',
params: {
courseName: courseName,
chapterNumber: lesson.number.split('.')[0],
lessonNumber: lesson.number.split('.')[1],
chapterNumber: lesson.number.split('-')[0],
lessonNumber: lesson.number.split('-')[1],
},
}"
>
@@ -109,6 +112,14 @@
v-else-if="lesson.icon === 'icon-quiz'"
class="h-4 w-4 stroke-1 mr-2"
/>
<NotebookPen
v-else-if="lesson.icon === 'icon-assignment'"
class="h-4 w-4 stroke-1 mr-2"
/>
<SquareCode
v-else-if="lesson.icon === 'icon-code'"
class="h-4 w-4 stroke-1 mr-2"
/>
<FileText
v-else-if="lesson.icon === 'icon-list'"
class="h-4 w-4 text-ink-gray-9 stroke-1 mr-2"
@@ -174,7 +185,11 @@ import {
FilePenLine,
HelpCircle,
MonitorPlay,
NotebookPen,
Plus,
SquareCode,
Trash2,
Notebook,
} from 'lucide-vue-next'
import { useRoute, useRouter } from 'vue-router'
import ChapterModal from '@/components/Modals/ChapterModal.vue'
@@ -389,8 +404,8 @@ const redirectToChapter = (chapter) => {
const isActiveLesson = (lessonNumber) => {
return (
route.params.chapterNumber == lessonNumber.split('.')[0] &&
route.params.lessonNumber == lessonNumber.split('.')[1]
route.params.chapterNumber == lessonNumber.split('-')[0] &&
route.params.lessonNumber == lessonNumber.split('-')[1]
)
}
</script>

View File

@@ -12,7 +12,7 @@
</div>
<div class="grid gap-8 mt-10">
<div v-for="(review, index) in reviews.data">
<div class="flex items-center">
<div class="flex">
<router-link
:to="{
name: 'Profile',
@@ -46,11 +46,11 @@
"
/>
</div>
<div v-if="review.review" class="mt-4 leading-5 text-ink-gray-7">
{{ review.review }}
</div>
</div>
</div>
<div v-if="review.review" class="mt-4 leading-5 text-ink-gray-7">
{{ review.review }}
</div>
</div>
</div>
</div>
@@ -80,7 +80,7 @@ const props = defineProps({
required: true,
},
membership: {
type: Object,
type: Object || null,
required: false,
},
})

View File

@@ -9,5 +9,5 @@
</div>
</template>
<script setup>
import AppSidebar from './AppSidebar.vue'
import AppSidebar from '@/components/Sidebar/AppSidebar.vue'
</script>

View File

@@ -93,11 +93,19 @@
</div>
</template>
<script setup>
import { createResource, TextEditor, Button, Dropdown, toast } from 'frappe-ui'
import {
call,
createResource,
TextEditor,
Button,
Dropdown,
toast,
} from 'frappe-ui'
import { timeAgo } from '@/utils'
import UserAvatar from '@/components/UserAvatar.vue'
import { ChevronLeft, MoreHorizontal } from 'lucide-vue-next'
import { ref, inject, onMounted, onUnmounted } from 'vue'
import { useTelemetry } from 'frappe-ui/frappe'
const showTopics = defineModel('showTopics')
const newReply = ref('')
@@ -107,6 +115,7 @@ const allUsers = inject('$allUsers')
const mentionUsers = ref([])
const renderEditor = ref(false)
const readOnlyMode = window.read_only_mode
const { capture } = useTelemetry()
const props = defineProps({
topic: {
@@ -143,19 +152,6 @@ const replies = createResource({
auto: true,
})
const newReplyResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Reply',
reply: newReply.value,
topic: props.topic.name,
},
}
},
})
const fetchMentionUsers = () => {
if (user.data?.is_student) {
renderEditor.value = true
@@ -178,78 +174,61 @@ const fetchMentionUsers = () => {
}
const postReply = () => {
newReplyResource.submit(
{},
{
validate() {
if (!newReply.value) {
return 'Reply cannot be empty'
}
},
onSuccess() {
newReply.value = ''
replies.reload()
},
onError(err) {
toast.error(err.messages?.[0] || err)
},
}
)
}
const editReplyResource = createResource({
url: 'frappe.client.set_value',
makeParams(values) {
return {
if (!newReply.value) {
toast.error(__('Reply cannot be empty.'))
return
}
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Reply',
name: values.name,
fieldname: 'reply',
value: values.reply,
}
},
})
reply: newReply.value,
topic: props.topic.name,
},
})
.then((data) => {
newReply.value = ''
replies.reload()
capture('discussion_reply_created')
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const postEdited = (reply) => {
editReplyResource.submit(
{
name: reply.name,
reply: reply.reply,
},
{
validate() {
if (!reply.reply) {
return 'Reply cannot be empty'
}
},
onSuccess() {
reply.editable = false
replies.reload()
},
}
)
if (!reply.reply) {
toast.error(__('Reply cannot be empty.'))
return
}
call('frappe.client.set_value', {
doctype: 'Discussion Reply',
name: reply.name,
fieldname: 'reply',
value: reply.reply,
})
.then(() => {
reply.editable = false
replies.reload()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const deleteReplyResource = createResource({
url: 'frappe.client.delete',
makeParams(values) {
return {
doctype: 'Discussion Reply',
name: values.name,
}
},
})
const deleteReply = (reply) => {
deleteReplyResource.submit(
{
name: reply.name,
},
{
onSuccess() {
replies.reload()
},
}
)
call('frappe.client.delete', {
doctype: 'Discussion Reply',
name: reply.name,
})
.then(() => {
replies.reload()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
onUnmounted(() => {

View File

@@ -1,7 +1,7 @@
<template>
<Dialog v-model="showDialog">
<template #body-title>
<h2 class="text-lg font-bold">{{ __('Install Frappe Learning') }}</h2>
<h2 class="text-lg font-bold">{{ __('Установить приложение IIE') }}</h2>
</template>
<template #body-content>
<p>
@@ -29,7 +29,7 @@
class="mb-1 flex flex-row items-center justify-between px-3 text-center"
>
<span class="text-base font-bold text-gray-900">
{{ __('Install Frappe Learning') }}
{{ __('Установить приложение IIE') }}
</span>
<span class="inline-flex items-baseline">
<FeatherIcon
@@ -76,7 +76,14 @@ const isIos = () => {
const isInStandaloneMode = () =>
'standalone' in window.navigator && window.navigator.standalone
if (isIos() && !isInStandaloneMode()) iosInstallMessage.value = true
if (
isIos() &&
!isInStandaloneMode() &&
localStorage.getItem('learningIosInstallPromptShown') !== 'true'
) {
iosInstallMessage.value = true
localStorage.setItem('learningIosInstallPromptShown', 'true')
}
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()

View File

@@ -1,217 +0,0 @@
<template>
<div
v-if="hasPermission() && !props.zoomAccount"
class="flex items-center space-x-2 mb-5 bg-surface-amber-1 py-1 px-2 rounded-md text-ink-amber-3"
>
<AlertCircle class="size-4 stroke-1.5" />
<span>
{{ __('Please add a zoom account to the batch to create live classes.') }}
</span>
</div>
<div class="flex items-center justify-between">
<div class="text-lg font-semibold text-ink-gray-9">
{{ __('Live Class') }}
</div>
<Button v-if="canCreateClass()" @click="openLiveClassModal">
<template #prefix>
<Plus class="h-4 w-4" />
</template>
<span>
{{ __('Add') }}
</span>
</Button>
</div>
<div
v-if="liveClasses.data?.length"
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 mt-5"
>
<div
v-for="cls in liveClasses.data"
class="flex flex-col border rounded-md h-full text-ink-gray-7 hover:border-outline-gray-3 p-3"
:class="{
'cursor-pointer': hasPermission() && cls.attendees > 0,
}"
@click="
() => {
openAttendanceModal(cls)
}
"
>
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
{{ cls.title }}
</div>
<div class="short-introduction">
{{ cls.description }}
</div>
<div class="mt-auto space-y-3">
<div class="flex items-center space-x-2">
<Calendar class="w-4 h-4 stroke-1.5" />
<span>
{{ dayjs(cls.date).format('DD MMMM YYYY') }}
</span>
</div>
<div class="flex items-center space-x-2">
<Clock class="w-4 h-4 stroke-1.5" />
<span>
{{ dayjs(getClassStart(cls)).format('hh:mm A') }} -
{{ dayjs(getClassEnd(cls)).format('hh:mm A') }}
</span>
</div>
<div
v-if="canAccessClass(cls)"
class="flex items-center space-x-2 text-ink-gray-9 mt-auto"
>
<a
v-if="user.data?.is_moderator || user.data?.is_evaluator"
:href="cls.start_url"
target="_blank"
class="cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-ink-gray-8 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 focus-visible:ring focus-visible:ring-outline-gray-3 h-7 text-base px-2 rounded"
:class="cls.join_url ? 'w-full' : 'w-1/2'"
>
<Monitor class="h-4 w-4 stroke-1.5" />
{{ __('Start') }}
</a>
<a
:href="cls.join_url"
target="_blank"
class="w-full cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-ink-gray-8 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 focus-visible:ring focus-visible:ring-outline-gray-3 h-7 text-base px-2 rounded"
>
<Video class="h-4 w-4 stroke-1.5" />
{{ __('Join') }}
</a>
</div>
<Tooltip
v-else-if="hasClassEnded(cls)"
:text="__('This class has ended')"
placement="right"
>
<div class="flex items-center space-x-2 text-ink-amber-3 w-fit">
<Info class="w-4 h-4 stroke-1.5" />
<span>
{{ __('Ended') }}
</span>
</div>
</Tooltip>
</div>
</div>
</div>
<div v-else class="text-sm italic text-ink-gray-5 mt-2">
{{ __('No live classes scheduled') }}
</div>
<LiveClassModal
:batch="props.batch"
:zoomAccount="props.zoomAccount"
v-model="showLiveClassModal"
v-model:reloadLiveClasses="liveClasses"
/>
<LiveClassAttendance v-model="showAttendance" :live_class="attendanceFor" />
</template>
<script setup>
import { createListResource, Button, Tooltip } from 'frappe-ui'
import {
Plus,
Clock,
Calendar,
Video,
Monitor,
Info,
AlertCircle,
} from 'lucide-vue-next'
import { inject, ref } from 'vue'
import { formatTime } from '@/utils/'
import LiveClassModal from '@/components/Modals/LiveClassModal.vue'
import LiveClassAttendance from '@/components/Modals/LiveClassAttendance.vue'
const user = inject('$user')
const showLiveClassModal = ref(false)
const dayjs = inject('$dayjs')
const readOnlyMode = window.read_only_mode
const showAttendance = ref(false)
const attendanceFor = ref(null)
const props = defineProps({
batch: {
type: String,
required: true,
},
zoomAccount: String,
})
const liveClasses = createListResource({
doctype: 'LMS Live Class',
filters: {
batch_name: props.batch,
},
fields: [
'title',
'description',
'time',
'date',
'duration',
'attendees',
'start_url',
'join_url',
'owner',
],
orderBy: 'date',
auto: true,
})
const openLiveClassModal = () => {
showLiveClassModal.value = true
}
const canCreateClass = () => {
if (readOnlyMode) return false
if (!props.zoomAccount) return false
return hasPermission()
}
const hasPermission = () => {
return user.data?.is_moderator || user.data?.is_evaluator
}
const canAccessClass = (cls) => {
if (cls.date < dayjs().format('YYYY-MM-DD')) return false
if (cls.date > dayjs().format('YYYY-MM-DD')) return false
if (hasClassEnded(cls)) return false
return true
}
const getClassStart = (cls) => {
return new Date(`${cls.date}T${cls.time}`)
}
const getClassEnd = (cls) => {
const classStart = getClassStart(cls)
return new Date(classStart.getTime() + cls.duration * 60000)
}
const hasClassEnded = (cls) => {
const classEnd = getClassEnd(cls)
const now = new Date()
return now > classEnd
}
const openAttendanceModal = (cls) => {
if (!hasPermission()) return
if (cls.attendees <= 0) return
showAttendance.value = true
attendanceFor.value = cls
}
</script>
<style>
.short-introduction {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
width: 100%;
overflow: hidden;
margin: 0.25rem 0 1.5rem;
line-height: 1.5;
}
</style>

View File

@@ -76,12 +76,20 @@ const isModerator = ref(false)
const isInstructor = ref(false)
onMounted(() => {
// Вызываем addSideBar только если userResource уже загружен
if (userResource.data) {
addSideBar()
}
addOtherLinks()
filterLinksToShow(data)
sidebarSettings.reload(
{},
{
onSuccess(data) {
if (userResource.data) {
addSideBar()
} else {
destructureSidebarLinks()
}
filterLinksToShow(data)
addOtherLinks()
},
}
)
})
const handleOutsideClick = (e) => {
@@ -100,6 +108,16 @@ watch(showMenu, (val) => {
}
})
const destructureSidebarLinks = () => {
let links = []
sidebarLinks.value.forEach((link) => {
link.items?.forEach((item) => {
links.push(item)
})
})
sidebarLinks.value = links
}
const filterLinksToShow = (data) => {
Object.keys(data).forEach((key) => {
if (!parseInt(data[key])) {

View File

@@ -20,11 +20,15 @@
:options="assessmentTypes"
v-model="assessmentType"
:label="__('Type')"
placeholder=" "
@update:modelValue="() => (assessment = null)"
/>
<Link
v-if="assessmentType"
v-model="assessment"
:doctype="assessmentType"
:label="__('Assessment')"
placeholder=" "
:onCreate="
(value, close) => {
close()
@@ -49,7 +53,7 @@
</template>
<script setup>
import { Dialog, FormControl, createResource, toast } from 'frappe-ui'
import Link from '@/components/Controls/Link.vue'
import { Link } from 'frappe-ui/frappe'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'

View File

@@ -27,6 +27,12 @@
:label="__('Submission Type')"
:required="true"
/>
<Link
v-model="assignment.course"
:label="__('Course')"
doctype="LMS Course"
placeholder=" "
/>
<div>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Question') }}
@@ -37,7 +43,7 @@
@change="(val) => (assignment.question = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
/>
</div>
</div>
@@ -66,6 +72,8 @@
<script setup lang="ts">
import { Button, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { computed, reactive, watch } from 'vue'
import { escapeHTML, sanitizeHTML } from '@/utils'
import { Link } from 'frappe-ui/frappe'
const show = defineModel()
const assignments = defineModel<Assignments>('assignments')
@@ -74,6 +82,7 @@ interface Assignment {
title: string
type: string
question: string
course?: string
}
interface Assignments {
@@ -88,6 +97,7 @@ const assignment = reactive({
title: '',
type: '',
question: '',
course: '',
})
const props = defineProps({
@@ -106,6 +116,7 @@ watch(
assignment.title = row.title
assignment.type = row.type
assignment.question = row.question
assignment.course = row.course || ''
}
})
}
@@ -121,35 +132,49 @@ watch(show, (newVal) => {
}
})
const validateFields = () => {
assignment.title = escapeHTML(assignment.title.trim())
assignment.question = sanitizeHTML(assignment.question)
}
const saveAssignment = () => {
validateFields()
if (props.assignmentID == 'new') {
assignments.value.insert.submit(
{
...assignment,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment created successfully'))
},
}
)
createAssignment()
} else {
assignments.value.setValue.submit(
{
...assignment,
name: props.assignmentID,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment updated successfully'))
},
}
)
updateAssignment()
}
}
const createAssignment = () => {
assignments.value.insert.submit(
{
...assignment,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment created successfully'))
},
}
)
}
const updateAssignment = () => {
assignments.value.setValue.submit(
{
...assignment,
name: props.assignmentID,
},
{
onSuccess() {
show.value = false
toast.success(__('Assignment updated successfully'))
},
}
)
}
const assignmentOptions = computed(() => {
return [
{ label: 'PDF', value: 'PDF' },

View File

@@ -2,8 +2,8 @@
<Dialog
v-model="show"
:options="{
title: __('Add a course'),
size: 'sm',
title: __('Add a course to the batch'),
size: 'lg',
actions: [
{
label: __('Submit'),
@@ -19,14 +19,13 @@
v-model="course"
:label="__('Course')"
:required="true"
:filters="{ published: 1 }"
:onCreate="
(value, close) => {
close()
router.push({
name: 'CourseForm',
params: {
courseName: 'new',
},
name: 'Courses',
query: { newCourse: '1' },
})
}
"
@@ -42,7 +41,7 @@
</Dialog>
</template>
<script setup>
import { Dialog, createResource, toast } from 'frappe-ui'
import { Dialog, toast } from 'frappe-ui'
import { ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { useOnboarding } from 'frappe-ui/frappe'
@@ -64,37 +63,28 @@ const props = defineProps({
},
})
const createBatchCourse = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Batch Course',
parent: props.batch,
parenttype: 'LMS Batch',
parentfield: 'courses',
course: course.value,
evaluator: evaluator.value,
},
}
},
})
const addCourse = (close) => {
createBatchCourse.submit(
{},
courses.value.insert.submit(
{
course: course.value,
evaluator: evaluator.value,
parent: props.batch,
parenttype: 'LMS Batch',
parentfield: 'courses',
},
{
onSuccess() {
if (user.data?.is_system_manager)
updateOnboardingStep('add_batch_course')
close()
courses.value.reload()
course.value = null
evaluator.value = null
toast.success(__('Course added to batch successfully'))
},
onError(err) {
toast.error(err.messages?.[0] || err)
console.log(err)
},
}
)

View File

@@ -1,146 +0,0 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
}"
>
<template #body>
<div class="p-5 space-y-10 text-base">
<div class="flex items-center space-x-2">
<Avatar :image="student.user_image" size="3xl" />
<div class="space-y-1">
<div class="flex items-center space-x-2">
<div class="text-xl font-semibold text-ink-gray-9">
{{ student.full_name }}
</div>
<Badge
v-if="
Object.keys(student.assessments).length ||
Object.keys(student.courses).length
"
:theme="student.progress === 100 ? 'green' : 'red'"
>
{{ student.progress }}% {{ __('Complete') }}
</Badge>
</div>
<div class="text-sm text-ink-gray-7">
{{ student.email }}
</div>
</div>
</div>
<div class="space-y-8">
<!-- Assessments -->
<div
v-if="Object.keys(student.assessments).length"
class="space-y-2 text-sm"
>
<div
class="flex items-center border-b pb-1 font-medium text-ink-gray-9"
>
<span class="flex-1">
{{ __('Assessment') }}
</span>
<span>
{{ __('Percentage/Status') }}
</span>
</div>
<router-link
v-for="assessment in Object.keys(student.assessments)"
class="flex items-center text-ink-gray-7 font-medium"
:to="{
name:
student.assessments[assessment].type == 'LMS Assignment'
? 'AssignmentSubmission'
: '',
params:
student.assessments[assessment].type == 'LMS Assignment'
? {
assignmentID:
student.assessments[assessment].assessment,
submissionName:
student.assessments[assessment].submission,
}
: {},
}"
>
<span class="flex-1">
{{ assessment }}
</span>
<span v-if="isAssignment(student.assessments[assessment].status)">
<Badge
:theme="
getStatusTheme(student.assessments[assessment].status)
"
>
{{ student.assessments[assessment].status }}
</Badge>
</span>
<span v-else>
{{ student.assessments[assessment].status }}
</span>
</router-link>
</div>
<!-- Courses -->
<div
v-if="Object.keys(student.courses).length"
class="space-y-2 text-sm"
>
<div
class="flex items-center border-b pb-1 font-medium text-ink-gray-9"
>
<span class="flex-1">
{{ __('Courses') }}
</span>
<span>
{{ __('Progress') }}
</span>
</div>
<div
v-for="course in Object.keys(student.courses)"
class="flex items-center text-ink-gray-7 font-medium"
>
<span class="flex-1">
{{ course }}
</span>
<span>
{{ Math.floor(student.courses[course]) }}
</span>
</div>
</div>
</div>
<!-- Heatmap -->
<StudentHeatmap :member="student.email" :days="120" />
</div>
</template>
</Dialog>
</template>
<script setup>
import { Avatar, Badge, Dialog } from 'frappe-ui'
import StudentHeatmap from '@/components/StudentHeatmap.vue'
const show = defineModel()
const props = defineProps({
student: {
type: Object,
default: null,
},
})
const isAssignment = (value) => {
return isNaN(value)
}
const getStatusTheme = (status) => {
if (status === 'Pass') {
return 'green'
} else if (status == 'Not Graded') {
return 'orange'
} else {
return 'red'
}
}
</script>

View File

@@ -80,13 +80,13 @@ import {
} from 'frappe-ui'
import { reactive, watch, inject } from 'vue'
import { getFileSize } from '@/utils/'
import { capture } from '@/telemetry'
import { FileText, X } from 'lucide-vue-next'
import { useOnboarding } from 'frappe-ui/frappe'
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
const show = defineModel()
const outline = defineModel('outline')
const user = inject('$user')
const { capture } = useTelemetry()
const { updateOnboardingStep } = useOnboarding('learning')
const props = defineProps({

View File

@@ -1,231 +0,0 @@
<template>
<Dialog
v-model="show"
:options="{
title: __('Course Progress Summary'),
size: '5xl',
}"
>
<template #body-content>
<div
class="flex flex-col-reverse md:flex-row justify-between md:space-x-10 text-base mt-10"
>
<div class="w-full">
<div class="flex items-center justify-between space-x-5 mb-4">
<FormControl
v-model="searchFilter"
:placeholder="__('Search by Member')"
type="text"
class="w-full"
/>
</div>
<div class="max-h-[70vh] overflow-y-auto">
<ListView
v-if="progressList.loading || progressList.data?.length"
:columns="progressColumns"
:rows="progressList.data"
rowKey="name"
:options="{
selectable: false,
showTooltip: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
>
<ListHeaderItem
:item="item"
v-for="item in progressColumns"
:key="item.key"
>
<template #prefix="{ item }">
<FeatherIcon
:name="item.icon?.toString()"
class="h-4 w-4"
/>
</template>
</ListHeaderItem>
</ListHeader>
<ListRows v-for="row in progressList.data">
<router-link
:to="{
name: 'Profile',
params: { username: row.member_username },
}"
>
<ListRow :row="row">
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
>
<template #prefix>
<div v-if="column.key == 'member_name'">
<Avatar
class="flex items-center"
:image="row['member_image']"
:label="item"
size="sm"
/>
</div>
</template>
<div>
{{ row[column.key].toString() }}
</div>
</ListRowItem>
</template>
</ListRow>
</router-link>
</ListRows>
</ListView>
<div
v-if="progressList.data && progressList.hasNextPage"
class="flex justify-center my-5"
>
<Button @click="progressList.next()">
{{ __('Load More') }}
</Button>
</div>
</div>
</div>
<div class="mb-4 self-start w-full space-y-5">
<div
class="flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-4"
>
<NumberChart
class="border rounded-md w-full"
:config="{
title: __('Enrollments'),
value: memberCount || 0,
}"
/>
<NumberChart
class="border rounded-md w-full"
:config="{
title: __('Average Progress %'),
value: chartDetails.data?.average_progress || 0,
}"
/>
</div>
<DonutChart
:config="{
data: chartDetails.data?.progress_distribution || [],
title: __('Progress Distribution'),
categoryColumn: 'category',
valueColumn: 'count',
colors: [
theme.colors.red['400'],
theme.colors.amber['400'],
theme.colors.pink['400'],
theme.colors.blue['400'],
theme.colors.green['400'],
],
}"
/>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import {
Avatar,
Button,
createListResource,
createResource,
Dialog,
DonutChart,
FeatherIcon,
FormControl,
ListView,
ListHeader,
ListHeaderItem,
ListRows,
ListRow,
ListRowItem,
NumberChart,
} from 'frappe-ui'
import { computed, ref, watch } from 'vue'
import { theme } from '@/utils/theme'
const show = defineModel<boolean>({ default: false })
const searchFilter = ref<string | null>(null)
type Filters = {
course: string | undefined
member_name?: string[]
}
const props = defineProps<{
courseName?: string
enrollments?: number
}>()
const memberCount = ref<number>(props.enrollments || 0)
const chartDetails = createResource({
url: 'lms.lms.api.get_course_progress_distribution',
params: {
course: props.courseName,
},
auto: true,
})
const progressList = createListResource({
doctype: 'LMS Enrollment',
filters: {
course: props.courseName,
},
fields: [
'name',
'member',
'member_name',
'member_image',
'member_username',
'progress',
],
pageLength: 50,
auto: true,
})
watch([searchFilter], () => {
let filterApplied = false
let filters: Filters = {
course: props.courseName,
}
if (searchFilter.value) {
filters.member_name = ['like', `%${searchFilter.value}%`]
filterApplied = true
}
progressList.update({
filters: filters,
})
progressList.reload(
{},
{
onSuccess(data: any[]) {
memberCount.value = filterApplied ? data.length : props.enrollments || 0
},
}
)
})
const progressColumns = computed(() => {
return [
{
label: __('Member'),
key: 'member_name',
width: '60%',
icon: 'user',
},
{
label: __('Progress'),
key: 'progress',
align: 'right',
icon: 'trending-up',
},
]
})
</script>

View File

@@ -26,7 +26,7 @@
@change="(val) => (topic.reply = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
</div>
@@ -34,17 +34,13 @@
</Dialog>
</template>
<script setup>
import {
Dialog,
FormControl,
TextEditor,
createResource,
toast,
} from 'frappe-ui'
import { call, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { reactive } from 'vue'
import { singularize } from '@/utils'
import { useTelemetry } from 'frappe-ui/frappe'
const topics = defineModel('reloadTopics')
const { capture } = useTelemetry()
const props = defineProps({
title: {
@@ -66,64 +62,50 @@ const topic = reactive({
reply: '',
})
const topicResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Topic',
reference_doctype: props.doctype,
reference_docname: props.docname,
title: topic.title,
},
}
},
})
const replyResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'Discussion Reply',
topic: values.topic,
reply: topic.reply,
},
}
},
})
const submitTopic = (close) => {
topicResource.submit(
{},
{
validate() {
if (!topic.title) {
return 'Title cannot be empty.'
}
if (!topic.reply) {
return 'Reply cannot be empty.'
}
},
onSuccess(data) {
replyResource.submit(
{
topic: data.name,
},
{
onSuccess() {
topic.title = ''
topic.reply = ''
topics.value.reload()
close()
},
}
)
},
onError(err) {
toast.error(err.messages?.[0] || err)
},
}
)
if (!topic.title) {
toast.error(__('Title cannot be empty.'))
return
}
if (!topic.reply) {
toast.error(__('Details cannot be empty.'))
return
}
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Topic',
reference_doctype: props.doctype,
reference_docname: props.docname,
title: topic.title,
},
})
.then((data) => {
createReply(data.name, close)
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
const createReply = (topicName, close) => {
call('frappe.client.insert', {
doc: {
doctype: 'Discussion Reply',
topic: topicName,
reply: topic.reply,
},
})
.then((data) => {
topic.title = ''
topic.reply = ''
topics.value.reload()
capture('discussion_topic_created')
close()
})
.catch((err) => {
toast.error(err.messages?.[0] || err)
console.error(err)
})
}
</script>

View File

@@ -1,91 +1,85 @@
<template>
<Dialog
v-model="show"
:options="{
title: __('Edit your profile'),
size: '3xl',
actions: [
{
label: __('Save'),
variant: 'solid',
onClick: (close) => saveProfile(close),
},
],
}"
>
<template #body-header>
<div class="flex items-center justify-between mb-5">
<div class="text-2xl font-semibold leading-6 text-ink-gray-9">
{{ __('Edit Profile') }}
</div>
<div class="space-x-2">
<Badge v-if="isDirty" theme="orange">
{{ __('Not Saved') }}
</Badge>
<div class="pb-5 float-right">
<Button variant="solid" @click="saveProfile()">
{{ __('Save') }}
</Button>
</div>
</div>
</div>
</template>
<template #body-content>
<div class="grid grid-cols-2 gap-5">
<div class="space-y-4">
<!-- <Uploader
v-model="profile.image.file_url"
label="Profile Image"
description="Your profile image to help others recognize you."
/> -->
<div>
<div class="text-xs text-ink-gray-5 mb-1">
{{ __('Profile Image') }}
</div>
<FileUploader
v-if="!profile.image"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template
v-slot="{ file, progress, uploading, openFileSelector }"
>
<div class="mb-4">
<Button @click="openFileSelector" :loading="uploading">
{{
uploading
? `Uploading ${progress}%`
: 'Upload a profile image'
}}
</Button>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img
:src="profile.image.file_url"
class="object-cover h-[50px] w-[50px] rounded-full border-4 border-white object-cover"
/>
<div class="text-base">
<div class="grid grid-cols-2 gap-10">
<div class="space-y-4">
<div class="space-y-4">
<Uploader
v-model="profile.image"
:label="__('Profile Image')"
:required="true"
shape="circle"
/>
<div class="text-base flex flex-col ml-2">
<span>
{{ profile.image.file_name }}
</span>
<span class="text-sm text-ink-gray-4 mt-1">
{{ getFileSize(profile.image.file_size) }}
</span>
</div>
<X
@click="removeImage()"
class="bg-surface-gray-3 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
/>
</div>
<FormControl
v-model="profile.first_name"
:label="__('First Name')"
/>
<FormControl
v-model="profile.last_name"
:label="__('Last Name')"
/>
<FormControl v-model="profile.headline" :label="__('Headline')" />
<FormControl
v-model="profile.linkedin"
:label="__('LinkedIn ID')"
/>
<FormControl v-model="profile.github" :label="__('GitHub ID')" />
<FormControl
v-model="profile.twitter"
:label="__('Twitter ID')"
/>
</div>
</div>
<FormControl v-model="profile.first_name" :label="__('First Name')" />
<FormControl v-model="profile.last_name" :label="__('Last Name')" />
<FormControl v-model="profile.headline" :label="__('Headline')" />
<Link
:label="__('Language')"
v-model="profile.language"
doctype="Language"
/>
</div>
<div>
<div class="mb-4">
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Bio') }}
</div>
<TextEditor
:fixedMenu="true"
@change="(val) => (profile.bio = val)"
:content="profile.bio"
editorClass="prose-sm py-2 px-2 min-h-[200px] border-outline-gray-2 hover:border-outline-gray-3 rounded-b-md bg-surface-gray-3"
<div class="space-y-4">
<FormControl
v-model="profile.open_to"
type="select"
:options="[' ', 'Work', 'Hiring']"
:label="__('Open to')"
:placeholder="__('Looking for new work or hiring talent?')"
/>
<Link
:label="__('Language')"
v-model="profile.language"
doctype="Language"
/>
<div>
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Bio') }}
</div>
<TextEditor
:fixedMenu="true"
@change="(val) => (profile.bio = val)"
:content="profile.bio"
:rows="15"
editorClass="prose-sm py-2 px-2 min-h-[280px] border-outline-gray-2 hover:border-outline-gray-3 rounded-b-md bg-surface-gray-3"
/>
</div>
</div>
</div>
</div>
@@ -94,22 +88,22 @@
</template>
<script setup>
import {
Dialog,
FormControl,
FileUploader,
Badge,
Button,
createResource,
Dialog,
FormControl,
TextEditor,
toast,
} from 'frappe-ui'
import { ref, reactive, watch } from 'vue'
import { X } from 'lucide-vue-next'
import { getFileSize, decodeEntities } from '@/utils'
import { sanitizeHTML } from '@/utils'
import Link from '@/components/Controls/Link.vue'
import DOMPurify from 'dompurify'
const show = defineModel()
const reloadProfile = defineModel('reloadProfile')
const hasLanguageChanged = ref(false)
const isDirty = ref(false)
const props = defineProps({
profile: {
@@ -124,19 +118,10 @@ const profile = reactive({
headline: '',
bio: '',
image: '',
})
const imageResource = createResource({
url: 'lms.lms.api.get_file_info',
makeParams(values) {
return {
file_url: values.image,
}
},
auto: false,
onSuccess(data) {
profile.image = data
},
open_to: '',
linkedin: '',
github: '',
twitter: '',
})
const updateProfile = createResource({
@@ -146,7 +131,7 @@ const updateProfile = createResource({
doctype: 'User',
name: props.profile.data.name,
fieldname: {
user_image: profile.image.file_url,
user_image: profile.image || null,
...profile,
},
}
@@ -156,28 +141,13 @@ const updateProfile = createResource({
},
})
const saveProfile = (close) => {
profile.bio = DOMPurify.sanitize(decodeEntities(profile.bio), {
ALLOWED_TAGS: [
'b',
'i',
'em',
'strong',
'a',
'p',
'br',
'ul',
'ol',
'li',
'img',
],
ALLOWED_ATTR: ['href', 'target', 'src'],
})
const saveProfile = () => {
profile.bio = sanitizeHTML(profile.bio)
updateProfile.submit(
{},
{
onSuccess() {
close()
show.value = false
reloadProfile.value.reload()
if (hasLanguageChanged.value) {
hasLanguageChanged.value = false
@@ -191,20 +161,26 @@ const saveProfile = (close) => {
)
}
const validateFile = (file) => {
let extension = file.name.split('.').pop().toLowerCase()
if (!['jpg', 'jpeg', 'png'].includes(extension)) {
return 'Only image file is allowed.'
}
}
const saveImage = (file) => {
profile.image = file
}
const removeImage = () => {
profile.image = null
}
watch(
() => profile,
(newVal) => {
if (!props.profile.data) return
let keys = Object.keys(newVal)
keys.splice(keys.indexOf('image'), 1)
for (let key of keys) {
if (newVal[key] !== props.profile.data[key]) {
isDirty.value = true
return
}
}
if (profile.image !== props.profile.data.user_image) {
isDirty.value = true
return
}
isDirty.value = false
},
{ deep: true }
)
watch(
() => props.profile.data,
@@ -215,15 +191,20 @@ watch(
profile.headline = newVal.headline
profile.language = newVal.language
profile.bio = newVal.bio
if (newVal.user_image) imageResource.submit({ image: newVal.user_image })
profile.open_to = newVal.open_to
profile.linkedin = newVal.linkedin
profile.github = newVal.github
profile.twitter = newVal.twitter
profile.image = newVal.user_image
isDirty.value = false
}
}
)
watch(
() => profile.language,
(newVal, oldVal) => {
if (newVal !== oldVal) {
() => {
if (profile.language !== props.profile.data.language) {
hasLanguageChanged.value = true
}
}

View File

@@ -48,7 +48,7 @@
:rows="10"
:placeholder="
__(
'<p>Dear {{ member_name }},</p>\n\n<p>You have been enrolled in our upcoming batch {{ batch_name }}.</p>\n\n<p>Thanks,</p>\n<p>Frappe Learning</p>'
'<p>Dear {{ member_name }},</p>\n\n<p>You have been enrolled in our upcoming batch {{ batch_name }}.</p>\n\n<p>Thanks,</p>\n<p>IIE</p>'
)
"
/>
@@ -64,10 +64,10 @@
:fixedMenu="true"
:placeholder="
__(
'Dear {{ member_name }},\n\nYou have been enrolled in our upcoming batch {{ batch_name }}.\n\nThanks,\nFrappe Learning'
'Dear {{ member_name }},\n\nYou have been enrolled in our upcoming batch {{ batch_name }}.\n\nThanks,\nIIE'
)
"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
/>
</div>
</div>

View File

@@ -2,7 +2,7 @@
<Dialog
v-model="show"
:options="{
title: __('Schedule Evaluation'),
title: __('Schedule your evaluation'),
size: 'xl',
actions: [
{
@@ -14,64 +14,71 @@
}"
>
<template #body-content>
<div class="flex flex-col gap-4">
<div>
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Course') }}
<div class="flex flex-col gap-4 text-base max-h-[60vh]">
<FormControl
v-model="evaluation.course"
type="select"
:label="__('Course')"
:options="getCourses()"
/>
<div v-if="slots.data?.length" class="space-y-4 overflow-y-auto mt-4">
<div class="text-ink-gray-9 font-medium">
{{ __('Available Slots') }}
</div>
<Select v-model="evaluation.course" :options="getCourses()" />
</div>
<div>
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Date') }}
</div>
<FormControl
type="date"
v-model="evaluation.date"
:min="
dayjs()
.add(dayjs.duration({ days: 1 }))
.format('YYYY-MM-DD')
"
/>
</div>
<div v-if="slots.data?.length">
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Select a slot') }}
</div>
<div class="grid grid-cols-2 gap-2">
<div v-for="slot in slots.data">
<div
class="text-base text-center border rounded-md text-ink-gray-8 bg-surface-gray-3 p-2 cursor-pointer"
@click="saveSlot(slot)"
:class="{
'border-outline-gray-4':
evaluation.start_time == slot.start_time,
}"
>
{{ formatTime(slot.start_time) }} -
{{ formatTime(slot.end_time) }}
<div class="space-y-5">
<div v-for="row in slots.data" class="space-y-2">
<div class="flex items-center text-ink-gray-7 space-x-2">
<Calendar class="size-3" />
<div class="text-ink-gray-9">
{{ dayjs(row.date).format('DD MMMM YYYY') }}
</div>
<div>&middot;</div>
<div class="text-ink-gray-5">
{{ row.day }}
</div>
</div>
<div class="grid grid-cols-3 gap-2">
<div
v-for="slot in row.slots"
class="text-base text-center border rounded-md text-ink-gray-8 p-2 cursor-pointer text-ink-gray-7 hover:bg-surface-gray-2 hover:border-outline-gray-3"
@click="saveSlot(slot, row)"
:class="{
'border-outline-gray-4 text-ink-gray-9':
evaluation.date == row.date &&
evaluation.start_time == slot.start_time,
}"
>
{{ formatTime(slot.start_time) }} -
{{ formatTime(slot.end_time) }}
</div>
</div>
</div>
</div>
</div>
<div
v-else-if="evaluation.course && evaluation.date"
class="text-sm italic text-ink-red-4"
>
{{ __('No slots available for this date.') }}
<div v-else-if="!evaluation.course" class="text-ink-gray-7">
{{ __('Please select a course to view available slots.') }}
</div>
<div v-else class="text-ink-red-3">
{{ __('No slots available for the selected course.') }}
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog, createResource, Select, FormControl, toast } from 'frappe-ui'
import { reactive, watch, inject } from 'vue'
import {
call,
createResource,
dayjs,
Dialog,
FormControl,
toast,
} from 'frappe-ui'
import { ref, watch, inject } from 'vue'
import { Calendar } from 'lucide-vue-next'
import { formatTime } from '@/utils/'
const user = inject('$user')
const dayjs = inject('$dayjs')
const show = defineModel()
const evaluations = defineModel('reloadEvals')
@@ -90,7 +97,7 @@ const props = defineProps({
},
})
const evaluation = reactive({
const evaluation = ref({
course: '',
date: '',
start_time: '',
@@ -100,48 +107,28 @@ const evaluation = reactive({
member: user.data.name,
})
const createEvaluation = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'LMS Certificate Request',
batch_name: values.batch,
...values,
},
}
},
})
function submitEvaluation(close) {
createEvaluation.submit(evaluation, {
validate() {
if (!evaluation.course) {
return 'Please select a course.'
}
if (!evaluation.date) {
return 'Please select a date.'
}
if (!evaluation.start_time) {
return 'Please select a slot.'
}
if (dayjs(evaluation.date).isBefore(dayjs(), 'day')) {
return 'Please select a future date.'
}
if (dayjs(evaluation.date).isAfter(dayjs(props.endDate), 'day')) {
return `Please select a date before the end date ${dayjs(
props.endDate
).format('DD MMMM YYYY')}.`
}
},
onSuccess() {
evaluations.value.reload()
close()
},
onError(err) {
toast.warning(__(err.messages?.[0] || err))
if (!evaluation.value.date || !evaluation.value.start_time) {
toast.warning(__('Please select a slot for your evaluation.'), {
duration: 10,
})
return
}
call('frappe.client.insert', {
doc: {
doctype: 'LMS Certificate Request',
batch_name: evaluation.value.batch,
...evaluation.value,
},
})
.then(() => {
evaluations.value.reload()
close()
})
.catch((err) => {
console.log(err.messages?.[0] || err)
toast.warning(__(err.messages?.[0] || err), { duration: 20 })
})
}
const getCourses = () => {
@@ -156,7 +143,7 @@ const getCourses = () => {
}
if (courses.length === 1) {
evaluation.course = courses[0].value
evaluation.value.course = courses[0].value
}
return courses
@@ -167,34 +154,22 @@ const slots = createResource({
makeParams(values) {
return {
course: values.course,
date: values.date,
batch: props.batch,
}
},
})
watch(
() => evaluation.date,
(date) => {
evaluation.start_time = ''
if (date && evaluation.course) {
slots.submit(evaluation)
}
}
)
watch(
() => evaluation.course,
() => evaluation.value.course,
(course) => {
evaluation.date = ''
evaluation.start_time = ''
slots.reset()
slots.reload(evaluation.value)
}
)
const saveSlot = (slot) => {
evaluation.start_time = slot.start_time
evaluation.end_time = slot.end_time
evaluation.day = slot.day
const saveSlot = (slot, row) => {
evaluation.value.start_time = slot.start_time
evaluation.value.end_time = slot.end_time
evaluation.value.date = row.date
evaluation.value.day = row.day
}
</script>

View File

@@ -22,7 +22,10 @@
</div>
</Tooltip>
<Tooltip :text="__('Course')">
<div class="flex items-center space-x-2 w-fit">
<div
class="flex space-x-2 w-fit cursor-pointer"
@click="openLink('course', event.course)"
>
<BookOpen class="h-4 w-4 stroke-1.5" />
<span>
{{ event.course_title }}
@@ -30,7 +33,10 @@
</div>
</Tooltip>
<Tooltip v-if="event.batch_title" :text="__('Batch')">
<div class="flex items-center space-x-2 w-fit">
<div
class="flex space-x-2 w-fit cursor-pointer"
@click="openLink('batch', event.batch_name)"
>
<Users class="h-4 w-4 stroke-1.5" />
<span>
{{ event.batch_title }}
@@ -189,6 +195,8 @@ const user = inject('$user')
const dayjs = inject('$dayjs')
const tabIndex = ref(0)
const showCertification = ref(false)
const evaluation = reactive({})
const certificate = reactive({})
const props = defineProps({
event: {
@@ -197,9 +205,6 @@ const props = defineProps({
},
})
const evaluation = reactive({})
const certificate = reactive({})
watch(user, () => {
if (userIsEvaluator()) {
defaultTemplate.reload()
@@ -335,7 +340,7 @@ const certificateDetails = createResource({
}
},
onError(err) {
certificate.template = defaultTemplate.data.value
certificate.template = defaultTemplate.data?.value
},
auto: false,
})
@@ -378,6 +383,16 @@ const openCertificate = (certificate) => {
)
}
const openLink = (type, name) => {
let url = ''
if (type === 'course') {
url = `/lms/courses/${name}`
} else if (type === 'batch') {
url = `/lms/batches/${name}#students`
}
window.open(url, '_blank')
}
const statusOptions = computed(() => {
return [
{

View File

@@ -7,7 +7,7 @@
>
<template #body>
<div class="p-5 min-h-[300px]">
<div class="text-lg font-semibold mb-4">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Training Feedback') }}
</div>
<ListView

View File

@@ -17,7 +17,7 @@
}"
>
<template #body-content>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-4 text-base">
<p class="text-ink-gray-9">
{{
__(
@@ -29,6 +29,7 @@
<FileUploader
:fileTypes="['.pdf']"
:validateFile="validateFile"
:uploadArgs="{ private: 1 }"
@success="
(file) => {
resume = file
@@ -38,6 +39,9 @@
<template v-slot="{ file, progress, uploading, openFileSelector }">
<div class="">
<Button @click="openFileSelector" :loading="uploading">
<template #prefix>
<Upload class="size-4 stroke-1.5" />
</template>
{{
uploading ? `Uploading ${progress}%` : 'Upload your resume'
}}
@@ -65,7 +69,7 @@
</template>
<script setup>
import { Dialog, FileUploader, Button, createResource, toast } from 'frappe-ui'
import { FileText } from 'lucide-vue-next'
import { FileText, Upload } from 'lucide-vue-next'
import { ref, inject } from 'vue'
import { getFileSize } from '@/utils/'
@@ -95,7 +99,7 @@ const jobApplication = createResource({
doc: {
doctype: 'LMS Job Application',
user: user.data?.name,
resume: resume.value?.file_name,
resume: resume.value?.file_url,
job: props.job,
},
}

View File

@@ -84,16 +84,10 @@
</Dialog>
</template>
<script setup>
import {
Dialog,
createResource,
Tooltip,
FormControl,
Autocomplete,
toast,
} from 'frappe-ui'
import { Dialog, createResource, Tooltip, FormControl, toast } from 'frappe-ui'
import { reactive, inject, onMounted } from 'vue'
import { getTimezones, getUserTimezone } from '@/utils/'
import Autocomplete from '@/components/Controls/Autocomplete.vue'
const liveClasses = defineModel('reloadLiveClasses')
const show = defineModel()

View File

@@ -1,7 +1,6 @@
<template>
<Dialog
v-model="show"
class="text-base"
:options="{
title: __('Add web page to sidebar'),
size: 'lg',
@@ -17,15 +16,17 @@
}"
>
<template #body-content>
<Link
v-model="page.webpage"
doctype="Web Page"
:label="__('Web Page')"
:filters="{
published: 1,
}"
/>
<IconPicker v-model="page.icon" :label="__('Icon')" class="mt-4" />
<div class="text-base">
<Link
v-model="page.webpage"
doctype="Web Page"
:label="__('Web Page')"
:filters="{
published: 1,
}"
/>
<IconPicker v-model="page.icon" :label="__('Icon')" class="mt-4" />
</div>
</template>
</Dialog>
</template>

View File

@@ -31,7 +31,7 @@
@change="(val) => (question.question = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
editorClass="prose-sm max-w-none border-b border-x border-outline-gray-modals bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
<div class="grid grid-cols-2 gap-8 mt-4">

View File

@@ -2,8 +2,8 @@
<Dialog
v-model="show"
:options="{
title: __('Add a Student'),
size: 'sm',
title: __('Enroll a Student'),
size: 'lg',
actions: [
{
label: 'Submit',
@@ -18,10 +18,25 @@
<Link
doctype="User"
v-model="student"
:filters="{ ignore_user_type: 1 }"
placeholder=" "
:label="__('Student')"
:onCreate="
(value, close) => {
openSettings('Members', close)
() => {
openSettings('Members')
show = false
}
"
:required="true"
/>
<Link
doctype="LMS Payment"
v-model="payment"
placeholder=" "
:label="__('Payment')"
:onCreate="
() => {
openSettings('Transactions')
show = false
}
"
/>
@@ -30,54 +45,49 @@
</Dialog>
</template>
<script setup>
import { Dialog, createResource, toast } from 'frappe-ui'
import { call, Dialog, toast } from 'frappe-ui'
import { ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { useOnboarding } from 'frappe-ui/frappe'
import { openSettings } from '@/utils'
import Link from '@/components/Controls/Link.vue'
const students = defineModel('reloadStudents')
const batchModal = defineModel('batchModal')
const student = ref()
const student = ref(null)
const payment = ref(null)
const user = inject('$user')
const { updateOnboardingStep } = useOnboarding('learning')
const show = defineModel()
const props = defineProps({
batch: {
type: String,
type: Object,
default: null,
},
students: {
type: Object,
default: null,
},
})
const studentResource = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'LMS Batch Enrollment',
batch: props.batch,
member: student.value,
},
}
},
})
const addStudent = (close) => {
studentResource.submit(
{},
props.students.insert.submit(
{
member: student.value,
payment: payment.value,
batch: props.batch.data?.name,
},
{
onSuccess() {
if (user.data?.is_system_manager)
updateOnboardingStep('add_batch_student')
students.value.reload()
batchModal.value.reload()
student.value = null
payment.value = null
props.batch.reload()
close()
},
onError(err) {
toast.error(err.messages?.[0] || err)
console.error(err)
},
}
)

Some files were not shown because too many files have changed in this diff Show More