@ihabdevteam/core 0.321.0 โ 0.323.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +617 -0
- package/dist/components.cjs.js +183 -183
- package/dist/components.esm.css +1 -1
- package/dist/hooks.cjs.js +3 -3
- package/dist/i18n.cjs.js +3 -3
- package/dist/ihabdevteam-core.cjs.js +187 -187
- package/dist/ihabdevteam-core.d.ts +108 -66
- package/dist/packages/core/src/components/Accordion.js +3 -3
- package/dist/packages/core/src/components/Alert.js +2 -2
- package/dist/packages/core/src/components/BarChart.js +120 -119
- package/dist/packages/core/src/components/BottomNav.js +18 -18
- package/dist/packages/core/src/components/BulkBar.js +25 -25
- package/dist/packages/core/src/components/Button.js +16 -16
- package/dist/packages/core/src/components/Calendar.js +17 -17
- package/dist/packages/core/src/components/ChartSection.js +30 -30
- package/dist/packages/core/src/components/CountTile.js +38 -38
- package/dist/packages/core/src/components/DatePicker.js +59 -59
- package/dist/packages/core/src/components/Dialog.js +14 -14
- package/dist/packages/core/src/components/Effect/EffectPartyPopperHost.js +8 -8
- package/dist/packages/core/src/components/FilterPanel.js +6 -6
- package/dist/packages/core/src/components/FilterSectionInfo.js +6 -6
- package/dist/packages/core/src/components/GameIntro.js +1 -1
- package/dist/packages/core/src/components/HearingDeviceInfo.d.ts +5 -5
- package/dist/packages/core/src/components/HearingDeviceInfo.js +2 -2
- package/dist/packages/core/src/components/Icon.js +2 -2
- package/dist/packages/core/src/components/ItemActions.js +16 -16
- package/dist/packages/core/src/components/LandingHeader.js +6 -6
- package/dist/packages/core/src/components/LineChart.js +24 -24
- package/dist/packages/core/src/components/Loader.js +5 -5
- package/dist/packages/core/src/components/NoticeBox.js +3 -3
- package/dist/packages/core/src/components/PageTitle.js +2 -2
- package/dist/packages/core/src/components/Pagination.js +4 -4
- package/dist/packages/core/src/components/Panel.js +2 -2
- package/dist/packages/core/src/components/PanelRow.d.ts +2 -4
- package/dist/packages/core/src/components/PanelRow.js +8 -15
- package/dist/packages/core/src/components/PassCard/PassCard.js +9 -9
- package/dist/packages/core/src/components/PassCard/passCardProduct.js +2 -2
- package/dist/packages/core/src/components/PaymentPlan.js +5 -5
- package/dist/packages/core/src/components/Popup/ConfirmPopup.js +29 -29
- package/dist/packages/core/src/components/Popup/CouponInputPopup.js +2 -2
- package/dist/packages/core/src/components/Popup/DeviceListPopup.d.ts +4 -8
- package/dist/packages/core/src/components/Popup/DeviceListPopup.js +1 -1
- package/dist/packages/core/src/components/Popup/DeviceSideTable.js +19 -26
- package/dist/packages/core/src/components/Popup/ListPopup.js +6 -6
- package/dist/packages/core/src/components/Popup/LoadTablePopup.js +10 -10
- package/dist/packages/core/src/components/Popup/Popup.js +27 -27
- package/dist/packages/core/src/components/Popup/SaveTablePopup.js +5 -5
- package/dist/packages/core/src/components/Popup/TrainingResultPopup.js +4 -4
- package/dist/packages/core/src/components/Popup/customerDetailUtils.js +18 -18
- package/dist/packages/core/src/components/Popup/useDiscardGuard.js +4 -4
- package/dist/packages/core/src/components/ProgressIndicator.js +5 -5
- package/dist/packages/core/src/components/PromoBannerBg/promoBannerBg.js +16 -16
- package/dist/packages/core/src/components/Rating.js +20 -20
- package/dist/packages/core/src/components/Report/ReportDocument.js +21 -21
- package/dist/packages/core/src/components/RingChart.js +5 -5
- package/dist/packages/core/src/components/SectionHeadFloat.js +15 -15
- package/dist/packages/core/src/components/SegmentedControl.js +34 -34
- package/dist/packages/core/src/components/SideNav.js +37 -37
- package/dist/packages/core/src/components/Slider.js +2 -2
- package/dist/packages/core/src/components/SoundCompass.js +5 -5
- package/dist/packages/core/src/components/Table.js +257 -98
- package/dist/packages/core/src/components/Tabs.js +12 -12
- package/dist/packages/core/src/components/TextField.js +34 -34
- package/dist/packages/core/src/components/Toast.js +6 -6
- package/dist/packages/core/src/components/Tooltip/MenuTooltip.js +4 -4
- package/dist/packages/core/src/components/Tooltip/Tooltip.js +22 -22
- package/dist/packages/core/src/components/Tooltip/TrainingTooltip.js +4 -4
- package/dist/packages/core/src/components/TopBanner.js +28 -28
- package/dist/packages/core/src/components/Training/AudioPlayerWaveForm.js +11 -11
- package/dist/packages/core/src/components/Training/CategoryIcon.js +2 -2
- package/dist/packages/core/src/components/Training/NoisePlayerContext.d.ts +7 -4
- package/dist/packages/core/src/components/Training/NoisePlayerContext.js +12 -12
- package/dist/packages/core/src/components/Training/NoiseSettingsTooltip.js +2 -2
- package/dist/packages/core/src/components/Training/TrainingBlock.js +12 -12
- package/dist/packages/core/src/components/Training/TrainingBlockButton.js +10 -10
- package/dist/packages/core/src/components/Training/TrainingBlockGrid.js +21 -21
- package/dist/packages/core/src/components/Training/TrainingCard.css +13 -1
- package/dist/packages/core/src/components/Training/TrainingCard.js +2 -2
- package/dist/packages/core/src/components/Training/TrainingCardGroup.js +6 -6
- package/dist/packages/core/src/components/Training/TrainingPagePopup.js +83 -83
- package/dist/packages/core/src/components/Training/TrainingPagePopupBottom.js +17 -17
- package/dist/packages/core/src/components/Training/TrainingStepAudioPlayer.js +128 -128
- package/dist/packages/core/src/components/Training/TrainingStepCard.js +18 -18
- package/dist/packages/core/src/components/Training/TrainingStepConfirmWithText.js +4 -4
- package/dist/packages/core/src/components/Training/TrainingStepOrderList.js +12 -12
- package/dist/packages/core/src/components/Training/TrainingStepTextBox.js +13 -13
- package/dist/packages/core/src/components/Training/TrainingStepTitle.js +4 -4
- package/dist/packages/core/src/components/Training/TrainingWordCard.js +17 -17
- package/dist/packages/core/src/components/Training/WordTablePopup.js +5 -5
- package/dist/packages/core/src/components/Training/audioPlaybackRules.js +8 -8
- package/dist/packages/core/src/components/Training/audioTimeline.js +25 -25
- package/dist/packages/core/src/components/Training/oneShotSound.d.ts +1 -1
- package/dist/packages/core/src/components/Training/oneShotSound.js +14 -14
- package/dist/packages/core/src/components/Training/stepAnswerable.d.ts +2 -2
- package/dist/packages/core/src/components/Training/trainingMissStats.js +22 -22
- package/dist/packages/core/src/components/Training/trainingPopupUtils.js +7 -7
- package/dist/packages/core/src/components/Training/useAudioPreview.js +15 -15
- package/dist/packages/core/src/components/Training/useFloatingFocus.js +14 -14
- package/dist/packages/core/src/components/Training/useNoiseFade.js +22 -22
- package/dist/packages/core/src/components/Training/useObjectUrls.js +2 -2
- package/dist/packages/core/src/components/Training/usePlaybackSequencer.d.ts +2 -2
- package/dist/packages/core/src/components/Training/usePlaybackSequencer.js +23 -23
- package/dist/packages/core/src/components/Training/useProgressTicker.js +33 -33
- package/dist/packages/core/src/components/Training/useTTSPlayback.js +17 -17
- package/dist/packages/core/src/components/Training/useWaveReady.js +11 -11
- package/dist/packages/core/src/components/UpdateAlert.js +6 -6
- package/dist/packages/core/src/components/Word/PhonemeSelectBlock.js +7 -1
- package/dist/packages/core/src/components/Word/TableFilterPanel.js +3 -3
- package/dist/packages/core/src/components/Word/WordSearchResultBlock.js +18 -18
- package/dist/packages/core/src/components/Word/WordTableBlock.js +28 -28
- package/dist/packages/core/src/components/Word/WordTableButton.css +16 -2
- package/dist/packages/core/src/components/Word/WordTableThumbnail.css +6 -1
- package/dist/packages/core/src/components/Word/generatedDraft.js +14 -14
- package/dist/packages/core/src/components/Word/useWordTableDisplaySettings.js +7 -7
- package/dist/packages/core/src/components/Word/wordScoreStats.js +17 -17
- package/dist/packages/core/src/components/Word/wordTablePrint.js +10 -10
- package/dist/packages/core/src/components/Word/wordTableRecommend.js +13 -13
- package/dist/packages/core/src/components/Word/wordTableReview.js +28 -28
- package/dist/packages/core/src/components/Word/wordTableSettings.d.ts +8 -11
- package/dist/packages/core/src/components/Word/wordTableSettings.js +48 -36
- package/dist/packages/core/src/components/WordTable.js +130 -130
- package/dist/packages/core/src/components/barChartData.d.ts +16 -1
- package/dist/packages/core/src/components/barChartData.js +49 -29
- package/dist/packages/core/src/components/barChartTypes.js +8 -8
- package/dist/packages/core/src/components/tableTrackList.js +11 -11
- package/dist/packages/core/src/components/updateAlertStack.js +28 -28
- package/dist/packages/core/src/components/useBarChartControl.js +4 -6
- package/dist/packages/core/src/components/useComboboxSuggestions.js +17 -17
- package/dist/packages/core/src/components/useToastLifecycle.js +18 -18
- package/dist/packages/core/src/components/wordTableLayout.d.ts +22 -7
- package/dist/packages/core/src/components/wordTableLayout.js +5 -5
- package/dist/packages/core/src/context/I18nProvider.js +6 -6
- package/dist/packages/core/src/hooks/focusReturn.js +27 -27
- package/dist/packages/core/src/hooks/inputModality.d.ts +1 -1
- package/dist/packages/core/src/hooks/inputModality.js +14 -14
- package/dist/packages/core/src/hooks/touchToggle.js +39 -39
- package/dist/packages/core/src/hooks/useAnchoredMenu.js +30 -30
- package/dist/packages/core/src/hooks/useDailyTrainingSelection.js +28 -28
- package/dist/packages/core/src/hooks/useDeviceManagement.d.ts +5 -17
- package/dist/packages/core/src/hooks/useDeviceManagement.js +20 -8
- package/dist/packages/core/src/hooks/useDocumentTitle.js +10 -10
- package/dist/packages/core/src/hooks/useEditableList.js +4 -4
- package/dist/packages/core/src/hooks/useFocusTrap.js +2 -2
- package/dist/packages/core/src/hooks/useFontScale.js +6 -6
- package/dist/packages/core/src/hooks/useIcon.js +7 -7
- package/dist/packages/core/src/hooks/useListControls.js +2 -2
- package/dist/packages/core/src/hooks/usePageResetOnChange.d.ts +3 -3
- package/dist/packages/core/src/hooks/usePageResetOnChange.js +6 -6
- package/dist/packages/core/src/hooks/usePasswordChange.js +4 -4
- package/dist/packages/core/src/hooks/usePendingPress.js +7 -7
- package/dist/packages/core/src/hooks/useRelativeDay.js +4 -4
- package/dist/packages/core/src/hooks/useSoundCompassExam.js +6 -6
- package/dist/packages/core/src/hooks/useTablePaging.d.ts +7 -7
- package/dist/packages/core/src/hooks/useTablePaging.js +40 -34
- package/dist/packages/core/src/hooks/useTrainingSessionLog.js +35 -35
- package/dist/packages/core/src/hooks/useVisitSchedule.js +4 -4
- package/dist/packages/core/src/hooks/useWordAttributeFilter.js +14 -14
- package/dist/packages/core/src/hooks/useWordMeanings.js +5 -5
- package/dist/packages/core/src/hooks/useWordTrainingFilter.d.ts +3 -1
- package/dist/packages/core/src/hooks/useWordTrainingFilter.js +14 -12
- package/dist/packages/core/src/i18n/locales/en.json +13 -2
- package/dist/packages/core/src/i18n/locales/ko.json +12 -1
- package/dist/packages/core/src/types/wordSearch.d.ts +9 -0
- package/dist/packages/core/src/utils/audioMeter.js +5 -5
- package/dist/packages/core/src/utils/audioPeaks.d.ts +1 -1
- package/dist/packages/core/src/utils/audioPeaks.js +18 -18
- package/dist/packages/core/src/utils/blockGrid.js +4 -4
- package/dist/packages/core/src/utils/chunkSkew.js +9 -9
- package/dist/packages/core/src/utils/clipboard.js +3 -3
- package/dist/packages/core/src/utils/dailySelectionUtils.js +12 -12
- package/dist/packages/core/src/utils/date.js +12 -12
- package/dist/packages/core/src/utils/devWarn.d.ts +2 -2
- package/dist/packages/core/src/utils/devWarn.js +7 -7
- package/dist/packages/core/src/utils/deviceKind.d.ts +37 -7
- package/dist/packages/core/src/utils/deviceKind.js +53 -10
- package/dist/packages/core/src/utils/errorMessage.js +7 -7
- package/dist/packages/core/src/utils/filterPanelSummary.d.ts +1 -1
- package/dist/packages/core/src/utils/filterPanelSummary.js +16 -16
- package/dist/packages/core/src/utils/format.js +4 -4
- package/dist/packages/core/src/utils/getTrainingData.js +9 -9
- package/dist/packages/core/src/utils/imageConfig.js +17 -17
- package/dist/packages/core/src/utils/koreanSyllable.js +18 -18
- package/dist/packages/core/src/utils/navScroll.d.ts +2 -2
- package/dist/packages/core/src/utils/navScroll.js +6 -6
- package/dist/packages/core/src/utils/phonemeConvert.d.ts +7 -0
- package/dist/packages/core/src/utils/phonemeConvert.js +19 -10
- package/dist/packages/core/src/utils/phonemeGroups.js +2 -2
- package/dist/packages/core/src/utils/phonemePairSearch.d.ts +1 -0
- package/dist/packages/core/src/utils/phonemePairSearch.js +118 -102
- package/dist/packages/core/src/utils/phonemeSectionCodes.d.ts +52 -0
- package/dist/packages/core/src/utils/phonemeSectionCodes.js +76 -0
- package/dist/packages/core/src/utils/prefetchChunk.js +6 -6
- package/dist/packages/core/src/utils/pressable.d.ts +1 -1
- package/dist/packages/core/src/utils/pressable.js +3 -3
- package/dist/packages/core/src/utils/safeHref.js +4 -4
- package/dist/packages/core/src/utils/savedWordTables.js +5 -5
- package/dist/packages/core/src/utils/sessionTab.d.ts +7 -7
- package/dist/packages/core/src/utils/sessionTab.js +9 -9
- package/dist/packages/core/src/utils/speak.js +12 -12
- package/dist/packages/core/src/utils/textSort.d.ts +5 -5
- package/dist/packages/core/src/utils/textSort.js +47 -47
- package/dist/packages/core/src/utils/theme.js +3 -3
- package/dist/packages/core/src/utils/trainingItemResults.js +3 -3
- package/dist/packages/core/src/utils/ttsConfig.js +2 -2
- package/dist/packages/core/src/utils/wordAttributeFilterCodes.d.ts +2 -2
- package/dist/packages/core/src/utils/wordAttributeFilterCodes.js +48 -48
- package/dist/packages/core/src/utils/wordContrast.js +3 -3
- package/dist/packages/core/src/utils/wordPairIndex.js +4 -4
- package/dist/packages/core/src/utils/wordTrainingGenerators.js +4 -4
- package/dist/packages/core/src/utils/wordTrainingPool.js +15 -15
- package/dist/packages/core/src/utils/wordTrainingRuntime.js +6 -6
- package/dist/packages/core/src/utils/wornDate.d.ts +3 -3
- package/dist/packages/core/src/utils/wornDate.js +14 -14
- package/dist/packages/core/src/version.d.ts +1 -1
- package/dist/packages/core/src/version.js +1 -1
- package/dist/utils.cjs.js +4 -4
- package/package.json +1 -1
|
@@ -43,13 +43,13 @@ export function useEditableList(emptyItem, initialList = []) {
|
|
|
43
43
|
/* ํธ์ง์ด **์ทจ์๋ก** ๋๋๋ฉด ๊ทธ ์ฌ์ด ๊ฑด๋๋ด ๋ชฉ๋ก์ ๋ฐ๋๋ค โ ์ ์๋ initialList ๊ฐ ๋ฐ๋ ๋๋ง ๋์, ํธ์ง ์ค ๋ค์ด์จ ์๋ฒ ๋ชฉ๋ก์ด
|
|
44
44
|
์ทจ์ํ ๋ค์๋ ๋๋ด ์ ๋ณด์๋ค(๋ฐฉ๋ฌธ ์์ ์ ๊ณ ์น๋ค ์ทจ์ํ๋ฉด ๊ทธ ์ฌ์ด ๋ค๋ฅธ ๋ฐฉ๋ฌธ์ด ๋น ์ง ์ฑ). commitEdit ์ผ๋ก ๋๋ ๋๋
|
|
45
45
|
๋ฐฉ๊ธ ์ปค๋ฐํ ํธ์ง์ ์ ๋ชฉ๋ก์ผ๋ก ๋ฎ์ง ์๋๋ก ํ ๋ฒ ๋๊ธด๋ค(๋ค์ ๋ชฉ๋ก์ด ์ค๋ฉด ๊ทธ๋ ๋ฐ๋๋ค). */
|
|
46
|
-
const
|
|
46
|
+
const endedByCommitRef = useRef(false);
|
|
47
47
|
const prevInitialRef = useRef(null);
|
|
48
48
|
useEffect(() => {
|
|
49
49
|
if (isEditing)
|
|
50
50
|
return;
|
|
51
|
-
if (
|
|
52
|
-
|
|
51
|
+
if (endedByCommitRef.current) {
|
|
52
|
+
endedByCommitRef.current = false;
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
const prev = prevInitialRef.current;
|
|
@@ -80,7 +80,7 @@ export function useEditableList(emptyItem, initialList = []) {
|
|
|
80
80
|
setSelected(updated);
|
|
81
81
|
// ํธ์ง ์ค์ด์์ ๋๋ง ํ์ํ๋ค(ํธ์ง์ด ์๋๋ฐ ํ์ํ๋ฉด ๋ค์ ๋ชฉ๋ก ํ ๋ฒ์ ํ๋์ด ๊ฑด๋๋ด๋ค). ๊ฐ์ ๊ฐ์ ๋ฃ๋ ํ์๋ผ ๋ ๋ฒ ๋ถ๋ ค๋ ๊ฐ๋ค
|
|
82
82
|
setIsEditing(prev => { if (prev)
|
|
83
|
-
|
|
83
|
+
endedByCommitRef.current = true; return false; });
|
|
84
84
|
}, []);
|
|
85
85
|
const remove = useCallback((item) => {
|
|
86
86
|
setList(prev => prev.filter(v => !isSameEditableItem(v, item)));
|
|
@@ -40,8 +40,8 @@ export function useFocusTrap(panelRef, enabled = true) {
|
|
|
40
40
|
const active = document.activeElement;
|
|
41
41
|
/* ํฌ์ปค์ค๊ฐ **ํ ์์ฒด**(tabIndex=-1 โ ๋ณธ๋ฌธ ๊ธ์๋ฅผ ๋๋ฅด๋ฉด ํ์ด ๋ฐ๋๋ค)์ ์์ผ๋ฉด ์ฒซ ์นธยท๋ ์นธ ์ด๋ ์ชฝ๋ ์๋๋ผ
|
|
42
42
|
๋๊ฐ์ง ์์, Shift+Tab ํ ๋ฒ์ ๋ค ํ๋ฉด์ผ๋ก ๋๊ฐ๋ค. ํ ์์ฒด๋ฉด Shift+Tab ์ ๋ ์นธ์ผ๋ก ๋ณด๋ธ๋ค. */
|
|
43
|
-
const
|
|
44
|
-
if (e.shiftKey && (active === first ||
|
|
43
|
+
const onPanelItself = active === panel;
|
|
44
|
+
if (e.shiftKey && (active === first || onPanelItself)) {
|
|
45
45
|
e.preventDefault();
|
|
46
46
|
last.focus();
|
|
47
47
|
}
|
|
@@ -5,7 +5,7 @@ export default function useFontScale({ storageKey = 'fontScale',
|
|
|
5
5
|
// ๊ธฐ๋ณธ๊ฐ์ ์ค์ํ: Training/Tooltip ํ
์คํธ ๋ฐ์ค ๊ณตํต๊ฐ
|
|
6
6
|
baseFont = 20, minScale = 0.8, maxScale = 1.4, step = 0.1, lineHeightRatio = 1.4, } = {}) {
|
|
7
7
|
const clamp = (v) => Math.max(minScale, Math.min(maxScale, Number(v)));
|
|
8
|
-
const
|
|
8
|
+
const read = (key) => {
|
|
9
9
|
try {
|
|
10
10
|
const raw = localStorage.getItem(key);
|
|
11
11
|
const parsed = raw ? Number(raw) : 1;
|
|
@@ -21,13 +21,13 @@ baseFont = 20, minScale = 0.8, maxScale = 1.4, step = 0.1, lineHeightRatio = 1.4
|
|
|
21
21
|
return clamp(1);
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
|
-
const [scale, setScaleRaw] = useState(() =>
|
|
24
|
+
const [scale, setScaleRaw] = useState(() => read(storageKey));
|
|
25
25
|
/* ์ด์ ๊ฐ ๋ฐ๋๋ฉด **๊ทธ ์ด์ ์ ์ ์ฅ๊ฐ** ์ผ๋ก โ ์ ์๋ ์ ์ด์ ์ ๊ฐ์ ๋ค๊ณ ์๋ค๊ฐ ์๋ ์ฐ๊ธฐ๊ฐ ์ ์ด์ ์ ์ ์ฅ๊ฐ์ ๋ฎ์๋ค.
|
|
26
26
|
๋ ๋ ์ค์ ๋ง์ถ๋ฏ๋ก ์ฐ๊ธฐ effect ๋ ์ ๊ฐ์ผ๋ก๋ง ๋๋ค. */
|
|
27
|
-
const [
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
setScaleRaw(
|
|
27
|
+
const [loadedKey, setLoadedKey] = useState(storageKey);
|
|
28
|
+
if (loadedKey !== storageKey) {
|
|
29
|
+
setLoadedKey(storageKey);
|
|
30
|
+
setScaleRaw(read(storageKey));
|
|
31
31
|
}
|
|
32
32
|
useEffect(() => {
|
|
33
33
|
try {
|
|
@@ -5,13 +5,13 @@ import { resolveAssetUrl } from '../utils';
|
|
|
5
5
|
/* ์์ฑ ๊ฐ์ด **๋ฌธ์ ๋ฐ์ ๊ฐ๋ฆฌํค๋** url( ์ ํ์๋. `url(#๊ทธ๋ผ๋ฐ์ด์
)`ยท`url(#clip)` ์ฒ๋ผ ๊ฐ์ SVG ์์ ์กฐ๊ฐ๋ง ๊ฐ๋ฆฌํค๋ ๊ฒ์ ๋๋ค โ
|
|
6
6
|
์ฝ์ด ๋ก๊ณ ์๋ฅธ์ฌ์ฏ ์ฅ์ด clip-pathยทfill ๋ก ๊ทธ๊ฒ์ ์ด๋ค(๊ฑท์ผ๋ฉด ์ธ๋ผ์ธ์ผ๋ก ๊ทธ๋ฆฐ ๋ก๊ณ ๊ฐ ๊นจ์ง๋ค).
|
|
7
7
|
CSS ์ด์ค์ผ์ดํ๋ก url ์ ์จ๊ธด ๊ผด(์ญ์ฌ๋์ + ๊ดํธ)๋ ๊ฑท๋๋ค โ ์์ด์ฝ ์์ฑ์ ๊ทธ๋ฐ ๊ฐ์ ์ธ ๊น๋ญ์ด ์๋ค. */
|
|
8
|
-
function
|
|
8
|
+
function hasExternalUrl(value) {
|
|
9
9
|
if (value.includes('\\') && value.includes('('))
|
|
10
10
|
return true;
|
|
11
11
|
if (!/url\s*\(/i.test(value))
|
|
12
12
|
return false;
|
|
13
|
-
const
|
|
14
|
-
return /url\s*\(/i.test(
|
|
13
|
+
const withoutFragments = value.replace(/url\(\s*(['"]?)#[^)'"]*\1\s*\)/gi, '');
|
|
14
|
+
return /url\s*\(/i.test(withoutFragments);
|
|
15
15
|
}
|
|
16
16
|
function sanitizeSvg(raw) {
|
|
17
17
|
if (typeof window === 'undefined')
|
|
@@ -19,11 +19,11 @@ function sanitizeSvg(raw) {
|
|
|
19
19
|
/* ์ธ๋ผ์ธ SVG ์์ **๋ฐ๊นฅ ์์์ ๋ถ๋ฅด๋ ๊ธธ** ์ ๊ฑท๋๋ค โ `<style>`(@importยทbackground:url) ๊ณผ ๋ฐ๊นฅ url( ์ด ๋ ์์ฑ(styleยทfillยทfilterโฆ).
|
|
20
20
|
๊ทธ๋๋ก ๋๋ฉด ์์ด์ฝ ํ ์ฅ์ด ์ 3์ ์ฃผ์๋ก ์์ฒญ์ ๋ณด๋ด๊ฑฐ๋(์ถ์ ) CSS ๋ก ํ๋ฉด์ ๋ฎ์ ์ ์์๋ค.
|
|
21
21
|
ํ
์ DOMPurify ์ ์ญ์ ๋ถ์ผ๋ฏ๋ก ์ด ํ ๋ฒ์ sanitize ๋์๋ง ๊ฑธ๊ณ ๊ณง๋ฐ๋ก ๋๋ค(์ฑ์ ๋ค๋ฅธ DOMPurify ์ฐ์์ ์์ง ์๊ฒ). */
|
|
22
|
-
const
|
|
23
|
-
if (typeof data.attrValue === 'string' &&
|
|
22
|
+
const hook = (_node, data) => {
|
|
23
|
+
if (typeof data.attrValue === 'string' && hasExternalUrl(data.attrValue))
|
|
24
24
|
data.keepAttr = false;
|
|
25
25
|
};
|
|
26
|
-
DOMPurify.addHook('uponSanitizeAttribute',
|
|
26
|
+
DOMPurify.addHook('uponSanitizeAttribute', hook);
|
|
27
27
|
try {
|
|
28
28
|
return DOMPurify.sanitize(raw, {
|
|
29
29
|
USE_PROFILES: { svg: true, svgFilters: true },
|
|
@@ -32,7 +32,7 @@ function sanitizeSvg(raw) {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
finally {
|
|
35
|
-
DOMPurify.removeHook('uponSanitizeAttribute',
|
|
35
|
+
DOMPurify.removeHook('uponSanitizeAttribute', hook);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
/** @public */
|
|
@@ -42,8 +42,8 @@ export function useListControls(opts = {}) {
|
|
|
42
42
|
const [page, setPage] = useState(1);
|
|
43
43
|
/* ๋ชป ์ฝํ๋ ์ชฝํฌ๊ธฐ('' ยท 'all' ยท '0')๋ ๊ธฐ๋ณธ๊ฐ์ผ๋ก โ ๊ทธ ์
์ด ํ๋ฉด๋ง๋ค ํ ๋ฒ์ฉ ์์๋ค.
|
|
44
44
|
๊ธฐ๋ณธ๊ฐ ์์ฒด๊ฐ ๋ชป ์ฝํ๋ฉด 25 ๋ค. ๋ชฉ๋ก์ด ํต์งธ๋ก ์ฌ๋ผ์ง๋ ๊ฐ(0ยทNaN)์ ์ฌ๊ธฐ์ ๋ซ๋๋ค. */
|
|
45
|
-
const
|
|
46
|
-
const perPage = Number(pageSize) ||
|
|
45
|
+
const defaultSize = Number(defaultPageSize) || 25;
|
|
46
|
+
const perPage = Number(pageSize) || defaultSize;
|
|
47
47
|
/* **๊ฒ์์ด์ ์ชฝํฌ๊ธฐ๋ฅผ ์ฌ๊ธฐ์ ๋ฃ๋๋ค** โ ๋ถ๋ฅด๋ ์ชฝ์ด ์ ์ผ๋ฉด ๋น ๋จ๋ฆด ์ ์๊ณ , ๋น ๋จ๋ ค๋ ์ ๋นจ๊ฐ์ง๋ค.
|
|
48
48
|
์ด์ด ๋ถ์ด๋ ๊ฒ์ `usePageResetOnChange` ๊ฐ ๊ธ ํ๋๋ฅผ ๋ฐ๊ธฐ ๋๋ฌธ์ด๊ณ , ๊ฐ์ด ๊ธยท์ยท์ฐธ๊ฑฐ์ง๋ฟ์ด๋ผ
|
|
49
49
|
`[object Object]` ๋ก ๋ญ๊ฐ์ง ์ผ์ด ์๋ค(ํ์
์ด ๋ง๋๋ค). */
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
* React ๊ฐ ์ด ๊ผด์ ์ํด ๋ด์ค ๊ธธ์ด๋ค(๊ฐ์ ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ๋ค์ ๊ทธ๋ฆฐ๋ค).
|
|
12
12
|
*
|
|
13
13
|
* ```ts
|
|
14
|
-
* usePageResetOnChange(`${๊ฒ์์ด}|${๊ฐ๋}|${
|
|
14
|
+
* usePageResetOnChange(`${๊ฒ์์ด}|${๊ฐ๋}|${sort}`, setPage)
|
|
15
15
|
* const { pageItems, totalPages } = usePagedList(๊ฑธ๋ฌ๋ธ๊ฒ, ์ชฝํฌ๊ธฐ, page)
|
|
16
16
|
* ```
|
|
17
17
|
*
|
|
18
|
-
* @param
|
|
18
|
+
* @param key ์กฐ๊ฑด์ ํ๋๋ก ์ด์ด ๋ถ์ธ ๊ธ โ ์ด๊ฒ์ด ๋ฌ๋ผ์ง๋ฉด ์ฒซ ์ชฝ์ด๋ค
|
|
19
19
|
* @param setPage ์ชฝ ๋ฒํธ๋ฅผ ์ฅ ์ชฝ์์ ์ค๋ค
|
|
20
20
|
* @public
|
|
21
21
|
*/
|
|
22
|
-
export declare function usePageResetOnChange(
|
|
22
|
+
export declare function usePageResetOnChange(key: string, setPage: (n: number) => void): void;
|
|
@@ -13,18 +13,18 @@ import { useState } from 'react';
|
|
|
13
13
|
* React ๊ฐ ์ด ๊ผด์ ์ํด ๋ด์ค ๊ธธ์ด๋ค(๊ฐ์ ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ๋ค์ ๊ทธ๋ฆฐ๋ค).
|
|
14
14
|
*
|
|
15
15
|
* ```ts
|
|
16
|
-
* usePageResetOnChange(`${๊ฒ์์ด}|${๊ฐ๋}|${
|
|
16
|
+
* usePageResetOnChange(`${๊ฒ์์ด}|${๊ฐ๋}|${sort}`, setPage)
|
|
17
17
|
* const { pageItems, totalPages } = usePagedList(๊ฑธ๋ฌ๋ธ๊ฒ, ์ชฝํฌ๊ธฐ, page)
|
|
18
18
|
* ```
|
|
19
19
|
*
|
|
20
|
-
* @param
|
|
20
|
+
* @param key ์กฐ๊ฑด์ ํ๋๋ก ์ด์ด ๋ถ์ธ ๊ธ โ ์ด๊ฒ์ด ๋ฌ๋ผ์ง๋ฉด ์ฒซ ์ชฝ์ด๋ค
|
|
21
21
|
* @param setPage ์ชฝ ๋ฒํธ๋ฅผ ์ฅ ์ชฝ์์ ์ค๋ค
|
|
22
22
|
* @public
|
|
23
23
|
*/
|
|
24
|
-
export function usePageResetOnChange(
|
|
25
|
-
const [
|
|
26
|
-
if (
|
|
27
|
-
|
|
24
|
+
export function usePageResetOnChange(key, setPage) {
|
|
25
|
+
const [seenKey, setSeenKey] = useState(key);
|
|
26
|
+
if (seenKey !== key) {
|
|
27
|
+
setSeenKey(key);
|
|
28
28
|
setPage(1);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -17,7 +17,7 @@ export function usePasswordChange(onChangePassword) {
|
|
|
17
17
|
const clearTimerRef = useRef(null);
|
|
18
18
|
/* ์ฒ๋ฆฌ ์ค์๋ ๋ค์ ๋ฐ์ง ์๋๋ค โ ๋ ๋ฒ ๋๋ฅด๋ฉด ๋ณ๊ฒฝ์ด ๋ ๋ฒ ๋๊ฐ, ๋จผ์ ์จ ์ฑ๊ณต ๋ค์ ๋ฆ๊ฒ ์จ "ํ์ฌ ๋น๋ฐ๋ฒํธ ํ๋ฆผ" ์ด
|
|
19
19
|
๋จ์ ๋ฐ๋์๋๋ฐ ์ค๋ฅ ๋ฌธ๊ตฌ๊ฐ ๋ด๋ค(๋ง๊ท ๊ณ์ ํ๋ฉด ๋จ์ถ๋ ๋ก๋ฉ ์ค์๋ ๋๋ฆฐ๋ค) */
|
|
20
|
-
const
|
|
20
|
+
const busyRef = useRef(false);
|
|
21
21
|
useEffect(() => {
|
|
22
22
|
return () => {
|
|
23
23
|
if (clearTimerRef.current !== null)
|
|
@@ -29,7 +29,7 @@ export function usePasswordChange(onChangePassword) {
|
|
|
29
29
|
setPwMsg(null);
|
|
30
30
|
}, []);
|
|
31
31
|
const handleChangePw = useCallback(async () => {
|
|
32
|
-
if (
|
|
32
|
+
if (busyRef.current)
|
|
33
33
|
return;
|
|
34
34
|
if (!pw.current) {
|
|
35
35
|
setPwMsg({ type: 'error', text: t('account.security.password.currentRequired', 'ํ์ฌ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ์ธ์.') });
|
|
@@ -46,7 +46,7 @@ export function usePasswordChange(onChangePassword) {
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
setPwLoading(true);
|
|
49
|
-
|
|
49
|
+
busyRef.current = true;
|
|
50
50
|
// ์ฝ๋ฐฑ์ด ๋ฌธ๊ตฌ ๋์ **๊ฑฐ์ **ํ๋ฉด(๋ง ๋๊น ๋ฑ) ๋ก๋ฉ์ด true ๋ก ๋จ์ ๋จ์ถ๊ฐ ์์ ์ ๊ฒผ๊ณ ,
|
|
51
51
|
// reset() ๋ ๋ก๋ฉ์ ์ ๊ฑด๋๋ ค ํ ๊ธธ์ด ์์๋ค. ๊ฑฐ์ ๋ ์ค๋ฅ ๋ฌธ๊ตฌ๋ก ๋ณด์ด๊ณ ๋ก๋ฉ์ ๋ ๋ด๋ฆฐ๋ค.
|
|
52
52
|
let err;
|
|
@@ -57,7 +57,7 @@ export function usePasswordChange(onChangePassword) {
|
|
|
57
57
|
err = getErrorMessage(e);
|
|
58
58
|
}
|
|
59
59
|
finally {
|
|
60
|
-
|
|
60
|
+
busyRef.current = false;
|
|
61
61
|
setPwLoading(false);
|
|
62
62
|
}
|
|
63
63
|
if (err) {
|
|
@@ -7,19 +7,19 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
7
7
|
*/
|
|
8
8
|
export function usePendingPress(fn) {
|
|
9
9
|
const [pending, setPending] = useState(false);
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
useEffect(() => {
|
|
10
|
+
const running = useRef(false);
|
|
11
|
+
const mounted = useRef(true);
|
|
12
|
+
useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);
|
|
13
13
|
const press = useCallback(() => {
|
|
14
|
-
if (!fn ||
|
|
14
|
+
if (!fn || running.current)
|
|
15
15
|
return;
|
|
16
16
|
const r = fn();
|
|
17
17
|
if (r && typeof r.then === 'function') {
|
|
18
|
-
|
|
18
|
+
running.current = true;
|
|
19
19
|
setPending(true);
|
|
20
|
-
const
|
|
20
|
+
const settle = () => { running.current = false; if (mounted.current)
|
|
21
21
|
setPending(false); };
|
|
22
|
-
r.then(
|
|
22
|
+
r.then(settle, settle);
|
|
23
23
|
}
|
|
24
24
|
}, [fn]);
|
|
25
25
|
return { pending, press };
|
|
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { getTodayKst, toKstYmd } from '../utils/date';
|
|
5
5
|
/** 'YYYY-MM-DD' ๋ ๋ ์ง ์ฌ์ด์ ๋ฌ๋ ฅ๋ ์ */
|
|
6
|
-
function
|
|
6
|
+
function dayDiff(from, to) {
|
|
7
7
|
const [fy, fm, fd] = from.split('-').map(Number);
|
|
8
8
|
const [ty, tm, td] = to.split('-').map(Number);
|
|
9
9
|
return Math.round((Date.UTC(ty, tm - 1, td) - Date.UTC(fy, fm - 1, fd)) / 86400000);
|
|
@@ -23,10 +23,10 @@ export function useRelativeDay() {
|
|
|
23
23
|
return useCallback((iso) => {
|
|
24
24
|
if (!iso)
|
|
25
25
|
return '';
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
26
|
+
const ymd = toKstYmd(iso);
|
|
27
|
+
if (!ymd)
|
|
28
28
|
return '';
|
|
29
|
-
const days =
|
|
29
|
+
const days = dayDiff(ymd, getTodayKst());
|
|
30
30
|
if (days <= 0)
|
|
31
31
|
return t('myHomework.relativeDay.today', '์ค๋');
|
|
32
32
|
if (days === 1)
|
|
@@ -26,7 +26,7 @@ export function useSoundCompassExam(speakers, { mode = 'playback', autoStart = f
|
|
|
26
26
|
useEffect(() => () => clearTimers(), []);
|
|
27
27
|
const answerId = queue[index]?.id ?? null;
|
|
28
28
|
/** ์ด๋ฏธ ๊ณ ๋ฅธ ๋ฌธํญ ๋ฒํธ โ ์ ๋ฌธํญ์ด ๋๊ธฐ์ ๋ค์ด๊ฐ๋ฉด ํ๋ฆฐ๋ค(playQuestion) */
|
|
29
|
-
const
|
|
29
|
+
const pickLockRef = useRef(null);
|
|
30
30
|
// โโ playback: ๋ชจ๋ ์คํผ์ปค๋ฅผ ์์๋๋ก โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
31
31
|
const runPlayback = useCallback(() => {
|
|
32
32
|
clearTimers();
|
|
@@ -49,7 +49,7 @@ export function useSoundCompassExam(speakers, { mode = 'playback', autoStart = f
|
|
|
49
49
|
onDoneRef.current?.({ score: scoreRef.current, total: q.length });
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
pickLockRef.current = null;
|
|
53
53
|
setIndex(i);
|
|
54
54
|
setSelectedId(null);
|
|
55
55
|
setPhase('playing');
|
|
@@ -87,7 +87,7 @@ export function useSoundCompassExam(speakers, { mode = 'playback', autoStart = f
|
|
|
87
87
|
/* eslint-disable react-hooks/set-state-in-effect */
|
|
88
88
|
/* ๋ฐฐ์น๊ฐ ๋ฐ๋์๋์ง๋ **์คํผ์ปค id ๋ค**๋ก ๋ณธ๋ค โ ๊ฐ์๋ง ๋ณด๋ฉด ์๊ฐ ๊ฐ์ ๋ค๋ฅธ ๋ฐฐ์น(HORIZON_7 โ PRESET_27 ๊ฐ๋ก)๋ก ๋ฐ๊ฟ๋
|
|
89
89
|
์ด๊ธฐํ๋์ง ์์ ์ ๋ฌธ์ ์ค์ด ๋จ๊ณ ์ ๋ต id ๊ฐ ์ ๋ฐฐ์น์ ์์ด ๋ฌด์์ ๊ณจ๋ผ๋ ์ค๋ต์ด์๋ค. */
|
|
90
|
-
const
|
|
90
|
+
const layoutKey = speakers.map((sp) => sp.id).join('|');
|
|
91
91
|
useEffect(() => {
|
|
92
92
|
if (autoStart)
|
|
93
93
|
start();
|
|
@@ -96,14 +96,14 @@ export function useSoundCompassExam(speakers, { mode = 'playback', autoStart = f
|
|
|
96
96
|
// ๋ฐฐ์นยท๋ชจ๋๊ฐ ๋ฐ๋๊ฑฐ๋ ๋ ๋ ๋ ์ ํ์ ์์ฝ์ ๊ฑท๋๋ค
|
|
97
97
|
return () => clearTimers();
|
|
98
98
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
-
}, [mode,
|
|
99
|
+
}, [mode, layoutKey, autoStart]);
|
|
100
100
|
/* eslint-enable react-hooks/set-state-in-effect */
|
|
101
101
|
const onSelectSpeaker = useCallback((id) => {
|
|
102
102
|
/* ๊ฐ์ ๋๊ธฐ์์ **ํ ๋ฒ๋ง** ๋ฐ๋๋ค โ ๋ค์ ๊ทธ๋ ค์ง๊ธฐ ์ ์ ๊ฐ์ ํธ๋ค๋ฌ๊ฐ ๋ ๋ฒ ๋ถ๋ฆฌ๋ฉด(๋ ๋ ์์ ์ phase ๋ง ๋ณด๋ฉด) ์ ์๊ฐ +2,
|
|
103
103
|
๋ค์ ๋ฌธ์ ๊ฐ ๋ ๋ฒ ์์ฝ๋๋ค. ๊ณ ๋ฅด๋ ์๊ฐ ref ๋ก ๋ซ๋๋ค. */
|
|
104
|
-
if (phase !== 'waiting' || mode === 'playback' ||
|
|
104
|
+
if (phase !== 'waiting' || mode === 'playback' || pickLockRef.current === index)
|
|
105
105
|
return;
|
|
106
|
-
|
|
106
|
+
pickLockRef.current = index;
|
|
107
107
|
setSelectedId(id);
|
|
108
108
|
const correct = id === queue[index]?.id;
|
|
109
109
|
if (correct)
|
|
@@ -56,26 +56,26 @@ export interface TableSortOptions<K extends string> {
|
|
|
56
56
|
* **์ ๋ ฌ์ด ๋ฐ๋๋ฉด ์ฒซ ์ชฝ์ผ๋ก** โ `useTablePaging` ์ `resetOn` ์ ๋๋ ค๋ฐ์ `์ ๋ ฌ` ์ ๊ทธ๋๋ก ๋ฃ๋๋ค.
|
|
57
57
|
* `์ ๋ ฌ` ์ ๋๋ฅผ ๋๋ง ์ ๊ฐ์ฒด๊ฐ ๋๋ฏ๋ก(๋ ๋๋ง๋ค ์๋ก ๋ง๋ค์ง ์๋๋ค) ๊ทธ ์์ฒด๊ฐ ์ด์ ๋ค. ์ชฝ์ ๋ฐ๋ก ๋ค๊ณ ์์ผ๋ฉด `onSortChange` ์์ ๋๋๋ฆฐ๋ค.
|
|
58
58
|
*
|
|
59
|
-
* `sortRows(rows,
|
|
59
|
+
* `sortRows(rows, getValue)` ์ ์ง๊ธ ์ ๋ ฌ๋ก ์ค์ ์ธ์ด **์ ๋ฐฐ์ด**์ ์ค๋ค(์ ๋ ฌ์ด ์์ผ๋ฉด ๋ฐ์ ๋ฐฐ์ด ๊ทธ๋๋ก) โ `compareCell`(์ซ์๋ ์๋ก,
|
|
60
60
|
* ํ๊ธยท์๋ฌธ์ ์ฌ๋ ์ฐจ๋ก) ์ผ๋ก ๊ฒฌ์ฃผ๊ณ **๋น ๊ฐ(nullยทundefinedยท'')์ ์ค๋ฆยท๋ด๋ฆผ ๋ชจ๋ ๋งจ ๋ค**(ListPopup ๊ณผ ๊ฐ์ ๊ท์น).
|
|
61
61
|
*
|
|
62
62
|
* @example
|
|
63
|
-
* const {
|
|
63
|
+
* const { sort, field, sortRows } = useTableSort<'name' | 'joined'>()
|
|
64
64
|
* const ์ธ์ด์ค = useMemo(() => sortRows(rows, (r, key) => r[key]), [rows, sortRows])
|
|
65
|
-
* const { pageRows, pageSizeProps, paginationProps } = useTablePaging(์ธ์ด์ค,
|
|
66
|
-
* <Table fields={[
|
|
65
|
+
* const { pageRows, pageSizeProps, paginationProps } = useTablePaging(์ธ์ด์ค, sort) // ์ ๋ ฌ์ด ๋ฐ๋๋ฉด ์ฒซ ์ชฝ
|
|
66
|
+
* <Table fields={[field('์ด๋ฆ', 'name'), field('๊ฐ์
์ผ', 'joined')]} โฆ />
|
|
67
67
|
*
|
|
68
68
|
* @public
|
|
69
69
|
*/
|
|
70
70
|
export declare function useTableSort<K extends string>(opts?: TableSortOptions<K>): {
|
|
71
|
-
|
|
71
|
+
sort: {
|
|
72
72
|
key: K;
|
|
73
73
|
asc: boolean;
|
|
74
74
|
} | null;
|
|
75
|
-
|
|
75
|
+
field: (label: string, key: K) => {
|
|
76
76
|
label: string;
|
|
77
77
|
sortDir: "desc" | "asc" | null;
|
|
78
78
|
onClick: () => void;
|
|
79
79
|
};
|
|
80
|
-
sortRows: <R>(rows: readonly R[],
|
|
80
|
+
sortRows: <R>(rows: readonly R[], getValue: (row: R, key: K) => unknown) => readonly R[];
|
|
81
81
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { VAP_PAGE_SIZE_OPTIONS } from './useVisitAllPanel';
|
|
5
|
-
import {
|
|
5
|
+
import { compareCellBlankLast } from '../utils/textSort';
|
|
6
6
|
/**
|
|
7
7
|
* ํ์ ์ชฝ๋๊น โ ๊ฐ์ ๋ณด๊ธฐ ยท ์ชฝ ยท ์ด๋ฒ ์ชฝ ์ค.
|
|
8
8
|
*
|
|
@@ -26,17 +26,17 @@ import { ์นธ์๋น๊ฐ๋ค } from '../utils/textSort';
|
|
|
26
26
|
export function useTablePaging(rows, resetOn, opts) {
|
|
27
27
|
const { t } = useTranslation();
|
|
28
28
|
const [pageSize, setPageSizeRaw] = useState(() => {
|
|
29
|
-
const
|
|
30
|
-
return
|
|
29
|
+
const initial = opts?.initialPageSize;
|
|
30
|
+
return initial && VAP_PAGE_SIZE_OPTIONS.some(o => o.value === initial) ? initial : '25';
|
|
31
31
|
});
|
|
32
32
|
const [page, setPage] = useState(1);
|
|
33
33
|
/* ๋์ด๋๋ ์ฐจ๋ก๊ฐ ๋ฐ๋๋ฉด ์ฒซ ์ชฝ์ผ๋ก โ 3์ชฝ์ ๋ณด๋ ์ค์ ์ ๋ ฌํ๋ฉด ๋ฌด์์ด ์๋ก ์๋์ง
|
|
34
34
|
๋ชป ๋ณธ๋ค. ๋ ๋ ์ค์ ๋๋๋ฆฐ๋ค(์ ์ฅ์์ ๋ค๋ฅธ ๊ณณ๋ค๊ณผ ๊ฐ์ ๊ผด: ๊ฐ์ด ๋ฐ๋ ๊ทธ ๋ ๋์์
|
|
35
35
|
๋ฐ๋ก ๋ง์ถฐ์ผ ํ ๋ฐ์ ๋ฆ์ ํ๋ฉด์ด ์ ๋ฌ๋ค). */
|
|
36
|
-
const [
|
|
36
|
+
const [seen, setSeen] = useState(resetOn);
|
|
37
37
|
// Object.is โ NaN ์ด๋ฉด `!==` ๊ฐ ๋ ์ฐธ์ด๋ผ ๋ ๋๊ฐ ๋๋์ง ์์๋ค. ๊ฐ์ฒด๋ ๋ ๋๋ง๋ค ์๋ก ๋ง๋ค๋ฉด ๋งค๋ฒ ๋ฐ๋ ๊ฒ์ด๋ค(์์๊ฐ์ ๋๊ธธ ๊ฒ)
|
|
38
|
-
if (!Object.is(
|
|
39
|
-
|
|
38
|
+
if (!Object.is(seen, resetOn)) {
|
|
39
|
+
setSeen(resetOn);
|
|
40
40
|
if (page !== 1)
|
|
41
41
|
setPage(1);
|
|
42
42
|
}
|
|
@@ -50,14 +50,14 @@ export function useTablePaging(rows, resetOn, opts) {
|
|
|
50
50
|
// 1 ๋ณด๋ค ์์ ์ชฝ(setPage(0)ยท-1)๋ ๋ง๋๋ค โ 0 ์ ๋น ์ชฝ, -1 ์ ์์ slice ๋ก ์๋ฑํ ์ค์ด ๋์๋ค
|
|
51
51
|
if (page > total || page < 1 || !Number.isFinite(page))
|
|
52
52
|
setPage(Math.min(Math.max(1, Number.isFinite(page) ? page : 1), total));
|
|
53
|
-
const
|
|
54
|
-
const pageRows = useMemo(() => rows.slice((
|
|
53
|
+
const clampedPage = Math.min(Math.max(1, Number.isFinite(page) ? page : 1), total);
|
|
54
|
+
const pageRows = useMemo(() => rows.slice((clampedPage - 1) * Number(pageSize), clampedPage * Number(pageSize)), [rows, clampedPage, pageSize]);
|
|
55
55
|
return {
|
|
56
56
|
pageRows,
|
|
57
57
|
/** Toolbar ์ pageSize ์๋ฆฌ์ ๊ทธ๋๋ก ๋ฃ๋๋ค */
|
|
58
58
|
pageSizeProps: { options: pageSizeOptions, value: pageSize, onChange: setPageSize },
|
|
59
59
|
/** Toolbar ์ pagination ์๋ฆฌ์ ๊ทธ๋๋ก ๋ฃ๋๋ค */
|
|
60
|
-
paginationProps: { page:
|
|
60
|
+
paginationProps: { page: clampedPage, total, onChange: setPage },
|
|
61
61
|
setPage,
|
|
62
62
|
};
|
|
63
63
|
}
|
|
@@ -67,43 +67,49 @@ export function useTablePaging(rows, resetOn, opts) {
|
|
|
67
67
|
* **์ ๋ ฌ์ด ๋ฐ๋๋ฉด ์ฒซ ์ชฝ์ผ๋ก** โ `useTablePaging` ์ `resetOn` ์ ๋๋ ค๋ฐ์ `์ ๋ ฌ` ์ ๊ทธ๋๋ก ๋ฃ๋๋ค.
|
|
68
68
|
* `์ ๋ ฌ` ์ ๋๋ฅผ ๋๋ง ์ ๊ฐ์ฒด๊ฐ ๋๋ฏ๋ก(๋ ๋๋ง๋ค ์๋ก ๋ง๋ค์ง ์๋๋ค) ๊ทธ ์์ฒด๊ฐ ์ด์ ๋ค. ์ชฝ์ ๋ฐ๋ก ๋ค๊ณ ์์ผ๋ฉด `onSortChange` ์์ ๋๋๋ฆฐ๋ค.
|
|
69
69
|
*
|
|
70
|
-
* `sortRows(rows,
|
|
70
|
+
* `sortRows(rows, getValue)` ์ ์ง๊ธ ์ ๋ ฌ๋ก ์ค์ ์ธ์ด **์ ๋ฐฐ์ด**์ ์ค๋ค(์ ๋ ฌ์ด ์์ผ๋ฉด ๋ฐ์ ๋ฐฐ์ด ๊ทธ๋๋ก) โ `compareCell`(์ซ์๋ ์๋ก,
|
|
71
71
|
* ํ๊ธยท์๋ฌธ์ ์ฌ๋ ์ฐจ๋ก) ์ผ๋ก ๊ฒฌ์ฃผ๊ณ **๋น ๊ฐ(nullยทundefinedยท'')์ ์ค๋ฆยท๋ด๋ฆผ ๋ชจ๋ ๋งจ ๋ค**(ListPopup ๊ณผ ๊ฐ์ ๊ท์น).
|
|
72
72
|
*
|
|
73
73
|
* @example
|
|
74
|
-
* const {
|
|
74
|
+
* const { sort, field, sortRows } = useTableSort<'name' | 'joined'>()
|
|
75
75
|
* const ์ธ์ด์ค = useMemo(() => sortRows(rows, (r, key) => r[key]), [rows, sortRows])
|
|
76
|
-
* const { pageRows, pageSizeProps, paginationProps } = useTablePaging(์ธ์ด์ค,
|
|
77
|
-
* <Table fields={[
|
|
76
|
+
* const { pageRows, pageSizeProps, paginationProps } = useTablePaging(์ธ์ด์ค, sort) // ์ ๋ ฌ์ด ๋ฐ๋๋ฉด ์ฒซ ์ชฝ
|
|
77
|
+
* <Table fields={[field('์ด๋ฆ', 'name'), field('๊ฐ์
์ผ', 'joined')]} โฆ />
|
|
78
78
|
*
|
|
79
79
|
* @public
|
|
80
80
|
*/
|
|
81
81
|
export function useTableSort(opts) {
|
|
82
|
-
const [
|
|
82
|
+
const [sortState, setSortState] = useState(null);
|
|
83
83
|
/* ๋ค์ ์ ๋ ฌ์ **ref ์์** ๊ณ์ฐํด ์๋ฆฐ๋ค โ ๊ฐฑ์ ํจ์ ์์์ ๋ถ๋ฅด๋ฉด StrictMode ๊ฐ ๋ ๋ฒ ๋ถ๋ฅธ๋ค */
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
const sortRef = useRef(sortState);
|
|
85
|
+
const onChangeRef = useRef(opts?.onSortChange);
|
|
86
|
+
onChangeRef.current = opts?.onSortChange;
|
|
87
|
+
const onHeadClick = useCallback((key) => {
|
|
88
|
+
const prev = sortRef.current;
|
|
89
|
+
const next = prev?.key === key ? { key, asc: !prev.asc } : { key, asc: true };
|
|
90
|
+
sortRef.current = next;
|
|
91
|
+
setSortState(next);
|
|
92
|
+
onChangeRef.current?.(next);
|
|
93
93
|
}, []);
|
|
94
94
|
/** Table ์ field ํ๋์ ๋ถ์ผ ๊ฒ โ ๋ผ๋ฒจ๊ณผ ํจ๊ป ์ด๋ค */
|
|
95
|
-
const
|
|
95
|
+
const fieldHead = useCallback((label, key) => ({
|
|
96
96
|
label,
|
|
97
|
-
sortDir:
|
|
98
|
-
onClick: () =>
|
|
99
|
-
}), [
|
|
97
|
+
sortDir: sortState?.key === key ? (sortState.asc ? 'asc' : 'desc') : null,
|
|
98
|
+
onClick: () => onHeadClick(key),
|
|
99
|
+
}), [sortState, onHeadClick]);
|
|
100
100
|
/** ์ง๊ธ ์ ๋ ฌ๋ก ์ธ์ด ์ ๋ฐฐ์ด(์ ๋ ฌ ์์ผ๋ฉด ๋ฐ์ ๋ฐฐ์ด ๊ทธ๋๋ก). ๋น ๊ฐ์ ์ค๋ฆยท๋ด๋ฆผ ๋ชจ๋ ๋งจ ๋ค. */
|
|
101
|
-
const sortRows = useCallback((rows,
|
|
102
|
-
if (
|
|
101
|
+
const sortRows = useCallback((rows, getValue) => {
|
|
102
|
+
if (!sortState)
|
|
103
103
|
return rows;
|
|
104
|
-
const { key, asc } =
|
|
105
|
-
return [...rows].sort((a, b) =>
|
|
106
|
-
}, [
|
|
107
|
-
/* ์ชฝ๋๊น์ ๊ทธ๋๋ก ๊ฑด๋จ๋ค โ ์ฐจ๋ก๊ฐ ๋ฐ๋๋ฉด ์ฒซ ์ชฝ์ผ๋ก
|
|
108
|
-
|
|
104
|
+
const { key, asc } = sortState;
|
|
105
|
+
return [...rows].sort((a, b) => compareCellBlankLast(getValue(a, key), getValue(b, key), asc));
|
|
106
|
+
}, [sortState]);
|
|
107
|
+
/* ์ชฝ๋๊น์ ๊ทธ๋๋ก ๊ฑด๋จ๋ค โ ์ฐจ๋ก๊ฐ ๋ฐ๋๋ฉด ์ฒซ ์ชฝ์ผ๋ก ๋์๊ฐ๊ฒ.
|
|
108
|
+
**์ ํ๊ธ ์ด๋ฆ(`์ ๋ ฌ`ยท`์นธ`)์ 2026-10-13 ๊ฑท์๋ค** โ ์๋น์๊ฐ ๋ค ์ฎ๊ธด ๊ฒ์ ์ธ์ด ๋ณด๊ณ ๊ฑท์๋ค
|
|
109
|
+
(๋ง๊ท์ ์ฝ์ด `useTableSort` ์๋ฆฌ ์ด, ์ ๋ถ `{ sort, field }`. kscanยท์๋ฆฌ๋ฐฉํฅ์ฑยทfe ๋ 0). */
|
|
110
|
+
return {
|
|
111
|
+
sort: sortState,
|
|
112
|
+
field: fieldHead,
|
|
113
|
+
sortRows,
|
|
114
|
+
};
|
|
109
115
|
}
|
|
@@ -54,72 +54,72 @@ import { logTrainingTime } from '../utils/trainingLogger';
|
|
|
54
54
|
* @public
|
|
55
55
|
*/
|
|
56
56
|
/** ์ง๊ธ ๊ทธ ๊ฐ๋์ ์๊ฐ์ ์ ๊ณ ์๋ ์ฃผ์ธ โ ๊ฐ๋๋ง๋ค ํ๋(์ ๋จธ๋ฆฌ๋ง ์ฐธ๊ณ ) */
|
|
57
|
-
const
|
|
57
|
+
const writers = new Set();
|
|
58
58
|
export default function useTrainingSessionLog(trainingType) {
|
|
59
59
|
useEffect(() => {
|
|
60
60
|
if (!trainingType)
|
|
61
61
|
return; // ํ์
๋ฏธ์์ด๋ฉด ๊ธฐ๋กํ์ง ์์
|
|
62
62
|
/* **๊ฐ๋๋ง๋ค ์ฃผ์ธ์ ํ๋๋ค.** ์ด๋ฏธ ๋๊ฐ ์ ๊ณ ์์ผ๋ฉด ์กฐ์ฉํ ๋ฌผ๋ฌ๋๋ค โ ์ ๊ทธ๋ฌ๋ฉด
|
|
63
63
|
๋ ์ฐฝ์ด ์ ์๊ฐ์ ๋ฐ๋ก ์ ์ด ๊ฒน์น ๋งํผ์ด ๋ ๋ฒ ์ธ์ด์ง๋ค. */
|
|
64
|
-
if (
|
|
64
|
+
if (writers.has(trainingType))
|
|
65
65
|
return;
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
let
|
|
69
|
-
let
|
|
70
|
-
let
|
|
71
|
-
let
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
66
|
+
writers.add(trainingType);
|
|
67
|
+
const isVisible = () => typeof document === 'undefined' || document.visibilityState !== 'hidden';
|
|
68
|
+
let firstVisibleAt = null;
|
|
69
|
+
let spanStartedAt = null;
|
|
70
|
+
let accumulatedMs = 0;
|
|
71
|
+
let sentMs = 0; // ์ง๊ธ๊น์ง ์ค๋ก ๋ด๋ณด๋ธ ๋๊ณ โ ๋ค์ ์ค์ ์ฌ๊ธฐ์๋ถํฐ ์ด์ด ๋ถ์ธ๋ค
|
|
72
|
+
const openSpan = () => { const t = Date.now(); if (firstVisibleAt == null)
|
|
73
|
+
firstVisibleAt = t; if (spanStartedAt == null)
|
|
74
|
+
spanStartedAt = t; };
|
|
75
|
+
const closeSpan = () => { if (spanStartedAt != null) {
|
|
76
|
+
accumulatedMs += Date.now() - spanStartedAt;
|
|
77
|
+
spanStartedAt = null;
|
|
78
78
|
} };
|
|
79
|
-
if (
|
|
80
|
-
|
|
79
|
+
if (isVisible())
|
|
80
|
+
openSpan();
|
|
81
81
|
/* ์์ง ์ ๋ณด๋ธ ๋งํผ์ ํ ์ค๋ก. ๋ ์๊ฐ์ ์ฒ์ ๋ณด์ธ ๋ + ๋ชจ์ ์๊ฐ, ์์ ์๊ฐ์ ์ ์ค์ด
|
|
82
82
|
๋๋ ์๋ฆฌ โ ์ค๋ผ๋ฆฌ ๊ฒน์น์ง ์์ผ๋ ํฉ์ฐํด๋ ์ด์ค์ผ๋ก ์ธ์ง ์๋๋ค.
|
|
83
83
|
์ ๋ฌ์ ๋ฑ๋ก๋ ๋ก๊ฑฐ(logTrainingTime)๋ก ๊ฐ๋ค. ์ฝ์ด๋ ๋ณด๋ผ ์ฃผ์๋ฅผ ๋ชจ๋ฅด๋ฏ๋ก
|
|
84
84
|
navigator.sendBeacon ์ ์ฌ๊ธฐ์ ์ง์ ์ธ ์ ์๋ค โ ์ฑ์ด ๋ฑ๋กํ๋ ๋ก๊ฑฐ์ ๋ชซ์ด๋ค. */
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
if (
|
|
85
|
+
const send = () => {
|
|
86
|
+
closeSpan();
|
|
87
|
+
if (firstVisibleAt == null)
|
|
88
88
|
return;
|
|
89
|
-
const
|
|
89
|
+
const pendingMs = accumulatedMs - sentMs;
|
|
90
90
|
/* 1์ด๊ฐ ์ ๋๋ ์กฐ๊ฐ์ ๋ณด๋ด์ง ์๋๋ค โ ์๋ฒ(log_training_category_time)๊ฐ 1์ด ๋ฏธ๋ง ์ค์ ๋ฒ๋ฆฌ๋ฏ๋ก
|
|
91
91
|
๋ถ๋ฅด๊ธฐ ์ ํ(10๋ถ 60๋ฒ)๋ง ์ถ๋ธ๋ค(๋ง๊ท ํ์ธ 2026-09-16). */
|
|
92
|
-
if (
|
|
92
|
+
if (pendingMs < 1000)
|
|
93
93
|
return;
|
|
94
94
|
// ์ฒซ ์ค์๋ง 5์ด ๋ฌธํฑ์ ๊ฑด๋ค(์๋ชป ์ด์๋ค ๋ซ์ ํ). ํ ๋ฒ ๋ณด๋ธ ํ์ ์ด๋ฏธ ์ค์ฌํ๋
|
|
95
95
|
// ํ๋ จ์ด๋ผ, ๋์์ ๋ ํ ์๊ฐ์ ์งง์๋ ๋ฒ๋ฆฌ์ง ์๋๋ค.
|
|
96
|
-
if (
|
|
96
|
+
if (sentMs === 0 && accumulatedMs < 5000)
|
|
97
97
|
return;
|
|
98
|
-
const
|
|
99
|
-
|
|
98
|
+
const startMs = firstVisibleAt + sentMs;
|
|
99
|
+
sentMs = accumulatedMs;
|
|
100
100
|
logTrainingTime({
|
|
101
101
|
trainingType,
|
|
102
|
-
startedAt: new Date(
|
|
103
|
-
endedAt: new Date(
|
|
102
|
+
startedAt: new Date(startMs).toISOString(),
|
|
103
|
+
endedAt: new Date(firstVisibleAt + accumulatedMs).toISOString(),
|
|
104
104
|
});
|
|
105
105
|
};
|
|
106
106
|
// ์จ์ผ๋ฉด ๋ณด๋ธ๋ค(์ฑ์ด ๊ทธ๋๋ก ์ฃฝ์ ์ ์๋ค). ๋ค์ ๋ณด์ด๋ฉด ์ด์ด์ ๋ชจ์๋ค.
|
|
107
|
-
const
|
|
108
|
-
|
|
107
|
+
const onVisibilityChange = () => { if (isVisible())
|
|
108
|
+
openSpan();
|
|
109
109
|
else
|
|
110
|
-
|
|
110
|
+
send(); };
|
|
111
111
|
if (typeof document !== 'undefined')
|
|
112
|
-
document.addEventListener('visibilitychange',
|
|
112
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
113
113
|
// ํ์ด์ง๊ฐ ๋ด๋ ค๊ฐ๋ฉด(ํญยท์ฑ ๋ซ๊ธฐ) React ์ ๋ฆฌ๊ฐ ์ ๋๋ค โ ๊ทธ๋ ๋ณด๋ธ๋ค
|
|
114
114
|
if (typeof window !== 'undefined')
|
|
115
|
-
window.addEventListener('pagehide',
|
|
115
|
+
window.addEventListener('pagehide', send);
|
|
116
116
|
return () => {
|
|
117
117
|
if (typeof document !== 'undefined')
|
|
118
|
-
document.removeEventListener('visibilitychange',
|
|
118
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
119
119
|
if (typeof window !== 'undefined')
|
|
120
|
-
window.removeEventListener('pagehide',
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
window.removeEventListener('pagehide', send);
|
|
121
|
+
send();
|
|
122
|
+
writers.delete(trainingType);
|
|
123
123
|
};
|
|
124
124
|
}, [trainingType]);
|
|
125
125
|
}
|
|
@@ -13,10 +13,10 @@ import { useEditableList, isSameEditableItem } from './useEditableList';
|
|
|
13
13
|
* ์์๋ค** โ V8 ์ TimSort ๋ ๊ฐ์ ๊ฐ ๊ตฌ๊ฐ์์ ๋น๊ต๋ฅผ ์์ ์ ๋ถ๋ฌ, ๋ค์์ธ ์๋ฃ๋ก
|
|
14
14
|
* ์ฌ ๋ด๋ ๊ฒฐ๊ณผ๊ฐ ๊ฐ์๋ค(์ค์ธก). ๊ทธ๋์ ์ด ํ ๊ฐ์ง๋ ์ํ์ผ๋ก ๋ชป ๋ฐ๋๋ค.
|
|
15
15
|
*/
|
|
16
|
-
const
|
|
16
|
+
const sortKeyOf = (v) => `${v.year.padStart(4, '0')}-${v.month.padStart(2, '0')}-${v.day.padStart(2, '0')}` +
|
|
17
17
|
` ${v.hour.padStart(2, '0')}:${v.minute.padStart(2, '0')}`;
|
|
18
18
|
const sortByDate = (list) => [...list].sort((a, b) => {
|
|
19
|
-
const x =
|
|
19
|
+
const x = sortKeyOf(a), y = sortKeyOf(b);
|
|
20
20
|
return x < y ? -1 : x > y ? 1 : 0;
|
|
21
21
|
});
|
|
22
22
|
const emptyScheduleItem = { year: '', month: '', day: '', hour: '09', minute: '00', memo: '' };
|
|
@@ -34,8 +34,8 @@ initialSchedule = []) {
|
|
|
34
34
|
useMemo ๋ก ๋ฌถ๋ ๊ฒ์ ์ฑ๋ฅ ๋๋ฌธ์ด ์๋๋ผ **๋๋์ด๋ฅผ ๋ง๊ธฐ ์ํด์๋ค** โ
|
|
35
35
|
useEditableList ๋ ์ด๊ธฐ ๋ชฉ๋ก์ ์์ ์ฐธ์กฐ๋ก ๊ฒฌ์ฃผ๋๋ฐ, ๋ ๋๋ง๋ค ์ ๋ฐฐ์ด์ ๋ง๋ค๋ฉด
|
|
36
36
|
์์๋ ๊ฐ์๋ ๋งค๋ฒ setList ๊ฐ ๋์ ๋ฌดํ ๋ ๋๊ฐ ๋๋ค. */
|
|
37
|
-
const
|
|
38
|
-
const { list: scheduleList, setList: setScheduleList, selected: selectedSchedule, setSelected: setSelectedSchedule, isEditing: isEditingSchedule, setIsEditing: setIsEditingSchedule, editForm: editScheduleForm, setEditForm: setEditScheduleForm, openDetail: openScheduleDetail, commitEdit, remove, } = useEditableList(emptyScheduleItem,
|
|
37
|
+
const sortedInitial = useMemo(() => sortByDate(initialSchedule), [initialSchedule]);
|
|
38
|
+
const { list: scheduleList, setList: setScheduleList, selected: selectedSchedule, setSelected: setSelectedSchedule, isEditing: isEditingSchedule, setIsEditing: setIsEditingSchedule, editForm: editScheduleForm, setEditForm: setEditScheduleForm, openDetail: openScheduleDetail, commitEdit, remove, } = useEditableList(emptyScheduleItem, sortedInitial);
|
|
39
39
|
/** ๋ฏธ๋ฐฉ๋ฌธ ์ฒ๋ฆฌ๋ ์์ฝ โ ์ญ์ ํ์ง ์๊ณ ๋ณด์กดํด ํตํฉ ๋ฐฉ๋ฌธ ๋ชฉ๋ก์์ ๋ฑ์ง๋ก ํ์ */
|
|
40
40
|
const [noShowList, setNoShowList] = useState([]);
|
|
41
41
|
const { year: ty, month: tm, day: td } = today();
|