This commit is contained in:
rizky 2024-08-12 01:30:01 -07:00
parent 74e2762abd
commit 362cfdff27
12 changed files with 186 additions and 71 deletions

View File

@ -134,8 +134,12 @@ export const FieldInput: FC<{
<> <>
{prefix && prefix !== "" ? ( {prefix && prefix !== "" ? (
<div <div
className=" className={cx(
c-px-2 c-bg-gray-200 c-flex c-flex-row c-items-center" "c-px-2 c-flex c-flex-row c-items-center",
css`
color: gray;
`
)}
> >
{prefix} {prefix}
</div> </div>
@ -194,8 +198,12 @@ export const FieldInput: FC<{
</div> </div>
{suffix && suffix !== "" ? ( {suffix && suffix !== "" ? (
<div <div
className=" className={cx(
c-px-2 c-bg-gray-200 c-flex c-flex-row c-items-center" "c-px-2 c-flex c-flex-row c-items-center",
css`
color: gray;
`
)}
> >
{suffix} {suffix}
</div> </div>

View File

@ -1,3 +1,4 @@
import { useLocal } from "lib/utils/use-local";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { ReactElement } from "react"; import { ReactElement } from "react";
@ -8,6 +9,25 @@ export const ThumbPreview = ({
url: string; url: string;
options: ReactElement; options: ReactElement;
}) => { }) => {
const local = useLocal({ size: "", is_doc: true }, async () => {
if (url.startsWith("_file/")) {
let _url = siteurl(`/_finfo/${url.substring("_file/".length)}`);
if (
location.hostname === "prasi.avolut.com" ||
location.host === "localhost:4550"
) {
const newurl = new URL(location.href);
newurl.pathname = `/_proxy/${_url}`;
_url = newurl.toString();
}
const info = await fetch(_url);
local.size = (await info.json())?.size;
local.render();
}
});
const file = getFileName(url); const file = getFileName(url);
if (typeof file === "string") return; if (typeof file === "string") return;
@ -34,14 +54,23 @@ export const ThumbPreview = ({
outline: 1px solid #1c4ed8; outline: 1px solid #1c4ed8;
} }
`, `,
"c-flex c-justify-center c-items-center" "c-flex c-justify-center c-items-center c-flex-col"
)} )}
onClick={() => { onClick={() => {
let _url = siteurl(url || ""); let _url = siteurl(url || "");
window.open(_url, "_blank"); window.open(_url, "_blank");
}} }}
> >
{file.extension} <div>{file.extension}</div>
<div
className={css`
font-size: 9px;
color: gray;
margin-top: -3px;
`}
>
{local.size}
</div>
</div> </div>
); );
@ -49,6 +78,7 @@ export const ThumbPreview = ({
if (url.startsWith("_file/")) { if (url.startsWith("_file/")) {
if ([".png", ".jpeg", ".jpg", ".webp"].find((e) => url.endsWith(e))) { if ([".png", ".jpeg", ".jpg", ".webp"].find((e) => url.endsWith(e))) {
is_image = true; is_image = true;
local.is_doc = false;
content = ( content = (
<img <img
onClick={() => { onClick={() => {

View File

@ -3,7 +3,6 @@ import { FieldLoading } from "lib/comps/ui/field-loading";
import { Typeahead } from "lib/comps/ui/typeahead"; import { Typeahead } from "lib/comps/ui/typeahead";
import { FC, useEffect } from "react"; import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings"; import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { call_prasi_events } from "lib/exports";
export const TypeDropdown: FC<{ export const TypeDropdown: FC<{
field: FieldLocal; field: FieldLocal;
@ -35,23 +34,27 @@ export const TypeDropdown: FC<{
data: e.data, data: e.data,
}; };
}); });
let v = typeof arg.opt_get_value === "function" let v =
? arg.opt_get_value({ typeof arg.opt_get_value === "function"
fm, ? arg.opt_get_value({
name: field.name, fm,
options: local.options, name: field.name,
type: field.type, options: local.options,
}) type: field.type,
: fm.data[field.name]; })
let f = list.find((ex) => ex.value === v); : fm.data[field.name];
if(!f){ if (field.type === "single-option") {
arg.opt_set_value({ let f = list.find((ex: any) => ex.value === v);
fm,
name: field.name, if (!f) {
type: field.type, arg.opt_set_value({
options: local.options, fm,
selected: [], name: field.name,
}); type: field.type,
options: local.options,
selected: [],
});
}
} }
local.options = list; local.options = list;
} else { } else {
@ -140,8 +143,8 @@ export const TypeDropdown: FC<{
popupClassName = cx( popupClassName = cx(
css` css`
.opt-item { .opt-item {
padding-top: 0px; padding-top: 4px;
padding-bottom: 0px; padding-bottom: 4px;
line-height: 15px; line-height: 15px;
font-size: 12px; font-size: 12px;
border: 0px; border: 0px;

View File

@ -11,38 +11,30 @@ export const FieldRichText: FC<{
prop: PropTypeInput; prop: PropTypeInput;
}> = ({ field, fm, prop }) => { }> = ({ field, fm, prop }) => {
const local = useLocal({ const local = useLocal({
ref: null as any, ref: null as null | HTMLDivElement,
q: null as null | Quill,
}); });
useEffect(() => { useEffect(() => {
if (local.ref) { if (local.ref) {
const q = new Quill(local.ref, { local.ref.innerHTML = fm.data[field.name] || "";
local.q = new Quill(local.ref, {
theme: "snow", theme: "snow",
modules: { modules: {
toolbar: [ toolbar: [
["bold", "italic", "underline", "strike"], // toggled buttons ["bold", "italic", "underline", "strike"], // toggled buttons
["blockquote", "code-block"],
["link", "image", "video", "formula"],
[{ header: 1 }, { header: 2 }], // custom button values
[{ list: "ordered" }, { list: "bullet" }, { list: "check" }], [{ list: "ordered" }, { list: "bullet" }, { list: "check" }],
[{ script: "sub" }, { script: "super" }], // superscript/subscript
[{ indent: "-1" }, { indent: "+1" }], // outdent/indent [{ indent: "-1" }, { indent: "+1" }], // outdent/indent
[{ direction: "rtl" }], // text direction
[{ size: ["small", false, "large", "huge"] }], // custom dropdown
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ font: [] }],
[{ align: [] }],
["clean"], // remove formatting button ["clean"], // remove formatting button
], ],
}, },
}); });
local.q.on("text-change", (delta, oldDelta, source) => {
fm.data[field.name] = local.q?.getSemanticHTML();
fm.render();
});
} }
}, []); }, []);
let value: any = fm.data[field.name];
return ( return (
<div <div
className={cx( className={cx(
@ -55,13 +47,22 @@ export const FieldRichText: FC<{
.ql-container { .ql-container {
border-top: 1px solid #cecece !important; border-top: 1px solid #cecece !important;
} }
.ql-editor {
resize: vertical;
overflow-y: scroll;
min-height: 5rem !important;
}
` `
)} )}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
> >
<div <div
ref={(e) => (local.ref = e)} ref={(e) => (local.ref = e)}
className={cx(css` className={cx(css`
height: 20rem !important; min-height: 5rem !important;
`)} `)}
></div> ></div>
</div> </div>

View File

@ -16,7 +16,6 @@ export const FieldUploadMulti: FC<{
arg: FieldProp; arg: FieldProp;
on_change: (e: any) => void | Promise<void>; on_change: (e: any) => void | Promise<void>;
}> = ({ field, fm, prop, on_change, arg }) => { }> = ({ field, fm, prop, on_change, arg }) => {
let value: string = (fm.data[field.name] || "").trim(); let value: string = (fm.data[field.name] || "").trim();
const input = useLocal({ const input = useLocal({
@ -136,6 +135,9 @@ export const FieldUploadMulti: FC<{
.upload-star { .upload-star {
border: 1px solid gray; border: 1px solid gray;
} }
.btn-del {
border: 1px solid red;
}
} }
`, `,
fm.data[cover.field] === value && fm.data[cover.field] === value &&
@ -188,9 +190,8 @@ export const FieldUploadMulti: FC<{
} }
}} }}
className={cx( className={cx(
"c-flex c-flex-row c-items-center c-px-1 c-rounded c-bg-white c-cursor-pointer hover:c-bg-red-100 transition-all", "c-flex c-flex-row c-items-center c-px-1 c-rounded c-bg-white c-cursor-pointer hover:c-bg-red-100 transition-all btn-del",
css` css`
border: 1px solid red;
width: 25px; width: 25px;
height: 25px; height: 25px;
` `
@ -258,11 +259,21 @@ export const FieldUploadMulti: FC<{
</div> </div>
)} )}
</div> </div>
<div className="c-flex"> <div className="c-flex c-pt-1">
<div className={cx("c-flex c-border c-rounded ")}> <div
className={cx(
"c-flex c-border c-rounded c-cursor-pointer hover:c-bg-blue-50",
css`
&:hover {
border: 1px solid #1c4ed8;
outline: 1px solid #1c4ed8;
}
`
)}
>
<div <div
className={cx( className={cx(
"c-flex c-flex-row c-relative c-flex-grow c-pr-2 c-items-center c-cursor-pointer hover:c-bg-blue-50", "c-flex c-flex-row c-relative c-flex-grow c-pr-2 c-items-center ",
css` css`
padding-top: 3px; padding-top: 3px;
padding-bottom: 2px; padding-bottom: 2px;
@ -289,7 +300,11 @@ export const FieldUploadMulti: FC<{
)} )}
/> />
)} )}
<div className="c-items-center c-flex c-text-base c-px-1 c-outline-none c-rounded c-cursor-pointer"> <div
className={cx(
"c-items-center c-flex c-text-base c-px-1 c-outline-none c-rounded c-cursor-pointer"
)}
>
<div className="c-flex c-flex-row c-items-center c-px-2"> <div className="c-flex c-flex-row c-items-center c-px-2">
<Upload className="c-h-4 c-w-4" /> <Upload className="c-h-4 c-w-4" />
</div> </div>

View File

@ -150,14 +150,16 @@ ${
fm.data = form; fm.data = form;
md.selected = form; md.selected = form;
if (md.props.mode !== "full") md.master.reload({ toast: false }); if (md.props.mode !== "full") md.master.reload({ toast: false });
md.render();
fm.render();
if (fm.props.back_on_save === "y") { if (fm.props.back_on_save === "y") {
md.selected = null; md.selected = null;
md.tab.active = "master"; md.tab.active = "master";
md.params.apply(); md.params.apply();
md.render(); md.render();
} else {
md.params.apply();
md.render();
fm.render();
} }
}` }`
} }

View File

@ -1,13 +1,12 @@
import { parseGenField } from "@/gen/utils"; import { parseGenField } from "@/gen/utils";
import { MDLocal } from "lib/comps/md/utils/typings";
import { Button } from "lib/comps/ui/button";
import { toast } from "lib/comps/ui/toast";
import get from "lodash.get"; import get from "lodash.get";
import { AlertTriangle, Check, ChevronLeft, Loader2, Plus } from "lucide-react"; import { AlertTriangle, Check, ChevronLeft, Loader2, Plus } from "lucide-react";
import { FMLocal, FMProps } from "../typings"; import { FMLocal, FMProps } from "../typings";
import { editorFormData } from "./ed-data"; import { editorFormData } from "./ed-data";
import { formError } from "./error"; import { formError } from "./error";
import { toast } from "lib/comps/ui/toast";
import { Button } from "lib/comps/ui/button";
import { MDLocal } from "lib/comps/md/utils/typings";
import { masterDetailApplyParams } from "lib/comps/md/utils/md-hash";
export const formInit = (fm: FMLocal, props: FMProps) => { export const formInit = (fm: FMLocal, props: FMProps) => {
for (const [k, v] of Object.entries(props)) { for (const [k, v] of Object.entries(props)) {

View File

@ -131,7 +131,8 @@ export const MasterDetail: FC<MDProps> = (arg) => {
return ( return (
<div <div
className={cx( className={cx(
"master-detail c-flex-1 c-flex-col c-flex c-w-full c-h-full" "master-detail c-flex-1 c-flex-col c-flex c-w-full c-h-full",
md.selected ? "mode-detail" : "mode-master"
)} )}
> >
{md.props.show_head === "always" && <MDHeader md={md} mdr={mdr} />} {md.props.show_head === "always" && <MDHeader md={md} mdr={mdr} />}

View File

@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/utils" import { cn } from "@/utils"
const badgeVariants = cva( const badgeVariants = cva(
"c-inline-flex c-items-center c-rounded-full c-border c-px-2.5 c-py-0.5 c-text-xs c-font-semibold c-transition-colors focus:c-outline-none focus:c-ring-2 focus:c-ring-ring focus:c-ring-offset-2", "c-inline-flex c-items-center c-rounded-full c-border c-px-2.5 c-py-0.5 c-font-semibold c-transition-colors focus:c-outline-none focus:c-ring-2 focus:c-ring-ring focus:c-ring-offset-2",
{ {
variants: { variants: {
variant: { variant: {

View File

@ -82,7 +82,7 @@ export const TypeaheadOptions: FC<{
})} })}
{searching ? ( {searching ? (
<div className="c-px-4 c-w-full c-text-xs c-text-slate-400"> <div className="c-px-4 c-w-full c-text-slate-400">
Loading... Loading...
</div> </div>
) : ( ) : (

View File

@ -337,18 +337,33 @@ export const Typeahead: FC<{
<div <div
className={cx( className={cx(
local.mode === "single" ? "c-cursor-pointer" : "c-cursor-text", local.mode === "single" ? "c-cursor-pointer" : "c-cursor-text",
"c-flex c-relative c-flex-wrap c-px-2 c-pb-0 c-items-center c-w-full c-h-full c-flex-1", "c-flex c-relative c-flex-wrap c-px-2 c-py-0 c-items-center c-w-full c-h-full c-flex-1",
css` className,
padding-top: 0.35rem; local.mode === "multi" && valueLabel.length > 0
`, ? css`
className input {
margin-top: 5px;
}
`
: css`
input {
margin-top: 5px;
}
`
)} )}
onClick={() => { onClick={() => {
if (!disabled) input.current?.focus(); if (!disabled) input.current?.focus();
}} }}
> >
{local.mode === "multi" ? ( {local.mode === "multi" ? (
<> <div
className={cx(
css`
margin-top: 5px;
margin-bottom: -3px;
`
)}
>
{valueLabel.map((e, idx) => { {valueLabel.map((e, idx) => {
return ( return (
<Badge <Badge
@ -356,7 +371,8 @@ export const Typeahead: FC<{
variant={"outline"} variant={"outline"}
className={cx( className={cx(
"c-space-x-1 c-mr-2 c-mb-2 c-bg-white", "c-space-x-1 c-mr-2 c-mb-2 c-bg-white",
!disabled && " c-cursor-pointer hover:c-bg-red-100" !disabled &&
" c-cursor-pointer hover:c-bg-red-100 hover:c-border-red-100"
)} )}
onClick={(ev) => { onClick={(ev) => {
if (!disabled) { if (!disabled) {
@ -377,7 +393,7 @@ export const Typeahead: FC<{
</Badge> </Badge>
); );
})} })}
</> </div>
) : ( ) : (
<></> <></>
)} )}
@ -531,7 +547,7 @@ export const Typeahead: FC<{
disabled={disabled} disabled={disabled}
spellCheck={false} spellCheck={false}
className={cx( className={cx(
"c-flex-1 c-mb-2 c-text-sm c-outline-none c-bg-transparent", "c-flex-1 c-mb-2 c-outline-none c-bg-transparent",
local.mode === "single" ? "c-cursor-pointer" : "" local.mode === "single" ? "c-cursor-pointer" : ""
)} )}
onKeyDown={keydown} onKeyDown={keydown}

View File

@ -130,6 +130,46 @@ export const FormatValue: FC<{
return <FilePreview url={value || ""} />; return <FilePreview url={value || ""} />;
} }
if (name.startsWith("desc")) {
return (
<div className="c-flex c-space-x-2 c-items-center">
<div dangerouslySetInnerHTML={{ __html: value }} />
</div>
);
}
if (typeof value === "string" && value.startsWith("_file/")) {
return (
<img
onClick={() => {
let _url = siteurl(value || "");
window.open(_url, "_blank");
}}
className={cx(
"c-rounded-md",
css`
&:hover {
outline: 2px solid #1c4ed8;
}
`,
css`
width: 25px;
height: 25px;
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%),
linear-gradient(135deg, #ccc 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #ccc 75%),
linear-gradient(135deg, transparent 75%, #ccc 75%);
background-size: 25px 25px; /* Must be a square */
background-position: 0 0, 12.5px 0, 12.5px -12.5px, 0px 12.5px; /* Must be half of one side of the square */
`
)}
src={siteurl(
`/_img/${value.substring("_file/".length)}?${"w=25&h=25&fit=cover"}`
)}
/>
);
}
return ( return (
<div className="c-flex c-space-x-2 c-items-center"> <div className="c-flex c-space-x-2 c-items-center">
<div>{value}</div> <div>{value}</div>