diff --git a/.dev-js/.eslintrc.js b/.dev-js/.eslintrc.js new file mode 100644 index 0000000000000000000000000000000000000000..40571d02f4c429d79d9ec85a4f37ba8c78f74b4c --- /dev/null +++ b/.dev-js/.eslintrc.js @@ -0,0 +1,252 @@ +module.exports = { + root: true, + overrides: [ + { + files: ["*.js", "*.vue"], + // parser: "vue-eslint-parser", + //processor: "@graphql-eslint/graphql", + extends: [ + "eslint:recommended", + "plugin:vue/strongly-recommended", + "plugin:@intlify/vue-i18n/recommended", + ], + rules: { + "no-unused-vars": "warn", + "vue/no-unused-vars": "off", + "vue/multi-word-component-names": "off", + "vue/attribute-hyphenation": "error", + "vue/v-slot-style": "error", + "@intlify/vue-i18n/key-format-style": [ + "error", + "snake_case", + { + splitByDots: false, + }, + ], + // "@intlify/vue-i18n/no-unused-keys": ["warn", {}], + "@intlify/vue-i18n/no-raw-text": [ + "error", + { + ignoreNodes: ["v-icon"], + ignorePattern: "^[-–—·#:()\\[\\]&\\.\\s]+$", + }, + ], + "@intlify/vue-i18n/no-deprecated-tc": "off", + // Fixes for prettier (avoid eslint-config-prettier) + // The following rules can be used in some cases. See the README for more + // information. (These are marked with `0` instead of `"off"` so that a + // script can distinguish them.) + curly: 0, + "lines-around-comment": 0, + "max-len": 0, + "no-confusing-arrow": 0, + "no-mixed-operators": 0, + "no-tabs": 0, + "no-unexpected-multiline": 0, + quotes: 0, + "@typescript-eslint/quotes": 0, + "babel/quotes": 0, + "vue/html-self-closing": 0, + "vue/max-len": 0, + + // The rest are rules that you never need to enable when using Prettier. + "array-bracket-newline": "off", + "array-bracket-spacing": "off", + "array-element-newline": "off", + "arrow-parens": "off", + "arrow-spacing": "off", + "block-spacing": "off", + "brace-style": "off", + "comma-dangle": "off", + "comma-spacing": "off", + "comma-style": "off", + "computed-property-spacing": "off", + "dot-location": "off", + "eol-last": "off", + "func-call-spacing": "off", + "function-call-argument-newline": "off", + "function-paren-newline": "off", + "generator-star": "off", + "generator-star-spacing": "off", + "implicit-arrow-linebreak": "off", + indent: "off", + "jsx-quotes": "off", + "key-spacing": "off", + "keyword-spacing": "off", + "linebreak-style": "off", + "multiline-ternary": "off", + "newline-per-chained-call": "off", + "new-parens": "off", + "no-arrow-condition": "off", + "no-comma-dangle": "off", + "no-extra-parens": "off", + "no-extra-semi": "off", + "no-floating-decimal": "off", + "no-mixed-spaces-and-tabs": "off", + "no-multi-spaces": "off", + "no-multiple-empty-lines": "off", + "no-reserved-keys": "off", + "no-space-before-semi": "off", + "no-trailing-spaces": "off", + "no-whitespace-before-property": "off", + "no-wrap-func": "off", + "nonblock-statement-body-position": "off", + "object-curly-newline": "off", + "object-curly-spacing": "off", + "object-property-newline": "off", + "one-var-declaration-per-line": "off", + "operator-linebreak": "off", + "padded-blocks": "off", + "quote-props": "off", + "rest-spread-spacing": "off", + semi: "off", + "semi-spacing": "off", + "semi-style": "off", + "space-after-function-name": "off", + "space-after-keywords": "off", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-before-function-parentheses": "off", + "space-before-keywords": "off", + "space-in-brackets": "off", + "space-in-parens": "off", + "space-infix-ops": "off", + "space-return-throw-case": "off", + "space-unary-ops": "off", + "space-unary-word-ops": "off", + "switch-colon-spacing": "off", + "template-curly-spacing": "off", + "template-tag-spacing": "off", + "unicode-bom": "off", + "wrap-iife": "off", + "wrap-regex": "off", + "yield-star-spacing": "off", + "@babel/object-curly-spacing": "off", + "@babel/semi": "off", + "@typescript-eslint/brace-style": "off", + "@typescript-eslint/comma-dangle": "off", + "@typescript-eslint/comma-spacing": "off", + "@typescript-eslint/func-call-spacing": "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/keyword-spacing": "off", + "@typescript-eslint/member-delimiter-style": "off", + "@typescript-eslint/no-extra-parens": "off", + "@typescript-eslint/no-extra-semi": "off", + "@typescript-eslint/object-curly-spacing": "off", + "@typescript-eslint/semi": "off", + "@typescript-eslint/space-before-blocks": "off", + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/space-infix-ops": "off", + "@typescript-eslint/type-annotation-spacing": "off", + "babel/object-curly-spacing": "off", + "babel/semi": "off", + "flowtype/boolean-style": "off", + "flowtype/delimiter-dangle": "off", + "flowtype/generic-spacing": "off", + "flowtype/object-type-curly-spacing": "off", + "flowtype/object-type-delimiter": "off", + "flowtype/quotes": "off", + "flowtype/semi": "off", + "flowtype/space-after-type-colon": "off", + "flowtype/space-before-generic-bracket": "off", + "flowtype/space-before-type-colon": "off", + "flowtype/union-intersection-spacing": "off", + "react/jsx-child-element-spacing": "off", + "react/jsx-closing-bracket-location": "off", + "react/jsx-closing-tag-location": "off", + "react/jsx-curly-newline": "off", + "react/jsx-curly-spacing": "off", + "react/jsx-equals-spacing": "off", + "react/jsx-first-prop-new-line": "off", + "react/jsx-indent": "off", + "react/jsx-indent-props": "off", + "react/jsx-max-props-per-line": "off", + "react/jsx-newline": "off", + "react/jsx-one-expression-per-line": "off", + "react/jsx-props-no-multi-spaces": "off", + "react/jsx-tag-spacing": "off", + "react/jsx-wrap-multilines": "off", + "standard/array-bracket-even-spacing": "off", + "standard/computed-property-even-spacing": "off", + "standard/object-curly-even-spacing": "off", + "unicorn/empty-brace-spaces": "off", + "unicorn/no-nested-ternary": "off", + "unicorn/number-literal-case": "off", + "vue/array-bracket-newline": "off", + "vue/array-bracket-spacing": "off", + "vue/arrow-spacing": "off", + "vue/block-spacing": "off", + "vue/block-tag-newline": "off", + "vue/brace-style": "off", + "vue/comma-dangle": "off", + "vue/comma-spacing": "off", + "vue/comma-style": "off", + "vue/dot-location": "off", + "vue/func-call-spacing": "off", + "vue/html-closing-bracket-newline": "off", + "vue/html-closing-bracket-spacing": "off", + "vue/html-end-tags": "off", + "vue/html-indent": "off", + "vue/html-quotes": "off", + "vue/key-spacing": "off", + "vue/keyword-spacing": "off", + "vue/max-attributes-per-line": "off", + "vue/multiline-html-element-content-newline": "off", + "vue/multiline-ternary": "off", + "vue/mustache-interpolation-spacing": "off", + "vue/no-extra-parens": "off", + "vue/no-multi-spaces": "off", + "vue/no-spaces-around-equal-signs-in-attribute": "off", + "vue/object-curly-newline": "off", + "vue/object-curly-spacing": "off", + "vue/object-property-newline": "off", + "vue/operator-linebreak": "off", + "vue/quote-props": "off", + "vue/script-indent": "off", + "vue/singleline-html-element-content-newline": "off", + "vue/space-in-parens": "off", + "vue/space-infix-ops": "off", + "vue/space-unary-ops": "off", + "vue/template-curly-spacing": "off", + }, + settings: { + "vue-i18n": { + localeDir: "./aleksis/core/frontend/messages/*.{json}", + messageSyntaxVersion: "^8.0.0", + }, + }, + env: { + es2021: true, + }, + parserOptions: { + ecmaVersion: "latest", + }, + }, + { + files: ["*.graphql"], + parser: "@graphql-eslint/eslint-plugin", + plugins: ["@graphql-eslint"], + extends: "plugin:@graphql-eslint/operations-recommended", + parserOptions: { + graphQLConfig: { + schema: "./schema.json", + documents: "../aleksis/**/*/frontend/**/*.graphql", + }, + }, + rules: { + "@graphql-eslint/no-anonymous-operations": "error", + "@graphql-eslint/no-duplicate-fields": "error", + "@graphql-eslint/naming-convention": [ + "error", + { + OperationDefinition: { + style: "camelCase", + forbiddenPrefixes: ["Query", "Mutation", "Subscription", "Get"], + forbiddenSuffixes: ["Query", "Mutation", "Subscription"], + }, + }, + ], + }, + }, + ], +}; diff --git a/.dev-js/package.json b/.dev-js/package.json index 99b0989e1dde9b0f71fdc229bac3aef501c06398..e3f89a123e2374a92c69575908c676a78cda095c 100644 --- a/.dev-js/package.json +++ b/.dev-js/package.json @@ -2,10 +2,12 @@ "name": "aleksis-builddeps", "version": "1.0.0", "dependencies": { - "@intlify/eslint-plugin-vue-i18n": "^2.0.0", + "@graphql-eslint/eslint-plugin": "^4.3.0", + "@intlify/eslint-plugin-vue-i18n": "^3.0.0", "eslint": "^8.26.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-vue": "^9.7.0", + "graphql": "^16.10.0", "prettier": "^3.4.0", "stylelint": "^15.0.0", "stylelint-config-prettier": "^9.0.3", diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 60317f4987d885fc380d731c6dd8792325342f44..0000000000000000000000000000000000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,215 +0,0 @@ -module.exports = { - extends: [ - "eslint:recommended", - "plugin:vue/strongly-recommended", - "plugin:@intlify/vue-i18n/recommended", - ], - rules: { - "no-unused-vars": "warn", - "vue/no-unused-vars": "off", - "vue/multi-word-component-names": "off", - "@intlify/vue-i18n/key-format-style": [ - "error", - "snake_case", - { - splitByDots: false, - }, - ], - // "@intlify/vue-i18n/no-unused-keys": ["warn", {}], - "@intlify/vue-i18n/no-raw-text": [ - "error", - { - ignoreNodes: ["v-icon"], - ignorePattern: "^[-–—·#:()\\[\\]&\\.\\s]+$", - }, - ], - // Fixes for prettier (avoid eslint-config-prettier) - // The following rules can be used in some cases. See the README for more - // information. (These are marked with `0` instead of `"off"` so that a - // script can distinguish them.) - curly: 0, - "lines-around-comment": 0, - "max-len": 0, - "no-confusing-arrow": 0, - "no-mixed-operators": 0, - "no-tabs": 0, - "no-unexpected-multiline": 0, - quotes: 0, - "@typescript-eslint/quotes": 0, - "babel/quotes": 0, - "vue/html-self-closing": 0, - "vue/max-len": 0, - - // The rest are rules that you never need to enable when using Prettier. - "array-bracket-newline": "off", - "array-bracket-spacing": "off", - "array-element-newline": "off", - "arrow-parens": "off", - "arrow-spacing": "off", - "block-spacing": "off", - "brace-style": "off", - "comma-dangle": "off", - "comma-spacing": "off", - "comma-style": "off", - "computed-property-spacing": "off", - "dot-location": "off", - "eol-last": "off", - "func-call-spacing": "off", - "function-call-argument-newline": "off", - "function-paren-newline": "off", - "generator-star": "off", - "generator-star-spacing": "off", - "implicit-arrow-linebreak": "off", - indent: "off", - "jsx-quotes": "off", - "key-spacing": "off", - "keyword-spacing": "off", - "linebreak-style": "off", - "multiline-ternary": "off", - "newline-per-chained-call": "off", - "new-parens": "off", - "no-arrow-condition": "off", - "no-comma-dangle": "off", - "no-extra-parens": "off", - "no-extra-semi": "off", - "no-floating-decimal": "off", - "no-mixed-spaces-and-tabs": "off", - "no-multi-spaces": "off", - "no-multiple-empty-lines": "off", - "no-reserved-keys": "off", - "no-space-before-semi": "off", - "no-trailing-spaces": "off", - "no-whitespace-before-property": "off", - "no-wrap-func": "off", - "nonblock-statement-body-position": "off", - "object-curly-newline": "off", - "object-curly-spacing": "off", - "object-property-newline": "off", - "one-var-declaration-per-line": "off", - "operator-linebreak": "off", - "padded-blocks": "off", - "quote-props": "off", - "rest-spread-spacing": "off", - semi: "off", - "semi-spacing": "off", - "semi-style": "off", - "space-after-function-name": "off", - "space-after-keywords": "off", - "space-before-blocks": "off", - "space-before-function-paren": "off", - "space-before-function-parentheses": "off", - "space-before-keywords": "off", - "space-in-brackets": "off", - "space-in-parens": "off", - "space-infix-ops": "off", - "space-return-throw-case": "off", - "space-unary-ops": "off", - "space-unary-word-ops": "off", - "switch-colon-spacing": "off", - "template-curly-spacing": "off", - "template-tag-spacing": "off", - "unicode-bom": "off", - "wrap-iife": "off", - "wrap-regex": "off", - "yield-star-spacing": "off", - "@babel/object-curly-spacing": "off", - "@babel/semi": "off", - "@typescript-eslint/brace-style": "off", - "@typescript-eslint/comma-dangle": "off", - "@typescript-eslint/comma-spacing": "off", - "@typescript-eslint/func-call-spacing": "off", - "@typescript-eslint/indent": "off", - "@typescript-eslint/keyword-spacing": "off", - "@typescript-eslint/member-delimiter-style": "off", - "@typescript-eslint/no-extra-parens": "off", - "@typescript-eslint/no-extra-semi": "off", - "@typescript-eslint/object-curly-spacing": "off", - "@typescript-eslint/semi": "off", - "@typescript-eslint/space-before-blocks": "off", - "@typescript-eslint/space-before-function-paren": "off", - "@typescript-eslint/space-infix-ops": "off", - "@typescript-eslint/type-annotation-spacing": "off", - "babel/object-curly-spacing": "off", - "babel/semi": "off", - "flowtype/boolean-style": "off", - "flowtype/delimiter-dangle": "off", - "flowtype/generic-spacing": "off", - "flowtype/object-type-curly-spacing": "off", - "flowtype/object-type-delimiter": "off", - "flowtype/quotes": "off", - "flowtype/semi": "off", - "flowtype/space-after-type-colon": "off", - "flowtype/space-before-generic-bracket": "off", - "flowtype/space-before-type-colon": "off", - "flowtype/union-intersection-spacing": "off", - "react/jsx-child-element-spacing": "off", - "react/jsx-closing-bracket-location": "off", - "react/jsx-closing-tag-location": "off", - "react/jsx-curly-newline": "off", - "react/jsx-curly-spacing": "off", - "react/jsx-equals-spacing": "off", - "react/jsx-first-prop-new-line": "off", - "react/jsx-indent": "off", - "react/jsx-indent-props": "off", - "react/jsx-max-props-per-line": "off", - "react/jsx-newline": "off", - "react/jsx-one-expression-per-line": "off", - "react/jsx-props-no-multi-spaces": "off", - "react/jsx-tag-spacing": "off", - "react/jsx-wrap-multilines": "off", - "standard/array-bracket-even-spacing": "off", - "standard/computed-property-even-spacing": "off", - "standard/object-curly-even-spacing": "off", - "unicorn/empty-brace-spaces": "off", - "unicorn/no-nested-ternary": "off", - "unicorn/number-literal-case": "off", - "vue/array-bracket-newline": "off", - "vue/array-bracket-spacing": "off", - "vue/arrow-spacing": "off", - "vue/block-spacing": "off", - "vue/block-tag-newline": "off", - "vue/brace-style": "off", - "vue/comma-dangle": "off", - "vue/comma-spacing": "off", - "vue/comma-style": "off", - "vue/dot-location": "off", - "vue/func-call-spacing": "off", - "vue/html-closing-bracket-newline": "off", - "vue/html-closing-bracket-spacing": "off", - "vue/html-end-tags": "off", - "vue/html-indent": "off", - "vue/html-quotes": "off", - "vue/key-spacing": "off", - "vue/keyword-spacing": "off", - "vue/max-attributes-per-line": "off", - "vue/multiline-html-element-content-newline": "off", - "vue/multiline-ternary": "off", - "vue/mustache-interpolation-spacing": "off", - "vue/no-extra-parens": "off", - "vue/no-multi-spaces": "off", - "vue/no-spaces-around-equal-signs-in-attribute": "off", - "vue/object-curly-newline": "off", - "vue/object-curly-spacing": "off", - "vue/object-property-newline": "off", - "vue/operator-linebreak": "off", - "vue/quote-props": "off", - "vue/script-indent": "off", - "vue/singleline-html-element-content-newline": "off", - "vue/space-in-parens": "off", - "vue/space-infix-ops": "off", - "vue/space-unary-ops": "off", - "vue/template-curly-spacing": "off", - }, - settings: { - "vue-i18n": { - localeDir: "./aleksis/core/frontend/messages/*.{json}", - messageSyntaxVersion: "^8.0.0", - }, - }, - env: { - es2021: true, - }, - parserOptions: { - ecmaVersion: "latest", - }, -}; diff --git a/.gitignore b/.gitignore index 79b5b76de6f6445254cbf64f7e4fb228ffdf0ab8..613db10966de0e5b57a87f57c6fd586925ade0b7 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,8 @@ docs/_build/ .dev-js/.yarn .dev-js/.pnp.cjs .dev-js/.pnp.loader.mjs +.dev-js/.yarnrc.yml +.dev-js/schema.json # Lock files poetry.lock @@ -92,3 +94,5 @@ htmlcov/ # Data maintenance_mode_state.txt media/ + +aleksis/core/static/style.css diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookPrintDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookPrintDialog.vue index 976dccbebb60ffc9dff257bb496542410cc9aeb2..8a9058d6d1a8391d6b72db9bb239e66afcefa9a8 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookPrintDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookPrintDialog.vue @@ -20,9 +20,10 @@ import CancelButton from "aleksis.core/components/generic/buttons/CancelButton.v {{ $t("alsijil.coursebook.print.title") }} </template> <template #content> - {{ $t("alsijil.coursebook.print.groups") }} <v-autocomplete + v-if="!group" :items="availableGroups" + :label="$t('alsijil.coursebook.print.groups')" item-text="name" item-value="id" :value="value" @@ -87,7 +88,16 @@ export default { */ availableGroups: { type: Array, - required: true, + required: false, + default: () => [], + }, + /** + * Set a group to use this dialog exclusively for + */ + group: { + type: Object, + required: false, + default: null, }, /** * Initially selected groups @@ -121,6 +131,9 @@ export default { }, computed: { selectedGroups() { + if (this.group) { + return [this.group.id]; + } if (this.currentGroupSelection.length == 0) { return this.value.map((group) => group.id); } else { diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/personal_notes.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/personal_notes.graphql index 014178c1326960f02dee24cf92f8eb5c679298f5..8676c02f6ad56ef3c1514c394820cbffe4586490 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/personal_notes.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/personal_notes.graphql @@ -1,15 +1,3 @@ -query personalNotes($orderBy: [String], $filters: JSONString) { - items: personalNotes(orderBy: $orderBy, filters: $filters) { - id - note - extraMark { - id - } - canEdit - canDelete - } -} - mutation createPersonalNotes($input: [BatchCreatePersonalNoteInput]!) { createPersonalNotes(input: $input) { items: personalNotes { diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForGroupTab.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForGroupTab.vue index 8f7d5b428da74d4bc3dc438dea0e0140678378cd..1575348b914f2afb1b86e4aca933bf991eeabd62 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForGroupTab.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForGroupTab.vue @@ -10,6 +10,9 @@ :show-select="false" @items="items = $event" > + <template #additionalActions> + <coursebook-print-dialog :group="group" /> + </template> <template v-for="(extraMark, index) in extraMarks" #[`extraMarks.${index}.count`]="{ item }" @@ -65,11 +68,11 @@ i18n-key="alsijil.coursebook.statistics.person_view_details" icon-text="mdi-open-in-new" :to="{ - name: 'alsijil.coursebook_statistics', + name: 'core.personById', params: { - personId: item.person.id, - mode: MODE.PARTICIPATIONS, + id: item.person.id, }, + hash: '#' + MODE.PARTICIPATIONS, }" /> </template> @@ -81,13 +84,14 @@ import groupOverviewTabMixin from "aleksis.core/mixins/groupOverviewTabMixin.js" import CRUDList from "aleksis.core/components/generic/CRUDList.vue"; import PersonChip from "aleksis.core/components/person/PersonChip.vue"; import SecondaryActionButton from "aleksis.core/components/generic/buttons/SecondaryActionButton.vue"; +import CoursebookPrintDialog from "../CoursebookPrintDialog.vue"; import AbsenceReasonChip from "aleksis.apps.kolego/components/AbsenceReasonChip.vue"; import ExtraMarkChip from "aleksis.apps.alsijil/components/extra_marks/ExtraMarkChip.vue"; import { statisticsByGroup } from "./statistics.graphql"; import { absenceReasons } from "../queries/absenceReasons.graphql"; -import { extraMarks } from "../../extra_marks/extra_marks.graphql"; +import { extraMarks } from "../queries/extraMarks.graphql"; import { MODE } from "./modes"; export default { @@ -99,6 +103,7 @@ export default { ExtraMarkChip, PersonChip, SecondaryActionButton, + CoursebookPrintDialog, }, data() { return { diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue index 546fc8e00701798b801427355328c14d0237578b..fd270fc374e7df35ed27fe76cef779fdf402ae47 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue @@ -7,18 +7,7 @@ <v-card-title v-else-if="compact"> {{ $t("alsijil.coursebook.statistics.person_compact.title") }} <v-spacer /> - <base-button - :icon="true" - icon-text="mdi-open-in-new" - i18n-key="" - :to="{ - name: 'alsijil.coursebook_statistics', - params: { - personId: person.id, - mode: MODE.PARTICIPATIONS, - }, - }" - /> + <slot name="header" /> </v-card-title> <v-card-title v-else> {{ $t("alsijil.coursebook.statistics.title_plural") }} @@ -59,7 +48,6 @@ <script> import personOverviewCardMixin from "aleksis.core/mixins/personOverviewCardMixin.js"; -import BaseButton from "aleksis.core/components/generic/buttons/BaseButton.vue"; import MessageBox from "aleksis.core/components/generic/MessageBox.vue"; import StatisticsAbsencesCard from "./StatisticsAbsencesCard.vue"; import StatisticsTardinessCard from "./StatisticsTardinessCard.vue"; @@ -73,7 +61,6 @@ export default { name: "StatisticsForPersonCard", mixins: [personOverviewCardMixin], components: { - BaseButton, MessageBox, StatisticsAbsencesCard, StatisticsTardinessCard, @@ -111,6 +98,9 @@ export default { MODE() { return MODE; }, + mode() { + return this.$hash; + }, gridTemplateAreas() { return this.compact ? `"absences extra_marks" "tardinesses tardinesses"` diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue index d90e166d264c5fde89d87d2bcd2c5366836149d1..c9c8bcd3ce8d762c9fe091f343adda5307cbfe8b 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue @@ -2,6 +2,13 @@ <fullscreen-dialog-page :fallback-url="{ name: 'core.personById', props: { id: personId } }" > + <template #title> + {{ + $t("alsijil.coursebook.statistics.person_page.title", { + fullName: personName?.fullName || "???", + }) + }} + </template> <div class="d-flex" style="gap: 4em"> <div class="flex-grow-1" style="max-width: 100%"> <!-- documentations for person list --> @@ -172,6 +179,14 @@ <v-list-item-action class="flex-row full-width justify-md-end ma-0 align-center fill-height" > + <v-chip + color="warning" + class="mx-1" + v-if="!item.relatedDocumentation.amended" + >{{ + $t("alsijil.coursebook.statistics.not_counted") + }}</v-chip + > <!-- chips: absences & extraMarks --> <absence-reason-chip v-if="item.absenceReason" @@ -301,11 +316,6 @@ export default { type: [Number, String], required: true, }, - mode: { - type: String, - required: false, - default: MODE.PARTICIPATIONS, - }, }, apollo: { personName: { @@ -315,13 +325,6 @@ export default { person: this.personId, }; }, - result({ data }) { - this.$setToolBarTitle( - this.$t("alsijil.coursebook.statistics.person_page.title", { - fullName: data.personName.fullName || "???", - }), - ); - }, }, absenceReasons: { query: absenceReasons, @@ -346,6 +349,9 @@ export default { MODE() { return MODE; }, + mode() { + return this.$hash; + }, }, methods: { gqlQuery() { @@ -360,12 +366,9 @@ export default { this.selected = []; - this.$router.push({ - name: "alsijil.coursebook_statistics", - params: { - personId: this.personId, - mode: mode, - }, + this.$router.replace({ + ...this.$route, + hash: "#" + mode, }); }, showEdit(item) { diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonWidget.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonWidget.vue new file mode 100644 index 0000000000000000000000000000000000000000..495e08c016bbcb9cb3dfc5f4a1bd9475da1a4c6a --- /dev/null +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonWidget.vue @@ -0,0 +1,47 @@ +<template> + <statistics-for-person-card v-bind="{ ...$attrs, ...$props }"> + <template #header> + <base-button + :icon="true" + icon-text="mdi-open-in-new" + i18n-key="" + :to="{ + name: $route.name, + params: $route.params, + hash: '#' + MODE.PARTICIPATIONS, + }" + /> + <statistics-for-person-page + v-if="Object.values(MODE).includes(mode)" + :person-id="person.id" + /> + </template> + </statistics-for-person-card> +</template> + +<script> +import personOverviewCardMixin from "aleksis.core/mixins/personOverviewCardMixin.js"; +import BaseButton from "aleksis.core/components/generic/buttons/BaseButton.vue"; +import StatisticsForPersonPage from "./StatisticsForPersonPage.vue"; +import StatisticsForPersonCard from "./StatisticsForPersonCard.vue"; + +import { MODE } from "./modes"; + +export default { + name: "StatisticsForPersonWidget", + mixins: [personOverviewCardMixin], + components: { + BaseButton, + StatisticsForPersonPage, + StatisticsForPersonCard, + }, + computed: { + MODE() { + return MODE; + }, + mode() { + return this.$hash; + }, + }, +}; +</script> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/modes.js b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/modes.js index c345316a23b880f73acddf2271759de63a6e760d..54ca270a7dd598878bed03179206e7cd1edce7be 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/modes.js +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/modes.js @@ -1,4 +1,4 @@ export const MODE = { - PARTICIPATIONS: "participations", - PERSONAL_NOTES: "personal_notes", + PARTICIPATIONS: "alsijil.participations", + PERSONAL_NOTES: "alsijil.personal_notes", }; diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql index 1cffdc7178defa256f5f9e46707541a526cb1dec..a83e3fa59d96aa5cf9fcdb103c73ceb0546d31a1 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql @@ -64,6 +64,7 @@ query participationsOfPerson($person: ID!) { colourFg colourBg } + amended } canEdit canDelete diff --git a/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarkButtons.vue b/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarkButtons.vue index 01deef40190efb422376dc7848185e8916400b43..8a8485bcca6e1ff9e33d62893879398f3188fab1 100644 --- a/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarkButtons.vue +++ b/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarkButtons.vue @@ -1,5 +1,5 @@ <script> -import { extraMarks } from "./extra_marks.graphql"; +import { extraMarksList } from "./extra_marks.graphql"; export default { name: "ExtraMarkButtons", @@ -10,7 +10,7 @@ export default { }, apollo: { extraMarks: { - query: extraMarks, + query: extraMarksList, update: (data) => data.items, skip() { return this.customExtraMarks.length > 0; diff --git a/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarks.vue b/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarks.vue index 9468ac1dba5d291abd251659586d24aa95fe0278..4d586e852225ee9592a5634807386012b2c57405 100644 --- a/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarks.vue +++ b/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarks.vue @@ -87,7 +87,7 @@ import InlineCRUDList from "aleksis.core/components/generic/InlineCRUDList.vue"; <script> import formRulesMixin from "aleksis.core/mixins/formRulesMixin.js"; import { - extraMarks, + extraMarksList, createExtraMarks, deleteExtraMarks, updateExtraMarks, @@ -121,7 +121,7 @@ export default { }, ], i18nKey: "alsijil.extra_marks", - gqlQuery: extraMarks, + gqlQuery: extraMarksList, gqlCreateMutation: createExtraMarks, gqlPatchMutation: updateExtraMarks, gqlDeleteMutation: deleteExtraMarks, diff --git a/aleksis/apps/alsijil/frontend/components/extra_marks/extra_marks.graphql b/aleksis/apps/alsijil/frontend/components/extra_marks/extra_marks.graphql index 73e8ba4121c215dc4c3968b3ed2021b71b5ecfc1..19e763f3b83639661b375ceddca9da2d73c34ad8 100644 --- a/aleksis/apps/alsijil/frontend/components/extra_marks/extra_marks.graphql +++ b/aleksis/apps/alsijil/frontend/components/extra_marks/extra_marks.graphql @@ -1,4 +1,4 @@ -query extraMarks($orderBy: [String], $filters: JSONString) { +query extraMarksList($orderBy: [String], $filters: JSONString) { items: extraMarks(orderBy: $orderBy, filters: $filters) { id shortName diff --git a/aleksis/apps/alsijil/frontend/index.js b/aleksis/apps/alsijil/frontend/index.js index fe222f4fee3fcc28ad93448b4482babcf0ab32cf..18e0f68eaca178cbaf3b358a8065257cc3e745b3 100644 --- a/aleksis/apps/alsijil/frontend/index.js +++ b/aleksis/apps/alsijil/frontend/index.js @@ -1,5 +1,4 @@ import { DateTime } from "luxon"; -import { MODE } from "./components/coursebook/statistics/modes"; export const collectionItems = { coreGroupActions: [ @@ -32,7 +31,7 @@ export const collectionItems = { key: "core-person-widgets", component: () => import( - "./components/coursebook/statistics/StatisticsForPersonCard.vue" + "./components/coursebook/statistics/StatisticsForPersonWidget.vue" ), shouldDisplay: () => true, colProps: { @@ -113,21 +112,5 @@ export default { permission: "alsijil.view_extramarks_rule", }, }, - { - path: `statistics/:personId/:mode(${Object.values(MODE).join("|")})`, - component: () => - import( - "./components/coursebook/statistics/StatisticsForPersonPage.vue" - ), - name: "alsijil.coursebook_statistics", - props: true, - meta: { - inMenu: false, - titleKey: "alsijil.coursebook.statistics.person_compact.title", - toolbarTitle: "alsijil.coursebook.statistics.person_compact.title", - // TODO: Add permission & change it here. - permission: "alsijil.view_documentations_menu_rule", - }, - }, ], }; diff --git a/aleksis/apps/alsijil/frontend/messages/de.json b/aleksis/apps/alsijil/frontend/messages/de.json index 4d134acfe04b8b28f42bc89c21c31ddccd5db5a0..b1650ed2b7076aef89c4405f7a87540a84a71460 100644 --- a/aleksis/apps/alsijil/frontend/messages/de.json +++ b/aleksis/apps/alsijil/frontend/messages/de.json @@ -82,7 +82,8 @@ "title": "Kursbuch · Statistiken · {fullName}" }, "person_view_details": "Details", - "title_plural": "Statistiken" + "title_plural": "Statistiken", + "not_counted": "nicht gezählt" }, "status": { "available": "Kursbucheintrag vorhanden", diff --git a/aleksis/apps/alsijil/frontend/messages/en.json b/aleksis/apps/alsijil/frontend/messages/en.json index a40f8d5475a9c3e9843e4a72ac745c723a1137e3..99ce86919f439acaed9902bdaae709ddefda8cd1 100644 --- a/aleksis/apps/alsijil/frontend/messages/en.json +++ b/aleksis/apps/alsijil/frontend/messages/en.json @@ -85,7 +85,8 @@ "summary": "Summary" }, "person_view_details": "Details", - "title_plural": "Statistics" + "title_plural": "Statistics", + "not_counted": "not counted" }, "notes": { "show_list": "List of participants", diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index a3d9c14fafedc02858b025ef221d97b08bfb5b90..e1a2bd0ef2a2c49d0bdf124b21a6f550d303176c 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -2,6 +2,7 @@ from django.db.models import FilteredRelation, Q, QuerySet, Value from django.db.models.aggregates import Count, Sum from django.utils.translation import gettext as _ +from aleksis.apps.chronos.models import LessonEvent from aleksis.apps.kolego.models import AbsenceReason from aleksis.core.models import Group, Person, SchoolTerm @@ -137,13 +138,6 @@ def annotate_person_statistics_for_school_term( datetime_end__date__lte=school_term.date_end, ) if group: - documentations = documentations.filter( - pk__in=Documentation.objects.filter(course__groups=group) - .values_list("pk", flat=True) - .union( - Documentation.objects.filter(course__groups__parent_groups=group).values_list( - "pk", flat=True - ) - ) - ) + lesson_events = LessonEvent.objects.filter(LessonEvent.objects.for_group_q(group)) + documentations = documentations.filter(amends__in=lesson_events) return annotate_person_statistics_from_documentations(persons, documentations) diff --git a/aleksis/apps/alsijil/schema/documentation.py b/aleksis/apps/alsijil/schema/documentation.py index ad88b2de383217984eb4cb8e2e2704daf68c84e9..9548581a63066a3c9e49465bd99ded9a95678dbc 100644 --- a/aleksis/apps/alsijil/schema/documentation.py +++ b/aleksis/apps/alsijil/schema/documentation.py @@ -48,6 +48,7 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp course = graphene.Field(CourseType, required=False) amends = graphene.Field(lambda: LessonEventType, required=False) + amended = graphene.Boolean(required=False) subject = graphene.Field(SubjectType, required=False) participations = graphene.List(ParticipationStatusType, required=False) @@ -66,6 +67,11 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp return root._amends_prefetched return root.amends + @staticmethod + @bypass_get_queryset + def resolve_amended(root: Documentation, info, **kwargs): + return root.amends_id is not None + @staticmethod @bypass_get_queryset def resolve_teachers(root: Documentation, info, **kwargs): diff --git a/aleksis/apps/alsijil/tasks.py b/aleksis/apps/alsijil/tasks.py index 5c0377d7468a56dd6539c18c8ea80e15b69738b6..71de0d315e6a2d7fbb66b99d5298d21b52502429 100644 --- a/aleksis/apps/alsijil/tasks.py +++ b/aleksis/apps/alsijil/tasks.py @@ -7,6 +7,7 @@ from django.utils.translation import gettext as _ from celery.result import allow_join_result from celery.states import SUCCESS +from aleksis.apps.chronos.models import LessonEvent from aleksis.apps.cursus.models import Course from aleksis.apps.kolego.models.absence import AbsenceReason from aleksis.core.models import Group, PDFFile @@ -118,6 +119,13 @@ def generate_full_register_printout( course__groups__parent_groups=group ).values_list("pk", flat=True) ) + .union( + Documentation.objects.filter( + amends__in=LessonEvent.objects.filter( + LessonEvent.objects.for_group_q(group) + ) + ).values_list("pk", flat=True) + ) ) ) diff --git a/pyproject.toml b/pyproject.toml index 213cef02d306616d8a7c911059215dbe0edcaa07..50737901a7a3578cb3960c9a46b0b0500253ceea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple" priority = "supplemental" [tool.poetry.dependencies] python = "^3.10" -aleksis-core = "^4.0.0.dev11" +aleksis-core = "^4.0.0.dev16" aleksis-app-chronos = "^4.0.0.dev7" aleksis-app-kolego = "^0.1.0.dev3" diff --git a/tox.ini b/tox.ini index 92f26b55d9ebd6f5b3db425cab8d151bde0c0c69..7bccb6bf67559b0150b61d36c83f5035ff7856a7 100644 --- a/tox.ini +++ b/tox.ini @@ -24,12 +24,13 @@ setenv = [testenv:lint] commands_pre = - poetry install --only=dev + poetry install yarnpkg --cwd=.dev-js commands = poetry run ruff check {posargs} aleksis/ yarnpkg --cwd=.dev-js run prettier --ignore-path={toxinidir}/.prettierignore {posargs} --check .. - yarnpkg --cwd=.dev-js run eslint ../aleksis/**/*/frontend/**/*.{js,vue} --config={toxinidir}/.eslintrc.js --resolve-plugins-relative-to=. + poetry run aleksis-admin graphql_schema --schema aleksis.core.schema.schema --out .dev-js/schema.json + yarnpkg --cwd=.dev-js run eslint ../aleksis/**/*/frontend/**/*.{js,vue,graphql} --config={toxinidir}/.dev-js/.eslintrc.js [testenv:security] commands_pre = @@ -42,6 +43,8 @@ commands = commands_pre = poetry install poetry run sh -c "cd aleksis; aleksis-admin compilemessages" + poetry run aleksis-admin yarn install + poetry run aleksis-admin compile_scss commands = poetry build [testenv:docs]